* fix(ui): actionable error and doctor warning for missing widget sandbox origin
* fix(ui): phrase sandbox-origin diagnostics as conditional guidance
* test(ui): satisfy deadcode and mock-factory gates for sandbox diagnostics
The .sidebar-view-archived svg rule set stroke: currentColor but never
fill: none, so the archive icon's lid <rect> rendered with SVG's default
black fill — a solid black bar instead of the outlined Lucide archive
box. Align the rule with sibling sidebar icon styles (fill: none,
1.7px stroke, round caps/joins).
* feat(system-agent): agentic cached caretaker greeting with quick actions
* test(gateway): pin caretaker greeting in session-ownership tests
* fix(system-agent): offer agent handoff chip only when a model is available
* fix(ci): typecheck narrowing, lint, and knip gates for greeting module
* fix(system-agent): reject structured greeting output on every retained line
* test(gateway): split caretaker welcome tests under the max-lines gate
* feat(anthropic): import Claude Desktop custom groups
* fix(anthropic): resolve Claude Desktop groups from LevelDB entries
Group discovery regexed whole decompressed blocks, so matches were not
attributable to a Local Storage value and byte order decided the winner.
On a real 5.4MB store that mislabelled 59 of 159 sessions, surfacing a
mojibake label spliced out of Snappy copy-record bytes.
Parse SSTable entries properly instead: prefix-delta keys bounded by the
restart array, newest internal sequence per user key (honoring deletions),
and record scanning confined to a single value. Values are normalized so
Chromium's UTF-16 form scans like Latin-1, and unflushed WAL writes keep
precedence over SSTables.
Prod LOC grows ~77; it buys structural correctness in place of ordering
luck, and folds the old index-only walk into one shared entry decoder.
Verified against the live store: 159 assignments, 0 mislabelled versus an
independent entry-level ground truth, 0 control-character labels.
* fix(ui): sort custom session groups ahead of project groups
Custom groups were pushed into the section list as encountered, so their
position depended on roster order rather than the documented behavior;
the existing test only passed because its fixture happened to be ordered
that way. Collect custom and project groups separately and concatenate,
and assert the guarantee with a reversed-input case.
* fix(anthropic): widen LevelDB fixture key type for test typecheck
The prefix-delta helper assigned a Buffer into a Buffer-typed accumulator
whose generic argument differed, which tsgo rejects in the test lane.
* chore(anthropic): drop release-owned changelog edit from the PR
CHANGELOG.md is generated at release time, so a normal PR must not carry
an entry. The release-note context lives in the feature commit message and
the PR body instead.
* fix(ui): make stale-chunk Reload survive the gateway restart
The 'A new version is available' banner appears precisely because the
gateway was just updated — so when the user clicks Reload, that gateway
is usually still restarting. The handler probed once and, on failure,
silently fell back to revalidating the same replaced chunk, so the
button looked dead and a manual hard reload was the only way out.
Poll the document probe until the gateway answers (bounded, so an
unreachable gateway still degrades to the recoverable panel error
rather than a fatal navigation), and show a disabled 'Reloading…' state
while waiting.
* fix(ui): enforce the reload wait deadline locally
The bound was only checked after each probe settled, so it relied on the
probe timing itself out. The default probe does self-abort, but a
caller-supplied one need not, and a probe that never settles would leave
the Reload button disabled forever. Race each probe against the
remaining deadline instead.
* fix(ui): satisfy lint and deadcode gates for the reload retry
CI caught two things local oxlint does not: the wait default returned
the timer id from a Promise executor (eslint no-promise-executor-return),
and retryStaleChunkReload became an unused export once the lazy-route
button moved to the polling variant.
Wrap the executor body, and collapse the two near-identical retry paths
into one: the polling helper with timeoutMs: 0 is exactly the old
single-shot behavior, which the stylesheet-recovery banner keeps.
* fix(ui): keep the reload wait bounded across interval waits
The deadline was checked before the interval wait, so a wait that
carried past it left the next iteration taking the unbounded
single-shot branch — a probe that never settles would then strand the
disabled Reload button past its own bound. Only the first attempt may
probe from outside the window.
* fix(clickclack): accept omitted nullable managed-channel fields and auto-open the discussion panel
* fix(ui): open the discussion when write access arrives after it resolved
* refactor(ui): extract the steered-message lifecycle into a typed state machine
The steer flow was an implicit state machine spread across five chat modules,
with chip states encoded in loosely coupled optional fields and duplicated
guard predicates. This extracts it behind structural types:
- steered-chip.ts: leaf chip model — SteeredChip union (in-flight requires
sendState "steering"; acknowledged requires pendingRunId and forbids
sendState), constructors, and narrowing guards; impossible states are
unrepresentable and every call site uses the guards
- steer-lifecycle.ts: owns the steer send flow, single terminal retirement
pipeline (remember -> materialize acked chips -> clear), and history-proven
retirement; absorbs queued-user-turn.ts
- terminal handling in chat-gateway/chat-state and the stored-outbox history
verification in chat-send consolidate onto the shared helpers; chat-send.ts
shrinks by ~300 lines; net prod LOC is flat
- behavior fixes riding the pipeline: acknowledged chips retire once
authoritative history contains their user turn (no more brief chip/history
double display) and lingering chips keyed to a filtered run id now retire on
the next history load
- isTerminalFailureChatSendAck moves to chat-send-contract.ts so ack
narrowing stays type-safe at every formatter call site
* fix(ui): satisfy type, lint, and dead-export gates for the steer-lifecycle split
- restore the snapshotChatAttachments helper (oxc no-map-spread fires on the
inlined form)
- select the stored-outbox drain head with an explicit loop; the find-predicate
form made TS narrow away the failed state and flagged the guard (TS2367)
- drop the unused chip re-exports from steer-lifecycle and the consumer-less
isSteeredChip guard; chip types stay module-local, guard consumers import
the steered-chip leaf directly
* style(ui): oxfmt line joins in chat-send and chat-state
* feat(ui): accept drag-and-drop attachments in new-session composer
The new-thread composer only accepted attachments via paste and the +
menu; dragging a file onto it was silently ignored. Wire the shared
chat attachment drop handling onto the composer shell with the same
balanced drag affordance as the chat pane, and gate both composers'
drop/dragover cancellation on file drags so text/URL drops keep the
textarea's native behavior.
* fix(ui): cancel non-file drops outside editable composer targets
A URL dropped on the transcript, header, or composer chrome would hit
the browser default and navigate the app away, discarding drafts. Keep
native text/URL drops only when the drop target is an editable control;
cancel them everywhere else in both composers.
* fix(ui): treat disabled and readonly inputs as non-editable drop targets
A URL dropped precisely on a disabled composer textarea would still hit
the browser default and could navigate away. Check actual editability
(disabled/readOnly/isContentEditable) instead of selector shape.
* fix(ui): limit native drops to text-entry inputs
Enabled non-text inputs (checkbox, range) counted as editable drop
targets, so a URL dropped on one skipped cancellation and could
navigate the browser away. Restrict the native-drop exception to
text-capable input types, textareas, and contenteditable content.