It is the same KIND of thing as the root location and the databases — this
machine, not the site's content — and a nav row leading to one card was a whole
screen to reach three lines.
Last of the three, because it needs both of the others: a server has nowhere to
run without the checkout and nothing to read without a database.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Derived media" was a section sitting beside the block that lists `originals/`
and `derived/` — the same subject described twice, leaving the reader to work
out that the two were about one thing.
So the control moves under those folders, inside Root Location, and appears only
once both exist: before that the button would be asking to write into a
directory that is not there, and Create is the thing to press. An order, not a
choice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mapping: derived/ mirrors originals/ directory for directory, and where
originals holds a FILE, derived holds a DIRECTORY of the same name — extension
included — with the renditions inside it. Keeping the extension is what makes it
total: a.jpg and a.png are different originals and must not share a folder. The
inverse is pure string work, so the filesystem is the record and there is no
second copy of the truth.
Images get a WebP ladder at 320/640/960/1280/1920/2560 plus full.webp, and
nothing is ever upscaled — a rung wider than the original would be a soft file
that srcset then confidently prefers over the sharp one. full.webp always
exists because for a HEIC it is the only browser-readable copy of the picture
there is.
Video gets av-club's web-1080 and web-720 presets by name, plus a poster. Images
are named by width because srcset descriptors are widths; video by height
because that is av-club's vocabulary and nobody misreads 1080.mp4.
The ladder is fixed here and the SITE chooses from it. This never reads
website/src/media/slots.ts — a plugin that knows a website's layout is a plugin
for one website, and that boundary is the difference between a template and
something fofos-shaped.
Each derived folder carries a manifest naming the size and mtime it was built
from, so the sync is idempotent and resumable: a run that died halfway is
finished by running it again. That is what makes an in-memory job acceptable
rather than merely convenient. It builds into <name>.partial and renames, so an
interruption can never leave a folder that looks finished.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
it was a permanent line under the controls saying "Saved." — which said nothing,
because everything on this screen has been saved, all the time. it looked exactly
like feedback, so the one moment it actually meant something was indistinguishable
from every moment it did not.
now it appears when a save LANDS and clears itself. top-right of the block, beside
the title rather than in the flow: it is a remark about the block, not part of what
you are doing, and inline it pushes everything below down a line and then pulls it
back up — a layout that jumps every time somebody saves.
"Not set yet" stays. that IS a state, and it is the one worth knowing; it goes when
it stops being true rather than being replaced by a line that never changes.
the timer is a ref and is cleared before each new one. without that, saving twice
in quick succession leaves the first timeout running and it fires against the
second save — the confirmation vanishing a moment after appearing, which reads as
the second save failing.
Block moved to its own file and each setting now renders its own. the status belongs
to the setting that produced it, and lifting one block's timer into the panel would
put it in a component with nothing to do with it. that is also what made the
top-right slot possible without absolutely positioning anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
defaults to `<owner home>/fofos`, and stores the FULL path — never the tilde. a
tilde means "whoever is asking", and the people asking will not all be the same
person: this plugin is grantable, and the site is in one place regardless of who
is looking at it. stored as `~/fofos` it would give every user a different answer
to a question that has one.
THE DEFAULT IS COMPUTED, NOT WRITTEN. no row means the default is worked out on
every read, so it follows the machine. writing it in at install freezes whichever
home existed on install day, and a restore onto a different machine then points at
a directory that is not there while looking deliberately chosen. a saved path is a
decision and is stored exactly as given; only its absence is derived. the form
says which of the two it is showing, because "not set" reading identically to "set
to the default" is how somebody comes to believe they configured something they
did not.
THE PICKER USES THE PLATFORM'S FILE API — /file-browser/ls and /mkdir — not routes
of this plugin's own. they already refuse to leave home, and they already tell a
missing directory from a forbidden one, which is a distinction that cost somebody
an afternoon and would have been re-guessed here. shipping a second listing and
mkdir would be a second filesystem surface to audit for the sake of one form.
that API is an execution capability and never grantable, so for anybody but the
owner it 403s — said in a sentence rather than shown as an empty tree, because an
empty listing is data and must never be how a refusal looks. it also browses the
owner's home and nothing above it; a path outside home is still reachable by
typing, since the picker fills the field and the FIELD is what saves. one value on
screen, one Save, and picking a folder commits nothing by itself.
"Use this folder" takes the one that is OPEN rather than one highlighted in the
list. that makes looking inside and choosing one gesture, and it is the only way
to choose a folder with nothing in it — which a freshly made one always is.
a SECOND table, not a column on fofos_admin_connection, whose url and token are
both notNull: a root path could not be saved there without also having an HTTP
connection, and that connection is being replaced rather than extended. when it
goes, its table is a clean drop instead of surgery on columns that outlived it.
the rule against tables in this plugin is unbent — what it forbids is the CLIENT'S
work, and this row holds a path somebody can retype.
needs `bun db:push`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
empty like the one below it, and for the same reason: the block settles the shape
and says what is coming, while what goes in it is not decided.
the order is the order things are needed in — where the site is on this machine,
then how to read its data.
this is the block list paying for itself. adding the second setting was one entry
and moved nothing, which is what the previous commit chose it for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
the address-and-token form is gone from the screen and ConnectionPanel.tsx with
it. leaving an unreferenced component behind would have been worse than deleting
it: it is in the history, and a file nothing renders rots without anyone noticing.
in its place, Administration is a LIST OF BLOCKS rather than one form. it will
hold several unrelated settings, and giving each a titled block now means the
second one moves nothing — a single form would have to be broken up the first time
anything joined it.
the first block is Database, and it is empty on purpose. it says "nothing to set
yet" rather than rendering nothing, because a section that draws blank reads as
one somebody abandoned. what goes in it is not decided, and inventing fields to
fill the space would be deciding it in the wrong place.
WHAT THIS COSTS, plainly: there is now no way to configure this plugin from the
screen at all. the server half is untouched — `/_config` still stores a URL and a
token and the proxy in api/router.ts still reads them — but nothing can set them.
that is consistent with the gate being hardcoded false rather than a second
breakage: both are the same unfinished change, and neither is worth half-fixing
while the shape of the connection is being replaced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>