* fix(dashboard): keep agent map rings open while the pointer works inside them
The project and workspace rings drove their hover styling from `:hover` on the
ring element itself. A ring is a sibling of everything drawn inside it, so the
style dropped the moment the pointer crossed onto a workspace or an agent, and
a pan drag killed it outright — pointer capture retargets `:hover` to the
`<svg>` for the whole gesture. Both read as the ring pulsing open and shut.
Move the trigger to the containing group, wrapped in `:where()` so the existing
`.is-open` / `.is-selected` / status rules keep winning the same properties, and
add `.is-held` for the pan, driven by which rings the press landed in. The hold
releases on the first move after the button comes up, when the browser
recomputes `:hover` in that same event, so the two signals never both read off.
Hovering a workspace now also always shows its name and agent count. That label
was gated behind the declutter pass, which suppresses any label overlapping an
agent — at fleet zoom that is nearly all of them, so hover revealed nothing. It
now bypasses declutter and draws in a layer above every project, so a
neighbouring ring cannot wash it out.
* fix(dashboard): complete agent map ring interaction lifecycle
* fix(terminal): skip a too-wide alt frame on snapshot replay
Reopening a parked worktree could paint a stale full-width TUI frame
through a narrower viewport, leaving clipped gutter fragments and
mid-word omissions until the live application repainted.
Replay pins xterm to the snapshot grid so soft-wrapped normal-buffer
history stays exact (#7279), then the post-replay fit returns the pane
to its container grid. Alternate buffers have no scrollback and do not
reflow; an absolutely positioned frame remains at its capture layout,
so narrowing exposes only clipped portions of those fixed-grid rows.
Skip only the visual frame when its capture is wider than the grid the
fit will land on. The alt buffer is still entered and cleared, so the
resize signal lands on a clean screen that the live application can
repaint. Equal-width and wider restores retain the frame, while normal
history always replays at its capture grid before fitting.
The target width comes from proposeDimensions, not terminal.cols: an
unfitted pane can still read xterm's default grid even when its actual
container matches the capture.
* fix(terminal): preserve offline SSH prepaint frame
* fix(terminal): drop a too-wide daemon alt frame on reattach
The renderer-only gate did not run on the user-visible remount path.
Instrumentation showed that daemon-connectResult-snapshot won before
the model-snapshot branches, so the composed snapshot painted in full
and the later narrower fit exposed its stale fixed-grid frame clipped
at the new viewport.
The daemon branch could not omit only the visual frame while main sent
one merged string. Publish the normal-buffer/mode prefix and visual alt
frame as additive optional metadata while retaining the merged snapshot
for mixed-version fallback. New renderers can keep history and restore
state without painting a frame captured for a wider grid.
Replay ordering remains capture-grid, write, then fit so normal-buffer
soft wrapping stays exact. The application owns the foreign-width alt
frame and repaints it after the resize signal rather than Orca trying to
transform an absolutely positioned screen.
* fix(terminal): preserve split daemon snapshot payload
* fix(terminal): preserve live state when dropping alt frame
* fix(terminal): restore DECOM cursor state exactly
* fix(terminal): preserve ordinary snapshot bytes
* test(terminal): cover fixed-grid alt replay resize
* fix(terminal): repaint after dropping mismatched frames
A hidden snapshot can omit an alternate-screen frame before the pane has a measurable target grid. If reveal later lands on the capture grid, a same-size PTY resize emits no SIGWINCH, so pulse the local PTY size whenever that frame was skipped.\n\nKeep performSafeFit's measurable-pane contract intact, publish daemon snapshot prefix and frame as explicit optional strings, and fall back to the merged payload when either field is absent. Cold owner-gone restores now omit a mismatched frame while retaining history and fresh-shell reset treatment; offline SSH preconnect remains unchanged.\n\nPin the vendored SerializeAddon out-of-range-row behavior used to capture live SGR state.
* improve automations page ui
* Improve automations page UI: fix menu interactions, extract components,
- Extract status cell to reusable component
- Fix portaled context menu clicks re-selecting rows
- Use explicit selection IDs instead of resolved objects for reliable detail state
- Close detail when selection is lost during refresh or deletion
- Improve escape key navigation through nested detail views
- Use native input for search field to avoid unwanted shadow elevation
- Add i18n keys for table and button labels
* fix: merge duplicate type imports in AutomationListLocalRows test
Static analysis fails under --deny-warnings when the same module is
imported twice; combine the Automation and external types into one import.
* Fix row keyboard activation to not intercept nested controls
Row handlers for Enter/Space were preventing the actions menu button from
opening when activated via keyboard. Extract a reusable check that only
treats these keys as row activation when they originate on the row itself,
not on child controls. Also consolidate the search input to use the shared
Input component for consistency.
* fix: use stable keys for automations page skeleton rows
React Doctor no-array-index-as-key failed static analysis on the
loading skeleton map. Give each static row an id for the React key.
* Reorganize i18n keys for automation components
Move translation keys to their proper component namespaces (rowActions to AutomationListLocalRows, normalize loading key), update all locale files.
* fix: move automations detail selection refs out of render
React Doctor fails CI when refs are mutated during render. Sync
selectedExternalKey and isDetailOpen into refs in effects instead.
* style: soften automation search field focus state
- Reduce focus ring opacity to 70% for a subtler focus indicator
- Set focus-visible:ring-0 to eliminate elevation effect
- Add dark:bg-background for consistent dark mode appearance
* fix(agent-hooks): refresh existing shared hook scripts when the CLI is no longer detected
A CLI that falls off PATH (moved npm prefix, relocated shim) keeps its user-wide
config invoking Orca's launcher script under ~/.orca/agent-hooks, but the
presence gate skips install() with no removal — freezing the script at whatever
Orca generated last. Anyone in that state kept the pre-#11568 more.com-leaking
.cmd forever, because no launcher script is ever deleted and Windows startup
deliberately skips shell PATH hydration.
Reconcile before gating: every existing shared launcher/statusline script is
rewritten to the current template on each install pass. Creating scripts stays
behind the presence gate — an existing file is proof of a prior install; a
missing one means the gate did its job. Amp and Hermes are deliberately absent:
they write provider-native plugin code with its own install lifecycle, not
shared launchers.
- refreshManagedScriptIfPresent() in installer-utils (no-op unless the file exists)
- refreshManagedScripts() on the 11 launcher-writing services (openclaude via
the shared Claude class)
- reconcile pass in installManagedAgentHooks before presence detection,
filtered by the agents option, best-effort per agent
- coverage gate: a launcher written to ~/.orca/agent-hooks without a matching
refresher entry fails the suite, in both directions
* perf(agent-hooks): refresh launchers off the main thread
* test(agent-hooks): keep refresh mode assertion POSIX-only
* fix(tabs): keep widths stable during title updates
* review(tabs): reduce the width rule to the definite width and harden its tests
Why: once the tab container has a definite width the shrink-wrapped strip never
has free space, so flex-grow and max-w-[280px] were unreachable and only
duplicated the 180/220 numbers across two properties. Measured widths, strip
scrollWidth and clientWidth are identical to the flex-based version across four
window widths, five tab counts and short/long titles.
Tests: pin the width classes literally so the guarantee cannot be edited away
via the constant, and guard the e2e check against passing vacuously on a
saturated strip.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>