Skip to content

feat(chat): scroll polish + pin/bubble system#250

Merged
blove merged 15 commits into
mainfrom
claude/practical-jepsen-c37edf
May 11, 2026
Merged

feat(chat): scroll polish + pin/bubble system#250
blove merged 15 commits into
mainfrom
claude/practical-jepsen-c37edf

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 11, 2026

Summary

Two work streams on the chat composition, spec'd at docs/superpowers/specs/2026-05-11-chat-scroll-and-input-polish-design.md, plan at docs/superpowers/plans/2026-05-11-chat-scroll-and-input-polish.md:

Stream A — Quick fixes

  • Final scroll when streaming ends so message-action buttons (reload/copy) stay visible
  • --ngaf-chat-input-gap token (default 0.75rem) between body and footer; consumer-overridable
  • Multiline textarea cap → min(40vh, 320px) (was a fixed 200 px)

Stream B — Pin/unpin + scroll-to-latest bubble

  • pinned signal driven by a 150 px tolerance via the (scroll) listener
  • programmaticScrollCount counter (not a boolean) suppresses self-scrolls without racing under fast streaming
  • New chat-scroll-bubble primitive (streaming mode: 3-dot pulse; idle mode: down arrow)
  • Bubble lives in .chat-footer-wrap, anchored above the input
  • Inline typing indicator gated on pinned() — bubble carries the streaming signal when the user is scrolled away
  • New user message force-pins; bubble click re-pins + scrolls to bottom

Plus, root-cause fix discovered during browser verification

  • libs/chat/package.json now declares sideEffects: ["**/chat-tokens.ts", "**/*.css"] so ensureChatRootStyles() survives consumer tree-shaking. This restores :root defaults for ALL SPACING_TOKENS, not just the new --ngaf-chat-input-gap — a pre-existing latent bug.

Public API additions

  • ChatScrollBubbleComponent, ChatScrollBubbleMode (exported from @ngaf/chat)
  • --ngaf-chat-input-gap CSS custom property

Test plan

  • Vitest suite (nx run chat:test) — 485/485 passing, including new pin-state.spec.ts (5) and chat-scroll-bubble.component.spec.ts (5)
  • nx run chat:build clean
  • Manual verification via Chrome MCP against examples-chat-angular at /embed:
    • Bubble appears (idle mode, down-arrow, aria-label "Scroll to latest") when scrolled up
    • Bubble click → scrolls to bottom + re-pins + typing indicator restored
    • Multiline cap clamps at exactly 288 px on a 720 px viewport (= min(40vh, 320px)); internal scroll engages past cap
    • Visible 12 px gap between body and input after the sideEffects fix lands
  • Live-stream end-state verification (final scroll on isLoading → false) — requires longer-running prompt; safe to verify post-merge

🤖 Generated with Claude Code

blove and others added 14 commits May 11, 2026 13:24
Spec covering two work streams: quick fixes (post-stream final scroll,
embed gap token, multiline auto-grow) and a pin/unpin state machine
driving a centered-bottom scroll bubble.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight tasks across Stream A (final scroll, embed gap, viewport-responsive
multiline cap) and Stream B (pin signal, bubble primitive, integration,
unit tests, manual browser verification).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract pure isPinned helper from onScroll and cover the 150px strict-less-than boundary with 5 unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eive it

The Task 2 token addition went into the TS-injected ROOT_TOKEN_STYLES,
but @ngaf/chat is published with sideEffects: false and the module-level
ensureChatRootStyles() call gets tree-shaken in consumer bundles. The
canonical surface for consumer-visible :root tokens is chat.css, which
the example imports from src/styles.css. The new spacing token belongs
there too.

Verified via the examples-chat dev server: with the var declared in
chat.css, .chat-window__footer now has margin-top: 12px instead of 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace boolean programmaticScroll with a counter to avoid race when
  multiple programmatic scrolls are queued in quick succession (a stale
  rAF could clear the flag while a newer scroll was still in flight,
  causing spurious unpins during fast token streaming).
- Mark chat-tokens.ts as side-effectful in package.json so the
  module-level ensureChatRootStyles() call is preserved through
  consumer tree-shaking. This restores :root token defaults for the
  whole SPACING_TOKENS group (edge-pad, space-*, input-gap), not just
  the new input-gap.
- Revert the chat.css duplication of --ngaf-chat-input-gap now that
  the root-cause fix supersedes it.
- Make ChatComponent.pinned protected instead of implicitly public.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Claude Preview entry for examples-chat-angular on port 4400,
used to manually verify chat composition behavior in the browser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 11, 2026 10:01pm

Request Review

- Drop redundant : number annotation on programmaticScrollCount
  (@typescript-eslint/no-inferrable-types).
- Regenerate chat API docs to include the new ChatScrollBubbleComponent
  and ChatScrollBubbleMode exports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove merged commit 2e8910c into main May 11, 2026
14 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