Margin is a live, annotation-led writing editor. It scores every sentence after a 300 ms typing pause and aligns the results beside the current text. It never rewrites or suggests replacement text; accepting a trim removes only the selected sentence.
This animated preview and the full video are committed in this repository. It shows the working app with Jev connected and a sentence being appended in real time. Select the preview to open the full-size MP4.
This animated preview and the full video are committed in this repository. It shows confidence-opacity heat mapping and trim decisions. Accepting a cut removes that sentence from the essay. Select the preview to open the full-size MP4.
- Splits plain text with
Intl.Segmenter. - Scores
role,cut_safety,redundancy,support, andclaritywith a0–1confidence for every verdict. - Follows the device's light or dark appearance preference.
- Reconciles unchanged sentences with an LCS diff, then re-scores only changed sentences and their immediate neighbors.
- Cancels stale requests when typing continues.
- Colors the heat map by cut safety and uses cut-safety confidence as opacity.
- Builds a trim plan from cuttable sentences, ranked by ascending confidence-weighted importance.
- Applies accepted sentence cuts and records keep decisions without changing unselected text.
- Exports aligned Markdown and a JSON score file.
- Drops malformed provider results instead of displaying an unvalidated verdict.
- TypeScript, React, and Vite
- TypeSafe AI's official
@typesafe-ai/sdk - Zod validation
Intl.Segmenter- Vitest and Playwright
There is no auth, database, or persistent editor state.
Requires Node.js 20 or newer.
npm install
cp .env.example .envSet one provider in .env.
SCORING_PROVIDER=jev
TYPESAFE_API_KEY=your_key
TYPESAFE_DEFAULT_MODEL=jev-latestCreate a key from the TypeSafe dashboard. Then run:
npm run devOpen the local URL printed by Vite. The API key is read only by the Vite server middleware and is never included in the browser bundle.
LLMProvider expects an OpenAI-compatible Chat Completions endpoint that supports strict json_schema response formats.
SCORING_PROVIDER=llm
LLM_API_KEY=your_key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=your_structured_output_modelOPENAI_API_KEY and OPENAI_BASE_URL are accepted as fallbacks. LLM_MODEL is required; Margin does not guess a model.
Verified on September 24, 2026 against TypeSafe AI's official documentation and your configured account:
- Introduction: Jev evaluates typed questions against state and returns structured decisions rather than generated prose.
- JavaScript/TypeScript SDK: official package
@typesafe-ai/sdk, Node.js 20+, usingTYPESAFE_API_KEY. - API reference: authenticated
POST https://api.typesafe.ai/v1/systemonewithstate,model, and a map of typed questions. - Models:
jev-latestcurrently resolves tojev-1.13.0;GET /v1/modelsis account-scoped. - Choice primitive: Choice answers include a selected label, probabilities, and
confidencefrom0–1.
Authenticated GET /v1/models returned HTTP 200 for the supplied key and listed jev-latest and jev-preview. The app therefore uses the documented SDK and endpoint rather than a guessed integration.
The editor and annotation history remain in browser memory. The local Vite API receives the current document's sentence strings, their zero-based indices, and the target sentences needed for scoring. The current document is sent to the selected scoring provider for analysis—no editor storage, replacement text, export history, or unrelated application state.
Only changed sentences and immediate neighbors are re-scored. Unchanged scores are reconciled locally. The full current sentence array is used as model context because redundancy and support can depend on earlier sentences.
src/core/schema.tsdefines the shared typed result schema and per-result sanitizer.src/core/provider.tsdefines the singleScoringProvider.score(sentences, context)interface.server/providers/jev.tsmaps each field to a typed Jev Choice question and validates every answer.server/providers/llm.tsrequests strict JSON matching the shared schema and validates each returned result independently.server/api.tsis the same-origin Vite API boundary; API keys remain server-side.src/lib/sentences.tsusesIntl.Segmenter, LCS reconciliation, and neighbor expansion.src/hooks/useIncrementalScoring.tsowns the 300 ms debounce, stale-request cancellation, and score replacement.src/lib/trim.tscomputes the transparent cut plan; accepted decisions are applied through the normal text update path.
Jev does not expose an Noul confidence value, so support uses a two-option Choice (n/a or unsupported_claim) rather than a Noul. Sentence 0's redundancy is deterministically none with confidence 1.0 because no earlier sentence exists.
evals/samples.json contains five original short essays and 25 human-authored cut/keep labels. A model prediction is cut only when cut_safety is cuttable; malformed or missing results count as disagreements.
set -a
source .env
set +a
npm run eval -- jev,llmRun one provider with npm run eval -- jev or npm run eval -- llm.
Live results on September 24, 2026:
| Provider | Configuration | Agreement | Correct | Dropped |
|---|---|---|---|---|
| Jev | jev-latest |
80.0% | 20/25 | 0 |
| LLM | gpt-4.1-mini, strict JSON schema |
92.0% | 23/25 | 0 |
These labels are small, author-created smoke evaluations, not an independent benchmark.
npm run lint
npm run typecheck
npm test
npm run buildWith npm run dev running in another terminal:
npm run smoke
npm run demo:recordThe smoke test covers live scoring, heat mapping, trim decisions, Markdown/JSON downloads, mobile overflow, and browser console errors. demo:record regenerates the two WebM walkthroughs.
- Sentence segmentation uses the
enlocale because Jev's strongest documented language support is English. - Trim mode uses a transparent greedy plan in ascending cut-safety confidence, not a globally optimal word-count subset.
- Jev Choice supports at most 255 options, so redundancy compares against the 254 nearest earlier sentences.
- The fallback assumes strict JSON-schema support from the configured OpenAI-compatible endpoint.
- First-sentence redundancy is logically
nonewith confidence1.0; it is not a model verdict. npm run devandnpm run previewprovide the local API middleware. A production host must preserve that server boundary or implement an equivalent one.

