Skip to content

Keep the last prompt sticky at the top of the transcript #9

Description

@felipeorlando

Problem

Settings → "Anchor prompts to top" (src/surfaces/SettingsView.tsx:428, .transcript-turn-anchor in src/index.css:142) makes the live turn fill the viewport so a new prompt starts at the top. But it only sets the starting scroll position — as soon as the reply grows and you scroll down, the prompt scrolls out of view and you lose track of what you actually asked.

Cursor keeps the prompt pinned: it sticks to the top of the transcript while its response scrolls underneath, and the next prompt pushes it out.

Interesting detail: UserMessageBlock already takes a stickyIndex and applies it as zIndex (src/surfaces/AgentTranscript.tsx:969) — the standard sticky-stacking pattern — but there is no position: sticky anywhere in the codebase. The scaffolding is half-built.

Proposal

Extend the existing toggle rather than adding a new one: "Anchor prompts to top" starts the prompt at the top and keeps it there. No new setting, no confusing anchor-off + sticky-on combination.

1. Unblock sticky — content-visibility currently defeats it

.transcript-turn carries content-visibility: auto (src/index.css:123), which implies paint containment. A sticky descendant would stick to the turn's own box instead of the scroller, i.e. a no-op. This is likely why stickyIndex was never wired up.

Split the turn wrapper:

.transcript-turn            ← no containment; this is the sticky travel track
  ├─ prompt row (sticky)
  └─ .transcript-turn-body  ← content-visibility:auto + contain-intrinsic-block-size
       └─ fold line, work, response

The prompt sits outside the contained block, so it sticks relative to .agent-transcript and its travel is the full turn height — when the turn ends, the next prompt pushes it out naturally. Stacking during the handoff is handled by the existing ascending zIndex: stickyIndex.

Trade-off: the prompt is no longer virtualized (negligible — it is short text) and the 240px intrinsic fallback now estimates the turn body only. The simpler alternative — dropping content-visibility while the setting is on — is a smaller diff but kills virtualization on long transcripts for exactly the people using the feature.

2. Sticky positioning

In the turn .map, the row for the role === "user" item gets sticky top-0 when promptAnchor (src/surfaces/AgentTranscript.tsx:166) is on. Nothing changes when the toggle is off. All prompts stick and stack, so scrolling back through history keeps the question visible above its answer.

3. Collapse to one line while stuck

:stuck / @container scroll-state(...) are not usable — macOS ships WKWebView. Use an IntersectionObserver per user message with root = scrollerEl (already in state at AgentTranscript.tsx:150), rootMargin: "-1px 0px 0px 0px", threshold: 1; not intersecting means stuck. There is already one ResizeObserver per user message (AgentTranscript.tsx:947), so the cost matches the existing pattern. scrollerEl drills down the same path as stickyIndex.

While stuck, the bubble switches line-clamp-4line-clamp-1, takes an opaque background (the current bg-content/10 is translucent — content underneath would bleed through and make it unreadable) and a subtle bottom border, turning it into a context bar. Released, it returns to normal. It collapses even if the user had expanded it.

4. Settings copy

Update the description at src/surfaces/SettingsView.tsx:430 to mention that the prompt stays at the top while you read.

Files

  • src/surfaces/AgentTranscript.tsx
  • src/index.css
  • src/surfaces/SettingsView.tsx (copy only)

Testing

AgentTranscript.test.ts uses renderToStaticMarkup, so it can cover: the sticky class is present on the prompt row when the anchor is on and absent when off, and content-visibility lands on the turn body rather than the wrapper. The IntersectionObserver stuck/released behavior is not reachable from SSR tests and needs manual verification in the app.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions