Commit Graph

4143 Commits

Author SHA1 Message Date
brooklyn! befff02e9b Merge pull request #73150 from NousResearch/bb/session-tab-slots
desktop: ⌘1–9 maps to visible tabs
2026-07-28 01:18:52 -05:00
Brooklyn Nicholson b6c7df6cb6 fix(desktop): sort session-row imports for eslint 2026-07-28 01:13:30 -05:00
Brooklyn Nicholson 319bcedfea desktop: open sessions where they already are
Notifications, ⌘K, switcher, session picker, cron/command-center, artifacts,
and @session refs all go through openSession. Plain click/Enter focuses the
existing tile or main; ⌘-click/⌘-Enter opens a new tab; ⇧⌘ still pops a window.
2026-07-28 01:13:30 -05:00
Brooklyn Nicholson e67e4604db desktop: shared openSession door for focus-or-route
Centralize open-in-place / tab / window so every surface can jump to an
already-open tile or main tab instead of forcing main.
2026-07-28 01:13:30 -05:00
Brooklyn Nicholson f65fa01926 desktop: ⌘1–9 slots map to visible tabs, not raw pane indices
Focus layout keeps `files` in the workspace group's panes while chrome-
hidden. Slot keys walked that raw array, so ⌘2 landed on the first session
chip and every later number was off-by-one (especially after a ⌘W main-tab
shift). Index the same shown filter the strip paints, and cycle the same.
2026-07-28 01:12:43 -05:00
Brooklyn Nicholson 589fd23947 test(desktop): cover floating pane adoption and live drag
Adoption: a floating contribution stays out of the tree while a docked
sibling in the same pass is adopted, so the exclusion is specific rather
than a broken run.

Live DOM: mounts FloatingPanes and drives real pointer + resize events —
anchored spawn, drag, persistence across remount, edge-riding on resize,
the titlebar floor, collapse, and the no-drag button opt-out.
2026-07-28 01:11:12 -05:00
Brooklyn Nicholson aad7f128bb feat(sdk): expose FloatingAnchor to plugins
Plugins previously had to fake a floating pane by registering into a
chrome slot and rendering position:fixed themselves, which broke inside
any transformed ancestor and reimplemented drag, clamping, and
persistence each time.
2026-07-28 01:11:12 -05:00
Brooklyn Nicholson f78521785f feat(desktop): render floating panes above the layout tree
Adds placement: 'floating' — the one non-tiling pane role. Adoption skips
those contributions so one can never become a track that steals width
from a zone; the tree renders them as fixed cards instead, draggable by
the header, with position and collapse persisted per pane id.

The card reuses HUD_SURFACE (command palette, session switcher) rather
than hand-rolling a second float surface.
2026-07-28 01:11:05 -05:00
Brooklyn Nicholson 6dd1c6c062 feat(desktop): floating pane geometry
Pure clamping/anchoring rules for a pane that lives outside the layout
tree: keep 48px grabbable at either horizontal edge, hard-bound the top
below the titlebar so the drag handle stays reachable, pin top-left
rather than invert when the card outgrows the viewport, and let an
edge-anchored card ride its edge as the window resizes.

No DOM, so the rules are testable directly.
2026-07-28 01:10:59 -05:00
Brooklyn Nicholson c883367bd2 fix(agent): keep interrupt-checkpoint scaffolding out of the steered transcript
A mid-stream steer persists an interrupted-turn checkpoint so the model knows
its reply was cut off. That scaffolding — "[This response was interrupted by a
user correction.]" and the "Visible response before the interruption:" header —
was written straight into message content, so every reload painted the raw
machinery as an assistant bubble (and merged it into the preceding tool-call
bubble). Steered transcripts became unreadable.

Reuse the existing display/replay split instead of inventing new surface:
- Carry the scaffolded form in the server-only api_content sidecar (the exact
  bytes replayed to the provider), keep content the user's/agent's real words.
- When nothing reached the screen there is no clean form, so mark the row
  display_kind=hidden — replayed to the model, dropped by every transcript
  surface, exactly like compaction-reference rows.
- Honor display_kind=hidden in the gateway's _history_to_messages projection
  (it only sniffed the [System: convention), so the checkpoint can't leak
  through the live/resume path to the TUI/CLI either.

The model still receives the full interrupted context on the wire; the
transcript shows the partial reply and the user's correction.
2026-07-28 01:05:02 -05:00
brooklyn! 5b22bd9556 Merge pull request #73105 from NousResearch/bb/tighter-turn-block-gap
Tighten the transcript's scaffolding rhythm, give diffs room
2026-07-27 23:52:49 -05:00
Brooklyn Nicholson 070093a318 feat(desktop): on by default for dev-server runs
Gating this behind an opt-in was the wrong call. A dev server already
executes arbitrary local JS — vite's module graph, every postinstall in
node_modules — so a loopback debugging port does not meaningfully widen
what a `npm run dev` session can already do, and `perf:serve` has opened
one unconditionally all along.

Requiring the variable also defeated the point: the tooling exists to be
reached for mid-task, and a capability you must remember to enable before
launching is one you don't have when you need it.

So the port opens on 9222 — the same port scripts/eval.mjs and
scripts/perf/lib/cdp.mjs already default to — for any dev-server run.
HERMES_DESKTOP_CDP_PORT stops being an on-switch and becomes an
override: a different port, or `off` to disable.

The hard gate is unchanged and still checked first: a packaged build
never opens the port, and no env value talks it into it. Neither does an
unpackaged `electron .` against dist/, which is how the packaged app
gets smoke tested.

Refusals only log when they contradict something the developer asked for
(a typo'd port, an explicit `off`). Packaged and dist runs are closed by
design and stay quiet.
2026-07-27 23:52:33 -05:00
brooklyn! 4cee9aab61 Merge pull request #73075 from NousResearch/bb/drop-leva-backdrop-controls
refactor(desktop): drop leva from the chat backdrop
2026-07-27 23:48:10 -05:00
brooklyn! 0ae2997345 Merge pull request #73110 from NousResearch/bb/composer-at-chip
Chip @ file/folder refs instead of dropping them as plain text
2026-07-27 23:46:14 -05:00
Brooklyn Nicholson 16ef964fdb feat(desktop): rest the thinking caret at a faint hint instead of invisible
A run of thinking headers is the one place the disclosure affordance isn't
otherwise discoverable — every other one sits in a row you're already
reaching for. The resting opacity becomes a token so only that surface opts
in; DisclosureRow is shared with tool rows and run headers, which keep the
invisible-until-hover default.
2026-07-27 23:27:35 -05:00
Brooklyn Nicholson 2881243df3 fix(desktop): tighten the gap between adjacent transcript scaffolding
The turn block gap is the space between the reply and the work around it, so
a back-to-back run of thinking headers and tool rows spent the full gap
between every line and read as a stack of cards rather than one column.
Adjacent scaffolding now ticks at a third of it.

The hook is the block list the rhythm rule already enumerates, minus prose,
rather than `data-conversation-scaffold`: that marker is absent on a
multi-call tool group — most of a real run — and present on rows nested
inside one, so it both missed the lines that needed tightening and moved
ones that didn't.

An open file edit is the exception. A diff is the deliverable, read like a
PR, so it keeps the full block gap on both sides while the scaffolding
around it stays tight. A streaming turn seals blocks into separate bubbles
where the flex gap would restore the full gap, so that seal is corrected to
match — and skipped when either side carries a diff.
2026-07-27 23:27:35 -05:00
Brooklyn Nicholson 6254c568c8 feat(desktop): opt-in renderer debugging port for dev runs
The renderer is a Chromium page, and apps/desktop already carries a whole
CDP toolkit for it — scripts/eval.mjs, scripts/perf/lib/cdp.mjs with its
shared SELECTORS map, and the diag-*/probe-* family. None of it can
attach to `hgui` or `npm run dev`, because neither passes
--remote-debugging-port. The only launcher that opens one is
`npm run perf:serve`, which is a separate isolated instance rather than
the app you're looking at.

Add HERMES_DESKTOP_CDP_PORT. When set, the shell opens a CDP port on
loopback so that existing tooling can read the live DOM: computed
styles, geometry, which rule actually won.

Three independent gates, all required, resolved by a pure function in
electron/dev-cdp.ts so the policy is testable without an Electron app:

  1. not packaged — a shipped build never opens the port, and this is
     checked first so no env combination can talk it into doing so;
  2. HERMES_DESKTOP_DEV_SERVER present — an unpackaged `electron .`
     against dist/ is how the packaged app gets smoke tested, so it
     behaves like the packaged app here;
  3. the port explicitly requested and a valid integer.

Default `npm run dev` is unchanged and silent: no port, no nag. An
opt-in that gets refused always logs why, so nobody loses an hour
wondering what isn't listening.

The address is pinned to 127.0.0.1 rather than left to Chromium's
default, and is deliberately not configurable — there's no reason to
expose a renderer debugger off-host and offering the knob invites
someone to try.

scripts/eval.mjs hardcoded :9222 and threw a raw ECONNREFUSED stack when
nothing was there. It now honours the same variable and explains itself.
2026-07-27 23:23:47 -05:00
Brooklyn Nicholson 90797eb224 fix(desktop): chip a bare @path into the ref it means
Tab-descending into a folder from the @ popover re-types the token as a
bare `@apps/desktop/` so the next complete.path lists its children. That
is right while typing, but a bare token is not a reference —
REFERENCE_PATTERN only matches @kind:value — so a draft sent with one
rendered as plain text and attached nothing at all.

Promote it to @file:/@folder: on the way out, the same shape url-refs.ts
uses for bare links: on the committing space, on paste, and on submit for
a path that never got its space. A '/' is required so a handle like
@teknium1 is never mistaken for a path.
2026-07-27 23:13:32 -05:00
hermes-seaeye[bot] 3af7b867fd fmt(js): npm run fix on merge (#73107)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 03:59:15 +00:00
Brooklyn Nicholson 73f8ddbb8b fix(desktop): stop hoisting an @ ref that is already inline in the prose
displayContentForMessage re-derived every ref from the attached context
block and re-emitted it as a detached list above the text. Now that the
token survives expansion, that list duplicated the inline chip. Hoist
only the refs the prose is missing, so turns persisted by an older
backend still render their chips.
2026-07-27 22:55:24 -05:00
brooklyn! 56aacbdb01 Merge pull request #73101 from NousResearch/bb/queue-double-send
feat(desktop): double-Enter sends the queued turn
2026-07-27 22:50:51 -05:00
Brooklyn Nicholson ac8310bcc2 test(desktop): cover the busy empty-Enter double-send 2026-07-27 22:43:42 -05:00
hermes-seaeye[bot] 9f4a6fdf4a fmt(js): npm run fix on merge (#73096)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-28 03:43:07 +00:00
Brooklyn Nicholson 84d77b608c feat(desktop): double-Enter sends the queued turn, and the row says Enter
Empty Enter while busy was a hard no-op, so a queued turn could only be
sent early via the panel's send arrow or Cmd+Shift+K. With prompts
queued, a second Enter now promotes the head and interrupts, mirroring
the idle empty-Enter drain. Nothing queued keeps the old no-op.

The panel's send-now row action switches from a bare up-arrow to the
return glyph, so the row states the keybind the double-send uses.
2026-07-27 22:42:21 -05:00
brooklyn! ef26701134 Merge pull request #73089 from NousResearch/bb/desktop-sidebar-counts
Drop the remaining session counts from the sidebar
2026-07-27 22:33:09 -05:00
Brooklyn Nicholson 67ea00327d style(desktop): drop the remaining sidebar session counts
#72336 removed the count from the flat list and #72912 from the entered
project, but three sites still tallied rows: search results, each
messaging group (Telegram, Discord, …), and cron jobs.

With them gone SidebarCount never renders a count — its one caller is
the projects-loading spinner — so rename it SidebarSectionMeta.
2026-07-27 22:27:26 -05:00
Brooklyn Nicholson 058c4376d2 refactor(desktop): drop leva from the chat backdrop
The backdrop shipped a leva control panel — a Shift+Y dev tweak surface
for opacity, blend mode, filters and a radius scalar — that has never
been touched since the app landed. Its hooks ran in every build, dev and
packaged alike: only the panel's visibility was gated on
`import.meta.env.DEV`, not the two `useControls` calls or the zustand
store behind them. Inline the values it was already rendering with and
delete the dependency; leva was the only thing importing it in the tree.

One of those controls was doing real damage. `--radius-scalar` shipped as
0.6 in styles.css, but the slider's 0.2 default was written onto the root
element on mount, so every window that mounts the backdrop rendered at
0.2 and every window that doesn't kept 0.6. The token now declares 0.2 —
the value the chat has actually rendered at all along.

The remaining static values (0.025 opacity, difference blend, 160dvh, top
left, invert) become classes; saturate(1)/brightness(1) were identity and
are dropped.
2026-07-27 22:18:51 -05:00
brooklyn! 202140db53 Merge pull request #73074 from NousResearch/bb/slash-after-command
fix(desktop): keep slash completion alive after a leading command
2026-07-27 22:16:35 -05:00
brooklyn! c1964f977b Merge pull request #73047 from NousResearch/bb/link-brand-icons
feat(desktop): brand icons on links to known domains
2026-07-27 22:16:24 -05:00
brooklyn! 9ed212e096 Merge pull request #73062 from NousResearch/bb/sane-tooltips
fix(desktop): stop tip-wrapping kebab menu triggers
2026-07-27 22:16:10 -05:00
brooklyn! ea0775eb8b Merge pull request #73073 from NousResearch/bb/composer-image-lightbox
fix(desktop): open a composer image attachment in the lightbox, not the rail
2026-07-27 22:16:01 -05:00
Brooklyn Nicholson 015353d970 test(desktop): cover short actions labels and tip-less close buttons 2026-07-27 22:03:37 -05:00
Brooklyn Nicholson 970c4b5336 fix(desktop): shorten Actions aria-labels and tautological copy
Use short static labels (Session actions / Actions) instead of
Actions for <name>. Trim toast fallback, settings echoes, YOLO click
lectures, and fat gateway/appearance/notifications intros.
2026-07-27 22:03:37 -05:00
Brooklyn Nicholson 9b5c15727c fix(desktop): strip Close-X tips and statusbar tip lectures
Drop tips on dialog/overlay/find-bar/review/master-detail close buttons.
Stop tip-wrapping connection/gateway/timer/context/cron/webhook chrome
that already names itself on screen; keep tip only when there's a real
gateway reason. Drop the titlebar swap paraphrase.
2026-07-27 22:03:37 -05:00
Brooklyn Nicholson 5dadacfce7 docs(desktop): ban Close-X tips and click-to chrome lectures
Extend the tip rule past kebabs — no tip on dismiss X, and no tips that
only paraphrase a visible label or say "click to…".
2026-07-27 22:03:37 -05:00
Brooklyn Nicholson eb851d619e fix(desktop): keep slash completion alive after a leading command
Typing a second slash command in the composer went dead whenever the
message started with one. `/work /cle` offered nothing, while `do /work
then /cle` completed fine — which read as an intermittent glitch rather
than a rule.

Two regexes detect a slash, and the `^`-anchored command shape was tried
first. Its argument tail (`(?:\s+\S*)*`) swallows the rest of the line,
so a later `/skill` parsed as an argument to the first command; a command
that takes no options then suppresses the popover outright, and every
slash after the first was unreachable.

Only the first slash can be an invocation, so detect the inline shape
first. It requires a whitespace-preceded slash sitting at the caret, so
it can't take over ordinary argument completion (`/personality alic` has
no second slash) — it fires only where completion was already dead.
2026-07-27 22:02:06 -05:00
Brooklyn Nicholson 71e66f3a11 test(desktop): cover the composer attachment click routing
An image attachment opens the lightbox and leaves the preview rail empty; a
file attachment still opens a rail tab. The lightbox case fails on main.
2026-07-27 22:00:31 -05:00
Brooklyn Nicholson d8cb73b4ab fix(desktop): open a composer image attachment in the lightbox, not the rail
Clicking an attached image routed it through normalizeOrLocalPreviewTarget and
into the right rail, where it rendered as a small contained <img> inside a
pane built for reading and editing files. The bytes were already in hand as a
data URL, so the rail's whole read/edit/reload apparatus was doing nothing for
a picture the user just wanted to look at.

Route images with a resolved thumbnail to ImageLightbox — the same overlay the
thread uses — so the attachment previews at full size with the download action,
and drop the dataUrl/previewKind graft that only existed to make the rail
render bytes it shouldn't have been handed. Non-image attachments, and images
whose thumbnail never resolved, still fall through to the rail unchanged.
2026-07-27 22:00:31 -05:00
Brooklyn Nicholson 2e61feb94e fix(desktop): anchor the inline image download button to the image, not the block
The <img> carried max-w-[min(100%,var(--image-preview-max-width))] while its
container was w-fit max-w-full. A percentage max-width resolves to none while
the container measures its fit-content width, so the container took the full
column while the image stayed capped — and the absolutely positioned download
button, which anchors to the container, drifted into the margin on any image
wider than it is tall.

Move the width cap onto the container and leave the image at max-w-full. The
container now shrink-wraps the rendered image, so right-2/top-2 lands on the
image's own corner at every aspect ratio.
2026-07-27 21:54:05 -05:00
Brooklyn Nicholson df7352aec6 style(desktop): quiet inline links to color-only until hover
The resting chip fill and the bold weight both fought the brand mark for
attention. Links now sit in the theme's primary color at body weight, and
the tint fades in on hover.

Weight is overridden on .link-chip itself: `@tailwindcss/typography` sets
`prose a { font-weight: 500 }`, which outranks a utility class on the anchor,
so the per-call-site classes could never win.
2026-07-27 21:45:42 -05:00
Brooklyn Nicholson 1910d613ee test(desktop): assert kebabs are not wrapped in Tip
Flip the #67500 structure tests that required tooltip-trigger on menu
kebabs; keep coverage that the menus still open on click.
2026-07-27 21:43:57 -05:00
Brooklyn Nicholson 26413bbf57 fix(desktop): stop tip-wrapping kebab menu triggers
Remove the ActionsMenu tooltip prop and Tip wrappers on session, project,
workspace, panel, and credential ⋯ menus. aria-label stays for a11y; drop
the unused credentialActions string.
2026-07-27 21:43:57 -05:00
Brooklyn Nicholson 58714e0e2a docs(desktop): tip only when hover teaches something new
Drop the blanket "every icon* button needs a Tip" rule — it produced
tautological kebab tips like "Actions for <row title>". Menu triggers keep
aria-label; tips stay for unlabeled discovery chrome and keybind hints.
2026-07-27 21:43:56 -05:00
brooklyn! 84858d76ba Merge pull request #73054 from NousResearch/bb/codeblock-chromeless
Chromeless markdown code blocks
2026-07-27 21:42:51 -05:00
Brooklyn Nicholson b3174092f6 chore(desktop): drop the now-unused assistant.tool.code string
The code-block header was its only consumer.
2026-07-27 21:28:00 -05:00
Brooklyn Nicholson 39bde52014 fix(desktop): let the expandable fade match its own surface
The overflow fade hardcoded --ui-chat-surface-background, so inside a code
block it smeared the chat background over the block's own fill. Read an
--expandable-fade-from override with the chat surface as the default.
2026-07-27 21:28:00 -05:00
Brooklyn Nicholson b7fc36cf3c feat(desktop): strip the header chrome off markdown code blocks
A fenced block carried a bordered card with a "Code · <lang>" title row and a
pinned copy button, which read as an attached artifact next to the reply. Drop
the border and the header: the block is now a tinted slab on --ui-bg-editor
with syntax highlighting and a copy button that reveals on hover. The streaming
glow moves entirely to box-shadow since there is no border left to animate.
2026-07-27 21:28:00 -05:00
Brooklyn Nicholson 23459c0361 feat(desktop): show brand icons on links to known domains
PrettyLink leads with the site's mark, so a GitHub PR link reads as a GitHub
link at a glance. The artifacts pane uses the same lookup in place of its
generic chain glyph; unknown hosts render as before.
2026-07-27 21:23:46 -05:00
Brooklyn Nicholson 0d19e93f5d feat(desktop): resolve a hostname to its Simple Icons brand mark
A lookup table of ~170 hosts plus a resolver that walks the hostname's
suffixes, so subdomains inherit their parent's mark (gist.github.com) while
a more specific entry (docs.google.com) still wins over the general one.
2026-07-27 21:23:46 -05:00
brooklyn! fa7b0fcf5d Merge pull request #73035 from NousResearch/bb/clarify-composer-enter
fix(desktop): let a typed message answer past a clarify prompt
2026-07-27 21:09:16 -05:00