Skip to content

Second Voice, design and engineering, independent product, 2026

Choose a literary voice. See exactly what changes.

Generating a rewrite is easy. Trusting one is not. The original stays close, and every edit stays inspectable.

Your draft

The street was empty when she arrived. She waited by the door and checked her phone again.

Voice

Longer rhythm, more image.

Strength

Rewrite

Lyrical, strongRun 1

The street was empty when had emptied itself by the time she arrived. She waited by lingered at the door and checked her, the phone again glowing in her palm, as if it might change its mind.

8 of 17 words kept4 replaced0 added0 removedaddedremoved

Prepared examples. This page doesn't call a model; every rewrite is authored and every diff is computed live.

A rewrite remembers how it was made.

Change the voice without pressing Rewrite. A naive interface relabels the old rewrite with the new controls. Second Voice keeps a snapshot of what was actually submitted.

A label read from the controls

Lyrical, strong

Correct only while nothing has changed.

A label read from the submission

Lyrical, strong

Run 1, from the snapshot taken at submit.

Voice

Same source, different transformation.

Comparison only means something when the source is fixed. Each voice rewrites the same two sentences, and the diff shows how far it travelled.

The street was empty when she arrived. She waited by the door and checked her phone again.

Spare

Fewer words, harder edges.

The Empty street was empty when she arrived. She waited by at the door and checked. Checked her phone again.

41%of the original's words kept

3 replaced, 0 added, 2 removed

Lyrical

Longer rhythm, more image.

The street was empty when had emptied itself by the time she arrived. She waited by lingered at the door and checked her, the phone again glowing in her palm, as if it might change its mind.

47%of the original's words kept

4 replaced, 0 added, 0 removed

Noir

Clipped, wry and shadowed.

The Nobody on the street was empty when she arrived showed up. She waited by leaned on the door and checked her phone again, like it owed her money.

65%of the original's words kept

3 replaced, 1 added, 1 removed

Behind Rewrite, the request has to earn a model call.

Generation costs real resources, so the server, not the button, decides. Run a request, then replay it: an operation that already exists never reaches the model twice.

Send

Illustrative model of the release rules.

  1. Submit

  2. Session

  3. Allowance

  4. Idempotency key

  5. Validate input

  6. Provider, 30 s deadline

  7. Output schema

  8. Persist result and usage

  9. Result

0Model calls
3Requests left today, per account

Operations

  • None yet

Private until you share it.

A writing tool holds personal text. A feature called share is also an authorisation problem, so public has to be a deliberate act.

  • Private writingthe default for everything
  • Explicitly public resultsonly after Share
  • Generated resultsowned, not published
  • Feedback and evaluationseparate from content
  • User identityseparate from shares
  • Telemetryoperational only

The blocker. An earlier migration could have treated historical content as public. It was caught before release and treated as a deployment blocker, and visibility became an explicit, first-class state.

What debugging can see

  • Request id
  • Provider and model
  • Latency
  • Schema fallback state
  • Feedback
  • Rewrite hash

Enough to trace a bad rewrite to its request.

What it avoids collecting

  • Raw drafts
  • Raw rewrites

Hashes stand in for text wherever that is enough. Observability is not a reason to read people's writing.

Shipping it was not frictionless.

Local verification and production readiness turned out to be different claims. The page states which one holds today.

  1. 136 tests verified at a milestone

    Verified

    Auth and sessions, quota rules, replay and idempotency, logout and revocation, and one real rewrite on Groq's free tier. Deployment readiness still waited on production checks.

  2. CI at 81 of 82

    Repaired and merged

    One security-environment expectation failed, and the install reported dependency vulnerabilities, one of them critical. Both were fixed and merged before moving on.

  3. 29 September 2026: the public app opens

    Live generation unverified

    A sample live rewrite returned Request blocked. Until that is resolved, live generation is not claimed as production-verified, and this page uses prepared examples.

Stack, limits and safeguards.

Limits for the free public release

25Portfolio users, not recycled
10Requests per account, lifetime
3Per account, rolling day
1Per account, rolling minute
2Global, per minute
25Global, per day
250Global, lifetime
1Active operation, globally

Built with

Next.js, React, TypeScript, server routes, Supabase, Zod and Playwright.

Sign-in

GitHub OAuth with PKCE, HttpOnly verifier and session cookies, fixed callback origins, durable sessions, revocation and stale-cookie cleanup. No anonymous generation.

Provider calls

Bounded reads, cancellation, a deadline of about 30 seconds, zero automatic retries and one model call per operation. Ambiguous operations stay reserved instead of running again.

Output

Validated against a schema before it becomes product state. Generative output is treated as untrusted input.