#120071 deleted hud-modifier-native.test.ts (darwin-only, so it ran nowhere:
js-tests is ubuntu-only) and its native fixtures. hud-modifier-gesture.h is
the clean-tap state machine both the macOS and Linux XI2 helpers include and
is portable C, so the restored test compiles hud-modifier-gesture.test.c with
cc (xcrun clang on macOS) and runs it inside the electron vitest project,
which js-tests runs via check:test:desktop:platforms. Loosening the 500 ms
tap bound in the header turns it red.
The macOS event-adapter (.m), Windows C# (.cs) and node:test (.mjs) files are
not restored: they need a macOS/Windows toolchain and no JS lane runs there.
composer-text-guard.test.tsx is rebuilt on the REAL useComposerDraft hook (the
deleted file tested an in-test copy of the helper): the mount-time draft
restore must not throw while assistant-ui's composer core is unbound, and must
write through once bound (#49903 / #49488). Removing the hook's try/catch
turns it red.
The two inline-code tests render the real component with the real
styles.css injected and read the computed style of the rendered <code>; they
are cascade outcomes, not source greps. Re-scoping the inline-code rule to the
assistant slot (the original bug) turns both red.
- system-message.test.tsx: background report inline code uses the chat
inline-code tokens (#107486)
- group-chat-view.inline-code.test.tsx: room message inline code uses the chat
inline-code tokens (#114086)
Each entry point forwards spawnPriority through its own branch to the
Electron bridge (getConnection / getConnectionFor); nothing else asserts the
IPC payload for these doors. Sabotage check: dropping spawnPriority from
retainGatewayForAgent's plain-profile branch turns the null-connection test red.
Restored (apps/desktop/src/store/gateway-spawn-priority.test.ts):
- openGatewayForProfile without a priority never tags a dial as foreground
(pre-warms stay background slots, #102281)
- openGatewayForAgent forwards spawnPriority to every registry dial (#102281)
- retainGatewayForAgent tags the lease dial when the caller says foreground (#105104)
- retainGatewayForAgent without options never tags a registry dial (#105104)
- retainGatewayForAgent on the plain-profile route (null connection) still
dials foreground (#110354 branch, gatewayForProfile path)
Review on #120071 (yoniebans) found tests deleted as 'dead' that were only
mis-gated, and regression tests with no remaining equivalent:
- Discord voice receive (real NaCl/RTP: wrong-key drop, DAVE decrypt
failure, malformed padding, non-allowlisted speaker) moves out of the
never-selected tests/integration/ to tests/plugins/platforms/, where no
conftest stubs discord.py; 34 pass with the messaging extra.
- Five *_windows_live.py files had a bare skipif(win32), so
list_os_marked_tests.py never selected them; now windows_only.
- iron-proxy token-swap E2E (opt-in gate) is back.
- Gateway regressions: cached agent iteration cap (#48127), clarify JSON
never renders as progress (#52374), handoff watcher DB work off the event
loop, delivery ledger single connection, checkpoint prune and memory trim
housekeeping ticks.
- Desktop: command-screenshot IPC rejection, inactive WSL bridge never
spawns wsl.exe, bootstrap runner git binary.
Review follow-up. The delete shield on adoptBotSectionsFromMeta was a
session-long Set: once a section was deleted here, a member filed back into
that id later (by another desktop that still has the section) could never
make it reappear on this one, and the module-level Set leaked between tests.
- The shield now lasts only while the delete's member clear is running and
expires when moveBotsToSection(members, null) settles. After that no member
cleared here carries the id, so one that does was filed there again and
adopting it is right.
- A per-delete token keeps an earlier delete's clear (delete -> Undo ->
delete) from lifting the shield while the later delete is still clearing.
- resetBotSectionDeletes() test seam, called from the user-sections beforeEach.
One invariant test: red on the PR head (shield never expires) and red with
the token guard removed (first clear lifts it early); green with this fix.
A delete clears its members one profile write at a time. While a write is
slow (a remote gateway, a pooled local backend waiting for a slot), the
not-yet-cleared members still carry the section's id and name in ui_meta,
and adoptBotSectionsFromMeta (#114983) rebuilt the section the user had just
deleted - under whatever name the member still carried, so a renamed section
came back with its pre-rename name.
- adoptBotSectionsFromMeta skips sections deleted on this desktop this
session; Undo takes the id back out.
- moveBotsToSection stops when its target section is deleted mid-loop, so a
rename re-stamp stuck behind a slow write cannot refile members into the
deleted section afterwards.
Since 10a78f9916 every member that knows its connection stamps its reply
with `from.source` (local members too, e.g. "This device"). The round's
own-entry watermark walk (e00aa13d7d, written against the pre-stamp base)
still only expected a source for remoteSource members, so a local member's
sourced reply did not read as its own: the watermark stayed before it, the
member got its own reply back as "new messages in the room" and answered
itself again, turn after turn, until the round cap. Live, a room that should
settle after one reply kept the Stop button up and posted extra replies.
authoredByMember now uses the same authorship rule as the `(you)` suffix
(isGroupChatSelf: gateway install_id first, then connection label/id, an
unsourced line only for a local viewer), so the two can no longer drift.
Use one shared grapheme-safe initial without widening the existing rail or changing profile identity and color behavior. Adapt the single-initial direction from the competing proposals.
Co-authored-by: Moisés Valero <moisesvs84@gmail.com>
The Archived sessions intro told users to Ctrl/⌘-click a chat in the sidebar to
archive it. That gesture opens the chat in a new tab: resolveSessionRowClick maps
⌘/⌃-click to 'newTab' and only ⌥+⇧-click to 'archive'. Anyone following the copy
never archives anything, and that line is the only in-app hint the gesture exists.
Swap the modifier in the six locales carrying the string, keeping each
translation's wording. The resolver and the row's own comment already document
⌥+⇧ — only the user-facing copy was stale.
Fixes#107999
Adapt David Metcalfe’s proposal to the current update overlay, preserving commit identity, ordering, counts, and the pure formatter’s English defaults.
Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
* fix(state): publish structural state.db corruption as one profile-level state
A structurally corrupt state.db showed up differently on every surface: the
sidebar endpoint returned 200 with empty slices plus an errors row, /api/sessions
returned 500, /api/status said components.storage ok and readiness was green.
None of them said the store was damaged, so Desktop rendered it as deleted
history (#72046).
hermes_state_health is now the single latch, keyed by resolved state.db path:
- SessionDB._halt_db_corrupt, the SessionDB read helpers, the web profile
reader and the readiness probe publish into it, only for structural
corruption (not FTS-scoped damage, not the malformed-schema case the web
open path heals).
- gateway.readiness reports it (state_db degraded/corrupt, and session_store
unavailable/corrupt even when the handle cache says ok), which also feeds
/api/status components.storage (now with reason: corrupt).
- /api/sessions, /api/profiles/sessions and /api/profiles/sessions/sidebar
carry storage: {profile: "corrupt"}; /api/sessions returns 503
state_db_corrupt instead of 500.
- A peer SessionDB handle in the same process refuses writes on a latched
path with the existing StateDbCorruptError, so gateway/agent transcript
diversion and classify_persistence_error keep working unchanged.
The latch never clears on its own and resets on restart, the recovery boundary
StateDbCorruptError already documents.
Co-authored-by: konsisumer <konsisumer@users.noreply.github.com>
* fix(desktop): say the session store is damaged instead of an empty sidebar
The sidebar reads the list endpoints' new storage map into
$corruptSessionStores and renders a persistent destructive Alert above the
session list naming the affected profile(s). The copy says missing chats were
not deleted and points at the non-destructive path (quit Hermes, then
`hermes sessions recover --source <state.db> --inspect-only` or restore a
snapshot) plus the recovery guide; it does not recommend `sessions repair`
for structural damage.
Co-authored-by: konsisumer <konsisumer@users.noreply.github.com>
---------
Co-authored-by: konsisumer <konsisumer@users.noreply.github.com>
When submit cannot prove the pane's runtime owns the selected stored session
(reverse binding lost to eviction, reconnect, or a compression rotation the
selection never followed), it resumes the stored session and continues on
the runtime id that session.resume returns. That path pinned only
activeSessionIdRef. ChatView renders the $sessionStates slice named by
$activeSessionId, so the optimistic prompt, the reply, and every later turn
landed in a slice the pane never painted, while the legacy $messages mirror
(which the ref drives) looked correct. The chat stayed frozen until a relaunch.
Rebind the atom together with the ref, as the session-not-found recovery in
the same file already does, and carry the transcript the pane was showing
into the resumed runtime's empty slice (session.resume omits messages) when
both name the same conversation by lineage. A pane runtime that belongs to a
different stored session is never carried over.
Refs #71733
Refs #117867
Regression tests from #118719: a failed turn the re-submitted prompt truncated
out of the stored history returns to its timeline position, and a failed tail
whose retry is still local-only stays at the end.
preserveLocalAssistantErrors appended every kept local error run after the
refreshed transcript, so an older failed turn repainted below newer turns
(#118002). Reseat each run after the refreshed row that preceded it locally,
and drop a run the refresh already stored under new ids (same role/text
sequence in the gap).
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
The Bot Mode relay drains every registered connection every 30s. The local
route is exempt from relay socket retention (#93594), so each tick dials a
fresh requestGatewayForAgent('local', ...) secondary and disposes it after
the RPC. From the second tick, openSecondary saw the scope in
openedSecondaryScopes, treated the dial as a backend reopen, and called
resetTileRuntimeBindings (added in 613822afff), which drops the runtime of
every tile without an owner route plus the delegate's stored->runtime cache.
Those tiles ride the ambient gateway, so every split pane re-resumed and
remounted its composer every ~30s: caret reset, layout shift, and a reverted
composer model pick on an idle Desktop.
A reopened secondary that is not the window's active scope now resets only
tiles whose ownerRoute names that route (resetRouteOwnedTileRuntimeBindings
plus a drop-only dropRuntimeBindings delegate hook). The active-scope reopen
keeps the window-wide reset, so the stale-runtime guard from 613822afff still
covers the tiles that actually live on that socket.
Refs #108088, #118856
The labelled "Comment"/"Send" button floating inside a one-row compact
textarea read as part of the input and felt cramped. The field now
matches the new-project idea field, the closest analogue (a dialog
textarea with an inset icon action):
- default control padding, the Textarea's standard min height, and 13px
text to match the rendered comments;
- a ghost arrow-up icon button (the chat composer's send glyph) inset
`top-1 right-1`, labelled by aria-label and a Tip ("Comment", or "Send"
while the task runs);
- 16px between the last comment and the field, instead of the list's
12px rhythm.
The running task's requeue row below is unchanged.
- The main column inherits the dialog's conversation text size instead of
a stray text-sm, and its sections breathe on gap-5 so the description no
longer butts against the feed's tab strip.
- Comments sit on gap-3 now that each body is a rendered markdown block.
- The off-scale 0.71rem (diagnostics detail, ready-unassigned callout, run
rows) snaps to the 0.6875rem caption step used everywhere else here.
- Description, result, latest summary and comment bodies go through the
app's MessageTextContent renderer instead of a pre-wrapped <p>, so
agent-written `**Goal:**`, lists and inline code render as they do in
chat. `media={false}`: kanban text is not session-scoped, so `MEDIA:`
paths must not resolve against the active gateway.
- When the feed has more than comments, the segmented control is the
heading (help tip on the same row); the Section label above it only
repeated the active tab ("Comments · 2" twice). A comments-only feed
keeps its label.
- Comment rows put author and time on one line above the rendered body.
- Property rows are Sections, so every sidebar label (including Estimate and
Attachments) uses FIELD_LABEL and one gap rhythm instead of MetaRow's
own 0.65rem label style.
- Values wrap anywhere: the workspace path wrapped nowhere and was
hard-clipped at the modal edge. The raw `dir: ` prefix is gone; a
non-dir kind (scratch, worktree) is a muted badge, the path is mono and
has a copy button.
- Priority uses the board card's amber up-arrow glyph, extracted to a shared
PriorityGlyph so the two stay identical.
- The sidebar no longer starts ~1rem below the main column (py-4 vs no top
padding); both columns share the header's baseline.
- Truncated dependency chips reveal the full linked-task title in a Tip.
The task modal was a hand-rolled fixed overlay: off-token chrome
(--ui-stroke-tertiary border, --ui-bg-elevated fill, bg-black/45 backdrop),
no focus trap, a window-level Esc listener, and dropdowns that portalled to
body underneath the backdrop, which needed a z-(--z-modal-popover) rung on
each menu.
DialogContent owns the shadow-nous / --stroke-nous chrome, the blurred
overlay, focus trap, Esc and outside-click dismissal, and publishes itself as
the portal container, so the status, assignee, actions and model menus open
inside the dialog and the per-menu z-index workaround goes away. The title is
a DialogTitle (the dialog's accessible name), the header actions use Button
icon-xs ghost, and the body sizes to its content up to the old cap instead of
a fixed height that left dead space under short tasks.
The repo's no-native-title lint test forbids title= on buttons (use <Tip>
or aria-label). The chip's visible label already IS the linked task's
title, so the tooltip was redundant; aria-label carries it for a11y.
- Blocked by / Blocks move from the main column into the right property
sidebar (chips resolve titles via link_tasks, short-id fallback kept).
- Main column's Comments/Activity/Runs/Worker-log sections collapse into
one tabbed feed with a segmented control (Jira's 'Show: Comments |
History | Work log'), defaulting to Comments; tabs with no content are
omitted, and the control hides entirely when only comments exist.
The comment composer (live-steer + note-and-requeue) rides the
Comments tab.
- Desktop drawer -> Linear-style modal (smaller than Settings): main column
holds diagnostics, description, result/summary, dependencies, comments,
activity, runs, and the worker log tail; a right property sidebar holds the
inline editors (assignee, model override) plus priority/tenant/workspace/
created rows, estimate, and attachments. Backdrop click or Esc closes.
- GET /tasks/:id gains 'link_tasks' ({id,title,status} per linked task) so
Blocks/Blocked By chips render titles instead of raw ids; older backends
fall back to short ids. Additive; 'links' shape unchanged.
- New backend tests (test_kanban_link_tasks.py) + drawer tests for title
chips and the id fallback.
Review follow-ups. An agent preview of an HTML file whose tab the user had switched to Source stayed in Source, so the agent could believe the page was on screen; the preview-tool route and the status-stack row now pass an explicit rendered mode (renderedHtmlTarget), while Files-pane re-opens still keep the user's pick. The pane read the store tab a second time and kept a test-only local mode; it now reads target.renderMode and offers the toggle only on a tab-backed pane. PreviewRenderMode names the mode once; canRenderHtmlFile is the reduced form; a stale load error or annotate draft no longer flashes for a frame on the switch to Source.
Opening an already-open HTML file from Files re-normalised the tab to preview, so a Source pick was undone and the page ran again; openPreview now keeps the mode the tab is in unless the caller names one. Picking Source on a file with uncommitted changes landed on Diff, because LocalFilePreview mounted with no user mode and its auto mode is diff-first; the pane hand-off now counts as the pick.
Since HTML files open rendered from every source, the PreviewRecordSource argument no longer changed anything; every caller still passed one. Remove the argument, the type and PreviewAttachment's source prop.
Follow-up to the Render | Source toggle for Files-pane HTML: in source mode the pane rendered its own switcher bar above LocalFilePreview's header, so the file showed two stacked header rows where Markdown shows one. LocalFilePreview now takes `onSelectRendered`; when present it adds `rendered` to its own switcher (next to Edit, as for Markdown) and routes the selection back to the pane. The pane-level switcher renders only in rendered mode, above the browser bar.
Also gate the toggle on a renderable target: a remote HTML file whose data URL failed validation arrives as `{ renderMode: 'source', transient: true }` with no `dataUrl`, and offering PREVIEW there would load a `file://` URL of a remote path.
Test asserts PREVIEW and Edit share one header row in source mode, and that the transient source fallback offers no PREVIEW. Prettier on the two lines the base commit left unformatted.
Opening a local HTML file from Files now defaults to the existing
sandboxed preview path, with Source one click away on the same tab.
Co-authored-by: Cursor <cursoragent@cursor.com>