Skip to content

chat input pill + chat-select + streaming/markdown fixes (chat 0.0.18, langgraph 0.0.10)#190

Closed
blove wants to merge 23 commits into
mainfrom
claude/chat-input-pill-and-select
Closed

chat input pill + chat-select + streaming/markdown fixes (chat 0.0.18, langgraph 0.0.10)#190
blove wants to merge 23 commits into
mainfrom
claude/chat-input-pill-and-select

Conversation

@blove

@blove blove commented May 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Chat input pill + chat-select primitive (the original plan): pill-shaped input, circle send/stop, content-width centering, symmetric edge padding, --ngaf-chat-edge-pad token, ghosted <chat-select> popover with keyboard nav, [chatInputModelSelect] slot, welcome subtitle dropped, glowing-dot caret.
  • First-class model picker on <chat>: pass [modelOptions] + [(selectedModel)] and the chat composition projects a <chat-select> into the input pill on both welcome and conversation views — no manual slot wiring required.
  • Welcome suggestions restyle: floating pills (rounded, surface bg, centered, wraps) instead of stacked rows with dividers.
  • Streaming complex-content fix (langgraph): OpenAI gpt-5/o-series Responses API streams typed content blocks ([{type:'text',text:'…',index}]) and emits both AIMessageChunk (messages-tuple) and canonical ai (values-sync) views of the same message. The bridge now extracts visible text from typed blocks, accumulates per-chunk deltas into a single slot, normalizes message types so dedupe matches across paths, drops empty-AI placeholders, and collapses adjacent AI bubbles. Single bubble accumulating cleanly.
  • Markdown rendering fix (chat): chat-streaming-md now uses ViewEncapsulation.None + element-scoped styles so CHAT_MARKDOWN_STYLES actually apply to innerHTML-injected DOM. Full CommonMark + GFM coverage: headings (sized hierarchy), strong/em/del, lists with visible markers (incl. nested + task lists), inline + fenced code, blockquote, hr, GFM tables (bordered + header bg), images. marked enabled with gfm: true, breaks: true.
  • Cockpit demos + superpowers docs: stale gpt-4o-mini references swept to gpt-5-mini. Reasoning models stream visibly with reasoning.effort='minimal' (langgraph default).

Bumps @ngaf/chat 0.0.16 → 0.0.18 and @ngaf/langgraph 0.0.9 → 0.0.10.

Test plan

  • Smoke: ~/tmp/ngaf against LangGraph backend — pick gpt-5/gpt-5-mini/gpt-5-nano, send a suggestion, verify single assistant bubble streams visibly with markdown bullets/headings/code formatted
  • Markdown sampler renders headings, bold, italic, strikethrough, links, blockquote, hr, bullet/ordered/task lists, GFM tables, fenced code
  • No left-flash on optimistic user message after suggestion click
  • chat-select keyboard nav (↑/↓/Enter/Esc) works in both welcome and conversation modes
  • CI green

🤖 Generated with Claude Code

blove and others added 23 commits May 2, 2026 22:34
- Add tabindex=-1 to the listbox div so the keydown handler attaches
  to a focusable element (a11y lint requirement).
- Bump @ngaf/chat for the pill input + glowing caret + chat-select
  primitive + left-flash fix release.
…ubbles

OpenAI's gpt-5/o-series Responses API streams `BaseMessage.content` as
arrays of typed blocks (`[{type:'text',text:'…',index:n}, …]`) rather
than accumulated strings, and emits two parallel views of the same
assistant message: per-event `AIMessageChunk`s on `messages-tuple` and
the canonical `ai` on `values`-sync. The bridge wasn't equipped for
either — chunks JSON-dumped into the bubble and the two paths each
spawned their own bubble that never collapsed.

Bridge fixes (`stream-manager.bridge.ts`):
- `extractText` walks complex-content arrays and pulls visible text
  blocks (`text` / `output_text`), skipping reasoning / tool_use /
  image blocks.
- `accumulateContent` merges incoming-chunk content into the prior
  slot's accumulated text. Handles the three cases: incoming is a
  strict superset (final-id swap), existing is a strict superset
  (chunk arrives after canonical), or pure delta append. Always
  returns string so downstream `findContentMatch` can prefix-compare
  cleanly without `JSON.stringify`-ing the array.
- `normalizeMessageType` collapses `AIMessage` / `AIMessageChunk` /
  `ai` / `assistant` to `ai` so `findContentMatch` and
  `sameRoleAndContent` correctly match across the values-sync and
  messages-tuple paths.
- `mergeMessages` gains an AIMessageChunk fallback: when an
  AIMessageChunk arrives without an id-match or content-prefix match,
  accumulate into the trailing AI message. The OpenAI Responses API
  emits per-chunk *event* ids, not message ids, so consecutive chunks
  would otherwise each create a fresh bubble.
- Empty-content AI placeholders are dropped from `state.messages`
  before the values-sync merge — keeping them creates a phantom slot
  that competes with the chunk-streamed AIMessageChunk.
- `collapseAdjacentAi` post-pass collapses adjacent AI messages where
  one's text is a prefix/equal of the other, keeping the older slot's
  id for stable track-by-id.

Also dropped an obsolete hand-rolled rawMessages throttle in
`agent.fn.ts` — `messages$` already emits at the bridge boundary and
extra signal-side throttling collapsed visible token streaming.

Bumps @ngaf/langgraph 0.0.9 → 0.0.10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k styles

Two related rendering bugs converged into a chat that showed raw JSON
arrays and then, after fixing that, plain unstyled paragraphs with no
heading sizes, bullets, or table borders.

`messageContent` shared util:
- Was JSON-stringifying complex-content arrays, dumping
  `[{"type":"text",…}]` into the chat bubble for OpenAI gpt-5 / o-series
  output. Now extracts visible `text` / `output_text` blocks the same
  way the langgraph adapter does and joins them; everything else
  (reasoning, tool_use, images) is skipped.

`chat-streaming-md` component:
- Switched to `ViewEncapsulation.None`. The component renders markdown
  by assigning sanitized HTML to `innerHTML`, so the resulting `<ul>`,
  `<p>`, `<table>`, etc. nodes never carry the `_ngcontent-…`
  attribute that emulated encapsulation requires. Without this the
  exported `CHAT_MARKDOWN_STYLES` were silently skipped for every
  selector below `:host`.
- Wired `CHAT_MARKDOWN_STYLES` into the component (it was exported but
  never applied anywhere).

`CHAT_MARKDOWN_STYLES`:
- Re-scoped from `:host` to `chat-streaming-md` element selectors so
  the rules stay locally meaningful under `ViewEncapsulation.None`.
- Expanded coverage to the full CommonMark + GFM surface: heading
  hierarchy (h1–h6 with size scale), `strong` / `b`, `em` / `i`,
  `del` / `s`, `mark`, `sub` / `sup`, `a` (with hover), bullet /
  ordered / nested lists with visible `disc` / `circle` / `decimal`
  markers, GFM task-list checkboxes, inline `code` / fenced `pre`,
  `blockquote`, `hr`, GFM `table` (bordered, header background), and
  `img` (max-width).

`marked` options:
- Enabled `gfm: true, breaks: true` so single newlines render as
  `<br>` (matching common chat UX) and tables / strikethrough / task
  lists / autolinks are honored.

Bumps @ngaf/chat 0.0.17 → 0.0.18.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…estions as pills

Two UX improvements driven by smoke-testing the chat-select primitive in
real apps.

`<chat>` composition:
- New `[modelOptions]`, `[(selectedModel)]`, `[modelPickerPlaceholder]`
  inputs that, when populated, render a `<chat-select chatInputModelSelect>`
  inside the chat-input pill on both the welcome screen and the
  conversation footer. Consumers no longer have to project the slot
  themselves for the common "model picker" use case — they just pass
  options + a model signal.
- Slot projection still works in conversation mode for any consumer
  that needs custom chat-input children (an inner `ngProjectAs`
  bridges the outer `[chatInputModelSelect]` content through).

Welcome suggestions:
- Restyled from full-width stacked rows separated by dividers to
  centered floating pills (`border-radius: 9999px`, surface
  background, equal gap). Matches the chat-input pill aesthetic.
- Container becomes `flex-wrap` + `justify-content: center` + `gap: 8px`
  so suggestions reflow naturally on narrow viewports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5-mini

Default model in every demo graph and prose example is now gpt-5-mini.
Reasoning models (gpt-5/o-series) stream visibly out of the box at
`reasoning.effort='minimal'`, which the langgraph adapter sets by
default — no developer-facing config needed. Older non-reasoning
gpt-4o-mini references in graphs and docs were stale.

No code path changed; pure model-name swap across cockpit examples
and docs/superpowers plan / spec files.

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

vercel Bot commented May 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 3, 2026 4:50pm

Request Review

@blove

blove commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by a clean rebase onto main: see new PR with the same 4 commits (langgraph bridge fix, chat markdown fix, chat model-picker + welcome pills, gpt-4o sweep). Closing because main has #189 squash-merged and rebasing this branch produced too many phantom conflicts.

@blove blove closed this May 3, 2026
@blove blove deleted the claude/chat-input-pill-and-select 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