Skip to content

feat: P2 — tool approvals, streaming markdown, and tool-call diffs - #6

Merged
saucam merged 1 commit into
mainfrom
feat/p2-approvals-streaming
Jul 25, 2026
Merged

feat: P2 — tool approvals, streaming markdown, and tool-call diffs#6
saucam merged 1 commit into
mainfrom
feat/p2-approvals-streaming

Conversation

@saucam

@saucam saucam commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

What

P2 for codeoid-mobile: the provider-agnostic approve/deny bar, a markdown streaming transcript, and Edit/Write tool diffs. Mobile-only — no daemon or protocol changes; builds on the already-published @codeoid/{protocol,core}@0.1.0.

This is the "approvals + streaming first" slice. Push notifications are a separate follow-up (they need protocol + daemon + a relay).

Highlights

  • ApprovalBar — binary approve/deny via session.approve. Detection reuses the shared @codeoid/core findPendingApproval kernel (usePendingApproval = throttled transcript + live session status), so it stays consistent with the web bar. Provider-agnostic by construction: claude / codex / pi / acp all normalize to the one waiting_confirmation + approvalId wire contract in the daemon, so the bar never branches on provider. The in-flight machine stays disabled until the tool-state delta lands and is keyed by approvalId so each approval starts clean.
  • Streaming transcriptFlashList v2 (chat-style stick-to-bottom via maintainVisibleContentPosition), MessageRow role dispatch, MarkdownText (react-native-marked, memoized by text). rAF-coalesced store subscriptions + a per-row useMessageVersion so a 20-40/s token stream repaints once per frame and streamed content never freezes behind memoization (the store mutates messages in place, so ref identity can't drive per-row updates).
  • ToolCard — tool-call state machine (phase badge + per-phase body) with a persistent Edit/Write diff across phases, reconstructed from tool.input with jsdiff and collapsed to ±2 context lines (src/lib/diff.ts).

Library choice

Deliberately react-native-marked (pure-JS; only native peer is react-native-svg, bundled in Expo Go SDK 57), not the design doc's react-native-streamdown — streamdown's native peers (react-native-enriched-markdown, react-native-worklets) aren't in Expo Go and would force a custom dev build. Keeping this slice Expo-Go-runnable is the whole point of shipping it first (usable over Tailscale today). FlashList 2.0.2 + react-native-svg 15.15.4 are pinned to the versions Expo Go SDK 57 bundles.

Scope notes

  • v1 approvals are binaryupdatedInput (edit-then-approve) is deferred: the daemon doesn't broadcast a tool's patchableKeys to clients, and codex ignores updatedInput.
  • gemini/openai backends expose no gateable mutating tools, so the bar simply never appears for them (correct behavior).
  • Rich parts[] rendering (code/table/tree/image beyond plain text) is a follow-up; assistant text renders from the authoritative content string, matching the web UI.

Verification

  • tsc --noEmit + eslint . clean.
  • expo export --platform web bundles 952 modules.
  • src/lib/diff.ts unit-tested — 14 assertions incl. context collapse + line numbering.
  • Read-only protocol probe against a live daemon (auth → list → attach) observed 525 real messages across all roles; every shape matches the renderers.
  • Pending: a live approval click-through (a session must be actively blocked on a tool) + on-device render — needs an interactive session, not exercisable headlessly.

🤖 Generated with Claude Code

Builds the P2 slice from the mobile design doc (§6-§8, §10): a
provider-agnostic approve/deny bar, a markdown-rendered streaming
transcript, and Edit/Write diffs — all on the already-published
@codeoid/{protocol,core}@0.1.0 (no package bump; the approval wire
contract already ships there).

- ApprovalBar: binary approve/deny over `session.approve`. Detection
  delegates to the shared @codeoid/core `findPendingApproval` kernel via
  usePendingApproval (throttled transcript + live session status), so
  mobile stays byte-for-byte consistent with the web bar. Provider-
  agnostic for free: claude/codex/pi/acp all normalize to this one wire
  contract in the daemon, so the bar never branches on provider. The
  in-flight machine stays disabled until the tool-state delta lands and
  is keyed by approvalId so each approval starts clean. v1 is binary-only
  — updatedInput is deferred (the daemon doesn't broadcast patchableKeys
  to clients and codex ignores it).
- MessageRow: role dispatch. assistant/thinking render markdown from the
  authoritative `content` string (matching web); tool_call -> ToolCard.
- ToolCard: tool-call state machine (phase badge + per-phase body) with a
  persistent Edit/Write diff across phases, reconstructed from tool.input
  with jsdiff and collapsed to +/-2 context lines (src/lib/diff.ts).
- Transcript list swapped FlatList -> FlashList v2 with chat-style
  maintainVisibleContentPosition (startRenderingFromBottom + autoscroll).
- Streaming perf: rAF-coalesced store subscriptions (useTranscript plus a
  new useMessageVersion) so a 20-40/s token stream repaints once per
  frame; MarkdownText memoized by text, and rows re-render off their own
  message version (the store mutates messages in place, so reference
  identity can't drive per-row updates).

Deps are all Expo-Go-compatible — this slice must run over Tailscale with
no custom dev build: @shopify/flash-list@2.0.2 + react-native-svg@15.15.4
(both bundled in Expo Go SDK 57), react-native-marked@8.1.1 (pure-JS
markdown; its only native peer is svg), diff@9. Deliberately NOT
react-native-streamdown — its native peers (react-native-enriched-markdown,
react-native-worklets) are not in Expo Go and would force a dev build.

Verified: tsc + eslint clean; expo export (web) bundles 952 modules;
src/lib/diff.ts unit-tested (14 assertions incl. context collapse and line
numbering); a read-only protocol probe against the live daemon (auth ->
list -> attach) observed 525 real messages across all roles and confirmed
every shape matches the renderers. A live approval click-through (needs a
session actively blocked on a tool) pends an on-device/browser session.

Signed-off-by: Yash Datta <saucam@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addeddiff@​9.0.010010010086100
Addedreact-native-svg@​15.15.49910010088100
Addedreact-native-marked@​8.1.19510010090100
Added@​shopify/​flash-list@​2.0.21001009894100

View full report

@saucam
saucam merged commit ccd45d8 into main Jul 25, 2026
3 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.

2 participants