v0.2 · Self-hosted · Elastic-2.0

Pin annotations
for live frontends.

Reviewers drop teardrop pins; AI dev assistants read them as JSON and reply with unified diffs. Same contract, two front doors.

Try the demo →
One docker container5–15 KB per fix loopDOM-anchored pins
markup.local/m/lumen-coffee-hero
Coffee, slow.
Specialty roasts, single origin, brewed in 14 cities.
Order now →
1
2
3
1
Headline kerning is loose. Try tracking -0.02em.
2
Sub-copy should sit closer. 6px margin-top, not 10.
3
CTA contrast is borderline. AAA fails.
Why Markup

Three reasons it's not just "Figma for code".

Not a comment layer over screenshots. A self-hosted review surface where the iframe is the artifact, pins are DOM-anchored, and the API is built so an AI agent can close the loop without a human pasting context into chat.

The iframe is the truth

Pins anchor to DOM nodes. Reflow, zoom, fullscreen, viewport changes — the pin stays on the element it was dropped on. No screenshot drift.

Single-mount deploy

One docker container. SQLite + filesystem. No PostgreSQL, no Redis, no S3. Mount one volume, back up one tree. 256 MB is enough.

An API agents can use

Server-side DOM resolution, computed-style extraction, unified-diff versioning. Same routes power the browser UI and autonomous orchestrators.

Try without signing up

Pin it yourself.

A live Markup surface, running on your browser. Drop pins, write annotations, react, reply — everything persists in localStorage so the next visitor finds a clean slate. Reset any time.

Demo modeLumen Coffee — Hero v3
localStorage only · no server
  • 12 replies

    Headline kerning is loose. Try letter-spacing: -0.025em — the current display tracking eats the rhythm at this size.

  • 21 reply

    Sub-copy should sit closer. 6px gap, not 10. Reads as a separate paragraph right now.

  • 33 replies

    CTA contrast was borderline. Pumped from #2a2a2a to #151515. AAA passes.

3 annotations · 1 open · 1 resolved · stored at localStorage.markup-demo:v1Real <Toolbar>, <AnnotationCard>, <ReactionPicker> components
The fix loop, end to end

Four calls. Five to fifteen kilobytes.

A reviewer drops a pin. The agent fetches one aggregated context payload, applies a patch, and replies on the thread. That's the loop — same shape whether the agent is Claude Code, Cursor, Aider, or your own CI script.

  1. Annotate

    Reviewer or agent posts a pin + thread to POST /api/mockups/[id]/annotations. Body required; pins optional.

  2. Context

    GET /api/agent/context/[id] returns the annotation, the current inline HTML, and a diff since creation — in one request.

  3. Patch

    PATCH /api/mockups/[id]/version-patch accepts standard unified diffs against a base version. Atomic, append-only.

  4. Reply

    POST /api/threads/[id]/reply closes the loop. The reviewer sees the diff inline and resolves.

What ships in the box

The first commit was a working agent API.

Inline DraftCard

Mounted at the top of the rail while drafting. Three terminal actions. Persists across reloads.

Pin-based review

Click anywhere on the live mockup. Pin reflows with the layout via DOM-anchored coordinates.

Versioning

Every upload is immutable. Side-by-side and overlay diff views compare any two versions.

Agent context

Single-call aggregator returns annotation, inline HTML, and a unified diff against creation.

Cookie or Bearer

Browser sessions and agent tokens hit the same routes. One contract, two front doors.

Single-mount deploy

All state under ${DATA_DIR}. SQLite WAL. Online backup via Litestream supported.

Vs. the screenshot loop

How Markup compares.

CapabilityMarkupSlack + screenshotsFigma comments
Live frontend in the loop✗ (static images)✗ (design surfaces)
DOM-anchored pinsN/A
Single-mount self-host✓ One dockerVendor onlyVendor only
First-class agent API✓ NativePlugin-only
Fix round-trip size5–15 KB unified diffRe-upload PNG or zipOut-of-band
Quickstart

Run it in 30 seconds. Open the agent API in 30 more.

~/markup/start.sh
# 1. Pull and start. The first request runs the setup wizard.
docker run -d --name markup \
  -p 3000:3000 \
  -e AUTH_SECRET=$(openssl rand -hex 32) \
  -v $(pwd)/markup-data:/app/data \
  ghcr.io/alkg-cloud/markup:latest
~/markup/agent-loop.sh
# 2. Read context, apply patch, reply on thread.
curl -H "Authorization: Bearer $TOKEN" \
     "http://localhost:3000/api/agent/context/$ANNOTATION_ID"
# → { annotation, thread, current_version, diff_since_creation }

curl -X POST -H "Authorization: Bearer $TOKEN" \
     -d "{ base_version_id, patches: { 'index.html': $DIFF } }" \
     "http://localhost:3000/api/mockups/$ID/version-patch"
Licensing, plainly

Free to self-host. Free for your team.

Commercial hosting

Reach out

Offering Markup as a hosted service requires a separate agreement. The license blocks third-party SaaS resale; everything else is open.

  • Custom CLA for code contributions
  • Partnership terms
Frequently asked

The questions reviewers always ask.

Does it work without an LLM?

Yes. The cookie-session reviewer UI is the same surface, with no required AI. The agent API is opt-in — issue a bearer token only if you want an automated client (or just don't issue any).

Why SQLite instead of Postgres?

Markup is sized for small-to-medium teams self-hosting on a 256 MB box. SQLite in WAL mode handles the workload; a single mounted volume is dramatically simpler to back up. If you outgrow it, the Prisma schema ports to Postgres without surgery.

Can I use it with Claude Code / Cursor / Aider?

Yes. The agent API is just an HTTP contract — bearer token in, annotation context out, unified diff back. Any dev assistant that can call curl can drive Markup. The docs show example loops.

How are pin coordinates persistent?

Pins anchor to DOM nodes via text-anchor and element-anchor selectors that are resilient to layout drift. The server resolves them on render so the pin reflows naturally with viewport, zoom, fullscreen, and visual updates.

Open source, made better by contributors.

Markup is built in the open. Bug reports, feature PRs, and design feedback all land on the same issue tracker. Become a contributor in one PR.