Commit graph

19581 commits

Author SHA1 Message Date
Brooklyn Nicholson
dba7bef5ce fix(desktop): stop right-click showing a lone Select All on bare surfaces 2026-07-30 05:55:30 -05:00
brooklyn!
937222f4ec
fix(desktop): keep a mid-turn model pick painted in the composer (#74759)
The gateway now queues a model switch made during a turn and applies it
at the next turn start (#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
2026-07-30 10:34:48 +00:00
brooklyn!
d8a9c17dae
fix(tui): keep the pending model shown at turn end instead of blipping back (#74766)
A model picked mid-turn is applied at the next turn start, but the end-of-turn
session.info (_emit_settled_session_info) reads the still-live agent — the OLD
model — and clobbered the optimistic paint, so the UI showed the new model, then
snapped back to the old one when the turn ended, then switched for real on the
next turn. Report the queued pick's model/provider in _session_info while a
switch is pending (it IS the model the next turn runs), so the display stays on
the user's choice through the settle. Cleared once the switch applies.
2026-07-30 10:32:55 +00:00
brooklyn!
206eda50a5
Merge pull request #74734 from NousResearch/bb/tab-context-menu
Every tab strip gets the standard right-click tab menu
2026-07-30 05:23:06 -05:00
brooklyn!
f27d45e288
feat(tui): reach the model picker without wrecking your draft, and switch mid-turn (#74756)
* feat(tui): Ctrl+O opens the model picker without clearing your draft

Reaching the model picker meant typing /model, which forces you to wipe
whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay
directly, leaving the composer untouched. Ctrl+O is added to the textInput
pass-through allowlist so the composer doesn't swallow it, mirroring the
existing Ctrl+X session-switcher path.

* feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it

Picking a model while a turn was streaming hit a 4009 'session busy' reject:
switch_model() mutates the agent's model/provider/base_url/client in place and
the worker thread reads those every iteration. Now config.set queues the pick
in session[pending_model_switch] and _apply_pending_model_switch applies it on
the turn thread at the next turn start, before any model call — no race, no
interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically
and notes '(applies next turn)'.
2026-07-30 10:18:30 +00:00
brooklyn!
dd4eadcf79
A finished turn ends on its changed files (#74732)
* feat(desktop): open the review pane on a given file

toggleReview is a toggle, so it can't back a "take me to the diff" affordance
-- pressing it when the pane is already up hides the thing you asked to see.
revealReview is the open-only half (toggleReview now calls it for its own open
branch), and openReviewForPath goes one further: refresh, then select the file.

A tool reports the path it wrote absolute while git reports repo-relative, so
the two are matched on the tail. fileEditPath is exported for the same reason
-- the caller needs the same path the tool row derives.

* feat(desktop): derive a turn's changed files from its tool parts

A finished turn already carries everything the summary needs: each file-edit
tool part holds the path it touched and the inline diff it produced. Folding
those into one row per file, with repeat edits to a file summed, means the card
costs no extra git probe.

Only landed edits count -- a call still running has no result, and a failed one
changed nothing.

* i18n(desktop): copy for the changed-files card

* feat(desktop): close a turn on its changed-files card

A turn that edited files now ends with a summary panel: one row per file with
its +/-, a Review action opening the diff pane, and a row click opening that
file's diff.

It rides only the newest message. The card describes a working tree, and that
tree has moved on by the next turn -- so rather than leaving a trail of stale
cards down the transcript, sending the next message retires it. While the turn
is still streaming the selector returns a stable empty list, so the tool rows
narrate the edits and the delta stream never re-renders the card.
2026-07-30 10:11:58 +00:00
hermes-seaeye[bot]
3b47e0c436
fmt(js): npm run fix on merge (#74751)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 10:11:53 +00:00
brooklyn!
9650f555d0
Merge pull request #74668 from NousResearch/bb/composer-attach
Attaching a file, folder, or link works like a picker, not a syntax
2026-07-30 05:01:45 -05:00
brooklyn!
19556db610
Merge pull request #74747 from NousResearch/bb/loading-bg
fix(desktop): match zone loading bg to chrome
2026-07-30 04:57:18 -05:00
brooklyn!
1ee75fc473
Merge pull request #74746 from NousResearch/bb/chat-widget-chrome
Inline chat widgets share one shell
2026-07-30 04:54:33 -05:00
Brooklyn Nicholson
7c59101c4a fix(desktop): give every tab strip the standard right-click tab menu
Right-clicking a tab that has no domain menu of its own — the main tab on a
fresh draft, the file tree, a terminal — fell through to the zone strip's
menu, which offered Split right/down/left/up and Hide header. Session tabs
never showed it (SessionTabMenu stops the event), so the split menu only ever
appeared on the surfaces least likely to want it.

ZoneMenu now renders the same verbs a session tab's menu does — Close, Close
others, Close to the right, Close all — over the shared ActionsContextMenu
kit, so both menus stay identical, above the strip's own header/minimize
toggles.

The Split actions were the only caller of splitTreeZone -> splitGroupZone, and
Move was the only caller of adjacentGroup; both chains are removed along with
the now-unused direction strings in every locale. Everything else the menu did
is still reachable: Move by dragging the tab, Hide header by double-tapping
the strip (and from a session tab's Hide tab bar), Minimize from the header
chevron.
2026-07-30 04:54:11 -05:00
brooklyn!
5e807390fd
Merge pull request #74487 from NousResearch/bb/update-eol-churn
fix(update): repair managed checkouts still running core.autocrlf=true
2026-07-30 04:53:24 -05:00
Brooklyn Nicholson
c999dc2e8e refactor(desktop): one .ref class, and the theme owns every accent
A reference had two styling systems: a Tailwind class string assembled in
TypeScript (`directiveChipClass`) and a separate `link-chip` for prose links,
each carrying its own color-mix(). Same concept, three appearances.

Now every inline reference — a composer chip, a sent message's mention, a
markdown link, a completion row's glyph — is `class="ref"` plus
`data-ref="<kind>"`, and styles.css owns the accent. No hex or color-mix()
ships from a component, so a skin restyles all of them at once.

Keying the accent on `[data-ref]` alone rather than `.ref[data-ref]` also lets
the popover's icon column take a kind's hue without inheriting its inline-text
layout.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
d83d296473 refactor(desktop): one reference vocabulary behind @ and / alike
`@` and `/` were two menus that happened to live in the same file: `@` rows
were horizontal with an icon, `/` rows were stacked with none, and each kept
its own hand-maintained icon map. Picking a file and picking a skill felt like
features from different apps.

Adds reference-kinds.ts — one table mapping every kind a reference can be
(file, folder, url, image, tool, line, terminal, session, git, diff, staged,
command, skill, theme, emoji) to its icon, accent, and section label. Both
surfaces that show a reference now read from it:

  - the popover row, browsing for one
  - the chip, having picked one

so a thing is the same colour with the same glyph wherever you meet it, and a
row looks like the chip it will become. `/` rows gain icons in the process,
which is what the shared layout gives them for free.

Chips lose their pill: no background, no padding, no border, just the icon and
coloured text. A filled badge turns every mention into a UI element the eye has
to step over, and the icon plus accent already carry the kind. Slash pills are
the same component — SLASH_CHIP_BASE_CLASS is now literally DIRECTIVE_CHIP_CLASS.

Emoji rows stay icon-less: the emoji is its own glyph.

Also drops three duplicated definitions (ICON_PATHS, SLASH_ICON_PATHS,
SLASH_CHIP_VARIANT) and an inline copy of DirectiveIcon inside SlashChip.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
c17053c4f7 perf(desktop): cache @ path completions like / completions already are
The `/` path has had a completion cache since the skills-scan work; the `@`
path never got one. Every keystroke was an uncached round trip behind the 60ms
debounce, so walking a tree — Tab in, Backspace out, retype a segment — paid
full price for paths it had just listed.

Measured in-process against this repo (8,036 files): `git ls-files` ~38ms,
ranking ~12ms. The backend already caches the file list for 5s, so the fix
belongs in the renderer: reuse the existing cache module with a short 15s TTL
(a directory listing, unlike the command catalog, can change under the user)
keyed on cwd + session + query, and wire `isCached` so a warm query skips BOTH
the debounce and the loading state.

That last part is what makes it feel instant rather than merely fast — a
spinner over an answer already in hand reads as latency the user isn't paying.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
47180dec83 fix(desktop): one label per reference, on every surface
Picking a folder showed three different names for it: the popover row said
`desktop/`, the editor mid-browse said `apps/desktop/`, and the committed chip
said `desktop`. Each surface derived its own label from the value.

Upstream keeps ONE label on the directive node and hands it to every consumer
verbatim (`DirectiveNode.__label = item.label`, rendered by `decorate()` and
carried through `:type[label]{name=id}`). Our wire format is `@kind:value`,
which can't carry a label, so the same invariant is held by deriving both ends
from refChipLabel: the popover row now shows exactly what the chip will show,
and the commit path passes the picked row's label into the chip rather than
letting it re-derive one.

refChipLabel keeps the directory for the reason it already keeps a URL's path —
a bare basename can't tell two references apart, and `src`, `index.ts`, and
`main.tsx` repeat all over a repo. Browsing into apps/desktop/ only to be
handed a chip reading `desktop` throws away the context you navigated for.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
1f72da4f88 test(desktop): cover the directive-scope contract
Nine cases against the real hook and a real contentEditable: the scope
surviving Tab-descend, Backspace climbing the path then dropping the scope
whole, a mid-message pick keeping its trailing prose, a paste consuming an
open scope, and the guards that keep @teknium1: / localhost:8080 from being
mistaken for a directive.

text-utils.test.ts picks up the additive `value` field and asserts the
scope/value split directly.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
cf5b6feae8 feat(desktop): show the active @ browse scope as a popover header
With the scope no longer sitting in the editor as raw syntax, the popover is
where it belongs: a FOLDERS / FILES / URL header above the list, so the filter
reads as the mode it is instead of as characters the user has to finish.

Reuses the existing group-header style the slash menu already renders — no new
chrome.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
130cc0b859 fix(desktop): pasting into an open @url: scope no longer doubles the directive
A pasted link linkifies into an `@url:` directive, so pasting one while an
@url: scope was already open stacked a second directive on the first and
submitted `@url:@url:` around the link. The leftover prefix rendered as
literal text in front of the chip.

insertComposerContentsAtCaret takes a consumeBefore length; both composers
pass the open scope's span so the scope is consumed by the paste rather than
left sitting in front of the chip.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
4fb4d78989 fix(desktop): keep the browse scope through Tab, Backspace, and a mid-message pick
Three defects in the commit engine, all from treating the scope as loose text:

Tab-descend rebuilt the token as a bare `@apps/desktop/`, silently widening an
explicit @folder: browse back to files and forcing the committed chip to
re-guess its kind from a trailing slash. It now carries the scope down.

Backspace only handled a path, so at `@folder:` it fell through to character
deletion and nibbled back out through the directive syntax one key at a time.
It now drops the scope as one unit, mirroring Tab's one-key descent.

The rebuild fallback sliced tokenLength off the END of the draft, which assumed
the trigger was the last thing in the editor — a pick made mid-message chopped
the trailing prose off and stranded a partial `folder:` in front of the chip.
rebuildAroundCaret splits around the caret instead, and is shared with the
ascend path and the edit composer rather than hand-rolled three times.

Also drop the chip's auto-inserted trailing space when the caret already has
whitespace after it, so a mid-sentence pick doesn't leave a double space.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
fb5efe917a feat(desktop): parse the @kind: prefix as a browse scope
detectTrigger split an `@folder:apps/desk` token into one opaque query, so
every consumer downstream had to re-parse the prefix — or, more often, treat
it as characters the user was expected to maintain by hand.

Split it into `scope` + `value` at the source, behind a known-kinds list so a
handle like @teknium1: or a host:port stays ordinary text, and add
openDirectiveScope() for callers that need to know the caret is sitting in an
empty scope.
2026-07-30 04:52:06 -05:00
Brooklyn Nicholson
1486046b36 fix(desktop): paint the zone floor with the chrome surface
The pane zone used the card/editor token, so session loading flashed white
against the titlebar and tab strip. Chat already paints its own surface;
this only fixes the empty floor underneath.
2026-07-30 04:49:06 -05:00
Brooklyn Nicholson
a48f1b6bda docs(desktop): record the widget shell and thread hairline
Named contracts are maintained with the code, so the token table gets
--ui-widget-surface-background and the chat section gets the two rules a
new inline widget has to follow.
2026-07-30 04:47:39 -05:00
Brooklyn Nicholson
277a6f944a fix(desktop): put the transcript's hairlines on one stroke token
A markdown table's border came from border-border (--dt-border, the
app-wide default) while the expanded tool block beside it used
--ui-stroke-tertiary. In dark mode the default resolves fully opaque and
the table glared next to everything around it.

Every bordered surface in the thread -- tables, fences, blockquotes,
callouts, media cards, attachments -- now uses --ui-stroke-tertiary,
which DESIGN.md already names the in-panel hairline. That also retires
the /45 /50 /55 /60 /70 opacity one-offs, five ad-hoc dilutions of one
color, so retuning the hairline is a single edit.

--dt-border keeps its 57 call sites outside the transcript.
2026-07-30 04:47:39 -05:00
Brooklyn Nicholson
148cb80771 feat(desktop): one shell for the transcript's inline widgets
Clarify and the artifact card were the only two tool results that render
as a panel, and neither looked like the other: clarify sat on a 2px
radius over the chat backdrop's own color, so the card was invisible
except for its hairline; the artifact card used a hardcoded 10px radius
and no fill at all.

Both now wear WIDGET_SHELL_CLASS -- one radius a rung above the
composer, one fill, no border. --ui-widget-surface-background is the
card token in light mode and a touch below it in dark, where a raw card
fill sits above the chrome and reads as a lit panel. It derives from
--ui-bg-editor so a skin's own card seed carries through.
2026-07-30 04:47:28 -05:00
hermes-seaeye[bot]
11089899fb
fmt(js): npm run fix on merge (#74740)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 09:42:51 +00:00
brooklyn!
c581ad402e
Merge pull request #74736 from NousResearch/bb/clear-draft
feat: double ESC discards the draft; make Ctrl+U/K line-scoped
2026-07-30 04:34:21 -05:00
brooklyn!
9d75c418ad
Merge pull request #74693 from NousResearch/bb/cmd-backspace
fix: Cmd+Backspace and Cmd+ForwardDelete reach the readline kill bindings
2026-07-30 04:31:48 -05:00
Brooklyn Nicholson
6d5b3e2520 docs(tui): list Esc Esc in the hotkey panel; correct the Ctrl+U/K wording
The kills are line-scoped now, so "delete to start / end" overstated them.
2026-07-30 04:27:44 -05:00
Brooklyn Nicholson
01022d737a feat(tui): double ESC discards the draft, even mid-stream
Mirrors the CLI binding. Placed above the isBlocked early-return so it
works while the agent streams — that is the whole gap, since Ctrl+C
interrupts a running turn and only clears the composer when idle.

Pushes the draft to history before clearing so Up recalls it.
2026-07-30 04:26:48 -05:00
Brooklyn Nicholson
59a7da9a89 fix(tui): scope Ctrl+U / Ctrl+K to the current line, per readline
Both kills operated on the whole buffer: Ctrl+U wiped everything before
the cursor and Ctrl+K everything after, regardless of newlines. Readline
scopes them to the current logical line, and Claude Code documents Ctrl+U
as "repeat to clear across lines in multiline input" — which only works
if a press at a line boundary consumes the newline and makes progress.

Extract killToLineStart / killToLineEnd and route all four call sites
through them, so the Cmd chords and their Ctrl equivalents cannot drift.
2026-07-30 04:26:42 -05:00
brooklyn!
94924c6430
Merge pull request #74674 from NousResearch/bb/yolo-palette
Put YOLO in ⌘K and show each toggle's live state
2026-07-30 04:25:18 -05:00
brooklyn!
1ae1eb47bc
Merge pull request #74729 from NousResearch/bb/no-auto-filetree
Opening the diff pane no longer opens the file tree
2026-07-30 04:23:18 -05:00
CK iRonin.IT
c1ec394160 feat(cli): double ESC discards the draft, even mid-stream
Ctrl+C interrupts a running turn and only clears the composer when idle,
so there was no way to discard a half-typed prompt while the agent was
streaming. Claude Code and Gemini CLI both bind that to double-Esc.

Appends the draft to history before clearing, so Up recalls it — the same
undo affordance Claude Code gives, which is what makes this safe on a key
people hit by reflex. Excluded when a modal prompt is up, since those bind
ESC eagerly and cancel should still win.

Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
2026-07-30 04:20:41 -05:00
Brooklyn Nicholson
7996ec66cd test(desktop): match the switch and disclosure labels to what they render
These assert the accessible name, and the name now states the action rather
than the word toggle: an enabled toolset offers "Turn X toolset off", a
collapsed project offers "Show X sessions".
2026-07-30 04:18:17 -05:00
Brooklyn Nicholson
6cb1aa44ed fix(desktop): opening the diff pane no longer opens the file tree
`revealTreePane` un-collapsed a pane's column by calling the side's bound
store setter. On the right that setter IS `setFileBrowserOpen` — the file
tree's own toggle — so anything sharing that column dragged the tree open
with it: ⌘G on the diff, a session tile reveal, `focus_pane`.

Un-collapse the column directly instead. The tree now opens only when its
own toggle is pressed. `revealPreview` had a local workaround for exactly
this, which the fix makes redundant.
2026-07-30 04:16:34 -05:00
brooklyn!
dd241cf0cd
Merge pull request #74644 from NousResearch/bb/double-tap-heart
feat(desktop): double-click a message to heart it
2026-07-30 03:56:01 -05:00
Brooklyn Nicholson
640056704c Merge remote-tracking branch 'origin/main' into bb/yolo-palette
# Conflicts:
#	apps/desktop/src/app/command-palette/index.tsx
2026-07-30 03:49:03 -05:00
Brooklyn Nicholson
3f5ec8b45d fix(tui): Cmd+Backspace kills the line; Option/Ctrl+Backspace stay delete-word
On terminals that send Cmd+Backspace as a CSI-u sequence rather than
rewriting it to Ctrl+U, the keystroke fell through to the word-delete
branch and erased a single word.

The modifier this hinges on is easy to get wrong. isActionMod accepts
key.meta on macOS, and hermes-ink reports Option as meta — so keying on
it would turn Option+Backspace, the platform's delete-word shortcut, into
delete-the-whole-line. On Linux/Windows isActionMod is key.ctrl, where
Ctrl+Backspace is likewise delete-word in readline, VS Code, browsers,
and Windows Terminal. Only the super bit, set by kitty CSI-u and xterm
modifyOtherKeys, unambiguously means Cmd.

Extract that decision into isLineKillModifier so the reasoning lives in
one place and the regressions above are pinned by tests.
2026-07-30 03:39:34 -05:00
Brooklyn Nicholson
8d112c05f7 fix(cli): route Cmd+Backspace and Cmd+ForwardDelete to the kill bindings
Terminals that rewrite Cmd+Backspace to Ctrl+U already reach
unix-line-discard. Kitty keyboard protocol and xterm modifyOtherKeys
terminals instead report Cmd as the super modifier bit, producing CSI
sequences prompt_toolkit has no entry for — the raw bytes fall through
the VT100 parser and land in the buffer as literal text.

Alias those to the readline kill bindings prompt_toolkit already ships.
Backspace is a CSI-u codepoint (127); ForwardDelete is a CSI tilde key,
so its modifier rides in the CSI 3 ; mod ~ form rather than CSI-u.
Ctrl+ForwardDelete keeps its own binding — that is delete-word on
Linux/Windows, not kill-line.
2026-07-30 03:39:26 -05:00
hermes-seaeye[bot]
9accf79d83
fmt(js): npm run fix on merge (#74702)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 08:19:58 +00:00
Brooklyn Nicholson
129b4e7fef feat(desktop): make the ⌘K row note a shared primitive with a state variant
A toggle's on/off isn't a passive fact like a version string — the row is about
to change it. It keeps the label's color so the two read as one phrase, and
earns its separation from a faint underline instead of going muted. Both
variants live in floating-hud.ts beside the other shared HUD chrome.

That underline needs room to exist: flex items are blockified, so the note's
truncate/overflow-hidden bites, and the app's global 0.25rem offset sits at the
bottom edge of a text-xs line box. The state variant drops truncate (one word,
nothing to clip) and pulls the offset to 2px.

Flipping a setting isn't navigation either, so toggles keep the palette open the
way the theme and color-mode rows already do, and selecting a keepOpen row bumps
a counter that rebuilds the groups so the note can't report the state it left.

Also: yolo is lowercase, and logs gets its own icon instead of borrowing the
YOLO bolt.
2026-07-30 03:18:13 -05:00
Brooklyn Nicholson
2c71a37ad1 fix(desktop): clear double-click heart lint errors 2026-07-30 03:16:42 -05:00
Brooklyn Nicholson
b69d4e5a78 feat(desktop): double-click a message to heart it
The iMessage gesture, on the same opt-in toggle as the rest of reactions —
double-click any message and it gets a heart; double-click again and it comes
off. Off by default, and while it's off the message root carries no listener
at all.

The gesture is deliberately narrow about what it claims: only a true
double-click (detail === 2, so a triple-click to select the paragraph doesn't
re-toggle), and never over an element where a double-click already means
something — links, buttons, inputs, code blocks. It clears the word selection
the browser just made, since the tapback is what the gesture meant.

Reaction state for the handler is read lazily off the message runtime at
event time rather than subscribed to, mirroring how the footer already reads
its text: the handler renders nothing, so subscribing the message root to
every reaction change would be cost for no paint.
2026-07-30 03:16:42 -05:00
Brooklyn Nicholson
9d589b92d3 refactor(desktop): one hook for a message's reactions
The assistant footer and the user bubble each carried the same block: two
metadata reads, the three-store merge, and a local-first toggle that paints
before it persists. Same code, two files, and the next surface that wants to
react would have been a third copy.

useMessageReactions owns it now, with commitReaction as the single write path
so every caller applies identical tapback semantics.
2026-07-30 03:16:41 -05:00
brooklyn!
d437d3e54d
Merge pull request #74665 from NousResearch/bb/palette-perf
perf(desktop): ⌘K opens instantly, whatever else the shell is doing
2026-07-30 03:08:58 -05:00
hermes-seaeye[bot]
b5ca900508
fmt(js): npm run fix on merge (#74694)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 08:08:51 +00:00
Brooklyn Nicholson
0481766561 perf(desktop): make ⌘K cost nothing until it opens
⌘K is an overlay that is stateful to itself — pressing it owes the user a
frame immediately, whatever else the shell is doing. It was not built that
way.

`CommandPalette` is mounted for the life of the app, and its body ran
unconditionally: a dozen store subscriptions (connection, desktop version,
client + backend update status/apply, keybinds, worktrees, theme, i18n),
three `useQuery`s, and the group builders that assemble a few hundred rows.
`<Portal>` renders nothing while closed, so none of it was ever visible —
but all of it still ran. An in-flight update rewrites `$updateApply` on
every progress line, and each of those rebuilt the entire row set for a
surface nobody could see.

Split the body into `CommandPaletteBody`, mounted only while the palette is
on screen. A closed palette is now one store subscription. The body is keyed
by open count, so per-open state (search, sub-page) resets by remount and
the explicit close-reset effect goes away, and `mounted` lags `open` by the
150ms exit animation so Radix can still play `data-[state=closed]` instead
of the overlay vanishing.

Rows additionally move behind `useDeferredValue` in their own memo
component. Because that component mounts with the portal, the deferred
initial value applies per open: the first commit is the frame + input, and
the several-hundred-row list arrives in an interruptible follow-up render
rather than blocking the frame the keypress asked for. The empty state is
suppressed while rows are still pending so opening doesn't flash "no
results".

The `enabled: open` gates on the three queries are dropped — the component
only exists when open, so they are inherently lazy, and react-query still
serves a reopen from cache while revalidating.
2026-07-30 03:01:05 -05:00
Brooklyn Nicholson
09fa5c063c fix(desktop): make ⌘K group order the priority it already was
Group order is the only tiebreaker rankGroups has (stable sort), and ties are
the common case: "yolo" matches both "Toggle YOLO" and a worktree named
bb/yolo-palette as a whole word. Branch rows sat second and won, burying the
command under a list that grows with whatever's checked out.

The order now reads as the priority: where you're going, what you can do, what
you can configure, then the typed-only lists, and branches last.

Also pulls the muted detail back to ~4px — it reads as a suffix of the label,
not another flex item at the row's icon-to-label gap.
2026-07-30 03:00:58 -05:00
brooklyn!
c55159f185
Merge pull request #74683 from NousResearch/bb/osc-tab-title
fix(tui): split terminal tab title from window title
2026-07-30 02:58:04 -05:00