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.
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.
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.
- 1
Headline kerning is loose. Try letter-spacing: -0.025em — the current display tracking eats the rhythm at this size.
- 2
Sub-copy should sit closer. 6px gap, not 10. Reads as a separate paragraph right now.
- 3
CTA contrast was borderline. Pumped from #2a2a2a to #151515. AAA passes.
localStorage.markup-demo:v1Real <Toolbar>, <AnnotationCard>, <ReactionPicker> componentsFour 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.
Annotate
Reviewer or agent posts a pin + thread to
POST /api/mockups/[id]/annotations. Body required; pins optional.Context
GET /api/agent/context/[id]returns the annotation, the current inline HTML, and a diff since creation — in one request.Patch
PATCH /api/mockups/[id]/version-patchaccepts standard unified diffs against a base version. Atomic, append-only.Reply
POST /api/threads/[id]/replycloses the loop. The reviewer sees the diff inline and resolves.
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.
How Markup compares.
| Capability | Markup | Slack + screenshots | Figma comments |
|---|---|---|---|
| Live frontend in the loop | ✓ | ✗ (static images) | ✗ (design surfaces) |
| DOM-anchored pins | ✓ | ✗ | N/A |
| Single-mount self-host | ✓ One docker | Vendor only | Vendor only |
| First-class agent API | ✓ Native | ✗ | Plugin-only |
| Fix round-trip size | 5–15 KB unified diff | Re-upload PNG or zip | Out-of-band |
Run it in 30 seconds. Open the agent API in 30 more.
# 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
# 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"
Free to self-host. Free for your team.
Self-host
Elastic License 2.0. Run it inside your org, your CI, your homelab. No seat caps, no calls home.
- Multi-arch Docker image (amd64 + arm64)
- Full source on GitHub
- Community support via Issues
Commercial hosting
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
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.