Files
pastilhasandClaude Fable 5 a0349cc7a3 the officer shell: designs per user, and the handoff desk
manifest, schema, router and two panels. the editor is hosted and
saving; the last step is not finished and the readme says so.

designs are rows scoped per user, with userId in every WHERE rather
than checked after the read — another person's design is 'not found',
so the id space cannot be probed. the Doc is stored whole as jsonb
because upstream adds fields to it constantly and none of them should
be a migration here.

the handoff is the reason the plugin exists and it is the part that
does not work: /api/chat has NO session-creation route, a session is
constructed by the browser opening a websocket, so a plugin has nothing
to POST to. onExport returns false and the clipboard still runs, which
is upstream's own behaviour — degraded, not broken. docs/handoff.md
names the one platform change that would finish it, and argues it
should be generic rather than ours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 12:16:35 +01:00

3.4 KiB

The handoff, and the one platform hook it needs

This plugin exists for a single step: the generated prompt should not reach a clipboard. It should open a chat session with the agent, already containing the prompt, pointed at a directory on this machine.

That step is not finished, and the reason is worth writing down precisely, because it is a platform gap rather than an unwritten function.


Why a plugin cannot do this today

/api/chat has no session-creation route. Reading src/servers/api/chat/chat.ts, the surface is GET /sessions, GET /sessions/:id, DELETE /sessions/:id, the task verbs and the usage and model readers. There is no POST /sessions.

A session is created by the browser opening /api/chat/ws and sending its first message. The socket is the constructor. So there is nothing a plugin's server can POST to in order to start one, and nothing it can hand a prompt to.

That is a reasonable design — it keeps session lifetime tied to a live connection — but it means "start a session containing this text" is not expressible from a plugin.

What is built

POST /api/m3e-canvas/_handoff takes { prompt, title }, holds it in memory against a one-time id with a ten-minute expiry, and answers:

{ "id": "…", "chatUrl": "/chat/new?handoff=…", "ready": false }

GET /api/m3e-canvas/_handoff/:id collects it once and forgets it. A handoff belongs to whoever made it; another user asking is told it does not exist rather than that it is theirs.

ready: false is the honest part of the response. Nothing reads ?handoff= yet.

Meanwhile onExport returns false, so the editor still writes the clipboard. The feature is degraded rather than absent: you get the prompt, you just paste it yourself — which is exactly what upstream does, so nothing is worse than before.

What would finish it

One small change, in the platform rather than here. /chat/new reads a handoff search parameter, and when present fetches /api/m3e-canvas/_handoff/:id and puts the prompt in the composer before the first send.

That is a few lines in SessionListPage, and it is deliberately not written here for two reasons. It is a platform change, so it belongs to whoever owns the chat screen rather than arriving as a side effect of installing a drawing tool. And a plugin reaching into another surface's composer is the kind of coupling that is easy to add and unpleasant to find later.

The shape worth arguing about first

If this is worth doing at all, it is probably worth doing generically — ?handoff=<pluginPrefix>/<id>, resolved against the plugin that minted it, so the next plugin that wants to hand work to the agent does not add a second parameter. todo/sendoff/ is already circling the same idea from another direction.

Until that is decided, the seam here is small enough to throw away and specific enough to be honest about what it does not do.

The other half, unstarted

Even with the composer prefilled, "the agent writes the app" needs somewhere to write it. A design has no notion of a project directory, and picking one is a product decision:

  • a directory the user names when handing off
  • a per-design directory remembered on the row
  • whatever the chat session's own working directory already is

The third is the least work and probably the right default, since a session started from a dashboard already has a directory. It is not implemented, and the row has no column for it.