Skip to content

feat(desktop): let a tool say how it should be drawn - #271

Open
oratis wants to merge 1 commit into
mainfrom
feat/tool-render-intent
Open

feat(desktop): let a tool say how it should be drawn#271
oratis wants to merge 1 commit into
mainfrom
feat/tool-render-intent

Conversation

@oratis

@oratis oratis commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Fourth implementation PR from docs/DSH_ADOPTION_PLAN.md §1.6 — the UI half of the DeepSeek Harness adoption.

THREE_WAY_REVIEW.md concluded the next stage's ROI is almost entirely in UI outlets rather than the feature list. This is the most concentrated one: users spend most of their time looking at tool cards, and every card looked identical.

Before

An Edit showed its result sentence but never the change. A Bash showed output with no indication which command produced it. All of it was already in the arguments — nothing read them.

After

A tool declares a render intent next to its schema, and clients read that:

Tool Intent Card
Bash terminal the command as a $ prompt, output below a rule
Edit, Write, NotebookEdit diff real +/- lines, coloured
everything else generic unchanged

Why the mapping lives in core

The opponent case in the plan was that presentation does not belong in packages/core, which advertises no UI dependency — and that ToolCard already had a diff boolean, so the desktop could just check name === 'Edit'.

It gains no UI dependency: the intent is a string enum and the payload is plain data, no React or DOM types anywhere near it. And the alternative is that one fact — "an Edit is a diff" — gets written in the desktop, again in the CLI, again in the VS Code extension, with a fourth copy owed for every new tool. Declared once on the tool, each client reads the same answer. The desktop's own duplicate pickTarget key list is deleted in this PR for the same reason.

Purity, and the honest Write

Presentation is a pure function of the arguments. It never reads the result or the filesystem, so a session replayed from its log renders exactly as it did live.

That constraint is why Write renders as wholly added. Its arguments genuinely do not say what the file held before, and synthesising a before-side would be a better-looking lie. Edit states both sides, so it gets a true two-sided diff.

A declared intent is a request, not a guarantee: a call declaring diff with no usable path or text falls back to generic rather than handing the client an empty diff to draw. Tested.

Verification

Visually verified in a dev-only preview harness (screenshot below) across all six states — both diff cases, terminal with output, terminal with an error, generic untouched, and a running command with no output yet. Console clean on a fresh tab. The harness is excluded from the production bundle exactly as the FilePanel one is; pnpm --filter @deepcode/desktop build confirms the output still contains only index.html.

pnpm typecheck, lint, format:check, build clean. Full suite green — core 1045 passed / 28 skipped, desktop 104, cli 242, server 49, protocol 34, lsp 13, vscode 12, scripts 42. 11 new tests, including one asserting the built-in intent table and the tool definitions cannot drift apart.

One small fix included: Bash no longer repeats its command in the header, since the terminal body already opens with it in full.

Every tool call rendered as the same grey text blob. An Edit showed its result
sentence but never the change; a Bash showed its output with no sign of which
command produced it. The information was all present in the arguments — nothing
was reading it.

A tool now declares a render intent alongside its schema, and clients read that
instead of each hardcoding tool names. Bash declares terminal, Edit/Write/
NotebookEdit declare diff, everything else stays generic.

The alternative was `name === 'Edit'` in the desktop, again in the CLI, again in
the VS Code extension — three copies of one fact, and a fourth to write for the
next tool. Declaring it once on the tool is why the mapping is in core.

Presentation is a pure function of the call's arguments. It never reads the
result or the filesystem, so a session replayed from its log renders exactly as
it did live. That constraint is also why Write renders as wholly added: its
arguments genuinely do not say what the file held before, and inventing a
before-side would be a nicer-looking lie.

A declared intent is a request, not a guarantee. A call declaring diff with no
usable path or text falls back to generic rather than handing the client an
empty diff to draw.

Verified in a dev-only preview harness: real +/- colouring on both diff cases,
the command as a shell prompt above its output, running and error states, and
generic left untouched. The harness is excluded from the production bundle the
same way the FilePanel one is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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