Skip to content

[pull] main from xintaofei:main#91

Merged
pull[bot] merged 35 commits into
MoozLee:mainfrom
xintaofei:main
Jun 14, 2026
Merged

[pull] main from xintaofei:main#91
pull[bot] merged 35 commits into
MoozLee:mainfrom
xintaofei:main

Conversation

@pull

@pull pull Bot commented Jun 14, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

xintaofei and others added 30 commits June 10, 2026 16:47
Groundwork for replacing the plain-textarea message input with a Tiptap v3
rich-text composer (inline reference badges + live WYSIWYG Markdown). Phase 0
is the de-risk spike: deps, a standalone RichComposer, and the Markdown
round-trip / IME-submit logic later phases build on. Not yet wired into
message-input (that is Phase 3).

- deps: @tiptap/{core,pm,react,starter-kit,suggestion,markdown,
  extension-placeholder} pinned exact 3.26.0 (peers require lockstep)
- composer/editor-config.ts: shared StarterKit + Placeholder + Markdown set
- composer/rich-composer.tsx: forwardRef editor w/ imperative handle,
  immediatelyRender:false (static-export safe), IME-safe Enter-to-submit
- composer/submit-key.ts: pure Enter decision (IME / code block / list)
- globals.css: scoped .codeg-composer prose + placeholder styles
- test-setup.ts: jsdom polyfills so ProseMirror mounts headless in tests
- tests: 39 (markdown round-trip incl. CJK, submit-key, component smoke)

Verified: pnpm test (1069), eslint, build (static export) all green.
Reviewed by Codex (APPROVED).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generic inline `reference` ProseMirror atom node rendering any of five kinds
(file/agent/session/commit/skill) as a non-editable React badge, with an
`insertReference` command and injection-safe Markdown serialization. Not yet
wired into message-input (Phase 3); the `@` panel that populates references is
Phase 2.

- composer/types.ts: ReferenceKind / ReferenceMeta / ReferenceAttrs
- composer/reference-text.ts: referenceToMarkdown — `[label](uri)` for
  file/session/commit, `@label` for agent, `/id` for skill; escapes all
  inline-significant punctuation, angle-wraps + escapes link destinations, and
  code-spans URL/email-like free text so a crafted label/uri cannot inject a
  second link, autolink, image or emphasis
- composer/nodes/reference-node.ts: atom node (data-* HTML round-trip,
  renderMarkdown, insertReference); validates ref-type + allowlists uri schemes
  (file:/codeg:) on untrusted HTML parse
- composer/nodes/reference-view.tsx + badges/reference-badge.tsx: node view +
  presentational chip (AgentIcon/lucide, session status dot)
- editor-config.ts: register Reference; globals.css: .codeg-reference styles
- tests: 76 composer (reference-text incl. adversarial injection,
  parse-based one-link/zero-link, paste hardening, node insert/markdown/HTML)

Verified: pnpm test (1106), eslint, tsc --noEmit, build (static export) green.
Reviewed by Codex (APPROVED after 4 rounds hardening Markdown injection).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire @tiptap/suggestion (trigger @) to a React popup that owns data, rendering
and insertion via a state bridge — the plugin lives in ProseMirror, the panel
lives in the component tree (where data hooks work). Tested against a
referenceSearch provider with mock data; real data sources are Phase 3.

- suggestion/types.ts: SuggestionItem/SuggestionGroup/ReferenceSearch (async,
  abortable)/SuggestionState/SuggestionPopupHandle
- suggestion/adapters.ts: pure source→reference adapters (file/agent/session/
  commit/skill/expert); pathToFileUri percent-encodes per segment (matches
  message-input's toFileUri)
- suggestion/mention-suggestion.ts: MentionSuggestion extension; inert items/
  command (React owns them); allow-guard skips IME composition + code blocks;
  render() bridges lifecycle to a MentionController
- suggestion/suggestion-popup.tsx: grouped keyboard-navigable popup, debounced
  + abortable fetch, stale-result guard (only fresh results selectable),
  portal-positioned at the caret, mousedown-preventDefault to keep focus
- rich-composer.tsx: referenceSearch prop; plugin always installed but inert
  until enabled (runtime-gated via ref); Enter defers to an open panel; insert
  via deleteRange+insertReference; closeMention calls exitSuggestion; dismisses
  on referenceSearch removal mid-open
- editor-config: optional mentionController; reference-badge: export
  ReferenceIcon; test-setup: scrollIntoView jsdom polyfill
- tests: +31 (adapters, popup keyboard/click/escape/stale, integration:
  @-trigger insert, Enter-no-submit-while-open, Escape dismiss, no-search-no-
  panel, mid-open disable)

Verified: pnpm test (1133), eslint, build (static export), git diff --check
all green. Reviewed by Codex (APPROVED after hardening: file-uri encoding,
always-install gating, stale-result guard, plugin-exit on close).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dling (Phase 3a)

P3a foundation for wiring RichComposer into message-input (pure, unit-tested; no
production wiring yet beyond a type extraction):

- to-prompt-blocks: docToPromptBlocks lifts file references (file:// only) to
  trailing ResourceLink blocks and drops them from the prose; session/commit/
  agent/skill stay inline as text. Zero wire/display regression vs the textarea.
- from-prompt-blocks: blocksToRestoredDraft inverse for queue-edit (file:/codeg:
  uris -> badges, image/embedded/other -> attachments).
- submit-key: decideComposerKey generalizes shouldSubmitOnEnter to configurable
  send/newline bindings (IME + bare-Enter-in-code/list precedence).
- rich-composer: configurable submit/newline shortcuts, isExternalMenuOpen defer,
  onPasteFiles forwarding, getJSON/insertMarkdownAtCursor/insertReference handle.
- message-input: extract InputAttachment types to message-input-attachments.ts.

Gates: 1205 tests, eslint clean, build OK. Codex-reviewed APPROVED (fixed file://
scope check, free-form key bindings, onSubmit-absent fall-through).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on (Phase 3b)

Persist the composer's Tiptap document as a v2 draft alongside the legacy v1
text draft, reusing the existing in-memory cache + requestIdleCallback batch
writer + pagehide/visibilitychange flush.

- loadMessageInputDraftV2 prefers the v2 doc (validated by isTiptapDoc), falls
  back to a v1 text draft returned as {kind:"legacyMarkdown"} for the host to
  hydrate, else null.
- saveMessageInputDraftV2 caches the doc immediately (v2 wins in-session) and
  schedules a durable write; the legacy v1 draft is retired only once the v2
  write actually succeeds, so a deferred write that later fails cannot lose the
  draft.
- isTiptapDoc rejects arrays / partial payloads ({doc:{}}) so corrupt v2 data
  never reaches the editor.
- clearMessageInputDraftV2 drops both the v2 and legacy v1 entries.

Codex-reviewed (APPROVED).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… panel (Phase 3c)

Compose the live data sources (file tree, ACP agents, conversations, git log,
skills, experts) into one referentially-stable ReferenceSearch for the
composer's unified @ mention panel.

- buildReferenceGroups (pure): filter/adapt/order into the fixed file → agent →
  session → commit → skill groups, each capped at 50, skills+experts deduped by
  id. No workspace path → file/commit groups stay empty (graceful R8).
- The returned search is an empty-dep useCallback reading every source from a
  ref, so a background refresh of any hook (e.g. agents reloading on focus)
  never changes its identity — the open panel keeps its results and selection.
- Sessions/commits are fetched lazily on the first @, key-cached, and awaited so
  the first open is populated without a keystroke; window focus busts the caches.
  A rejected fetch is NOT cached (the entry is cleared) so the next @ retries.
- Folder-switch safety: the post-await freshness guard discards a stale result
  when the workspace path changed mid-fetch. pathRef/enabledRef are mirrored in a
  commit-synchronous layout effect so a stale git-log resolving in the
  post-commit/pre-passive-effect window can't leak the old folder's commits.

Codex-reviewed (APPROVED) across three rounds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…chComposer (Phase 3d)

Swap the chat composer's plain <textarea> for the rich-text RichComposer:
inline reference badges (file/agent/session/commit/skill), live Markdown, and
the unified @ mention panel — wiring in the Phase 3a–3c building blocks.

- Send: buildDraft now serializes the editor via docToPromptBlocks (file
  mentions → first-class resource_link; agent/session/commit/skill → inline
  text), then appends the existing attachment blocks. No agent/display
  regression; displayText = getMarkdown().trim().
- Drafts: v2 JSON persistence, debounced 300ms; hydrate on RichComposer.onReady
  (v2 doc via setDoc, legacy v1 via setMarkdown); queue-edit payloads via a
  guarded re-hydration effect.
- `/` (commands) + `$` (Codex skills) menu is now editor-driven: caret-based
  detection (skips code), inline filtering, token replacement via the editor,
  and key routing through RichComposer.onExternalMenuKeyDown (ProseMirror's DOM
  handler fires before a host capture handler could). The `@` textarea path
  (FileMentionMenu / useFileTree / handleAtSelect / handleTextChange) is removed.
- Expert prefix, quick messages, append-text event, focus, and paste-as-
  attachment all migrated to editor commands. The editor stays editable while
  `disabled` (agent busy) so enqueue/queue-edit still work — the send is gated
  in handleSend, matching the old textarea.

New RichComposer primitives: setDoc(JSONContent), onReady, onExternalMenuKeyDown.
New composer-commands.ts (isComposerEmpty — whitespace-only is unsendable but a
badge-only doc is sendable; applyExpertPrefix — keeps the prefix ahead of a
block marker). Tests: composer-commands (11), RichComposer setDoc (2), a
MessageInput mount smoke test.

Codex-reviewed (APPROVED). NOTE: CJK IME and live multi-agent send (plan risks
R1–R8) remain manual real-device QA — jsdom can't simulate IME.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Phase 3e)

Round-trip a queued message's full draft.blocks into the composer when editing
it, so inline reference badges and attachments come back — not just the display
text (the old textarea path lost attachments on edit).

- composer-commands.ts: restoreBlocksIntoEditor(editor, blocks) replays a sent
  PromptInputBlock[] (inverse of docToPromptBlocks) — clears the editor, replays
  prose + reference badges in order, and returns the out-of-band attachments
  (images / embedded resources / non-composer links) for the host to set.
- message-input.tsx: new editingItemId + editingDraftblocks props. The two
  queue-edit hydration paths restore from blocks when present (else fall back to
  setMarkdown(editingDraftText)). Re-hydration is keyed on the queue item id, not
  the display text, so switching between two attachment-only items (which share
  "Attached 1 attachment") still reloads.
- editingDraftBlocks threaded conversation-detail-panel (editingQueueDraftBlocks
  memo over the editing item's draft.blocks) → conversation-shell → chat-input.

+5 restore tests (16 in composer-commands.test.ts). Completes the P3 composer
rich-text work (3a–3e). Codex-reviewed (APPROVED).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hase 4a)

The @ panel was anchored at the caret with a fixed wrapper + `absolute
bottom-full` and no viewport clamping: it overflowed the right edge near the
window edge, and was clipped off-screen above when the caret sat near the top
(short window, split view, inline queue-edit). clientRect===null fell to (0,0).

- Add pure, unit-tested `placeMentionPopup()` — horizontal clamp + above/below
  flip (prefers above; falls back to the larger side) + null-caret fallback.
- Measure the rendered panel in a layout effect and position via the helper;
  the wrapper stays hidden until measured so there is no (0,0) flash.
- Cap the panel to the viewport (minus the 8px edge margins) so it always fits
  and scrolls internally rather than overflowing on small windows.
- Thread Tiptap's live `clientRect` getter through the render state so the panel
  re-anchors on window resize, editor scroll, and page scroll (capture phase)
  instead of reusing a stale snapshot.

Codex-reviewed (APPROVED, independently re-ran the gate). Gate: 1271 tests,
eslint clean, build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…adges (Phase 4b)

The `@` panel was a bare div+button list with no listbox semantics, no live
region, and the editor exposed no combobox relationship; inline reference
badges had only a `title`. Screen-reader users got keyboard nav but no
announcements.

- Panel: the suggestions container is a `role="listbox"` (id `mention-listbox`,
  aria-label) that owns ONLY option/group children; each row is a `role="option"`
  with `aria-selected` and a stable id; loading/empty status sits outside the
  listbox; an sr-only `role="status"` live region announces loading / "N results"
  / empty.
- Editor combobox wiring: while the panel is open the contentEditable gets
  `aria-autocomplete="list"`, `aria-controls`, and `aria-activedescendant`
  (mirrored from the active option); all cleared on close / select / disable.
  Role stays "textbox" (the recognized textbox-autocomplete pattern).
- Reference badge: `role="img"` + `aria-label="<type>: <label>"` gives the inline
  atom a reliable computed accessible name. `ReferenceIcon` is now decorative
  (aria-hidden) at the source, so AgentIcon's titled <svg> no longer leaks into
  option/badge names.

Codex-reviewed (APPROVED after 3 rounds: added aria-autocomplete, role=img,
listbox-owns-only-options, decorative icon). Gate: 1278 tests, eslint clean,
build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `@` panel capped each group at 50 and silently dropped the rest, and its
chrome (empty/loading/listbox name/count) + group headings were hardcoded
English in a 10-language app.

- buildReferenceGroups flags `truncated` per group cheaply (sets it when a match
  is found past the cap — no full scan) rather than silently dropping overflow.
- The panel renders a per-group, aria-hidden, non-selectable "keep typing to
  filter" hint (kept out of `flat`, so Arrow/Enter never land on it; the listbox
  still owns only options), and the polite live region appends it after the
  count so screen-reader users learn of truncation too.
- mentionUiLabels threads localized empty/loading/listbox/more/count from
  message-input → RichComposer → SuggestionPopup; group headings are localized
  via useReferenceSearch({labels}). 10 new keys added to all 10 locales
  (mentionCount uses ICU plurals; Arabic carries the full category set).

Codex-reviewed (APPROVED, first round). Gate: 1281 tests (i18n key parity
included), eslint clean, build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nal_id> format (P5a)

Extract the reference-uri grammar (file:/codeg://session/codeg://commit →
ReferenceAttrs) out of from-prompt-blocks.ts into a shared reference-uri.ts so
the transcript renderer can reuse it; re-export parseReferenceUri under its old
name for existing importers. Change the session serialization uri to
codeg://session/<agent_type>_<external_id> (falling back to the numeric id when
external_id is null) so a transcript badge can recover the agent icon and a
future codeg-mcp can resolve sessions. Agent types contain underscores, so the
parser recovers the type by prefix-matching ALL_AGENT_TYPES, never by splitting
on the first underscore; legacy numeric ids degrade to a session badge without
an agent icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Intercept codeg: hrefs in the Streamdown anchor override and render them with
the composer's ReferenceBadge instead of a plain (and previously inert, since
link-safety rejects the scheme) underlined link. The shared parseCodegReferenceUri
recovers refType/id/meta from the uri and the link text is the label; a
new-format session uri yields the agent icon, a legacy numeric one degrades to a
generic icon. Unknown codeg:// uris and all other schemes keep their existing
link rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give agent mentions a codeg://agent/<agent_type> uri so they behave like
session/commit: serialize inline as [@Label](codeg://agent/…), render as a
transcript badge (via the P5b interceptor), and parse back. The no-uri fallback
keeps the prior plain @Label form (with @ kept outside inlineText so a URL-like
label is still code-spanned). The uri is a frontend anchor only — opaque to the
agent, which still reads the @Label; real routing is a separate future concern.
to-prompt-blocks still lifts only file:// to a resource_link, so agent stays
inline; the node scheme allow-list already permits codeg:.

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

Replace the single scrolling grouped list with a tabbed panel: one tab per
reference kind in [agent, file, session, commit, skill] order, only the active
tab's group shown, per-tab result counts. The active tab auto-follows the first
non-empty tab (agent-first) until the user pins one via Tab/Shift+Tab or click,
so a file/session query never strands the user on an empty agent tab; the panel
remounts per @ session so the pin never leaks. Tab/Shift+Tab switch tabs (Enter
still selects); the tab strip never takes DOM focus (tabIndex=-1 + mousedown
preventDefault, switch on click for AT) so the editor keeps focus and drives the
listbox via a tab-namespaced aria-activedescendant. Option ids are namespaced by
kind; the listbox still owns only options; P4a positioning and P4c truncation are
preserved. tabLabels are threaded from message-input's localized group labels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r messages (P6)

extractUserResourcesFromText lifted any [label](uri) link whose label
started with @ (or whose uri was file://) into the bottom resource-chip
row. P5c serialized agents as [@Label](codeg://agent/…), so the leading
@ matched hasMentionLabel and the agent reference was pulled out of the
prose — the P5b/P5c inline ReferenceBadge never rendered in the real
transcript (a session whose title starts with @ hit the same trap).

Short-circuit any codeg: reference link to stay inline (→ markdown-link
→ ReferenceBadge), mirroring markdown-link's own codeg: interception.
file:// files and @name [blocked] mentions are unchanged (still chips),
per the user's 'don't touch files this round' directive. Backend,
session parsers, send/restore serialization untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ges (P7)

User-message @agent/@session/@commit references rendered as literal
"@codex CLI [blocked]" instead of inline badges. Streamdown's default rehype
pipeline runs rehype-sanitize before rehype-harden; the sanitize schema's
protocols.href allow-list omits the app-internal `codeg` scheme, so it strips
the href off `[label](codeg://…)` links. harden then sees a hrefless <a> and
replaces it with a "… [blocked]" span — all before react-markdown maps <a> to
MarkdownLink (→ ReferenceBadge), so the badge code never ran.

Re-derive the pipeline (rehype-allow-codeg) with `codeg` added to the sanitize
allow-list so the href reaches MarkdownLink. harden is left untouched: it
already permits all protocols via its `*` default and still hard-blocks
javascript:/data:/file:/vbscript:, so widening sanitize by one inert app scheme
adds no XSS surface. file:// links are unaffected (rewritten to local paths at
the remark layer before sanitize runs).

Tests: helper unit test (adds codeg once, preserves plugin order, no schema
mutation) + MessageResponse integration tests through the REAL Streamdown
pipeline (agent/session/commit each render a badge, never "[blocked]"; plus an
http-link regression guard). Add defaultRehypePlugins to message.test.tsx's
streamdown mock so the module-scope call doesn't throw.

Gate: vitest 1319 pass, eslint clean, next build OK. Codex review APPROVED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lization (P8a)

Badge presentation/serialization layer for the composer optimization pass:

- ReferenceMeta gains `invocationPrefix?: "/" | "$"`; the skill branch of
  referenceToMarkdown now emits `${prefix}${id}` (commands & most skills `/`,
  Codex skills/experts `$`) instead of a hardcoded `/id`.
- ReferenceBadge: each refType gets its own soft tinted color set (light+dark) —
  file=blue, agent=violet, session=emerald, commit=amber, command/skill=sky,
  expert=fuchsia; the `skill` icon splits to a star (experts) vs cmd glyph.
- Fix vertical alignment: badges use `align-middle` so text no longer sits at the
  bottom relative to the taller badge (transcript + composer).
- Harden skill-token serialization (Codex review): an id is emitted raw only when
  it is a literal invocation slug (alnum slug, no autolink trigger, `_` strictly
  intraword); filesystem-sourced ids that could inject Markdown (`![x](http://…)`,
  `a/_b_/c`) fall through to the safe escaped/code-spanned path. Escaping is not
  applied to real ids (would defeat invocation).

Gate: vitest 1360 pass, eslint clean, next build OK. Codex APPROVED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Skills/commands/experts are no longer surfaced in the unified `@` mention panel —
they are reached via the `/` and `$` triggers and the expert menu (the badge
insertion wiring lands in P8c).

- suggestion-popup: TAB_ORDER → 4 tabs (agent/file/session/commit); Shift+Tab
  from agent now wraps to commit.
- use-reference-search: buildReferenceGroups returns 4 groups; the hook no longer
  loads skills/built-in experts/agent experts/locale, and the `agentType` option
  (which only scoped those) is removed. R7 referential stability and the R8
  folder-switch guard are unchanged.
- adapters: retire skillToSuggestion/expertToSuggestion (only the panel used them).
- message-input: drop the now-removed agentType arg from the useReferenceSearch call.

ReferenceKind keeps `skill` (the badge node kind / serialization). Consequence:
non-Codex agents no longer see disk skills in `@` (they were only ever there);
their commands stay reachable via `/` ACP availableCommands and experts via the
expert menu.

Gate: vitest 1354 pass, eslint clean, next build OK. Codex APPROVED (Findings: none).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `/` (commands), `$` (Codex skills) triggers and the expert menu now insert
inline reference badges (refType `skill`) instead of plain text. On send each
badge serializes back to its literal invocation token (`/cmd`, `$skill`,
`/expert`) via referenceToMarkdown, so the agent CLI sees exactly what it did
before — the badges are a composer-only nicety (the transcript shows the literal
text, which is correct).

- new invocation-reference.ts: pure builders commandToReference / skillToReference
  / expertToReference → ReferenceAttrs (meta.invocationPrefix; experts also
  meta.scope "expert" → star icon).
- composer-commands: applyExpertPrefix → applyExpertReference — front-anchors the
  expert badge (prepends a paragraph when the first block isn't one) and replaces
  an existing leading expert badge rather than stacking. Replacement keys solely
  on meta.scope === "expert" (the unambiguous badge marker), which also fixes a
  latent bug where agent-linked experts — absent from the built-in id set the old
  code checked — would stack (Codex review).
- message-input: replaceTriggerToken → replaceTriggerWithReference; the slash/
  skill/"+"-command/expert handlers build refs and insert badges. Expert badge
  label matches the menu's pickExpertLocalized name.

Gate: vitest 1360 pass, eslint clean, next build OK. Codex APPROVED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously only the editor surface itself was clickable; the card's padding, the
blank space below a short message, and the gaps in the action bar were dead. Now
a mousedown on that empty chrome focuses the editor.

- new pure helper isComposerChromeClick(target) in composer-commands.ts: true
  unless the target (or an ancestor) is the editor surface, an interactive
  control, or an inline badge.
- message-input: a handleChromeMouseDown handler on the bordered card
  preventDefaults (so the editor doesn't blur first) and refocuses the editor
  when the click lands on empty chrome; bails on disabled.

Gate: vitest 1365 pass, eslint clean, next build OK. Codex APPROVED (Findings: none).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ors (P9a)

Per user request, inline reference badges are restyled:

- Commands, skills and experts now all render the SAME command glyph (the expert
  star is gone). `meta.scope === "expert"` is kept in the data model only for the
  editor's expert-replace logic, not the icon/color.
- Badges drop the background, border, rounded corners and padding — they are now
  text-only colored tokens that sit cleanly on the user-message bubble
  (`bg-secondary`). One color per kind: file blue / agent violet / session
  emerald / commit amber / skill (cmd) rose. Light shades are `-700` so they
  clear WCAG AA contrast on the near-white bubble; dark shades `-400`.

Gate: vitest 1365 pass, eslint clean, next build OK. Codex APPROVED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (P9b)

Slash commands / Codex skills / experts are sent as bare invocation tokens
(`/review`, `$deploy`) — the agent needs them literal, so there's no link to key
a badge off. This restores the badge FOR DISPLAY in transcript user messages via
a heuristic rehype plugin.

- new ai-elements/rehype-command-badges.ts: a hand-rolled hast walk (no new deps,
  mirrors remark-file-uri-links) that scans text for `/slug`·`$slug` tokens and
  wraps each in a `codeg://skill/<slug>` link (keeping the literal prefix as the
  label), which the existing MarkdownLink → ReferenceBadge path renders. Skips
  code/pre, existing links, and math; skips paths (`/a/b`), digit-leading tokens,
  and word-glued tokens (`a/b`).
- reference-uri.ts: parse `codeg://skill/<slug>` → refType skill.
- message.tsx: apply the plugin ONLY to user messages (`softBreaks`), appended
  after harden and (per the Streamdown dist) before the math/katex rehype plugin
  — so `$x$` math (already a `.math` element by then) is never mistaken for a
  `$skill` token. Assistant messages are untouched; copy still yields bare text.

Gate: vitest 1383 pass (rehype unit 10 + real-Streamdown integration 6 + uri 2),
eslint clean, next build OK. Codex APPROVED (Findings: none).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-image files attached to a user message now render as inline file badges inside the message body, at the position where they were inserted — in the composer and in sent messages alike — alongside the file chip row kept below the text. The badges are clickable and open the file in the workspace panel, and sit vertically centered on the text line.

Files travel through the send pipeline as inline [name](file://…) markdown links, so their in-text position is preserved when a conversation is reloaded, for every agent. Path-less pasted attachments render as inert codeg://embedded badges and have their bytes attached out of band.
Clicking anywhere in the composer — including the blank padding and the
dead space around a short message — now places the caret at the click
point instead of jumping to the end of the document, matching a native
textarea. The whole input box paints the text I-beam cursor over its
blank areas, while interactive controls keep the pointer cursor.
Conversation titles that embed inline reference links — the
`[label](uri)` form produced by file, session, commit and agent
mentions — now display just the bracket label across the tab bar, the
sidebar, search, the manage dialog and the pet panel, instead of raw
Markdown. The change is display-only; the stored title is unchanged.
…itles

Inline session reference badges — in the composer and in user-message
transcripts — now show a neutral conversation icon instead of the owning
agent's icon and no longer render a trailing status dot, so a session
reads as a conversation rather than as the agent that owns it. The @
panel's option rows keep the agent icon so sessions stay distinguishable
while picking one.

The @ panel also folds any inline reference link inside a conversation's
title down to its label text, matching the sidebar, so a session row and
the badge it inserts read as plain titles instead of raw Markdown.
Resolve a session by its numeric conversation id and return metadata,
token-usage stats, and an optional bounded view of recent messages
(max_messages, default 20). The composer now emits
codeg://session/<conversation_id> for session mentions so the agent can
read the id straight out of the link. Gated by a new `sessions` feature
group with a Settings toggle, defaulting on.
Conversation titles derived from a user's first message kept raw Markdown reference links such as `[name.xlsx](file:///…)`, and the length cap could slice a link mid-destination into a broken, unfoldable fragment. Parsers now fold reference links to their labels before truncating, so titles show the clean label across the sidebar, tabs and search.

Consolidate the previously duplicated reference-link logic into a single canonical module, src/lib/reference-link.ts: file:// URI building, label unescaping, and an O(n) [label](destination) tokenizer/folder. formatConversationTitle and the transcript resource extractor use it, and the drag-to-input and @-mention paths share one file:// builder. A matching folder in the Rust parsers keeps backend-generated titles in step.
The interface font now defaults to System Monospace, joining the editor
and terminal so all three font targets in appearance settings share the
same monospace stack out of the box. The interface picker still offers
every sans and mono option, so the former System UI default stays one
click away.

The :root --font-sans fallback carries the monospace stack as well, so
the first paint matches the resolved default with no flash, and the
pre-hydration script only applies a cached interface font stack when an
explicit selection exists — users who never picked a font follow the new
default while explicit choices are preserved.
…badge

Select lines in the file editor and send them to the composer as an inline file badge (e.g. foo.ts:10-25) via the context menu, Cmd/Ctrl+L, or a floating Add to Chat pill. The badge serializes to a file:// link with an #L<start>-<end> fragment and opens to the start line when clicked.
@pull pull Bot locked and limited conversation to collaborators Jun 14, 2026
@pull pull Bot added the ⤵️ pull label Jun 14, 2026
@pull pull Bot merged commit 1670ebd into MoozLee:main Jun 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant