todo: m3e-canvas as a plugin, with the measurements
not decided. the card carries numbers so the decision is not made from a readme — which is what produced the first, wrong estimate. the finding: next.js is not actually there. one import in 14,822 lines and it is type-only, so there is nothing to strip. the fork line then falls on a file boundary — page.tsx and lib/ai.ts are ours (~3,600 lines), the rest vendors (~11,200), and the split is meaningful rather than convenient. buildPrompt/effectivePrompt are pure functions, so the export point is a call rather than a bridge. against it: 37 commits, three days old, 22 of them landed today. two authors. 'vendor and track upstream' assumes an upstream that has settled and this one has not started. so the cheapest next step is an issue asking upstream for an explicit seam — depend rather than fork. the answer changes what gets built. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,14 +22,14 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
||||
|
||||
## Themes
|
||||
|
||||
**108 open, 33 closed**, across 35 themes.
|
||||
**109 open, 33 closed**, across 35 themes.
|
||||
|
||||
| Theme | Open | Closed |
|
||||
| --- | --- | --- |
|
||||
| [Affordances](todo/affordances/) | 2 | 0 |
|
||||
| [Agents coordinating agents](todo/agent-coordination/) | 5 | 0 |
|
||||
| [Speaking into a session from the backend](todo/agent-delivery/) | 1 | 1 |
|
||||
| [Backburner](todo/backburner/) | 3 | 0 |
|
||||
| [Backburner](todo/backburner/) | 4 | 0 |
|
||||
| [Background jobs](todo/background-jobs/) | 2 | 1 |
|
||||
| [Calendar and contacts](todo/calendar/) | 1 | 0 |
|
||||
| [Chat interface](todo/chat/) | 1 | 0 |
|
||||
@@ -71,6 +71,7 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
||||
|
||||
## Newest ten
|
||||
|
||||
- `2026-09-04` **M3E Canvas as a plugin — sketch a UI, hand the prompt to the agent** — [backburner/m3e-canvas-as-a-plugin](todo/backburner/m3e-canvas-as-a-plugin.md)
|
||||
- `2026-08-29` **Officer blocks its own event loop for ~0.9s re-parsing a live transcript, and everything it serves stalls with it** — [known-bugs/officer-blocks-its-event-loop-parsing-transcripts](todo/known-bugs/officer-blocks-its-event-loop-parsing-transcripts.md)
|
||||
- `2026-08-27` **Turn an ordinary chat into a commander or a bot, carrying its context along** — [agent-coordination/turn-a-chat-into-a-commander-or-a-bot](todo/agent-coordination/turn-a-chat-into-a-commander-or-a-bot.md)
|
||||
- `2026-08-27` **The calendar API cannot express repeating events, reminders, invitees or a cancelled occurrence** — [calendar/the-json-api-cannot-express-recurrence-alarms](todo/calendar/the-json-api-cannot-express-recurrence-alarms.md)
|
||||
@@ -80,4 +81,3 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
||||
- `2026-08-27` **Setting up a phone for calendar and contacts should be one link inside Officer, not a manual account form** — [mobile-apps/setting-up-a-phone-should-be-a-link-in-the-app](todo/mobile-apps/setting-up-a-phone-should-be-a-link-in-the-app.md)
|
||||
- `2026-08-27` **Marketplace: browse by tag — filter list, card grid, detail with a back button** — [plugin-ecosystem/marketplace-browse-by-tag](todo/plugin-ecosystem/marketplace-browse-by-tag.md)
|
||||
- `2026-08-25` **An affordance: a scoped agent warns when a request lands outside its scope** — [affordances/an-affordance-a-scoped-agent-warns-on-the](todo/affordances/an-affordance-a-scoped-agent-warns-on-the.md)
|
||||
- `2026-08-25` **Commit signing is inconsistent across harnesses — Claude names itself, opencode names nothing** — [agent-coordination/commit-signing-is-inconsistent-across-harnesses](todo/agent-coordination/commit-signing-is-inconsistent-across-harnesses.md)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
id: backburner/m3e-canvas-as-a-plugin
|
||||
title: "M3E Canvas as a plugin — sketch a UI, hand the prompt to the agent"
|
||||
theme: backburner
|
||||
status: open
|
||||
created: 2026-09-04
|
||||
date_source: stated
|
||||
order: 4
|
||||
---
|
||||
|
||||
**M3E Canvas as a plugin.** [github.com/lnkiai/m3e-canvas](https://github.com/lnkiai/m3e-canvas) — a
|
||||
browser design tool for Material 3 Expressive screens that exports the design as a natural-language
|
||||
prompt for an AI coding tool. MIT.
|
||||
|
||||
Not decided. This card holds the measurements so the decision can be made from numbers rather than from
|
||||
a README. Everything below was read from the code at **`cfde698`, 2026-09-04**.
|
||||
|
||||
## Why it is interesting
|
||||
|
||||
Its output is *a prompt you paste into Claude*. Officer is the thing that removes the paste — the export
|
||||
button could open a chat session with the prompt already in it, pointed at a project directory on this
|
||||
machine. **That last step is the only part Officer uniquely adds, and it is the whole reason to bother.**
|
||||
The canvas already works standalone today.
|
||||
|
||||
## What it actually is
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| size | 65 files, **14,822 lines** of TS/TSX |
|
||||
| deps | `next` 16.3.4, `react` 19.2.8, `motion` ^13.1.1, `html-to-image` ^1.11.13 |
|
||||
| licence | MIT |
|
||||
| storage | `localStorage` only, no backend |
|
||||
|
||||
**Next.js is not actually there.** The entire codebase imports it **once**, and it is a type-only import:
|
||||
`import type { Metadata, Viewport } from "next"` in `app/layout.tsx`. Eighteen files carry `'use client'`.
|
||||
So it is a client-side React SPA that happens to be scaffolded in Next — there is nothing to strip, and
|
||||
porting means giving it a different mount point.
|
||||
|
||||
That is the finding that changes the estimate. An earlier read of the README concluded "weeks of work,
|
||||
own a fork forever", and that was wrong.
|
||||
|
||||
## The fork line falls on a file boundary
|
||||
|
||||
`app/page.tsx` imports about **twenty modules, every one a named export** through the `@/` alias, and
|
||||
reaches into no internals. `lib/tokens.ts` is the shared spine with 16 importers. `app/` holds exactly
|
||||
one file that matters.
|
||||
|
||||
| | lines | |
|
||||
| --- | --- | --- |
|
||||
| `app/page.tsx` | 3,433 | the whole editor: 15 components, 103 hook calls. **Yours.** |
|
||||
| `lib/ai.ts` | ~200 | **Delete it** — stores provider API keys in `localStorage`. Officer has a secret store and the agent. |
|
||||
| `lib/tokens.ts` | 1,614 | Material 3 tokens. Data. Vendor. |
|
||||
| `components/M3Node.tsx` | 1,233 | the component renderer. Vendor. |
|
||||
| `components/Inspector.tsx` | 1,171 | Vendor. |
|
||||
| `lib/prompt.ts` | 1,151 | the export engine. Vendor. |
|
||||
| `components/Preview.tsx` | 899 | Vendor. |
|
||||
| `components/ui.tsx` | 882 | Vendor. |
|
||||
| rest | ~4,200 | `Mobile`, `i18n`, `tidy`, `Toolbar`, `color`, `shapes`, `theme`, `project`. Vendor. |
|
||||
|
||||
**~3,600 lines owned, ~11,200 vendored** — and the split means something. What is vendored is *"what a
|
||||
Material 3 component looks like and how to describe it in words"*, where upstream's value accumulates and
|
||||
we have no wish to innovate. What is owned is *"where does this live in the workspace, whose is it, and
|
||||
what happens on export"*, where upstream can never help because it does not know Officer exists.
|
||||
|
||||
**So deep integration and tracking upstream are not in tension.** The integration work lives entirely in
|
||||
the one file that was always going to be rewritten.
|
||||
|
||||
## The export point is clean
|
||||
|
||||
```ts
|
||||
buildPrompt(doc: Doc, widths: Record<string, number>, onlyFrameId?: string, lang: Lang): string
|
||||
effectivePrompt(doc, widths, lang): string
|
||||
```
|
||||
|
||||
Two pure functions in `lib/prompt.ts`. `Doc` in, string out, no side effects. No `postMessage` bridge and
|
||||
no clipboard interception needed — **call the function.** Today it ends in
|
||||
`navigator.clipboard.writeText` in three places; that is what gets replaced.
|
||||
|
||||
`Doc` is plain serialisable JSON (`lib/project.ts` already downloads and reloads it), so persisting to a
|
||||
plugin table instead of `localStorage` is nearly free. Current keys: `DOC_KEY` and `UI_KEY` in
|
||||
`page.tsx`, `m3e:ai`, and `m3e:sec:<id>`.
|
||||
|
||||
## The upstream problem, measured
|
||||
|
||||
This is the part that makes it a backburner card rather than a build.
|
||||
|
||||
- **37 commits, 3 days old** — first 2026-09-02, and **22 of them landed on 2026-09-04**, the day this
|
||||
was read.
|
||||
- **Two human authors** (`lnkiai` 25, `CQMHV` 10) plus dependabot.
|
||||
|
||||
It is under heavy active development *right now*. The export surface will churn, and no amount of
|
||||
discipline on our side changes that. "Vendor it and track upstream" assumes an upstream that has settled,
|
||||
and this one has not started.
|
||||
|
||||
### If it is vendored anyway
|
||||
|
||||
`web/vendor/m3e/`, committed with upstream URL, commit SHA and date in a README beside it. **The
|
||||
precedent is already in the tree** — `plugins/jellyfin/web/vendor/` and `public/vendor/pako/`.
|
||||
|
||||
The piece that makes it survivable is **an adapter**: Officer's panels import from one `adapter.ts` that
|
||||
re-exports from `vendor/`, never from `vendor/` directly. An upstream rename then costs one file instead
|
||||
of twenty. Updating is: re-copy, read the diff, fix the seam.
|
||||
|
||||
## Do this first
|
||||
|
||||
**Open an issue asking upstream to make the seam explicit.** If `page.tsx` grew an entry point taking the
|
||||
persistence layer and the export sink — `<Canvas storage={…} onExport={…} />` — we would **depend rather
|
||||
than fork**, and the 3,600 owned lines shrink to a few hundred.
|
||||
|
||||
Two authors, three days in, MIT, actively committing, and visibly careful about structure. That is the
|
||||
most receptive an upstream ever gets, and it costs one issue to find out. **The answer changes which
|
||||
version of this gets built**, so it is the cheapest thing that can happen next.
|
||||
|
||||
## Open questions
|
||||
|
||||
1. **Is this a design tool that lives in Officer, or the front end of "sketch it and the agent builds
|
||||
it"?** If the second, `page.tsx` is where we deliberately diverge, and adopting the fork is the right
|
||||
frame from day one rather than something discovered in six months.
|
||||
2. **Whose designs are they?** `Doc` is per-user data. Multi-user makes that a permissions question, and
|
||||
the plugin table answers it in a way `localStorage` cannot.
|
||||
3. **Does the phone editor survive the port?** `components/Mobile.tsx` (534 lines) is a separate
|
||||
phone-optimised editor. Officer's mobile story is a native app, so this may be redundant or may be the
|
||||
best part.
|
||||
4. **An iframe was considered and rejected.** It buys a day and costs the function call, the theming, the
|
||||
persistence, and forces back the clipboard round-trip the whole idea exists to remove. Recorded so it
|
||||
is not re-proposed as the cheap option.
|
||||
Reference in New Issue
Block a user