Skip to content

Conversation

@archandatta
Copy link
Contributor

@archandatta archandatta commented Jan 15, 2026

Note

Adds a post-connection disconnected UX and tightens connection lifecycle handling.

  • New neko-disconnected component and disconnected locale string; app.vue now gates neko-connect vs neko-disconnected via wasConnected and connected
  • Track wasConnected and set it upon connection to distinguish initial login from later disconnections
  • connect.vue simplified to a loader-only view with auto-login behavior retained
  • WebSocket handlers fixed/improved: proper onerror binding and detailed onclose logging in base.ts
  • Reconnect flow updated in neko/index.ts: if WebSocket is closed during ICE disconnected, perform cleanup (showing overlay); otherwise let WebRTC recover

Written by Cursor Bugbot for commit 0b98dc4. This will update automatically on new commits. Configure here.

Copy link
Contributor

@tembo tembo bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small things to consider from the diff:

  1. images/chromium-headful/client/src/app.vue
  • There are now two @Watch('connected', { immediate: true }) handlers (onConnected and onConnectedChange). Might be simpler/less error-prone to fold the wasConnected behavior into the existing onConnected watcher so there’s only one place reacting to connection transitions.
@Watch('connected', { immediate: true })
onConnected(connected: boolean) {
  if (connected) {
    this.wasConnected = true
    this.applyQueryResolution()
  }
}
  1. images/chromium-headful/client/src/components/connect.vue
  • Removing the non-connecting form means the overlay can render empty if we’re not connecting but also not connected (e.g. transient failures, bad creds, server down). Even a minimal fallback text + retry/reload affordance would help avoid a “blank screen” state.
  1. images/chromium-headful/client/src/components/disconnected.vue
  • Since this is user-facing UI, consider using existing theme vars + i18n rather than hard-coded strings/colors so it stays consistent with the rest of the client.
  1. images/chromium-headful/client/src/neko/base.ts
  • Nice fix on the handler binding. Since you already log code/reason on close, it could be useful to include that detail in the Error you pass into onDisconnected so the UI/toasts can surface it.
this.onDisconnected(
  new Error(
    `websocket closed: code=${event.code}${event.reason ? `, reason=${event.reason}` : ''}`,
  ),
)
  1. images/chromium-headful/client/src/neko/index.ts
  • EVENT.RECONNECTING now calls cleanup() and the comment says “Don’t … attempt reconnection”. If the intent is to fully stop reconnect attempts, it might be worth ensuring any underlying reconnect timers/sockets are also stopped, or updating the comment to match what actually happens.

@archandatta archandatta marked this pull request as ready for review January 15, 2026 18:40
@archandatta archandatta requested a review from rgarcia January 15, 2026 18:40
Copy link
Contributor

@tembo tembo bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall direction makes sense: distinguish first-connect vs post-connect disconnect, and fixing the websocket handler bindings is definitely the right move.

A few small follow-ups:

  • Consider including the websocket close code/reason in the Error passed into the disconnected path (right now it’s only in debug logs).
  • The new disconnected overlay likely wants an explicit z-index so it always sits above the video/menus.
  • connect.vue can render a blank overlay when connecting is false; making the loader unconditional avoids that.
  • Minor copy tweak: “shut down” reads more natural than “shutdown”.
  • wasConnected never resets; if there’s ever an explicit logout/new-session flow, it can trap users on the disconnected overlay.

Comment on lines +73 to +76
this._ws.onclose = (event) => {
this.emit('debug', `websocket closed: code=${event.code}, reason=${event.reason}`)
this.onDisconnected(new Error('websocket closed'))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix on the event handler binding.

Right now we log event.code/event.reason but the user-facing Error('websocket closed') drops that context (it ends up in the disconnected notification text). Might be worth carrying the details into the Error message.

Suggested change
this._ws.onclose = (event) => {
this.emit('debug', `websocket closed: code=${event.code}, reason=${event.reason}`)
this.onDisconnected(new Error('websocket closed'))
}
this._ws.onclose = (event) => {
const message = `websocket closed: code=${event.code}, reason=${event.reason || 'unknown'}`
this.emit('debug', message)
this.onDisconnected(new Error(message))
}


<style lang="scss" scoped>
.disconnected-overlay {
position: fixed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor UI nit: since this is a full-screen overlay, adding an explicit z-index helps ensure it always renders above the video/menus.

Suggested change
position: fixed;
position: fixed;
z-index: 9999;

{{ $t('connect.connect') }}
</button>
</form>
<div class="loader" v-if="connecting">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the login form is gone, v-if="connecting" can leave an empty overlay if connecting briefly flips false/undefined during init. Making the loader unconditional avoids a blank screen.

Suggested change
<div class="loader" v-if="connecting">
<div class="loader">

}

export const disconnected = {
message: 'Browser has been shutdown and is no longer available',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny copy tweak: "shut down" reads a bit more natural here.

Suggested change
message: 'Browser has been shutdown and is no longer available',
message: 'Browser has been shut down and is no longer available',

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with tembo!

<neko-side v-if="!videoOnly && side" />
<neko-connect v-if="!connected" />
<neko-connect v-if="!connected && !wasConnected" />
<neko-disconnected v-if="!connected && wasConnected" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case with wasConnected: if there’s an explicit logout flow (or any future “start a new session” flow), this flag never resets so you can get stuck on the disconnected overlay forever. Might be worth either (a) renaming it to something like hasEverConnected to make the semantics explicit, and/or (b) resetting it when initiating a new login attempt (or when the store indicates a new session).

<div class="window">
<form class="message" v-if="!connecting" @submit.stop.prevent="connect">
<span v-if="!autoPassword">{{ $t('connect.login_title') }}</span>
<span v-else>{{ $t('connect.invitation_title') }}</span>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connect screen shows empty overlay after connection failure

Medium Severity

After removing the login form, the neko-connect component only displays content when connecting is true (showing the loader). If the initial connection attempt fails, setConnected(false) in the store sets connecting=false, resulting in neko-connect being shown (because !connected && !wasConnected) but with an empty window. Users see a blocking semi-transparent overlay with no visible content and no way to retry or interact, leaving them stuck.

Additional Locations (1)

Fix in Cursor Fix in Web

background: #000;
display: flex;
justify-content: center;
align-items: center;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disconnected overlay missing position fixed for proper layering

High Severity

The .disconnected-overlay CSS uses width: 100vw; height: 100vh but lacks position: fixed (or position: absolute with positioning offsets). Since the parent #neko container uses display: flex; flex-direction: row, this overlay becomes a flex item laid out alongside main.neko-main rather than overlaying the content. Other overlay components like neko-connect and neko-about correctly use position: fixed/absolute with top/left/right/bottom: 0 to achieve proper overlay behavior.

Fix in Cursor Fix in Web

Copy link
Contributor

@rgarcia rgarcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with the one comment about copy but otherwise lgtm

}

export const disconnected = {
message: 'Browser has been shutdown and is no longer available',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with tembo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants