Skip to content

feat(plan): complete sticky actions — pin toolstrip + badges on scroll#510

Merged
backnotprop merged 2 commits intomainfrom
feat/complete-sticky-actions
Apr 7, 2026
Merged

feat(plan): complete sticky actions — pin toolstrip + badges on scroll#510
backnotprop merged 2 commits intomainfrom
feat/complete-sticky-actions

Conversation

@backnotprop
Copy link
Copy Markdown
Owner

Summary

The stickyActionsEnabled setting only pinned the right-side action button cluster. The annotation toolstrip (selection mode + editor mode toggles) and the left-side badges (repo / branch / plan-diff) scrolled away, forcing users to scroll back to the top of long plans to switch modes or jump into diff view.

This PR adds a ghost sticky header lane that pins the toolstrip and badges on the same horizontal plane as the already-sticky action buttons once the user scrolls past the top of the document.

  • Top of document: unchanged. The original toolstrip sits above the card and the badge cluster stays absolute on the card's top-left, exactly as before.
  • Scrolled: a compact bar fades + slides in at top: 12px, containing the full toolstrip (in compact mode — only the active mode shows its label) and a row-layout badge cluster, sized to leave the right side of the lane clear for the existing action buttons.
  • Gating: hidden in plan-diff mode, archive mode, linked-doc mode, and when stickyActionsEnabled is off.

Implementation highlights

  • DocBadges extracted from Viewer.tsx so the card's absolute cluster and the ghost bar share the same markup via a layout: 'column' | 'row' prop. No JSX duplication.
  • AnnotationToolstrip gets a compact prop — the same component renders in both locations, just with hover-expansion disabled and the help link hidden.
  • StickyHeaderLane is a zero-height sticky wrapper with an absolute-positioned bar inside it, so it never pushes content down at top of doc. An IntersectionObserver with rootMargin: '72px 0 0 0' delays the trigger until the real toolstrip has actually scrolled off, avoiding a brief double-header.
  • 180ms ease-out fade + 4px slide; honors prefers-reduced-motion.
  • Action button cluster in Viewer.tsx is untouched — same DOM, same classes, same isStuck chrome behavior.

Test plan

  • Top-of-document rendering matches main pixel-for-pixel (toolstrip above card, badges absolute top-left, action buttons float-right, no ghost bar visible)
  • Scrolling down ~80px fades the ghost bar in on the same y as the action-button chrome
  • Compact toolstrip's input-mode and editor-mode buttons toggle state correctly
  • Compact PlanDiffBadge correctly toggles plan-diff view on resubmitted plans
  • Scrolling back to top fades the bar out cleanly
  • Toggling Sticky actions off in Settings hides both the ghost bar and the action-button pinning (existing behavior)
  • Ghost bar hidden in plan-diff, archive, and linked-doc modes
  • prefers-reduced-motion: reduce drops the slide, keeps opacity

The sticky actions setting previously only pinned the action button cluster
on the right side of the plan card. The annotation toolstrip (selection
mode + editor mode toggles) and the left-side badges (repo / branch /
plan-diff) scrolled away, forcing users to scroll back to the top of long
plans to switch modes or jump into diff view.

Adds a ghost sticky header lane that pins the toolstrip and badges on the
same horizontal plane as the already-sticky action buttons once the user
scrolls past the top of the document. Top-of-document rendering is
unchanged.

- Extract the badge cluster into a reusable `DocBadges` component with
  `column` / `row` layouts so the card's absolute cluster and the ghost
  bar share the same markup.
- Add a `compact` prop to `AnnotationToolstrip` that disables hover
  expansion (only the active button shows its label) and hides the help
  link, for use inside the ghost bar.
- Introduce `StickyHeaderLane`: a zero-height sticky wrapper with an
  absolute-positioned bar that fades + slides in via IntersectionObserver
  on a sentinel. Uses a 72px top rootMargin so the bar only materializes
  after the real toolstrip has scrolled off.
- Hidden in plan-diff, archive, and linked-doc modes, and gated by the
  existing `stickyActionsEnabled` preference.

For provenance purposes, this commit was AI assisted.
Iteration on the sticky header lane addressing review feedback and
in-browser testing across viewport sizes:

- Mobile (<640px): the bar now drops to its own full-width row at
  top: 52px, directly below the action buttons row. Two stacked
  horizontal lanes — no horizontal collision possible. The toolstrip
  switches to icon-only on mobile so the diff badges fit alongside.
- sm-md (640-1023px): bar shares the top: 12px lane with action
  buttons, capped at `max-w-[calc(100%-340px)]` to clear the short-
  label action button cluster (Attachments + Comment + Copy ≈ 320px).
- lg+ (1024px+): same shared-lane behavior, cap raised to 400px to
  clear the full-label cluster.
- Bar is `inline-flex` (content-width), not flex (full bounding-box
  width), so the chrome wraps tightly to the toolstrip + badges and
  doesn't extend visually past where it needs to.
- Add `inert` to the bar when not stuck — removes the hidden subtree
  from the tab order entirely so keyboard users don't land on
  invisible duplicate controls before reaching the real toolstrip.
- Add `iconOnly` prop to AnnotationToolstrip; passed via JS-driven
  matchMedia('(max-width: 639px)') in StickyHeaderLane. Active button
  expansion is preserved on sm+ so users still see the current mode
  label there.
- Bump action button label breakpoint md → lg in Viewer so "Comment"
  and "Copy" stay short labels until 1024px, giving the action button
  cluster more breathing room on tablet/laptop widths.

For provenance purposes, this commit was AI assisted.
@backnotprop backnotprop merged commit 1e8a60b into main Apr 7, 2026
5 checks passed
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.

1 participant