Skip to content

fix: gallery/dialog selection safety + stable local-build session channel - #90

Merged
androidand merged 3 commits into
devfrom
fix/gallery-selection-and-build-channel
Sep 19, 2026
Merged

androidand merged 3 commits into
devfrom
fix/gallery-selection-and-build-channel

Conversation

@androidand

Copy link
Copy Markdown
Owner

Summary

  • DialogSelect no longer hides disabled: true rows entirely — every current caller uses them for status/informational text (loading, offline, incompatible-with-reason), and dropping them left store.selected free to silently land on a real, unrelated row once the list reshuffled under it (e.g. once async host data replaced a loading placeholder), with zero user input in between. move()/moveTo()/submit()/the row's mouse handler now all refuse a disabled target.
  • DialogConfirm now defaults focus to "cancel" instead of "confirm", so reaching a confirm dialog for a consequential action (e.g. installing a model) requires a deliberate move rather than firing on the next stray Enter.
  • DialogGalleryOperations no longer replays success/failure toasts for every already-finished operation on every remount — it now baselines already-terminal ops on first load and only toasts for ones that finish while the screen is open.
  • build-local.ts no longer derives the local dev-build session-database channel from the current git branch — every differently-named branch was silently forking off its own session history, making an in-progress session "disappear" (reported as "not found") after building from a different branch. The channel now defaults to a stable dev value; OPENCODE_CHANNEL= still overrides it for real isolated testing.

Test plan

  • bun typecheck clean in packages/tui and packages/opencode
  • bun test in packages/tui: 33/33 pass in test/ui (dialog-select coverage); full suite shows only the 9 pre-existing sync/hydration failures already tracked, none touching these files
  • bun run script/fork-verify.ts clean (registered the new dialog-confirm.tsx divergence)

…afe side

DialogSelect's filtered() dropped every disabled:true row entirely instead
of showing it inert, so every caller relying on disabled rows for status
text (a loading placeholder, an offline/incompatible entry with a reason)
rendered nothing there at all. Worse, store.selected is a bare index with
no validity check, so once a disabled row's absence let something else
shift into its slot, the "selected" row could silently become a real,
different one purely from async data arriving — no user input in between.
A stray or fast-follow Enter would then act on whatever landed there.

In the model gallery specifically this reached an install: opening a
candidate's host list showed nothing while host data loaded (the
placeholder and the disabled/incompatible hosts were invisible), and once
data arrived the first compatible host could occupy the still-selected
index, ready to fire on the next keypress. That flowed straight into
DialogConfirm, which defaulted its own focus to "confirm" rather than
"cancel" — so reaching it required zero deliberate action to proceed.

Fixes, in the shared DialogSelect/DialogConfirm components so every caller
benefits, not just the gallery:
- keep disabled rows in the render list (dimmed via a new `disabled` prop
  on Option) instead of stripping them
- move()/moveTo() skip a disabled row when navigating and refuse to land
  on one at all, closing the async-reshuffle race
- submit() and the row's onMouseUp both refuse a disabled option
- DialogConfirm now defaults focus to "cancel"

Also: DialogGalleryOperations replayed a success/failure toast for every
already-terminal operation on every remount (seenTerminal reset to empty
each time), rather than only for ones that finished while the screen was
open. Now baselines already-terminal ops on the first resolution instead
of toasting for them.
…anch

opencode picks its session database as opencode-<channel>.db, and
build-local.ts defaulted OPENCODE_CHANNEL to the current branch name. Every
differently-named branch a local dev build ran from silently forked off its
own session history, so building from a feature branch made the whole
session list "disappear" (and made an in-progress session unreachable)
until you switched back to the branch you'd been building from before.

Confirmed live: 6 stray channel databases had accumulated on this machine
from past branch builds, one of them holding a still-busy session that
`opencode -s <id>` reported as "not found" under today's dev-branch build.

OPENCODE_VERSION already carries a commit sha + dirty flag + timestamp, so
per-build distinguishability doesn't depend on the channel matching the
branch. Pin the channel to a stable "dev" default; an isolated channel is
still available via an explicit OPENCODE_CHANNEL= override.
fork:verify flagged it as unregistered divergence after the previous
commit changed its default confirm-dialog focus. Registering it with a
marker so a future upstream merge that silently reverts the safer default
gets caught, same as every other tracked fork edit.
@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@androidand
androidand merged commit 1e5fa71 into dev Sep 19, 2026
3 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant