Skip to content

Selected work / SearchScribe AI Studio

SearchScribe AI Studio

One topic becomes a researched, structured draft with SEO metadata, exported as sanitised HTML or Markdown.

Role
Solo
Status
Free public beta
License
MIT, open source
Stack
FastAPI · Pydantic · SQLAlchemy 2 · Postgres · Next.js 16 · Gemini · Tavily

The problem

Getting from a topic to something publishable means research, drafting, metadata and safe HTML: four chores, each with a way to ship something wrong or unsafe.

Constraints

  • Model output is untrusted. The model never writes HTML and never retypes a source. It returns data that code checks and renders.

  • One deadline for everything. Research, retries, one fallback and the SEO repair share a 100-second budget, and nothing keeps generating after it expires.

  • Free-tier hosting. Vercel, Render and Neon Postgres, with the limits and cold starts that come with them.

Architecture

  1. 01 · Data

    Topic

    from the editor

  2. 02 · LLM step

    Plan research

    LLM · 15 s cap

  3. 03 · Code

    Tavily search

    byte-bounded

  4. 04 · Data

    Source records

    at most 5

  5. 05 · LLM step

    Select evidence

    LLM · returns IDs

  6. 06 · Code

    Check passage IDs

    server owns the text

    An unknown or cross-source passage ID stops the run before anything is saved.

  7. 07 · LLM step

    Write article

    LLM · JSON schema

  8. 08 · Code

    Validate

    Pydantic

    Invalid output returns an error. No placeholder article is ever saved.

  9. 09 · Code

    Render

    Jinja2 · autoescape

  10. 10 · Code

    Sanitise

    nh3

  11. 11 · Data

    New version

    every save

  12. 12 · Code

    Preview

    sandboxed iframe

The model plans, selects and writes; code fetches, checks, renders and sanitises. No step lets model text reach the page as markup.

Decisions

The model returns JSON; a template writes the HTML.

Chose
A structured article (title, sections, paragraphs, bullets) validated by Pydantic, rendered by Jinja2 with autoescape, sanitised by nh3 and previewed in a sandboxed iframe.
Over
Sanitising HTML the model wrote: sanitiser bypasses exist, and there’s no output contract.
Gain
The model has no way to write markup, so a whole class of injection risk is gone.

The model cites passages by ID.

Chose
The server numbers each source passage and keeps its text. The model returns passage IDs; an unknown or cross-source ID stops the run before anything is saved.
Over
Letting the model retype or paraphrase its quotations.
Cost
It is model-assisted source selection, not proof of accuracy, and the docs say so.

Cookies behind a same-origin proxy.

Chose
Next.js proxies /api to the backend, so HttpOnly refresh cookies stay same-origin and rotate on use, with theft detection.
Over
Bearer tokens in localStorage, which hands token theft to any XSS bug.

A modular monolith, on purpose.

Chose
One FastAPI app: routers, then services, then repositories. No broker, workers or agent framework.
Over
Celery workers from day one, or a service per feature.
Gain
A status field already marks long generations, so they can move behind a queue later without changing routes.

Outcome

  • Live as a free, noncommercial beta with password and Google sign-in.
  • 286 backend test functions (pytest) and 141 front-end and end-to-end tests (Vitest, Playwright), counted on 15 October 2026. Ruff, mypy and builds gate CI too.
  • Six architecture decision records, each naming the alternatives it rejected.

What I’d change next

Turn on draft review in the live beta once it passes its quality gate, so every paragraph is checked against the excerpts it came from before a draft is saved. Then persistent daily quotas and rehearsed backup restores.