Interfaces · Shipped
Paste any text and see it rewritten in five registers at once, with every changed word highlighted against the original.
Paste any text and see it rewritten in five registers at once, with every changed word highlighted against the original.
Live demo · Video · Day 01 / 30
You drop in a paragraph — an email, a bio, a product description — and get five rewrites side by side: formal, warm, direct, technical, and persuasive. Each version streams in as it's written, and changed words are underlined so you can see exactly what was altered rather than re-reading five near-identical blocks of text.
The point is comparison. Most rewriting tools give you one answer and make you guess whether a different instruction would have been better.
The highlighted node is where the interesting work happens. The five calls fire simultaneously and each streams into its own pane independently — a slow or failed register doesn't block the other four. The panes fill at different speeds, which is why the interface feels fast even though total token usage is higher than a single combined call.
Diffing runs on the client against the settled text of each pane, not on every streamed token.
tone-studio/
├── app/
│ ├── api/
│ │ └── rewrite/
│ │ └── route.ts # fan-out + SSE stream
│ ├── layout.tsx
│ └── page.tsx
│
├── components/
│ ├── ComparePane.tsx # one register column
│ ├── DiffText.tsx # word-level highlighting
│ └── InputPanel.tsx
│
├── lib/
│ ├── registers.ts # the 5 prompt configs — edit here, not in the route
│ ├── claude.ts # API client, streaming, retry
│ └── diff.ts # diff-match-patch wrapper
│
├── shell/ # reusable across all 30 projects
│ ├── useStream.ts # SSE hook
│ ├── Toast.tsx
│ ├── FileDrop.tsx
│ └── theme.css # shared tokens
│
├── .env.example
├── package.json
└── README.md
Two things are deliberate. lib/registers.ts holds the prompts as data, so adding a register is a config change rather than a code change. And shell/ stays separate from components/ because nothing in it knows anything about this project — it's the generic scaffolding, copied forward into the next build.
| Layer | Choice | Why this one |
|---|---|---|
| Model | Claude (claude-sonnet-4-6) | Strong instruction-following on register control; streaming supported natively |
| Prompting | Templates in lib/registers.ts | Registers are data, not code — new ones need no redeploy of logic |
| Backend | Next.js route handlers | Same project as the frontend, no separate service to run for a single endpoint |
| Streaming | Server-Sent Events | One-directional token flow; simpler than WebSockets and needs no connection state |
| Concurrency | Promise.all fan-out | Five registers run in parallel and fail independently |
| Diffing | diff-match-patch | Battle-tested word-level diff; runs client-side so streaming stays cheap |
| UI | React 19 + Tailwind | Fast iteration, and the shared shell/ is built on it for reuse across later projects |
| Guards | Token cap + request timeout | Public demo can't run up an unbounded bill |
Five parallel calls instead of one call returning five variants.
A single call is cheaper — you pay for the input text once instead of five times — and it's the obvious first implementation. I rejected it for two reasons:
The cost difference is real but small at this input size. If the input grew to full documents, I'd switch to prompt caching on the shared input block rather than back to a single call.
Secondary decision: diffing happens client-side. Server-side diffing would mean recomputing the comparison on every streamed token, which is wasted work for a result nobody reads until the pane settles.
git clone https://github.com/AhmadTawil1/tone-studio && cd tone-studio
cp .env.example .env.local # add ANTHROPIC_API_KEY
npm install
npm run dev # http://localhost:3000
Environment variables:
| Variable | Required | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | yes | Model access |
MAX_INPUT_CHARS | no | Input ceiling, defaults to 4000 |
DAILY_TOKEN_CAP | no | Spend guard for the public demo |
/debug is a local-only page (it 404s in production) that visualizes the most recent /api/rewrite request: a timeline chart showing all 5 registers' bars running concurrently, per-register stats (time to first word, total duration, tokens), and a parallel-vs-sequential time comparison. It exists to make the "Key decision" above verifiable instead of just asserted.
