Skip to content

feat(web): composer bang grammar library + terminal_command receipt field plumbing#3034

Draft
btli wants to merge 2 commits into
omnigent-ai:mainfrom
btli:polly/t3-bang-grammar-lib
Draft

feat(web): composer bang grammar library + terminal_command receipt field plumbing#3034
btli wants to merge 2 commits into
omnigent-ai:mainfrom
btli:polly/t3-bang-grammar-lib

Conversation

@btli

@btli btli commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Related issue

N/A — task T3 of the composer bang-shell-commands workstream (grammar + receipt plumbing; sibling backend PRs: runner input route #3033).

Summary

Client-side groundwork for Claude Code-style ! shell commands in the chat composer. Pure library/plumbing code — the composer submit wiring and autocomplete menu land in a follow-up PR, so nothing here is user-reachable yet:

  • web/src/lib/composerBang.ts — framework-free bang grammar (mirrors composerMentions.ts): ! cmd (new default-type shell), !<running-key> cmd (send into an existing shell), !<shell-type> cmd (new shell of that type), bare ! / focus-only forms, and the \! + leading-space escape hatches. Resolution is exact-token, first match wins: running-shell session key → full resource id → declared type → inline error. Dead shells and agent panes are never targetable.
  • Receipt field plumbing — the optional terminal_command receipt fields (action, terminal_id, terminal_name, session_key, status, error) plus item-level created_by thread through sse.tsevents.tsblockStream.ts / itemsToBlocks.tsblocks.tsrenderItems.tsBlockRenderer.tsx. The delivery outcome rides the item-level status slot (the server flattens the data payload over the item envelope), so only "ok"/"error" surface as outcomes and lifecycle statuses stay invisible.
  • TerminalCommandCard — receipts grow a muted target-shell chip (→ zsh · u-ab12cd, spawn variant → new zsh · u-ab12cd), destructive icon + message line on status="error", and an optional onFocusShell callback so the follow-up can wire click-to-focus of the shell rail. Legacy no-action items (the Claude Code TUI observer path) render byte-identically — pinned by snapshots recorded against the pre-change markup.

ELI5: we taught the web app the !command mini-language and how to display "this command was sent to that shell" receipts, but haven't plugged either into the message box yet.

composer text ──> composerBang.parse ──> {new|send|focus|error}   (wired in follow-up)

SSE / reload item ──> sse.ts / itemsToBlocks.ts ──> TerminalCommandBlock
      (+action, terminal_id, …, created_by)            │
                                                renderItems.ts
                                                       │
                                              TerminalCommandCard
                                        legacy: unchanged  │  receipt: chip/error

Test Plan

All from web/ on this branch:

  • bun run lint (oxlint) — no new findings; the 10 pre-existing errors on main (unrelated files) are unchanged.
  • bun run type-check (tsc -b) — passed.
  • bunx vitest run src/lib/composerBang.test.ts src/lib/sse.test.ts src/lib/blockStream.test.ts src/lib/itemsToBlocks.test.ts src/lib/renderItems.test.ts src/components/blocks/TerminalCommandCard.test.tsx src/components/blocks/BlockRenderer.test.tsx — 7 files, 235 passed.
  • Full bun run test — identical pass/fail profile to clean main on this machine (646 pre-existing local-env failures on both, +54 new passing tests, 0 new failures).
  • pre-commit run --all-files — passed.
  • Byte-identity: the legacy-markup snapshots were recorded before modifying the card, then re-run unchanged against the extended card.

Demo

N/A — non-visual plumbing behind dead code; legacy rendering is snapshot-pinned unchanged, and the new receipt variants only render once the server-side shell_command event (separate PR) starts emitting the fields.

Note on the e2e-ui gate: this touches web/** but adds no reachable UI behavior; the Playwright tests/e2e_ui coverage for the bang-command flow lands with the follow-up composer-integration task (or the maintainer skip-e2e-ui-test label applies to this plumbing-only PR).

Type of change

  • Bug fix
  • Feature
  • UI / frontend change
  • Refactor / chore
  • Docs
  • Test / CI
  • Breaking change

Test coverage

  • Unit tests added / updated
  • Integration tests added / updated
  • E2E tests added / updated
  • Manual verification completed
  • Existing tests cover this change
  • Not applicable

Coverage notes

Exhaustive vitest table for every grammar row (escapes, verbatim multiline commands, precedence/collisions, dead shells, agent panes, no-shell-access) plus snapshot tests for all card variants and field-passthrough tests at each plumbing chokepoint. E2E UI coverage intentionally deferred to the follow-up task that makes the feature reachable (see Demo note).

🤖 Generated with Claude Code

…bing

Add the client-side grammar for composer `!` shell commands and thread
the bang-receipt fields through the terminal_command render pipeline.
Pure library/plumbing code — composer submit wiring and the autocomplete
menu land in a follow-up, so nothing is user-reachable yet.

- web/src/lib/composerBang.ts: framework-free grammar (mirrors
  composerMentions): `! cmd` (new default-type shell), `!<key> cmd`
  (send into a running shell), `!<type> cmd` (new shell of that type),
  bare `!` / focus-only forms, `\!` + leading-space escapes. Exact-token
  resolution: running-shell session key → full resource id → declared
  type → error; dead shells and agent panes are never targetable.
- Receipt fields (action, terminal_id, terminal_name, session_key,
  status, error) plus item-level created_by threaded through sse.ts,
  events.ts, blockStream.ts, blocks.ts, itemsToBlocks.ts,
  conversationItems.ts, renderItems.ts, BlockRenderer.tsx. The delivery
  outcome rides the item-level status slot (the server flattens the
  data payload over the item envelope), so only "ok"/"error" surface.
- TerminalCommandCard: target-shell chip (`→ zsh · u-ab12cd`, spawn
  variant `→ new …`), destructive styling + message line for error
  receipts, optional click-to-focus hook for the shell rail. Legacy
  no-`action` items render byte-identically (snapshot-pinned against
  the pre-change markup).

Tests: exhaustive grammar table for composerBang; card variant
snapshots; field passthrough in the sse/blockStream/itemsToBlocks/
renderItems suites.

Signed-off-by: Bryan Li <bryan.li@gmail.com>

Co-authored-by: omnigent <noreply@omnigent.ai>
@github-actions
github-actions Bot requested a review from serena-ruan July 21, 2026 21:26
@github-actions github-actions Bot added the size/XL Pull request size: XL label Jul 21, 2026
Three blocking review fixes against the spec's grammar and receipt
contracts:

- Whitespace-only commands are preserved verbatim: only a truly empty
  remainder after the single separator counts as "no command", so
  `!u-ab12cd   ` sends the literal spaces instead of collapsing to a
  focus action. The grammar test that pinned the trimming behavior is
  corrected, with new coverage for tab separators, tab-only and
  Unicode-whitespace-only commands, the literal `u-` token, and the
  no-command running-key-vs-type precedence collision.
- Receipt status is gated on the `action` discriminator via one shared
  `terminalCommandStatus(action, status)` rule used by both the SSE and
  reload chokepoints, and the card ignores status/error entirely without
  `action` — a legacy item carrying a flattened "ok"/"error" status now
  provably renders byte-identically to plain legacy (new equality +
  snapshot tests).
- Error receipts draw a destructive border in addition to the icon and
  message line, per the receipt rendering contract.

Signed-off-by: Bryan Li <bryan.li@gmail.com>

Co-authored-by: omnigent <noreply@omnigent.ai>
@btli

btli commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Cross-review fixes pushed in 5268910:

  • B1 (whitespace-only commands)parseBangCommand now treats only a truly empty remainder after the single separator as "no command"; whitespace remainders are verbatim command content per §3.1. The test that pinned the trimming behavior is corrected, plus new coverage: tab separators (!\tcmd, !zsh\tcmd, !u-ab12cd\t\t), NBSP/ideographic-space-only commands, the literal u- token, and the no-command running-key-vs-declared-type collision (!zsh focuses when a session is literally keyed zsh).
  • B2 (status gated on action) — one shared terminalCommandStatus(action, status) rule in conversationItems.ts now feeds both the SSE parser and the reload translator; the card additionally ignores status/error entirely without action. New tests assert a no-action item carrying a flattened "ok"/"error" status renders byte-identically to plain legacy (direct innerHTML equality + snapshots), and both chokepoints drop the stray status.
  • B3 (destructive border) — error receipts now draw border-destructive/50 alongside the destructive icon and message line per §6; the error-variant snapshot is updated (only that snapshot changed — the four legacy byte-identity snapshots are untouched).

Deferred as agreed: forwarding createdBy into the terminal branch of BlockRenderer lands with the T4 composer-integration task.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

@btli This PR is a Bug fix, Feature, or UI / frontend change but the Demo section is missing or only contains a placeholder.

These change types require a screenshot or screen recording so reviewers can see the new behaviour without checking out the branch. Please update the Demo section with:

  • A screenshot or screen recording of the change, or
  • A link to a hosted video or GIF showing the new behaviour.

Use N/A only when the change has no user-visible effect whatsoever (e.g. a pure refactor or test-only change). If that's the case, uncheck the relevant type box and check Refactor / chore or Test / CI instead.

@github-actions github-actions Bot added the needs-demo PR needs a demo screenshot or recording label Jul 21, 2026
@btli
btli marked this pull request as draft July 22, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-demo PR needs a demo screenshot or recording size/XL Pull request size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants