Skip to content

fix(chat,langgraph): jank — stable id + global keyframes + round send + host overflow#175

Merged
blove merged 3 commits into
mainfrom
claude/chat-10-stable-id-fix
May 2, 2026
Merged

fix(chat,langgraph): jank — stable id + global keyframes + round send + host overflow#175
blove merged 3 commits into
mainfrom
claude/chat-10-stable-id-fix

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 2, 2026

Why

After 0.0.9 the user reported residual jank: caret animation flicker, typing dots showing but not animating, page-level scroll on embedded mode, and an angular send button. Investigation in this PR addresses each.

Changes

  1. Stable optimistic-message id (stream-manager.bridge): stamp injected human messages with an `optimistic--` id. Without this, `toMessage()` called `randomId()` on every recompute → fresh `message.id` per token → `track message.id` tore down the user bubble repeatedly.
  2. Identity-stable Message projections (`agent.fn`): WeakMap-cache `BaseMessage → Message` so projected ids stay stable across recomputes.
  3. Streaming flag is last-only (`chat.component`): only the most recent assistant gets `[streaming]=true`; prevents historical messages from repainting their data-streaming attr per token.
  4. Hoist @Keyframes globally (`chat-tokens`): Angular emulated encapsulation scopes per-component @Keyframes names, which desynchronised from `animation:` references in helper style strings. Result: the typing dots rendered but never animated. Keyframes now ship from the auto-injected `<style id="ngaf-chat-root-tokens">` so names match.
  5. Round send button (`chat-input.styles`): `border-radius: 9999px` to match the launcher aesthetic.
  6. Lock chat host overflow (`chat.component`): `overflow: hidden` on host + `max-height: 100%` so embedded mode can never push the page into scroll.

Versions

  • `@ngaf/chat`: 0.0.9 → 0.0.10
  • `@ngaf/langgraph`: 0.0.3 → 0.0.4

Test plan

  • `nx test chat` — green
  • `nx test langgraph` — 100 tests green
  • `nx build chat` / `nx build langgraph` — clean
  • Manual smoke against ngaf-smoke-05: caret blinks, typing dots animate, send is round, no page scroll

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented May 2, 2026

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

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 2, 2026 2:23pm

Request Review

@blove blove changed the title fix(chat,langgraph): stabilise optimistic message id for track-by-id fix(chat,langgraph): jank — stable id + global keyframes + round send + host overflow May 2, 2026
@blove blove closed this May 2, 2026
@blove blove reopened this May 2, 2026
blove and others added 3 commits May 2, 2026 07:21
The 0.0.9 jank fix switched chat-message-list to `track message.id`, but
the optimistic human message injected at submit() had no id — so
toMessage() called randomId() on every BaseMessage→Message recompute.
Each token re-emission produced a fresh id for the user bubble, defeating
track-by-id and tearing down the chat-message DOM (and its caret /
typing-dot animations) on every streamed token.

- stream-manager.bridge: stamp optimistic input messages with an
  `optimistic-<ts>-<rand>` id at injection time. Real LangGraph echoes
  with a server id arrive as a separate merge, naturally taking over.
- agent.fn: WeakMap-cache projected Message objects by raw BaseMessage
  identity so the projected `id` is stable across recomputes when the
  raw reference is stable (additional belt-and-braces for caret/typing
  animation continuity).
- chat.component: only mark the LAST assistant message as
  `streaming=true` (was every assistant). Avoids re-painting historical
  messages' caret/streaming attrs every token.
- Bumps: @ngaf/chat 0.0.9 → 0.0.10, @ngaf/langgraph 0.0.3 → 0.0.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three additional jank fixes for 0.0.10:

1. Hoist @Keyframes (typing-dot, caret-blink, spin, pulse) into the
   global ROOT_TOKEN_STYLES sheet that's already auto-injected into
   <head>. Previously they lived in CHAT_HOST_TOKENS and were appended
   to every chat component's styles array. Angular's emulated view
   encapsulation scopes @Keyframes names per-component, which can
   desynchronise from `animation: name` references when those live in
   a sibling style helper string. Result: the typing dots rendered
   but never animated. Hoisting to global scope makes the names match
   what component CSS references (Angular leaves `animation:` props
   untouched).
2. Make the send button fully round (`border-radius: 9999px`) instead
   of the 8px button radius — matches the floating-launcher aesthetic.
3. Constrain the chat host with `overflow: hidden` and add
   `flex: 1 1 auto; max-height: 100%` so content can never push the
   embedding page into a scroll state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove force-pushed the claude/chat-10-stable-id-fix branch from 1e46bc8 to a251674 Compare May 2, 2026 14:21
@blove blove merged commit 4dde81f into main May 2, 2026
14 checks passed
@blove blove deleted the claude/chat-10-stable-id-fix branch May 7, 2026 16:30
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