Inspiration

Reports often start as a pile of notes, logs, and copied excerpts. Once the prose is edited into a clean narrative, the link between a factual sentence and its source can be difficult to reconstruct. A changed number may hide inside otherwise similar wording, and the reviewer has to search the source set again.

CiteMargin adds a small, explicit review step between drafting and publication.

What it does

CiteMargin places a report, its candidate claims, and likely source passages in one review desk. It finds sentences with factual cues such as numbers, dates, quantities, comparisons, or named entities. It then ranks up to three source passages by lexical similarity and shows the selected passage beside the claim.

If the claim and passage contain different numbers or dates, CiteMargin highlights both values and keeps the item in Possible conflict. A reviewer can select a different passage, confirm a link, remove a link, and record a note. The final review can be exported as deterministic JSON or Markdown.

The three states are intentionally narrow:

  • Linked means the reviewer confirmed the selected passage and no number or date difference is currently shown.
  • Needs review means no passage link has been confirmed.
  • Possible conflict means the claim and selected passage contain different numbers or dates.

CiteMargin never treats a lexical match as proof that a claim is true.

How I built it

The review engine is written as pure TypeScript functions for sentence splitting, claim cues, Markdown source parsing, TF-IDF weighting, cosine similarity, value comparison, state derivation, and export. React manages the workspace and reviewer actions. localStorage preserves the current browser session.

The application has no server, database, analytics, or runtime AI service. After the static assets load, report text and source notes stay in the browser. Vite produces the static build deployed on GitHub Pages.

Vitest and Testing Library cover the engine and component behavior. Playwright and axe-core test the complete example flow, keyboard focus, accessibility, the absence of document-data requests after interaction, and horizontal overflow at 320, 768, and 1440 pixels. The same unit tests and production build also pass on Ubuntu 24.04.

Challenges I ran into

The hardest product decision was defining states that would help a reviewer without overstating what the software knows. A high passage score can still be wrong, so the interface calls it a word-overlap cue and leaves every link under reviewer control.

Value comparison needed the same restraint. An early implementation overflagged phrases such as 48 boxes versus 48 delivered boxes and treated extra context numbers as a direct contradiction. I changed the comparison to atomic dates, currencies, percentages, months, and numbers. The included sample now reserves the conflict treatment for its intentional 64 versus 62 discrepancy.

A desktop layout bug also stretched the footer to 174 pixels because an optional confirmation row was always present in the grid definition. The final layout derives the grid rows from the actual state; the footer is 48 pixels on the verified wide view.

Accomplishments

  • A complete local-first review flow that works without an account or API key
  • Reviewer-controlled evidence links with visible numeric and date differences
  • Deterministic JSON and Markdown ledgers with state definitions and notes
  • Responsive layouts tested at phone, tablet, and desktop widths
  • Passing accessibility, privacy-behavior, unit, interaction, build, and dependency-audit checks
  • A public repository, reproducible setup instructions, and a live static demo

What I learned

Evidence software needs careful vocabulary as much as it needs matching logic. Calling a passage “support” or marking a claim “verified” would imply more than a lexical method can establish. Narrow labels make the workflow more honest and make the exported ledger easier to interpret later.

I also learned that a small deterministic tool can provide a useful checkpoint without sending unpublished text to a model or server. That boundary made the privacy story simple enough to test: after interaction begins, the browser test observes no application data requests.

AI assistance and my contribution

I defined the problem, product boundaries, review states, privacy constraint, and release criteria. I used OpenAI Codex to implement the prototype, write and debug tests, refine the responsive interface, and draft release materials. I reviewed the working behavior and public artifacts before release.

AI assistance is part of the development process only. CiteMargin itself uses deterministic TypeScript and has no runtime generative model.

Try it

  1. Open the live demo and choose Load example.
  2. Choose Check claims.
  3. Open the first claim to see 64 and 62 highlighted as a possible conflict.
  4. Open the second claim, confirm its passage, and add a reviewer note.
  5. Export JSON or Markdown and inspect the ledger.

Live demo: https://hyunsikparker.github.io/citemargin/

Source and setup instructions: https://github.com/HyunsikParker/citemargin

Known limitations

Candidate claim detection is pattern based. Passage scores measure word overlap, not authority, entailment, or truth. The value comparator focuses on numbers and dates, so it can miss semantic contradictions or flag differences that are valid in context. Workspaces remain in one browser profile and do not sync across devices.

What’s next

Future work could add reviewer-defined claim types, CSV import, source-page anchors, and an optional encrypted workspace file. Before expanding the detector, I would evaluate missed conflicts and false alarms on a permission-cleared report set.

Built with

React, TypeScript, Vite, Vitest, Testing Library, Playwright, axe-core, Fontsource, Source Sans 3, IBM Plex Mono, GitHub Actions, GitHub Pages, and OpenAI Codex.

Built With

Share this project:

Updates

Submission history