Skip to content

Place helper terminals beside their source with an animated shared focus ring - #768

Merged
nedtwigg merged 26 commits into
mainfrom
helper-placement
Sep 24, 2026
Merged

nedtwigg merged 26 commits into
mainfrom
helper-placement

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Sep 23, 2026

Copy link
Copy Markdown
Member

Helper terminals now open beside their source in multi-pane Walls, using available space without resizing the source. Single-pane and zoomed Walls place the helper in the top or bottom half opposite the visible cursor, with a 16px inset and a top default when the cursor is unknown.

Adjacent helpers overlap the source by 16px. Above helpers overlap only 4px and extend upward over neighboring headers. The zoomed-pane halo separates the popup from underlying content, and one animated focus ring follows the source/helper outline without an internal or native ghost ring.

Placement icons beside Close show the current side and let users switch without losing terminal identity, unfinished input, or focus. Placement is chosen automatically on opening; there is no Auto button. Context details always appear, with wrapping and scrolling for small panels.

Validation:

  • Geometry, component, focus-ring, and retained-terminal tests.
  • 14 live helper-placement Storybook scenarios, 17 context gallery scenarios, and affected alert/title stories.
  • Full library suite: 3,754 tests across 232 files; TypeScript, spec lint, and production Storybook build pass.
  • Claude simplify reviewed and committed as 3a3cbfd. Affected stories also pass in Chromium and WebKit.

nedtwigg and others added 8 commits September 23, 2026 10:03
Write animator frames straight to the overlay DOM so tweens no longer
re-render the whole context; React re-renders only when the side or the
available sides change. Reuse lath's Edge/edgeAxis, route Details and
placement buttons through ContextAction, drop the unread placement mode
and the view's style prop, and trim spec duplication.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	scripts/spec-word-budgets.json
@nedtwigg
nedtwigg marked this pull request as ready for review September 23, 2026 18:14
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 23, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 783a971
Status: ✅  Deploy successful!
Preview URL: https://02941c94.mouseterm.pages.dev
Branch Preview URL: https://helper-placement.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Header context menu section in docs/specs/layout.md now has two Source of truth: pointers. The new one sits in the middle of the section, and the existing one at the end still names TerminalLeafOverlay in lib/src/components/wall/LathHost.tsx as a context owner. After this PR that component renders only the alert ring. AGENTS.md → Specs says to "Consolidate Source of truth: pointers at the end of a section", so fold the new entries into the section-end line (the one starting Source of truth: `TerminalContext` in). Replace TerminalLeafOverlay there with TerminalContextOverlay and placeTerminalContext, and add the two new test files. That line is outside the diff, so the inline suggestion only removes the mid-section pointer.

Comment thread docs/specs/layout.md Outdated
@argos-ci

argos-ci Bot commented Sep 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
storybook-chromium (Inspect) 👍 Approved by Ned Twigg 21 changed, 19 added Sep 23, 2026, 11:37 PM
storybook-webkit (Inspect) 👍 Approved by Ned Twigg 21 changed, 19 added Sep 23, 2026, 11:37 PM
Deployment Status Branch Updated (UTC)
preview (Open) Ready helper-placement Sep 23, 2026, 11:38 PM

nedtwigg and others added 2 commits September 23, 2026 12:34
Carry the union on the displayed ring frame instead of a side ref, resolve the
helper once per effect, skip rewrites when the union is unchanged, and compute
the bounds with a plain min/max. The contour returns absolute points, the union
corner radius is clamped like ringPoints, and the context overlay drops its dead
painted-rect state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: the union ring outlasts the helper on close. contextSourceId stays set while the context is closing, so for TERMINAL_CONTEXT_EXIT_MS (180ms) the ring keeps tracing the helper's full footprint while .terminal-context-exit collapses the helper to a pinhole with clip-path. That clip doesn't change getBoundingClientRect, so neither observer sees it. The ring only snaps back to the source when the context unmounts. Dropping the union once closing is set brings the ring back while the helper animates out.

Comment thread lib/src/components/Wall.tsx Outdated
nedtwigg and others added 2 commits September 23, 2026 15:53
- Fold the helper's side into the selection-ring identity, deleting the
  parallel union tween path: opening, closing and side changes still tween,
  while same-side motion (Wall resize, Lath animation) snaps or retargets
  instead of restarting a 220ms ease every frame.
- The overlay writes its side to the host with its bounds, renders geometry
  from one imperative writer, and memoizes the panel so LathHost's commit and
  resize renders no longer re-render the whole context.
- Drop the always-true `compact` prop and the unread placement `rect`;
  extract the placement icon unchanged; name the inset, above-overlap and
  above-extension constants.
- Move the union ring outline's inset and origin math into
  `unionRingOutline`; drop the helper ResizeObserver the MutationObserver
  already covers.
- Tests and stories: reuse `settleTerminalContext`, drop assertions on a
  mocked getter, replace a DOM-depth check with `closest`, and pin the new
  same-side 1:1 tracking.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@nedtwigg nedtwigg changed the title Position helper terminals beside their source pane Place helper terminals beside their source with an animated shared focus ring Sep 23, 2026
The helper overlay registers a placer on the engine instead of subscribing
to frames. LathHost's paint calls it with the frames it just wrote to the
leaves, then notifies chrome, so the selection ring reads the placed helper
from the engine where it is painted.

- Removes the ring's DOM query and MutationObserver, the `contextSourceId`
  prop, and the helper's imperative side attribute.
- Removes the overlay's per-frame `framesAt` rebuild and its drift from the
  leaves' paint time; `notifyFrames` is unchanged.
- Fixes the subscriber-order race where the ring could measure the helper
  before it moved, forcing a second layout that frame.
- Dismissal still returns the ring to the source alone: the placer
  publishes no helper while the context is closing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@nedtwigg
nedtwigg merged commit 39235ae into main Sep 24, 2026
19 checks passed
@nedtwigg
nedtwigg deleted the helper-placement branch September 24, 2026 00:03

This branch is waiting to be deployed

1 waiting deployment
hosted-preview 783a971b Waiting Sep 24, 2026 by nedtwigg via cleanup #331
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants