Commit Graph

2513 Commits

Author SHA1 Message Date
Brooklyn Nicholson 9e7700e8a3 fix(desktop): title HUD windows Hermes HUD
The floating HUD inherited the default Hermes title from index.html.
Set it explicitly in main and the renderer so the OS window label
matches the mode.
2026-08-10 16:19:50 -05:00
brooklyn! 920beecfa8 perf(desktop): long streaming agent sessions — steady window cut, stable rows, stepped backfill, pane-shared budget (#83446)
* test(desktop): stress long agent sessions in the multitab perf scenario

--tools seeds every transcript with settled tool rounds and drives the live
stream as a working agent turn (tool calls opened and completed between text
chunks), and each tile reveal is timed to next paint (reveal_max_ms) so deep
transcripts report their mount cost.

* perf(desktop): hold the transcript window cut steady while streaming

A fresh weight-walk per store flush slid the cut forward one message at a
time, ~30x/s, and every slide re-indexed the whole windowed transcript —
each row rendered a different message and the runtime repository took its
O(window) rebuild path instead of the one-message update. advanceTranscriptWindow
anchors the cut to a message id and re-cuts once per ~half page of new
content instead of once per flush.

* perf(desktop): stable rows, stepped backfill, pane-shared render budget

Three thread-list fixes for long streaming sessions: memoize the visible-
groups slice and each turn row so a budget-cut advance no longer re-renders
every mounted turn per streamed token; raise the first-paint backfill in
BACKFILL_STEP slices (one bounded commit per frame) instead of a single
20-to-600 transition whose commit landed as a 780ms freeze mid-stream; and
share RENDER_BUDGET across mounted panes so a 4-way grid mounts a quarter
page per pane instead of 4x the fibers.
2026-08-10 21:06:59 +00:00
hermes-seaeye[bot] 1b5da4aacf fmt(js): npm run fix on merge (#83409)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 19:40:25 +00:00
embwl0x 8edcdd1a80 fix(desktop): isolate plugin render hooks 2026-08-10 10:57:35 -07:00
hermes-seaeye[bot] 03fa32c92d fmt(js): npm run fix on merge (#83143)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 11:07:34 +00:00
brooklyn! 6735e6a10a Merge pull request #83138 from NousResearch/bb/titlebar-tahoe-fullscreen
fix(desktop): skip titlebar Y nudge on Tahoe and macOS fullscreen
2026-08-10 05:56:55 -05:00
hermes-seaeye[bot] 86d747e00d fmt(js): npm run fix on merge (#83139)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 10:55:40 +00:00
Brooklyn Nicholson 4ed6f4f7f1 fix(desktop): satisfy no-extra-boolean-cast in fullscreen guard 2026-08-10 05:50:10 -05:00
brooklyn! 3139a30e52 perf(desktop): multi-tile grids stop lagging — evict leaked session states, index lineage aliases, split the turn journal (#83133)
* fix(desktop): evict settled session states nothing on screen references

Closing a tile never removed its runtime's entry from $sessionStates, so
every tile ever closed parked its full transcript in the map for the life
of the process. Each leftover entry taxes every subsequent stream flush —
the map is spread-copied per delta and the busy/attention/draft projections
walk every entry per publish — so the app got slower the longer it ran,
which users read as "I need to clean my sessions/dbs".

Publish now evicts a settling state when no tile and not the primary view
holds its runtime (transition side effects still fire, so the settle keeps
its unread dot), and closing a tile drops an already-settled state on the
spot. Busy and needs-input states stay: background turns feed the sidebar
dots, and a first publish always lands because a resume can publish a beat
before the surface binds the runtime.

16 tiles streaming in a 2x2 grid with a day's worth of closed-tile residue:
worst-second 34 -> 58 fps, p99 frame 90 -> 28 ms, longtasks 37 -> 0.

* perf(desktop): index lineage aliases per sessions-list reference

lineageAliases scanned the whole recents list per call, and it is called
per cached session state per status projection per message delta — with a
populated sessions DB and a few busy sessions that multiplied out to
millions of row checks a second during streaming. Build the alias index
once per list reference (the list is replaced wholesale, never mutated)
and look aliases up in O(1).

* perf(desktop): journal each in-flight turn under its own storage key

The v1 journal kept every session's tail in one localStorage key, so each
throttled write re-parsed and re-stringified EVERY busy session's snapshot
— a grid of concurrent streams turned that into a whole-store JSON round
trip dozens of times a second, all on the main thread. Per-session keys
make a write O(own tail) no matter how many other sessions are streaming.
A v1 store migrates on first touch; expired/overflow crash residue is
pruned once per renderer.

* perf(desktop): stress the multitab scenario across grid/streaming/DB axes

The one-stack multitab run hid every cost this round of fixes removed: it
drove hook.publish (store only — no journal, no wiring cache), with an
empty recents list and no closed-tile residue. Streaming now routes through
hook.update (the real gateway write path), and the scenario grows axes for
the workloads users actually hit: --zones splits tiles across visible grid
zones, --streaming caps how many sessions are mid-turn (zone leaders
first), --sessions seeds a lived-in recents list, --dead models settled
sessions no surface references. launch.mjs pins HERMES_DESKTOP_CDP_PORT so
a non-default --port survives the app's own dev-CDP flag.
2026-08-10 10:45:43 +00:00
Brooklyn Nicholson 4f9d345956 fix(desktop): skip titlebar Y nudge on Tahoe and macOS fullscreen
Tahoe already aligns traffic lights without the optical translate. In
fullscreen, drop windowButtonPosition in the main process and clear the
right-cluster inset so traffic-light dodge chrome goes away on both sides.
2026-08-10 05:45:29 -05:00
hermes-seaeye[bot] c08b086013 fmt(js): npm run fix on merge (#83132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 10:44:00 +00:00
Brooklyn Nicholson 422b256939 fix(desktop): sort hud snap imports for eslint 2026-08-10 05:26:38 -05:00
Brooklyn Nicholson 440e6fb640 fix(desktop): list HUD snap chord in keyboard shortcuts panel
Document ⌘⇧G as a read-only global shortcut active while HUD mode is up.
2026-08-10 05:21:07 -05:00
Brooklyn Nicholson 9c75e4863f feat(desktop): snap HUD to cursor with global ⌘⇧G
Register CommandOrControl+Shift+G in main while HUD mode is open so the
floating bar can jump under the pointer from any app. Tap-to-snap only —
Electron globalShortcut has no keyup for hold-to-follow.
2026-08-10 05:21:05 -05:00
brooklyn! 5a3920b734 Merge pull request #83115 from NousResearch/bb/browser-pane-resize
fix(desktop): in-app browser kept squishing the chat and jamming its resize sash
2026-08-10 05:07:01 -05:00
brooklyn! 75dad8b15c fix(desktop): stop HUD window growing on drag; add corner resize handle (#83091)
* fix(desktop): stop HUD window growing on drag; add corner resize handle

The HUD window is created frame:false + transparent:true + resizable:true.
On Windows, a transparent frameless window silently grows ~1px per
setPosition call (worse at >100% DPI scaling) — every drag of the composer
bar accumulated size drift, and the HUD could end up enormous (reported at
1385x1052 against a 620x320 default). Reading the size back mid-drag
compounds the drift because getSize() returns the already-drifted value.

Fix, mirroring the pet overlay's pattern:
- create the HUD window non-resizable (no system edge resize hot-zone)
- moveBy uses setBounds with a size snapshotted on the first move of each
  drag, so the OS can never accumulate drift (verified: 500 moveBy calls
  with zero size change on Electron 40 / Win11 / 175% DPI)
- add a bottom-right corner resize handle (resize-handle.ts) driving a new
  hermes:hud:set-bounds IPC that flips resizable on for the call, restoring
  the ability to resize a window that is otherwise non-resizable

* fix(desktop): pin HUD drag size in renderer, not main-process globals

The superseding pass drops hudDragWidth/hudDragHeight from main: composer
drag snapshots outerWidth/outerHeight when the hold arms (pet overlay
pattern) and passes them on every moveBy. Adds one test for that contract.

Supersedes #82455.

Co-authored-by: Ringo6107 <199014580+Ringo6107@users.noreply.github.com>

* fix(desktop): keep the HUD solid through a corner resize; drop dead handle state

The resize handle's `resizing` flag only fed a CSS rule that restated the
cursor it already had, so nothing pinned the window mid-gesture: click-through
hands the mouse away the moment the growing edge outruns the cursor. Raise the
composer drag's existing `data-hud-grabbing` instead — one flag for "a gesture
owns the window" — and cover it in click-through's tests.

Also drops the hook's always-true `enabled` param and routes teardown through a
`reset` callback, matching composer-drag.ts and clearing the atom-mirrored-ref
lint rule.

---------

Co-authored-by: Ringo6107 <199014580+Ringo6107@users.noreply.github.com>
2026-08-10 10:05:14 +00:00
Brooklyn Nicholson 33ac4bce56 fix(desktop): satisfy eslint on pane-share-memory test 2026-08-10 04:59:26 -05:00
Brooklyn Nicholson 92ed8be923 fix(desktop): reopen docked tiles at their last split share 2026-08-10 04:52:45 -05:00
Brooklyn Nicholson d67337ee1a fix(desktop): keep min-width floors on stacked flex zones 2026-08-10 04:52:44 -05:00
Brooklyn Nicholson 7267adafdf fix(desktop): don't let webview guests swallow drag gestures 2026-08-10 04:52:19 -05:00
hermes-seaeye[bot] 54e33c95a6 fmt(js): npm run fix on merge (#83099)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:47:13 +00:00
Brooklyn Nicholson a843bcc940 fix(desktop): sort titlebar import for eslint 2026-08-10 04:30:58 -05:00
Brooklyn Nicholson d1f2e32b13 fix(desktop): titlebar clusters — macOS Y nudge, 24px targets, 13.9px icons
Left cluster gets a macOS-only translate to sit on the traffic-light row.
All titlebar tools use 24×24 hit areas with 13.9px Codicons (inline size
beats unlayered codicon.css). Clusters share one flex shell with no gap —
buttons abut and the hit target is the spacing.
2026-08-10 04:24:42 -05:00
hermes-seaeye[bot] 92dd230c8c fmt(js): npm run fix on merge (#83078)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 09:07:07 +00:00
brooklyn! b278dcb2d3 Merge pull request #83052 from NousResearch/bb/sidebar-all-profiles
Sidebar: show every profile at once
2026-08-10 03:57:47 -05:00
Brooklyn Nicholson 7e1f4f6f36 fix(desktop): ship the sidebar grouped by date in every scope
The all-profiles scope defaulted to grouping by profile, so "Reset to defaults"
handed back a grouping the user never picked. Both scopes now ship by date, and
a reset clears the scope you are not looking at too — otherwise flipping the
rail restored the customization the reset was supposed to undo.

Hovering a row's PR chip also holds the kebab back now: the chip is a link, and
the button that covers the end of the trailing slot was taking the click.
2026-08-10 03:50:34 -05:00
Brooklyn Nicholson 8de786c7a7 fix(desktop): give every row's trailing metadata one right-aligned slot
The PR and profile chips rendered in the row body, left of the kebab's own
column: they never sat flush right and never handed their space to the kebab
on hover, so a row showing only a PR left a hole where the age would have been.
Both now join the tokens/cost/age figures in the actions slot, and the kebab
covers the end of it — losing whichever item reads last, not the whole slot.
2026-08-10 03:35:57 -05:00
Brooklyn Nicholson 6c371e944c feat(desktop): fade the sidebar's scrollbars out until you're in the list
A thumb parked on a list you aren't touching is chrome, not information,
and the sidebar stacks several scrollers so it draws several of them at
once. Fade them in on hover instead, sharing the existing scrollbar
colors and the webkit/Firefox split rather than styling a second kind of
bar. Only the thumb's color changes, so the reserved gutter still keeps
rows from shifting sideways.
2026-08-10 03:24:08 -05:00
Brooklyn Nicholson 771b214516 feat(desktop): show every profile's sessions in the sidebar
All-profiles mode listed a flat page of chats and stopped there: the
project tree was the active profile's, grouping and filtering had no
notion of an owner, and each profile lane paged itself against a
separate endpoint. Multi-agent workflows live across profiles, so the
sidebar now treats the owner as a first-class axis.

Group by profile (the default in this scope, with its own persisted
choice so flipping the rail doesn't reset how you read one profile),
filter by profile, and start or import one from the same menu. Profile
groups take the project row's shape rather than a hand-rolled header,
preview the same three sessions a project does, and carry their whole
tokens-and-spend total in the slot the kebab hovers over.

Grouped lanes now rank by the active sort key, before they trim
themselves, so the rows a group hides are the ones the sort ranked last.

Defaults live in one const: the sidebar ships grouped by date, sorted by
recency, with the timestamp pinned — and "Reset to defaults" puts back
exactly that.
2026-08-10 03:13:08 -05:00
Teknium 7e1bfeab88 fix(desktop): read-only keyless plugin rows + backend contract v6
Rework of the salvaged #82828 compatibility layer: keep the crash guards
(optional key, safe filter/search, synthetic React row identity) but drop
the name-addressed toggle fallback — bare names collide across category
dirs (image_gen/fal vs video_gen/fal), which is exactly why the backend
moved to key-addressed toggles (a60b492e07). Keyless rows from a
pre-contract backend now render with a disabled switch and an 'update
your backend' tooltip instead of resurrecting the collision-prone
protocol.

Bump DESKTOP_BACKEND_CONTRACT / REQUIRED_BACKEND_CONTRACT to 6 so the
existing skew toast surfaces the real remedy (one-click backend update)
on session open.
2026-08-10 01:13:08 -07:00
Brooklyn Nicholson 8fdb92f449 fix(desktop): hoist the sidebar's sort key out of the flat list
The sort key was applied where the flat recents list is assembled, so it
did nothing at all once rows moved into groups: picking "cost" while
grouped by project or profile left every lane in the order the backend
sent it. Rank in a store instead, above any one view, so a grouped
surface can order the rows it owns by the same key.
2026-08-10 03:13:08 -05:00
fangliquanflq 03a9c69dc6 fix(desktop): preserve keyless plugin row identity 2026-08-10 01:13:08 -07:00
fangliquanflq 6e19c20d0a fix(desktop): support keyless plugin rows 2026-08-10 01:13:08 -07:00
notkisk 481ccdafb7 fix(desktop): keep react-router in one runtime chunk 2026-08-10 02:04:47 -04:00
hermes-seaeye[bot] e400dca96e fmt(js): npm run fix on merge (#82962)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 05:40:43 +00:00
joaomarcos abd85a94bc fix(gateway): keep the personality pivot out of the truncate ordinal space (#82756)
`truncate_before_user_ordinal` is an index into the list of *real* user
turns. The gateway builds that list with `role == "user" and not
display_kind`, and `test_prompt_submit_truncate_ordinal_skips_display_kind_rows`
already pins why: "Without the filter, a trailing marker shifts the ordinal
so the wrong message is targeted for truncation."

`_apply_personality_to_session` broke that invariant at the producer. Its
pivot marker rides as `role=user` — deliberately, so strict
OpenAI-compatible providers accept it mid-conversation (the same reason
`_append_model_switch_marker` does) — but unlike the model-switch marker it
carried no `display_kind`. The gateway therefore counted it as a real user
turn while no client ever renders it as one.

After a personality change the two sides address different lists: every
later rewind/edit/regenerate resolves one slot too early, and
`replace_messages()` hard-DELETEs the extra span. That is the reported
signature — an in-range, valid ordinal, `confirm_truncate: true`, and a cut
that moved backwards with no user rewind action.

Tag the pivot like the model-switch marker, and teach the desktop to
project the kind as a timeline row so a persisted marker is never rendered
— or counted — as a user turn on the client side either. Both ends must
exclude it; excluding it on only one end just inverts the drift.

The regression test drives the real injection point rather than a
hand-written marker dict. Without the fix it fails with "the pivot shifted
the ordinal: the cut landed at 3 instead of 5", losing a turn the user
never asked to drop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 11:01:15 +05:30
ethernet 3bd844edf1 fix(desktop): make un-highlighted code readable while streaming in light theme
streaming code blocks in the light theme render near-white text on the
white code card until shiki's highlight lands, then snap to normal token
colors. the pale text is @tailwindcss/typography's pre foreground: its
prose theme styles pre as a dark slab (--tw-prose-pre-code = gray-200 on
a gray-800 bg). we strip the bg for our own code card but the near-white
foreground survives on the container. shiki's opaque per-token span
colors normally hide it — it shows through wherever text renders without
spans: the streaming delay window, the lazy-chunk suspense fallback, and
over-budget blocks that never highlight.

traced on the live renderer: computed color on the wrapper of mid-stream
code was oklch(0.928 0.006 264.531) (gray-200), supplied by the
.prose :where(pre) rule.

fix: prose-pre:text-foreground on the markdown container, so every
fenced path inherits the transcript foreground instead. the utility
layer is emitted after typography's base rule in the built css, so the
override wins by order at equal specificity.
2026-08-09 19:29:59 -04:00
hermes-seaeye[bot] e34c4c009a fmt(js): npm run fix on merge (#82771)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 22:43:38 +00:00
Teknium aaf0fbfa95 test(desktop-ssh): cover wrapper preservation and explicit-path passthrough in locateHermes
Replaces the canonicalization test (which pinned the behavior #74425
removes) with wrapper-preservation coverage for auto-detection and an
explicit remoteHermesPath, both asserting no python3 -c parser call is
issued. Verified both fail against the pre-fix implementation.
2026-08-09 15:35:25 -07:00
webtecnica 8b752603c4 fix(desktop-ssh): stop resolving exec-wrappers to python in locateHermes (#74411)
Problem 1: resolveLauncher() read bash 'exec <python> <script>' wrappers
and returned ONLY the python interpreter path, discarding the script.
This made probeHermesVersion() run '<python> --version', which always
printed 'Python x.y.z' instead of the Hermes version. And
remoteSupportsSshOwnership() ran '<python> serve --help' which failed
entirely because no 'serve' module exists in the python stdlib.

Problem 2: When the user set remoteHermesPath (an explicit override),
resolveLauncher() resolved it to the python interpreter, replacing the
user's specified path. The override was effectively ignored for version
checking and capability probing.

Fix: resolveLauncher now returns the candidate path directly. The hermes
binary or wrapper script is already executable and handles argument
forwarding (e.g. 'exec <python> <script> "$@"') correctly on its own.
No additional remote SSH round-trip or python script needed.
2026-08-09 15:35:25 -07:00
ethernet c002b6fbe5 fix(desktop): send full tool args so expanded rows show the whole command
The gateway sent only an 80-char preview (context) for a tool call.
The desktop rebuilds the expanded tool row from the args of the part.
When the args were absent, the row showed the preview, and long
commands ended in '...' after the user expanded them.

Two paths had this fault:

- tool.start: the payload had no args until tool.complete, so the
  expanded row was truncated while the tool ran. Now tool.start ships
  the args, the same as tool.complete already does.
- _history_to_messages: the projection read the full arguments, then
  discarded them. Hydration from this projection (watch windows,
  compress, branch, seeded create) kept only the preview, so the
  truncation was permanent. Now tool rows carry the args. This
  projection is the display view of the transcript — each renderer
  decides what to paint, and the preview stays for collapsed titles.

The DB rows do not change: the args already persist in tool_calls.
2026-08-09 17:48:37 -04:00
Teknium 244d296646 fix(personality): single-owner personality state + one-time reset migration
Personality persistence used to be split per surface: the TUI/desktop wrote
the NAME to display.personality while the CLI/gateway wrote rendered TEXT
into agent.system_prompt (and their /personality none only blanked the
text, leaving the name behind). When #81946 made display.personality
authoritative everywhere, stale names written long ago resurrected
personalities users had turned off - kawaii defaulting on after updating.

- hermes_cli/personality.py: new single owner of personality state.
  Built-in personality definitions, neutral-name normalization, rendering,
  availability (built-ins overlaid by agent.personalities), overlay
  resolution, and the ONLY sanctioned persistence path
  (persist_personality -> display.personality; never agent.system_prompt).
- v34 config migration: one-time reset of display.personality to none
  (announcing which personality was cleared and how to re-enable), plus a
  scrub of agent.system_prompt when it verbatim-equals a known personality
  render (machine-written by the old CLI/gateway). Hand-written manual
  prompts are never touched.
- All surfaces rewired through the module: CLI /personality (incl. active
  marker in the list), gateway /personality, TUI config.set + slash path
  (which previously applied without persisting), TUI config.get (reports
  the EFFECTIVE personality), completer, hermes config display, and the
  tui_gateway health probe.
- cli.py/config duplicates removed: built-ins now defined once; the
  desktop mirrors them from one lib module (src/lib/personalities.ts).
- Docs updated: selection lives in display.personality, built-ins always
  available, one-time reset note.
2026-08-09 10:33:58 -07:00
hermes-seaeye[bot] 28e401c30b fmt(js): npm run fix on merge (#82468)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 11:49:06 +00:00
brooklyn! a4970d07d5 Sidebar filter menu (#82463)
* feat(desktop): resolve a session's pull request

A session row can say whether its work is open, merged or closed, and link
to it. The join is the session's own repo + branch, asked of GitHub in one
batched GraphQL request per repo (branch aliases, not a `gh pr list` page
that a busy repo crowds ours out of), through the remote-aware git facade so
a desktop on a remote gateway asks the backend's `gh`.

Two ways a session's branch can't answer, both covered:

- It ran on trunk. Fork PRs share our branch namespace, so asking about
  `main` badges a stranger's PR onto it — trunk is never asked about, and
  cross-repository PRs are dropped server-side either way.
- It worked in a worktree, so the branch it recorded at start isn't where
  the PR came from. Creating a PR from the review pane binds the session to
  the branch it actually used, and for sessions that predate that, the PR is
  recovered from the transcript: `gh pr create` prints a bare PR url and
  nothing else, so a tool result whose whole output is one is a claim rather
  than a mention. Scanned read-only across profiles, once per session ever.

* refactor(desktop): one profile glyph

The rail, the profiles page and the session-row chip each drew the same
tinted initial square from scratch, so a row tag could disagree with the
rail about a profile's color. One component owns the square, its tint, and
the home icon the default profile gets instead of a letter.

* feat(desktop): sidebar filter menu

The sessions header's project/list toggle was one binary choice standing in
for a view. It becomes a menu: group by date, project or status; order by
updated, created, status, tokens or cost; show tokens, cost, PR, profile or
an always-visible timestamp per row; filter by status, pull request, project
or archived. Everything persists, and one reset puts it all back.

The pieces that make it read right:

- Status groups reuse the date dividers rather than inventing a second
  separator, and a magnitude sort (tokens, cost) drops the calendar
  entirely — "Today" above the priciest session you have ever had is a lie.
- Row metadata shares the trailing slot the kebab covers on hover, so only
  the last fact steps aside and the number you switched on stays readable.
- A filter deepens the loaded page to 300 rows and hands the window back
  when cleared, so "merged PRs" doesn't quietly answer for the last 50.
- Archived is a view of its own set, and dragging is still what picks a
  manual order — the menu only offers a way back out of one.
2026-08-09 06:41:05 -05:00
Brooklyn Nicholson 868d2161b7 test(desktop): stub repoStatusForCwd in the review store tests
Binding a new PR to its session reads the repo's live branch, which the
suite's coding-status mock didn't provide.
2026-08-09 06:34:04 -05:00
Brooklyn Nicholson 83b8ca1e12 feat(desktop): sidebar filter menu
The sessions header's project/list toggle was one binary choice standing in
for a view. It becomes a menu: group by date, project or status; order by
updated, created, status, tokens or cost; show tokens, cost, PR, profile or
an always-visible timestamp per row; filter by status, pull request, project
or archived. Everything persists, and one reset puts it all back.

The pieces that make it read right:

- Status groups reuse the date dividers rather than inventing a second
  separator, and a magnitude sort (tokens, cost) drops the calendar
  entirely — "Today" above the priciest session you have ever had is a lie.
- Row metadata shares the trailing slot the kebab covers on hover, so only
  the last fact steps aside and the number you switched on stays readable.
- A filter deepens the loaded page to 300 rows and hands the window back
  when cleared, so "merged PRs" doesn't quietly answer for the last 50.
- Archived is a view of its own set, and dragging is still what picks a
  manual order — the menu only offers a way back out of one.
2026-08-09 06:21:15 -05:00
Brooklyn Nicholson 9c73cbe66a refactor(desktop): one profile glyph
The rail, the profiles page and the session-row chip each drew the same
tinted initial square from scratch, so a row tag could disagree with the
rail about a profile's color. One component owns the square, its tint, and
the home icon the default profile gets instead of a letter.
2026-08-09 06:21:08 -05:00
Brooklyn Nicholson 21aaa8b4f8 feat(desktop): resolve a session's pull request
A session row can say whether its work is open, merged or closed, and link
to it. The join is the session's own repo + branch, asked of GitHub in one
batched GraphQL request per repo (branch aliases, not a `gh pr list` page
that a busy repo crowds ours out of), through the remote-aware git facade so
a desktop on a remote gateway asks the backend's `gh`.

Two ways a session's branch can't answer, both covered:

- It ran on trunk. Fork PRs share our branch namespace, so asking about
  `main` badges a stranger's PR onto it — trunk is never asked about, and
  cross-repository PRs are dropped server-side either way.
- It worked in a worktree, so the branch it recorded at start isn't where
  the PR came from. Creating a PR from the review pane binds the session to
  the branch it actually used, and for sessions that predate that, the PR is
  recovered from the transcript: `gh pr create` prints a bare PR url and
  nothing else, so a tool result whose whole output is one is a claim rather
  than a mention. Scanned read-only across profiles, once per session ever.
2026-08-09 06:21:04 -05:00
hermes-seaeye[bot] 8295d24736 fmt(js): npm run fix on merge (#82417)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 09:51:41 +00:00
brooklyn! c8318460e4 feat(desktop): read the window below through Hyprland's IPC (#82226)
`read_window_below` enumerates through get-windows, which on Linux reads
`_NET_CLIENT_LIST_STACKING` via xprop. That is an X11 protocol, and Wayland
deliberately refuses to tell one application about another's windows. Under
XWayland it is worse than nothing: it finds the few legacy X11 clients and
silently misses every native Wayland window, which on a Hyprland desktop is
most of them — so the HUD floats over an app it cannot name.

Hyprland answers the question directly. `j/clients` on its command socket
returns every window with class, title, position, size, pid and focus history.
Ask it first when HYPRLAND_INSTANCE_SIGNATURE is set, fall back to get-windows
everywhere else, and keep the picking logic shared and unchanged.

Three things the provider has to get right, all covered by tests: order comes
from focusHistoryID rather than the list; windows on other workspaces are
dropped, since they share coordinates with the visible ones and would win the
overlap test; and our own window is left out, because focus history is not
stacking order — the HUD floats on top while the user works underneath it, so
slicing after ourselves would skip past the very app we are trying to report.

One request per tool call, opened and closed immediately: Hyprland evaluates
this socket synchronously and freezes until a five-second timeout on a
connection left hanging.
2026-08-09 04:43:10 -05:00