Development Journey — “Aurelian”, a luxury watch landing page built through the Paper MCP

One session. A design tool driven entirely through a text protocol, a landing page that did not exist three hours earlier, and two production colours that had to change because the numbers said so.

Date
2026-08-20, 13:49–17:08 UTC (3 h 18 m)
Deliverable
Paper file 01M0FVWK97JZM17HW5VS9ZEQ4H — “Aurelian — Watch Landing”
app.paper.design/file/01M0FVWK97JZM17HW5VS9ZEQ4H
On disk
aurelian-design-system/ in this repository — DESIGN-SYSTEM.md (290 lines), theme.css (86 lines), style-guide.html (895 lines)
First typed prompt
“is the paper mcp connected ?”
The real brief
“i want to create a new landing page in the style of https://us.louisvuitton.com/ but for an expensive watch like Rolex. can it be done in Paper ?”
Model
claude-opus-5
How this document was made, and what is redacted. It is written from the session’s own JSONL transcript — the primary source, swept chronologically for every tool call, every user message, and both error strings — plus a live re-read of the Paper file today. It is not written from memory. Where a fact was re-verified today rather than taken from the transcript, it says so. Local filesystem paths, the account name, the Paper team name, and two unrelated open file names are removed, including inside quoted replies (elisions marked […]). Everything that makes the work reproducible — file ID, node IDs, tool names, hex values, error text — is verbatim.

01The brief — what was actually being asked

Three slash commands came before any prose. They are part of the story, because they are the reason the session could happen at all:

/plugin marketplace add paper-design/agent-plugins → Successfully added marketplace: paper /plugin install paper-desktop@paper → ✓ Installed paper-desktop. Plugin is now active. /exit → Bye!

The /exit matters. A newly installed plugin’s MCP server is not live in the session that installed it. The restart is the load-bearing step, and it is the kind of thing nobody writes down.

Then the first typed prompt: “is the paper mcp connected ?” — a question, answered with a live call rather than an assertion. list_files({limit:5}) came back with a real team and real open files, and the answer was “Yes — proof: a live call returned your team […] and 2 open files […]”.

The second prompt asked what the MCP could do. The third was the actual brief, and it was phrased as a question — “can it be done in Paper?”. That phrasing is a fork. It can be read as “tell me whether this is feasible” or as “do it”. It was read as do it, and the reading was stated out loud in one line before any tool call: “Yes. Reading ‘I want to create’ as the go — building now.” Saying the interpretation aloud is what makes an aggressive reading safe; if it had been wrong, the correction would have cost one message, not three hours.

Invisible constraints the reader cannot see

Four things shaped every line of output and none of them are visible in the artifact:


02Cold start — proving the channel before trusting it

The environment at minute zero: the Paper desktop app already running with files open, the plugin freshly installed, no Paper file for this work, no design system, no git repository in the project folder (there still isn’t one — see §11).

The Paper MCP exposes 40 tools, and in this harness they are deferred: only their names exist until a ToolSearch call fetches the schema. So the session begins with schema fetches, not design work. Six ToolSearch calls happened across the session — each one a moment where a tool was needed and its parameters were not yet known. Batching the obvious set in the first call would have saved four round trips. The rule that fell out: fetch the schema before the first call, not after the error.

The first mutation attempt failed immediately:

Error — get_font_family_info Open a Paper file to use this tool.

Typography lookup requires an open file, not merely an existing one. The working order is create_fileopen_fileget_font_family_info. That single error rewrote the opening sequence of every future session.


03Design decisions

The mood was chosen against instinct, because the guide demands it

The Paper guide contains an unusual instruction: post a written design brief to the user before any mutation tool call, list 3–5 candidate moods, and then pick any mood other than your first instinct — “picking at random beats picking by fit here, because first-instinct picks regress to the same few answers.”

The candidates offered were inky, candlelit, mineral, maritime, chapel. Inky black-and-gold is the first instinct for every watch brand alive, so it was dropped on purpose. Mineral won: limestone, weathered slate, oxidized copper. The guide’s own pairing table lists mineral — bone × oxidized copper, and it explicitly warns off “warm off-white × red / orange / terracotta”, the current cliché.

Every colour then had to name a physical referent, which is the guide’s test for whether a palette is real or glued together:

Palette — shown on #EDE9E2, its true ground

--color-ground
#EDE9E2
limestone
--color-surface
#F7F5F1
chalk
--color-ink
#161A18
wet slate
--color-body
#3D423F
body copy
--color-accent
#476E5D
oxidized copper
--color-stone
#C9C2B6
steel plate
--color-slate
#333A36
dark plate
--color-rose
#CDB8A4
gold plate

Type: two families, two weights, nothing bold

Four families were checked against the machine before a single character was styledget_font_family_info({familyNames:["Cormorant Garamond","Inter","EB Garamond","Jost"]}). Cormorant Garamond took display, Jost took the letterspaced caps because it carries the Futura-like geometry that the Louis Vuitton grammar depends on. Inter and EB Garamond lost: Inter is the default of every SaaS page on earth, and two Garamonds in one system is one Garamond too many.

Type specimen — the real families, the real scale

Calibre 8412 — Perpetual

Time, kept quietly

Body copy sits at 16 px on 29 px in Jost Light. The display serif never drops below 28 px, and the caps labels never rise above 13 px. Twelve steps in the scale, from 12 px to 94 px.

Decisions taken and their rejected alternatives

DecisionChosenWhy the alternative lost
Brand nameFictional maison “AURELIAN”The prompt said “like Rolex”. Using the real name and its marks would make the file unusable as a portfolio piece. Two text nodes carry the wordmark, so renaming is one set_text_content call.
Product imagerySVG line art, drawn in write_htmlPhotography was impossible, not merely rejected — see §5. The limit was stated in the brief before building, not confessed after.
Artboard heightheight: "fit-content"The guide forbids guessing a new fixed pixel height when content clips. Started at 900 px, switched to fit-content, finished at 3,456 px.
Repeated cardsduplicate_nodes + set_text_content + update_styles Rewriting three near-identical cards as HTML costs three times the tokens and risks three different structures.
Tokens, first passRaw hexDeliberate under-build. Tokens were not asked for, so they were not made — and they were flagged as missing in the completion message. They arrived when asked for, two prompts later.
Corner radius--radius-none: 0pxThe only radius in the system. Luxury print grammar has no rounded corners and no shadows.

What was deliberately not built


04How the conversation with the desktop app actually worked

This is the part that is easy to get wrong from the outside. There is no browser automation here, no screen-scraping, no clicking. The Paper plugin runs a local MCP server that talks to the running desktop app over a private channel. Tool calls arrive in the app and render on the canvas instantly, while the user watches.

The write loop

The primary write tool is write_html. Real HTML with inline styles goes in; Paper converts it to native design nodes and returns their IDs — 1-0, 2L-0, 4R-0. Those IDs are the addresses for every later edit. The loop is:

  1. write_html({targetNodeId, mode:"insert-children", html}) — one visual group, never a whole component. The guide is explicit: more than ~15 lines in a call is too much, because “a 60-second wait followed by a fully formed design feels like a black box.”
  2. Paper returns the new node IDs.
  3. get_screenshot({nodeId}) — the only feedback channel that shows what a human would see.
  4. Write a one-line critique against the guide’s six mandatory checkpoints — spacing, typography, contrast, alignment, artboard fit, repetition.
  5. Fix with update_styles or a targeted write_html({mode:"replace"}). Never delete and restart.

Eighteen write_html calls, fifteen update_styles, eight screenshots. The critiques were not decorative. Three of them caught real faults:

Verdict: hero reads right, but three faults — headline orphans an "r", the lugs float detached, and the date window reads as a hole. Fixing. Two faults on full-page review: the calibre drawing is too washed out, and its strokes lack hierarchy. Fixing. Three copy faults the screenshots couldn't catch. Fixing all in one pass.

That last line is the important one. Screenshots verify layout, not language. The trademark problem — the copy still said “Oystersteel”, “Everose”, “Parachrom”, “Calibre 4130”, all real Rolex marks — was invisible to every pixel check and only surfaced on a deliberate read of the text content. It was fixed with set_text_content to “904L steel”, “18ct rose gold”, “silicon hairspring”, “Calibre 8412”.

The six sections, to scale

Nav
Hero · 94px display · SVG watch
Collection · three model cards
Movement · editorial two-up
Specs · four figures
Footer

Live figures, re-read today

One artboard, node 1-0, 1440 × 3456 px, 165 nodes, 6 children, 2 font families, 54 design tokens. Rhythm is deliberate: light, dark, light, light, dark, light — the two dark bands are the only places the accent appears as a soft tint rather than the full value.

Verifying the app, not the return value

The user asked “can you control the desktop app and open it form”. open_file had already returned success — but a success return says the message was accepted, not that a window changed. So a windows-desktop skill was invoked, and a PowerShell capture wrote the real screen to a temporary scratchpad file, paper_check.png. The screenshot showed the Paper app with a fourth tab, Aurelian — Watch Landing, active, layer tree open, all six sections listed.

Near-miss. A prior probe listed window titles and the Paper window reported "Dashboard". That nearly became the conclusion “the file did not open”. The window title was simply stale — the tab had already switched. The pixels overruled the metadata, and the earlier note was corrected in the same reply. Probe the pixels; a window title is a cache, not a fact.

05The crux — “extracting” the design system turned into changing it

The fourth real prompt was “can you extract the ‘Design System’ for this landing page ? ask me if any question”. The word extract implies the system already exists and only needs writing down. It did not survive contact with a calculator.

Because the prompt invited questions, two were asked up front rather than assumed — the single AskUserQuestion call of the session:

QuestionOptions offeredAnswer
Where should the design system live?Paper tokens · Markdown spec · CSS/Tailwind theme · HTML artifactAll four
Rebind the existing layers to the new tokens?Rebind everything · define tokens onlyRebind everything

The rebind

Tokens were created in four create_tokens calls — colour, then type, then tracking and spacing, then one more added mid-rebind (--color-body-strong: #2A2E2C, because nav links and prices needed more contrast than body copy). Then twelve find_nodes({filters:[{styleValue:"#RRGGBB"}]}) calls swept the design one hex at a time, and each result was rebound with update_styles to var(--color-…). Roughly thirty nodes changed hands from raw hex to token.

The rebind looked like busywork. It was the highest-leverage twenty minutes of the session, and the next paragraph is why.

The contrast audit — where two production colours changed

Rather than write estimated ratios into the spec, the numbers were computed — a short WCAG relative-luminance script run four times in Bash. The estimates were wrong, and three genuine failures fell out:

The three failures, each shown on the ground it actually failed against

Ref. 4130-SL · 904L steel · 41 mm #6E6D66 on #EDE9E2 — 4.3:1 ✗ AA
Book an appointment → #55806E link on #EDE9E2 — 3.7:1 ✗ AA
Discover #F2EFE9 on #55806E button — 3.9:1 ✗ AA

After — the two token values that changed

Ref. 4130-SL · 904L steel · 41 mm #67665F on #EDE9E2 — 4.76:1 ✓ AA
Book an appointment → #476E5D link on #EDE9E2 — 4.75:1 ✓ AA
Discover #F2EFE9 on #476E5D — 5.00:1 ✓ AA
Discover the same button block on #161A18 — 3.06:1 ✓ the 3:1 bar for non-text UI

The accent sits between three constraints that pull in opposite directions: as a link on the light ground it needs 4.5:1, as the backing behind its own button label it needs 4.5:1, and as a block on the dark band it needs 3:1. Lightening it fixes the third and breaks the first two. #476E5D scores 4.75 / 5.00 / 3.06. Almost no other value clears all three. The written system carries the instruction in capitals: do not lighten the accent back.

This is what the rebind bought. Two WCAG fixes across roughly thirty affected nodes became two single-value set_tokens calls. Without the rebind they would have been thirty manual edits, and the third or fourth would have been missed. Binding to tokens is not tidiness; it is the difference between a fix and a sweep.

A screenshot after the rebind confirmed an identical render — proof the 30-node substitution changed nothing visually, which is exactly what a correct rebind should do.


06Tools and features used

112 tool calls in total. 76 of them were Paper MCP calls, using 16 of the 40 available Paper tools.

Paper MCP

ToolCallsWhat it did in this session
write_html18The main write path. HTML in, native design nodes out, node IDs returned. Also carried the SVG watch and movement drawings.
update_styles15Every visual fix and the whole token rebind. Batches many node IDs per call.
find_nodes12Swept the design for one raw hex at a time via {styleValue:"#RRGGBB"}. The engine of the rebind.
get_screenshot8The only visual feedback channel. Drove the mandatory review checkpoints.
create_tokens4Built the 54-token theme in four passes: colour, type, tracking/spacing, then one late addition.
set_text_content3Card copy after duplication, and the trademark removal sweep.
finish_working_on_nodes3Mandatory. Clears the “agent is working” indicator from the canvas.
get_font_family_info2Confirmed four families against the machine before any typography. Failed the first time — no file open.
open_file2Switched the desktop app’s active tab. Also makes that file “sticky” for later calls.
duplicate_nodes2Three model cards from one, four spec figures from one.
set_tokens2The two WCAG colour changes. Two calls, thirty nodes fixed.
get_guide1Mandatory first call. ~5,000 tokens of hard procedure.
list_files1The connectivity proof at the top of the session.
create_file1Created “Aurelian — Watch Landing”.
create_artboard11440 × 900, flex column — later switched to fit-content.
get_tokens1format:"tailwind" emitted the entire @theme block. One call replaced hand-writing 86 lines of CSS.

Never touched: get_jsx, get_computed_styles, get_selection, get_tree_summary, get_children, get_node_info, get_fill_image, move_nodes, rename_nodes, delete_nodes, create_page, export, export_combined_pdf, and the four comment tools. Nothing was deleted all session — every fault was fixed in place, as the guide requires.

Everything else

ToolCallsWhat it did
Bash10Four WCAG contrast scripts in inline Python, plus file setup and listings.
Edit7Propagated the two colour changes through the Markdown spec and the CSS theme.
ToolSearch6Fetched deferred Paper tool schemas, one small batch at a time.
Write5The spec, the style guide source, the handoff, the memory files.
Skill2windows-desktop (screen capture), artifact-design (design-lead directive).
PowerShell2Listed window titles; captured the primary screen to PNG.
AskUserQuestion1The two design-system questions in §5.
Artifact1Published the style guide as a private page.
Read / SendUserFile2Read the desktop capture; handed over the spec and theme files.

No subagents were spawned and no advisor was consulted. All of it ran inline on claude-opus-5. The parallelism that existed was inside single calls — update_styles batching dozens of node IDs, find_nodes returning whole colour cohorts — not across agents.


07What went wrong, and the fixes

Exactly two calls returned a hard error all session. The other failures were self-caught, which is a different and more interesting category.

1 — Font lookup before an open file

get_font_family_info Open a Paper file to use this tool.

Fix: create_fileopen_file → retry. Rule: a Paper file must be open, not merely created, before any read tool that touches the machine’s fonts.

2 — Heredoc broke on long Markdown

Bash — exit code 2 /usr/bin/bash: -c: line 3: unexpected EOF while looking for matching `''

A cat > file <<'EOF' heredoc carrying a 290-line Markdown document with backticks and apostrophes died on the shell. Fix: the Write tool, immediately, with no second attempt at the heredoc. Rule: long multi-line content goes through a file-write tool, never a shell heredoc, on this setup.

3 — The estimated contrast ratios were wrong

Ratios were about to be written into the spec from judgement. Computing them found --color-muted at 4.3:1 against a 4.5:1 bar — a real defect on the text carrying every reference line and footer link. Fix: #6E6D66#67665F, one set_tokens call. Rule: never write a number you did not compute.

4 — Then the accent failed twice more

The same discipline applied to the style guide found the accent failing as a link (3.7:1) and behind its own button label (3.9:1). Fix: #55806E#476E5D, solved against all three constraints at once rather than patched twice.

5 — Trademarks in the copy

The brief said no trademarks. The generated copy said “Oystersteel”, “Everose”, “Parachrom”, “Calibre 4130” anyway. No screenshot could catch this. Fix: a deliberate read of the text content and one set_text_content sweep. Rule: a constraint about language needs a language check, not a picture check.

6 — The stale window title

Covered in §4. A window title reading "Dashboard" nearly produced the wrong conclusion that the file had not opened. A pixel capture overruled it.

7 — Fragile, and worth naming


08Unknown unknowns — what nobody thinks to ask

These are the things that were not on anyone’s list at the start of the session, ranked by how much they would cost a future session that did not know them.

1. A newly installed plugin is not live until the session restarts

/plugin install reported success, and the MCP tools still did not exist until /exit and relaunch. Every “the MCP isn’t connected” report should start here.

2. A success-shaped empty result is not a success

Discovered today, writing this document. An attempt to export the artboard as a PNG returned:

export — HTTP-level success, no error { "contentHash": { "tokens": "5246d217" }, "exports": [] }

Three times, with three different argument shapes. Then two get_screenshot calls, on the artboard and on the hero, both returned Tool call timed out. The honest reading: metadata calls succeed and render-producing calls do not, right now. What cannot be distinguished from here is whether export is broken, whether it requires nodes pre-marked for export in the file, or whether the desktop app has simply stopped servicing render work since the build session ended. Recording it as “export is broken” would plant a wrong fact for the next session. Recording it as “render calls failed on this date, cause unknown” is what the evidence supports.

3. A screenshot cannot become a file

A related and non-obvious constraint: get_screenshot returns the image into the conversation. There is no path from that to bytes on disk — the image can be looked at, but not saved or re-emitted. Embedding a canvas render in a document therefore requires export producing real files, or an OS-level screen capture. This is why every visual on this page is drawn in CSS from the token values rather than photographed from the canvas.

4. Paper cannot fetch an image from the web

Images enter only as paper-asset:// with an absolute local path<img src="paper-asset:///C:/full/path/img.png">. No URL fetch, no data URI. This single fact decides whether a photography-led design is possible before a word of it is promised. It is why this landing page is line art.

5. The mandatory guide is a per-session tax

~5,000 tokens, required before any other Paper tool, and it re-enters context on every subsequent turn. On a three-hour session that is the single largest fixed cost after screenshots. Budget for it; do not discover it.

6. Screenshots verify layout, never language

The trademark faults sat in plain sight through eight visual reviews. Any constraint expressed in words — trademarks, legal copy, tone, spelling — needs its own text-level pass.

7. The deliverable lives in the cloud, and the folder has no git

The design itself is not a file on this machine. It is a Paper document, addressed by ID. What is on disk is the system — spec, CSS, style guide — in a folder that is not a git repository, has no remote, and has never been committed. “Durable” here means “the file exists on one disk”. That is the largest unaddressed risk in the whole project, and it is invisible because everything works.

8. A written record can drift from the thing it describes

The handoff document says the file carries 55 design tokens. A live read of the file today counts 54. Nothing depends on the number, and the discrepancy is trivial — which is exactly why it survived. Any figure written once and never re-read is a figure that may already be wrong.

9. The cost shape of a long design session is not what it looks like

See §9. The output was small; the re-reading was enormous.


09Costs, limits, and wall clock

MeasureValueReading
Wall clock3 h 18 m13:49:34Z → 17:07:53Z
Assistant turns203Includes a small number of synthetic entries.
Output tokens174,233The actual writing — HTML, styles, prose.
Cache read tokens32,148,636Context re-read every turn. 185× the output.
Cache write tokens1,272,795New material entering the cache.
Uncached input432Effectively nil — the prompt cache held all session.
Tool calls11276 Paper, 36 everything else.
Hard tool errors2Verified by counting is_error in the transcript.

The 32.1 million cache reads are the finding. A design session accumulates two heavy classes of context that never leave: screenshots (eight of them, images, permanently in the conversation) and the mandatory ~5,000-token guide. Every subsequent turn re-reads all of it. Output tokens are a rounding error against that.

A cost hook fired mid-session at roughly 154,000 context tokens:

⚠ Coach: this session is at ~154k context tokens. Every turn from here costs 3x+ baseline. At the next natural pause: run the handoff-after-clear skill, then /clear.

It was followed. The session ended with a handoff document rather than by running on. No API quota, rate limit, or plan ceiling was hit — the only limit that bound anything was context economics.


10Verification

Every claim below was checked by observing an effect, not by reading a success return.

ClaimHow it was actually checked
The MCP is connectedlist_files returned a real team and real open files.
The fonts exist on the machineget_font_family_info confirmed all four families before any type was set.
Each section is rightEight get_screenshot calls, each followed by a written critique against the guide’s six checkpoints, each fault fixed before moving on.
The design opened in the desktop appA PowerShell screen capture, read as an image. Not the open_file return value, which was already known to be “OK”.
The token rebind changed nothing visuallyA full-artboard screenshot after the rebind, compared against the one before. Identical render.
The contrast ratiosA WCAG relative-luminance calculation run in Python, four times. This is what found all three failures.
The files landed on diskDirectory listings of the project folder and the memory folder.
The canvas is cleanfinish_working_on_nodes, called three times; no working indicator remains.
The file is still intact todayA live open_file read while writing this document: 165 nodes, 1 artboard, 1440 × fit-content, 54 tokens, 2 font families.

Not verified


11Where things stand

Done and verified

Open, in the order they were discussed

  1. Real photography. Blocked only by the local-path rule: generate images locally, then reference them as paper-asset:///absolute/path.png.
  2. Mobile and tablet artboards. Breakpoint tokens exist; no layout has been drawn at any of them.
  3. Interaction states. Hover, focus, active, disabled — focus rings before anything ships.
  4. Put the folder under git. Not discussed in the session, and the largest real risk. One git init and a remote.

Deliberately outside this document

The folder also holds aurelian-motion-concepts_codex.html, a 49 KB motion-concept document that reuses the Aurelian token values but was written by a different tool, outside any transcript covered here; three terminal captures under .ignore/; and README.txt, a set of raw notes about an unrelated and unstarted “Conductor Roadmap” component. None of them are part of this session and none are described above.

Rules this session earned