`1 | [1 | 1]`, and the brackets are a real container this time.
Nesting them as a GROUP was the first attempt and looked identical — a horizontal group inside a
horizontal group renders three columns, because that is what it is. All it bought was which divider
moved what, which is invisible until you drag one.
So the pair is a panel whose component hosts its own WorkspaceView, the way DashboardPreview does. It
has its own chrome, its own divider and its own persisted sizes, and the root layout has two children
rather than three: the shape on screen is now the shape in the file.
`useAuthoredLayout` rather than `useDashboardState`, and its own key beside the root's. The structure is
the author's and comes from source every render; only sizes are the user's. Sharing one key would have
the inner and outer trees persisting over each other's sizes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`1 | 1 | 1` became `1 | [1 | 1]`, and the nesting is the whole difference.
Three siblings means every divider sits between two of them: widening the editor took from the preview,
and dragging the nav re-proportioned all three. Three panels that could only be sized against each
other.
Nested, the outer divider moves the nav against THE WORK and the inner one splits the work between
looking and changing. Those are the two questions somebody actually has — "give me more room" and "I
need more of the controls right now" — and each is one drag that leaves the other answer alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spacing and free-text editors rendered below the preview, so they took their height from the thing
being edited: choosing a spacing shrank the page you were choosing it for, and on a laptop two
dropdowns cost a third of the frame.
They are a third panel now, to the right of the preview. The workspace already knows how to size and
move panels; a drawer was a second, worse layout system living inside one of them.
The panel holds nothing. Whichever panel owns a selection BUILDS the editor and announces it through a
`useGlobal` slot — the two are siblings and neither may own the other, and a provider around both would
mean something above them knowing they exist.
The slot carries the ELEMENT, not a description of one. A descriptor plus a switch would move every
editor's loading, saving and cancelling into a panel that would have to learn what a section is — a
second place that understands the page builder, kept in step by hand. SaveTarget makes the same trade
for the same reason.
The hazard is real and worth naming: an element captures the props it was built with, so the announcing
effect must depend on everything the editor DISPLAYS. The save handlers sit in refs so they cannot drag
that list wider — depending on them would re-announce on every keystroke, and omitting them without
refs would announce a stale one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
manifest, one table, a router and two panels. Discovery is by convention, so
this is the point at which the directory stops being a directory and becomes
something Officer can install.
The manifest holds only what a listing cannot say. One permission, grantable,
with ownerOnly on /_config — reading and editing the site is exactly what a
member might be brought in for, but the token is unrestricted write authority
over everything the public sees, so handing it over is the owner's.
db/schema.ts is ONE table and should stay that way. fofos owns its own Postgres
and its own media store; this holds a URL and a token. That is what makes the
strongest thing about this plugin true: no operation on it can destroy the
client's work, checkbox ticked or not, because none of it was ever here. A cache
would be the thin end of that.
The router mostly forwards. fofos's /api/admin already answers in the shape a
panel wants, so re-wrapping it would be a second place for the contract to drift.
Two things in it are load-bearing rather than style:
registerProxiedPrefix('/api/fofos-admin/media'). bodyParser calls parseBody on
multipart across the whole protected router, and Hono then caches the parsed
FormData — so asking for the bytes afterwards re-serialises them under a NEW
boundary while the ORIGINAL content-type header is what gets forwarded. Header
and body disagree and every upload fails with "Unexpected end of form".
Narrow to the one route: everything else is JSON that is fine to parse.
The catch-all is LAST in the file. Hono runs matching handlers in registration
order, so /* above /_config would swallow it and the settings form would
forward itself to fofos, which has no such endpoint — a 404 from a plugin that
is working perfectly.
Panels are a nav and a detail pane that never speak to each other: the nav writes
?section=, the detail pane reads it. Real <Link>s rather than the buttons
proxy-manager uses — AGENTS.md says nav items are links, and the file documenting
that pattern says not to copy the older one. <Link> and not <NavLink> because
active state here is a query parameter and NavLink compares paths, which would
light every row at once.
Which component renders which section is a MAP, not a conditional on `built`.
The first version read `built ? <MessagesPanel/> : <Planned/>`, which is correct
only while exactly one section exists — the second one finished would have
rendered Messages under its own name.
Messages is the only section built, chosen because it is one read-only route: it
proves the path from an Officer panel through this proxy to fofos and back with
nothing else able to be wrong at the same time. The other eight are in the nav,
dimmed and labelled, saying what they will do and that the old admin still edits
them. A nav that hides what is unfinished gives no sense of what the thing is for.
Typechecked against the platform — and the typechecker was verified to actually
cover plugins/ by planting an error and watching it fail, because a clean run
that does not include your files is worse than no run.
NOT yet: installed, or run against a live fofos. Nothing here has been through
the permission gate or a real request.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>