ETOT
ETOT
TOOL · RESEARCH SYNTHESIS · PART 3
Motif
type
Figma / FigJam plugin
version
0.3
surface
FigJam · Figma Design
author
Eric Frye
date
09.2026
license
MIT
part 1
the loop
part 2
the critic anywhere
Launch listing ↗

A privacy property is a test, not a sentence.

Part 1 built the loop. Part 2 gave it a server. Part 3 makes it something a designer can install: a hosted engine that runs on the user’s own key, a Figma and FigJam plugin that draws the result on the board, and a Community listing. Along the way the plan’s privacy line turned out to be false — the redacted logs were keeping the model’s answers, and the answers quote the transcripts — and it stayed false until a test said so.

tests/test_hosted.py · the run that failed, then the volume itselfnotes 2026-09-04 / 2026-09-05
tests/test_hosted.py::test_synthesize_job_end_to_end FAILED AssertionError: transcript text leaked into 003_synthesis.json the claim in the plan: “redacted logs contain no transcript text” what redact=True did: prompts → digest response → verbatim what a response is: the synthesis, receipts included fix (core/logger.py): response text → digest data dropped from the record usage · timing · stop reason kept live, on the volume (fly ssh console): /data/runs/20260905-054758-C-hosted 556 KB corpus/ under /data/runs none calls/003_synthesis.json system · user · text → {chars, sha256} user prompt 90,569 chars response 16,447 chars
The plan said the logs were clean. The test said they weren’t. No exhibit image exists for this — it was read live over fly ssh console, not captured — so the hero is the terminal record itself, verbatim.
1.9 shosting overhead on a 12-minute run (client wall 721.4 s against engine wall 719.5 s)
8 m 20 sinstall to run-start in Figma, first time, key lookup included
6 → 0mechanical failures when Motif’s own report is checked by Motif’s critic
16 sections · 71 stickies · 4 connectorsone run drawn on a FigJam board by the plugin
role
Designer and builder — service design, tier design, plugin UI, board renderer, QA gates, listing
team
Solo; Claude Code as build partner, Claude as hub for assets and copy
timeline
4–6 September 2026
surface
Figma Community plugin (FigJam + Figma Design) on a hosted engine; MCP server and CLI unchanged
tools
Python · FastAPI · Fly.io · TypeScript · esbuild · Figma Plugin API · Playwright
the problem

The engine ran in a terminal, on one key, in one process. A designer who will never open a terminal can’t reach it, and a plugin can’t run Python.

the solution

One hosted service wrapping synth/engine.py unchanged, with the user’s Anthropic key forwarded per job and never logged; a plugin that uploads transcripts, streams the loop’s own progress, and draws the board itself from the same layout the MCP tool returns. Free on your own key. ETOT’s key runs prepaid credits or nothing.

01

Three constraints wrote the architecture

context

Part 2 left an importable engine and five MCP tools. Neither could serve a plugin: core/llm.py built one module-level Anthropic() client from the environment, so every run used the machine’s key.

what was built

A contextvars client (using_key) so a hosted job runs on its caller’s key, with the environment still the default so the CLI and MCP local mode don’t change; surfaces/hosted/ (job API, job registry with caps and TTL, credits ledger, Dockerfile, fly.toml); surfaces/mcp/remote.py rewritten as a client of the job API, with the server’s five remote branches wired to it. Eleven new tests in tests/test_hosted.py; suite 30 passed in 4.19 s.

findings
  • 01A job API, not a request.From docs/part2-notes.md: the refactor regression took 440.9 s, the recorded 15-transcript run 2,064.6 s and $4.88. A single HTTP request that long dies at a proxy. Submit, stream, fetch — and a plugin window that closes mid-run picks the job up by id.
  • 02The key travels in a header.The plugin UI runs in a sandboxed iframe whose origin is null, so every call is a CORS preflight. CORSMiddleware allows any origin for GET and POST with Content-Type, X-Motif-Key, Authorization, Last-Event-ID; the key is never a cookie, so an open origin policy exposes nothing the caller didn’t already hold.
  • 03A second bug the tests caught.The remote client merged the engine’s result over the job id, so run_id came back as the guessable run-directory name instead of the random job id later calls need. Fixed in _as_tool_result.
  • 04Deploying is its own list.fly auth login refuses to run under Claude Code’s ! prefix; fly apps create refused until a card was on the account; [build] dockerfile in fly.toml resolves relative to the toml, not the repo root; the build context was 250 MB across 9,920 files until a root .dockerignore allow-listed six paths. Final image 62 MB.
verified live · CORS from a null origin
$ curl -X OPTIONS https://motif-hosted.fly.dev/v1/jobs \ -H "Origin: null" -i access-control-allow-origin: * access-control-allow-headers: Content-Type, X-Motif-Key, Authorization, Last-Event-ID the key is a header, never a cookie — an open origin exposes nothing the caller didn't already hold.
/healthz · motif-hosted.fly.dev
{"ok": true, "paid_tier": false, "jobs": 0} app motif-hosted region ord machine 1 shared-cpu · 1 GB memory volume 1 GB image 62 MB build ctx 250 MB → 6 paths (.dockerignore)
Deployed and checked from a laptop, before the plugin existed to call it.
stage 1 QA gate

The MCP server in remote mode synthesised michelle + david through the deployed service: job 5GfWRXE3yUt43vfGyPhNSmzb, hosted run 20260905-054758-C-hosted, 11,482 words, 15 insights, contested I-02, I-05, I-08, 3 iterations, $1.1409, engine wall 719.5 s, client wall 721.4 s, 21 progress notifications, run record redacted=true with 8 calls and 224,535 → 73,279 tokens.

The engine didn’t need rewriting. It needed a place to put the key.

02

The claim that was false until it was asserted

context

The spec’s privacy line is one sentence: transcripts processed for the run and discarded, nothing stored. In the plan that mapped onto something the logger already had — redact=True writes prompt bodies as digests and skips the corpus snapshot — and the processed corpus lives in the job’s memory for the run plus a short TTL, so remote motif_receipts only works inside that window and the plugin gets its receipts inline instead.

what happened

test_synthesize_job_end_to_end failed with transcript text leaked into 003_synthesis.json. redact=True had digested the prompts and kept the model’s response verbatim — and a synthesis response quotes the transcripts, because the receipts are the point. Fixed in core/logger.py: response text is a digest too, data is dropped from the record, usage, timing and stop reason stay.

checked on the machine

Not in the code — on the live volume, over fly ssh console: the run directory is 556 KB; there is no corpus/ directory anywhere under /data/runs; calls/003_synthesis.json holds system, user and text as {chars, sha256}. A five-word phrase from a cited turn appears only in output.md, the run’s own report, and in the job’s scratch directory — both deleted by the expiry sweep an hour after the job finishes, covered by test_expiry_drops_corpus_and_run_content and not re-observed live in that session.

given a deadline

So the privacy page could say “deleted 30 days after the run” before it went up, JobStore.sweep_records() deletes any run record whose meta.json started is older than retention_days (default 30, MOTIF_RETENTION_DAYS), with directory mtime as the fallback; it runs at startup and at most hourly, and /healthz reports retention_days and records_swept_at.

retention sweep · verified live, not just in the test suiteJobStore.sweep_records()
test_retention_sweep_removes_old_records_and_keeps_young PASSED 36 tests pass planted: one synthetic record, started 67 days ago startup sweep runs synthetic record gone 11 real records kept GET /healthz retention_days 30.0 records_swept_at <timestamp>
The page could not say “deleted after 30 days” until something deleted at 30 days.
findings
  • 01Redaction is a property of the record, not of the prompt.Half a redaction is a leak with a clean name.
  • 02A privacy sentence you can’t fail is marketing.The FAQ now points at a check that ran on the volume, and cites the notes entries by date.
  • 03Retention needs a sweeper, not a policy.The page could not say thirty days until something deleted at thirty days.

A privacy property is a test, not a sentence.

03

What ETOT’s key is for

context

The plugin ships free, on the user’s own Anthropic key: pasted once, stored in figma.clientStorage, sent only in a request header with a run, used for that run’s model calls, then dropped. The paid tier is prepaid credits through ETOT’s hosted engine on ETOT’s key. The rule underneath both is one sentence: ETOT’s key runs prepaid credits or it doesn’t run.

what that ruled out

Figma’s payments API sells one-time purchases or subscriptions, not a depleting balance, so credits need a ledger ETOT owns, topped up by Stripe Checkout through a webhook. Designed in stage 1, built when the prerequisites are dated. In this release the Credits · not yet control is hidden entirely rather than shown disabled — a disabled control in a public listing draws questions and reviewers read it as unfinished — and the code path stays behind the server’s MOTIF_PAID_ENABLED flag, which refuses any job without a BYOK key.

where the entity stands

LLC filing has started, single-member. Stripe and the credits ledger stay unbuilt until there are first users. [not in notes: LLC — entity name, state, filing and formation dates]

findings
  • 01No free run on ETOT’s key, ever.Not a trial, not an allowance. If a trial is needed later it is a coupon for credits.
  • 02A hidden control is a decision, not a gap.The listing’s version notes say a paid tier follows in a later release; the flag is the truth of it.
  • 03The paid tier is not a feature list.It sells not managing a key, an invoice, a licence, and a support address — which is why the prerequisites are entity, rails, support, terms, and not code.

ETOT’s key runs prepaid credits or it doesn’t run.

04

Run in the user’s own Figma files

context

Every stage of the plugin ends in a gate that cannot be run by the machine that built it: development plugins load only in the Figma desktop app, so install timing, the clipboard, and the drawn board are the user’s run, logged in the notes before anything is called done.

stage 1 — the service

Covered in section 01. Gate: the MCP server in remote mode synthesises the sample corpus against the deployed service, and the run directory on the volume holds a redacted record and no transcript text. Passed 2026-09-05.

stage 2 — the plugin

Built in surfaces/figma/: TypeScript, esbuild, no framework. code.ts holds client storage, notifications and resize; ui.ts the screens; api.ts submit, SSE with resume, fetch; docx.ts reads Word files in the plugin with a zip walk, DecompressionStream("deflate-raw") and word/document.xml paragraphs, no library. dist/ui.html 33 KB, dist/code.js 2 KB. The manifest allow-lists one domain.

Parity before trust. The plugin’s own docx extraction, run under Node against all 16 sample files (29.8 MB → 587 KB of text), ingests to the same manifest as python-docx: 15 transcripts, 101,042 words, one empty file skipped on both paths; 1,868 turns compared, 1,867 byte-identical, one differing only by a line break inside a paragraph, 0 mismatches.

a harness

Because the window won’t shrink: dist/ui.html runs in a browser tab with localStorage standing in for Figma’s client storage; test/harness.html frames it at 440, 375 and 320 px. It immediately caught a bug that would have shown on every first open in Figma: the “A run is still going from last time” card displayed with no run, because .card { display: flex } outranks the browser’s [hidden] rule. Fix: [hidden] { display: none !important; }.

The Motif plugin's setup screen framed in a test harness at 440, 375 and 320 pixels wide.
The harness that caught the “still going” card showing with no run — a display:flex outranking [hidden]. 440 / 375 / 320 px.
the browser-path live run

Job ngqJoYXl17yhFKHVFdosoV84, hosted run 20260905-171330-C-hosted, 11,482 words, 12 insights, contested I-05 and I-11, 3 rounds, $1.0328, engine wall 660.1 s, 20 progress events shown live; zero layout issues at 440 px.

The Motif plugin's result screen with cost, insight count and a copy-report button, at 440, 375 and 320 pixels wide.
The browser-path live run’s result screen. Zero layout issues at any of the three widths.
the gate, in Figma

Install to run-start 8 m 20 s including looking up the key and reading the instructions — call it 5–6 minutes for a repeat user; the run 12 m 33 s against the plugin’s own reported 12.4 minutes; install to result about 21 minutes. The report copied to the clipboard inside Figma as full Markdown with receipts, no fallback box. Nothing clipped or broken on any screen. Ruling: the ten-minute target applies to install-to-run-start, not install-to-result, because a run alone takes twelve minutes.

stage 3 — the board writer

Gate in section 05. Two observations came out of the stage-2 run and went into stage 3: with two transcripts every insight reads “low”, because high needs four participants, so the chip now says why — low · 1 of 2; and the UI still uses Figma’s default blue and Inter, so the restyle onto the ETOT chassis waits for the design-system session’s tokens.

findings
  • 01The clipboard is not guaranteed.In the harness both navigator.clipboard.writeText and execCommand("copy") failed on a synthetic click. The copy button falls back to revealing the report in a selected read-only box — 58,847 characters, no overflow at 440 and 375 px. In Figma, the real click worked and the fallback never appeared.
  • 02The error screen relays the engine verbatim.Anthropic’s 401 API key is invalid. arrives as written, under a plain hint for the three cases a user can act on, and last_job is cleared on failure so a failed run never offers itself for resumption.
  • 03The gate that the builder cannot run is the gate that matters.Everything up to the desktop app is a claim.

Development plugins load only in the desktop app. Which means the last gate is always someone else’s hands.

05

One layout, two editors

context

synth/board.py::layout is the single source of truth for what a Motif board looks like; part 2’s MCP tool handed that layout to a host, which ran the scripts. The plugin draws it itself, and had to grow a second renderer: stickies and connectors are FigJam nodes, and Figma Design has sections but no stickies.

what was built

verdict_layout in synth/board.py (a section per checked claim, coloured by outcome, one sticky per objection, a final section for corpus-level objections) and a run card on both layouts; engine.board serving critique runs; the hosted /board endpoint accepting any finished job. In the plugin, src/board.ts renders on the current page — FigJam with sections, stickies in two rows, “contested by” connectors and a card above the grid; Figma Design with the same sections as auto-layout frames and text, no connectors.

tested three ways

A fake Figma API first: 15 sections, 70 stickies, 7 connectors on FigJam and 0 on Design; the verdict layout 9 sections and 33 stickies. Then the plugin’s own renderer bundled and run on a throwaway FigJam board through Figma’s MCP: 4 sections, 20 stickies, 1 connector, run card at y = −568, every colour read back as a palette value and none as CUSTOM. The same script on a throwaway Design file: 4 sections, 20 frames, frames hugging their text.

two editors, one layoutdocs/exhibits/stage3-board/
FigJam · I-02
The I-02 section of the Motif board drawn on FigJam: sections, stickies and a contested-by connector.
Figma Design · I-02
The same I-02 section drawn on Figma Design: an auto-layout frame with text, no connectors.
The same JSON layout, drawn twice. FigJam gets stickies and connectors; Design gets frames hugging their text, no connectors.
the gate, in Figma

From the user’s FigJam run of run 20260905-221749-C-hosted (16 insights, 2 contested, 3 rounds, $1.14, 12.1 min): the board drew 16 sections, 71 stickies and 4 connectors — 16 claims, 32 receipts, 4 counters, 16 opportunities, 2 contested, 1 card — to the right of an existing verdict board, so the free-origin rule held on a page that wasn’t empty. Then the same result in Figma Design: 16 sections, 71 stickies, no connectors, on the dark page.

at a glance against part 2board overview, plugin-drawn vs host-run
part 2 · MCP tool, host-run script
Part 2's FigJam board: 23 sections drawn through Figma's MCP server by a host script.
part 3 · plugin-drawn, run card above the grid
Part 3's FigJam board: 16 sections drawn by the plugin itself, with a run card above the grid.
Two differences by design: the run card above the grid, and the verdict board’s I-nn · fail / I-nn · pass sections, which did not exist in part 2. Otherwise, the same shape.
findings
  • 01The sandbox has no toLocaleString.The run card read "11482 words" on the first real board; Number.toLocaleString returns bare digits in Figma's plugin sandbox, so thousands() formats by hand.
  • 02documentAccess: dynamic-page forbids the synchronous lookup.The board drew in full and the step after drawing threw Cannot call with documentAccess: dynamic-page. The MCP runtime allowed both forms, so the earlier verification never saw it. code.ts now resolves ids with getNodeByIdAsync.
  • 03Opening the plugin should show what you have.A fresh file opened on the empty form instead of the stored last result. Now, with a key, a stored result and no run in flight, it opens on the result screen with Build board ready.
  • 04Titles truncate at the section edge,because the title is the whole claim. FigJam hard-cuts, Design ellipsises. Rendering, not a defect — but an ellipsis at a fixed length is on the ledger.
06

Six failures Motif dealt itself

the round trip · Motif’s critic, on Motif’s own reportnotes 2026-09-05
1 the user's run — Check a synthesis, on Motif's own stage-2 report verdict FAIL 16 claims 6 fails 0 warnings every fail: quote_mismatch on a counter-evidence turn with an empty quote e.g. david:0012: "" 2 root cause — predicted the day before, in part 2's notes synth/report.py printed receipts for evidence only → parse_motif_markdown made counters with empty quotes → the deterministic check read absence as mismatch 3 ruling Motif's own report must round-trip through Motif's critic with zero mechanical failures. 4 fix, two parts report.py prints counter receipts too quote_mismatch counter with no quote → "no receipt in the source", skipped evidence with no quote → still fails two tests · 35 pass 5 re-verified live job lmIPSVZ36Q0pAf8xM4Sd5lcS 241.6 s the pre-fix-format report, 12 claims, 4 empty counters mechanical failures 0 remaining, model-judged I-11 unsupported (correct: selective quotation) I-01 merged_insights (warn)
docs/exhibits/stage3-board/critique-roundtrip-after-fix.json. The critic was right and the report was wrong.
what it means

The critic was right and the report was wrong. A deterministic rule counted a missing receipt as a mismatched one, and the only reason it surfaced is that Motif was pointed at its own output — the one document whose correctness nobody had checked mechanically. The fix touched one deterministic rule’s handling of one input shape and left the model-judged rules alone, so the instrument measured in parts 1 and 2 is unchanged.

check a synthesis, live

The post-fix pass on the plugin path: job PDt5IyygwZLrUc1Vni5o2L7V, “structured 16 insight(s) from Motif-format markdown (deterministic)”, “deterministic checks: 0 failure(s)”, verdict FAIL with 16 claims, 2 fails, 0 warnings, and the honest line “Not checked: missing_theme (no intake notes for a pasted document)”. The fails: I-01 missing_counterexample, which matches the original run’s own contested flag, and I-13 unsupported, which is new. I-10, contested in the original run, passed here — critic variance, recorded and not tuned. The verdict drew as a board: 16 sections, 19 stickies.

The Motif plugin's verdict screen showing insight I-01 with a missing_counterexample failure.
Cost, read from the volume at /data/runs/20260906-000123-critique-doc/meta.json: $0.1793, 116.6 s, 37,509 → 10,428 tokens, one call, stop critique_fail.
findings
  • 01A predicted failure left unfixed is a failure you scheduled.The root cause was written down on 2026-09-04 and left; it arrived on 2026-09-05 as six red stickies in the user's own file.
  • 02Verdicts need their own numbers.The verdict screen carried no cost or time until _critique returned cost_usd and wall_seconds; until the deploy ran, the live tiles showed "–". After the deploy, one live pass: job 6R0hYQM-Rccnlk6Yj0STOHCq, 15 claims, 3 fails, $0.1546, 88.4 s.
  • 03A dollar sign nearly didn’t survive the build.The cost tile rendered "0.49": build.mjs inlined the script with a string String.replace, and $$ in a replacement string collapses to one $, so $${cost} reached dist/ui.html as ${cost}. Every build to date had shipped the tile without the sign. Fixed with function replacers; verified on the rendered tiles as "$0.49".

Motif failed its own report six times. The report was wrong.

07

Everything that isn’t code

context

Submitted to Figma Community review on 2026-09-06 at 00:31 CDT under the ETOT team profile @etot, free tier only. Plugin id 1678295978273812914 — the first generated id was superseded, and the second is the one in the manifest at commit 7cd99fd. [not in notes: Community review outcome and date]

the icon is a rule

Not a picture. Per ETOT’s per-tool identity rule, each tool’s icon is its own generated field: one colour, no letterform, with the monogram reserved for the studio. surfaces/figma/listing/icon.png, 128 × 128, 2,165 bytes, generated by etot-site/scripts/icons.py at commit 23c596d from etot-site/assets/icons/motif-128.png.

the cover is a screenshot

And says so. surfaces/figma/listing/cover.png, 1920 × 960, composited from docs/exhibits/stage3-plugin/08-board-I-01-contested-zoom.png — the I-01 section of run 20260905-221749-C-hosted as the plugin drew it — cropped to the section and letterboxed on the FigJam canvas grey #F5F5F5. Screenshot-derived rather than a canvas export, because the boards were deleted before an export was taken. It replaces the spec’s I-09 choice, whose only exhibit is the part-2 MCP-drawn board.

icon · master SVG
The Motif plugin icon: a single generated colour tile, no letterform.
cover · 1920×960
The Motif Community listing cover image, composited from a cropped board screenshot letterboxed on FigJam grey.
carousel · 01
The first carousel image on the Motif Community listing: a board overview.
The published listing assets: an icon that is a rule, not a picture; a cover that says it is a screenshot; the carousel opening on the board.
the category doesn’t exist

The spec asked for Research / Whiteboarding. Figma’s plugin taxonomy has no such category — the Whiteboarding entries are templates — so the listing sits under Design tools › Content generation, with the five tags Figma allows: ux research, research synthesis, user interviews, transcripts, affinity mapping.

data-security form

A design surface. Answered as: a hosted backend that receives no plugin-API data; network requests only to motif-hosted.fly.dev; no user authentication; no plugin-API data stored; solo developer; no formal vulnerability process yet, reports via GitHub issues or hello@etot.design; no accreditation. Comments on; support hello@etot.design with a stated two-working-day response; privacy policy at https://etot.design/privacy/, live before submission. Two-factor authentication is required by Figma to publish, and the session had to be restarted after enabling it.

the near-miss

The carousel was nearly submitted as the raw exhibits — menu bar, browser tabs, clock, avatar all visible. Caught before submission. Worse, the same raw screenshots were already in the repo’s history: 22 of them under docs/exhibits/stage3-plugin/, showing employer file names in the Figma tab strip on every exhibit and the last four characters of an API key on 02. git filter-repo --path docs/exhibits/stage3-plugin/ --invert-paths in a fresh clone removed the folder from every commit — 3 commits had touched it, 0 after; pack 43.47 MiB → 24.22 MiB — with a mirror backup kept, and 22 cropped replacements committed on top under the same filenames, so every notes line that cites an exhibit by number stays valid.

the rule that came out of it

Now in CONTRIBUTING.md § Conventions and the plan’s stage-4 QA gate: raw screenshots never leave the repo and never enter it either. Every exhibit and every publication asset is cropped to the product surface and checked for personal information — menu bar, tabs, clock, avatar, file names — before it is saved. Publication assets live under surfaces/figma/listing/ or docs/figures/.

findings
  • 01An icon system is cheaper than an icon.The rule decided the artwork; the artwork took a script.
  • 02The taxonomy is the market’s, not the spec’s.Where the category doesn't exist, say which one you chose and why.
  • 03Screenshots carry the day job.A cropping rule is not tidiness; it is the difference between an exhibit and a leak.

The tab strip is part of the screenshot. So is the day job.

08

What it costs

hosting overhead

About two seconds. On the stage-1 QA run, client wall 721.4 s against engine wall 719.5 s — the event stream kept pace and no proxy cut it. On the shorter PyPI-installed run, 339.2 s against 338.3 s. The stage-1 run cost twice the part-2 local regression ($0.5529, 1 iteration, 440.9 s) because it ran all three iterations, not because it was hosted.

per run, on the user’s key
runcorpusresultcostwall
stage-1 QA, MCP remote
20260905-054758-C-hosted
michelle + david, 11,482 words15 insights, 3 contested, 3 iterations$1.1409719.5 s
PyPI 0.3.0 live
20260905-164234-C-hosted
michelle + david, max_iterations=113 insights, 2 contested$0.458338.3 s
browser path
20260905-171330-C-hosted
michelle + david12 insights, 2 contested, 3 rounds$1.0328660.1 s
the user’s Figma run
20260905-221749-C-hosted
David.txt + Michelle.txt16 insights, 2 contested, 3 rounds$1.1412.1 min
Check a synthesis
20260906-000123-critique-doc
the stage-2 report + its transcripts16 claims, 2 fails$0.1793116.6 s
Check a synthesis, post-deploy
20260906-010935-critique-doc
the stage-1 report + its transcripts15 claims, 3 fails$0.154688.4 s
part 2, full corpus
20260904-154633-C
15 transcripts23 insights, 3 contested$4.882,064.6 s

In the plugin’s own words, and in the listing: about $1 for two transcripts, about $5 for fifteen. The result screen shows the exact API cost of every run.

what ETOT pays

One shared-CPU machine with 1 GB of memory and a 1 GB volume, kept running, in ord. [not in notes: first Fly.io invoice — amount, period, date]. Total part-3 API spend, summed live from the volume’s redacted meta.json (/data/runs/*/meta.json over fly ssh console, not reconstructed from the table above): $5.7838 across 11 records — 9 billed, 2 abandoned at $0 (started, no model call, empty calls/), all from 2026-09-05/06, nothing yet aged out by the 30-day sweep. Motif repo commit 2ae47a8, docs/part3-notes.md.

findings
  • 01The hosted path costs what the CLI path costs.Same corpus, same settings, same shape as part 2's local regression.
  • 02The engine is the bill.Hosting is a machine; the model calls are the money, and they are the user's.
  • 03A critique pass is a fifth of a synthesis.One call, ninety to a hundred and twenty seconds, under twenty cents — which is what makes "check a synthesis someone else wrote" a reasonable thing to do twice.
09

Still open

OPENCommunity review — submitted 2026-09-06 00:31 CDT; outcome pending. Until it clears, the listing page is not public.
OPENNo users yet — the success criterion is ten installs and three pieces of written feedback, logged in docs/part3-notes.md; they become the next section on this page.
OPENThe paid tier — hidden control, server flag off, ledger and Stripe unbuilt until first users; single-member LLC filing started, entity details to follow.
OPENSection titles truncate on the board, because the title is the whole claim: FigJam hard-cuts, Design ellipsises. An ellipsis at a fixed length in board.ts is the fix; deferred.
OPENThe plugin still wears Figma’s default blue and Inter. Restyle on the ETOT chassis — blue accent, mono for ids and receipts — deferred; the design system it draws from now exists.
OPENConfidence with a small corpus. With two transcripts every insight reads low, because high needs four participants. The chip now says why (low · 1 of 2); scaling the threshold with corpus size is an instrument change and waits for the v3 eval.
OPENDuplicate receipts: an insight can cite the same turn twice as two evidence entries (I-06, david:0026). Candidate duplicate_receipt check; instrument change, so it waits for the v3 eval.
OPENHosting cost — first Fly.io invoice pending.
PUBLICgithub.com/sleepycobalt/motif (MIT) · pypi.org/project/etot-motif · the listing · the runs, verdicts, and board read-backs are all in the repo.

The plan said the logs were clean. The test said they weren’t. Everything published here rests on the second sentence.

Motif · part 3 · ETOT · research synthesisCorpus: Hanchard & San Roman Pineda 2023, University of Sheffield, CC-BY-NCEric Frye · 2026