10 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 6e33e89e2c finish the carousel rename in the panels
Two of these were live: PageBuilderPanel still compared kind === 'gallery' in
two places, so a carousel block's editor and its Choose button were both dead
branches. The type checker could not see them — both compare against string.

The rest is copy somebody reads: "Change gallery", "Name a new gallery",
"Reading the gallery", "Add to this gallery", Home's Gallery heading, and the
"Create Gallery" file action, now "Carousel from folder".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 18:06:06 +01:00
pastilhasandClaude Opus 5 15042ccc76 a gallery is a carousel
It always rendered as one — data-carousel, a snap track, two arrows, driven by
carousel.client.js. The entity was the last thing still calling it something
else, and that collided with the site's own Gallery section: two nav rows
reading Gallery and Galleries, meaning a list of objects and a list of
photograph sets.

GalleriesPanel, GalleryPicker and GalleryItems become Carousels*, the endpoint
becomes /fofos-admin/carousels, the block kind becomes carousel and
subject-gallery becomes subject-carousel. The publish allowlist follows the
renamed tables.

What did NOT move: GALLERY_CONFIG at /fofos-admin/gallery, subjects.ts's
base '/gallery', and home_gallery_media. Those are the Gallery section and the
legacy rows, and both are correct.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 18:01:15 +01:00
pastilhasandClaude Opus 5 b5ca67e68e every control writes itself
The Save button is gone, and so is the SaveTarget context that announced it.
Every screen here is a preview of the thing being edited, so a change is already
visible before it is written — a button then asks somebody to confirm what they
can see.

useAutosave debounces a burst into one request. It takes the unsaved state as a
string rather than a flag, so a failed save is not retried on the next render,
and it flushes on unmount so a section change loses nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:34:34 +01:00
pastilhasandClaude Opus 5 5fb447cf21 the panels stop explaining themselves
"The About page, in the order it is shown: a header, the text about you, the product, and the
manifesto." above four bands headed Header, About us, Product and Manifesto. "The photograph at the top,
and the line over it" above a photograph at the top and a line over it. "Who you are" above a box headed
About us.

Every one was a caption for something already visible, and together they were most of the vertical
space before any content — so the actual work started below the fold on a laptop.

The person using this is not a developer and is not a child. A heading names a thing; a sentence
underneath saying what the thing is says it twice and trusts them less each time.

Removed from About, Home, Galleries and the record sections. What stays is anything reporting STATE
rather than describing furniture — a count of photographs, a loading line, an error, a save
confirmation — because those say something the screen does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:12:08 +01:00
pastilhasandClaude Opus 5 abdb519e8b one Save, in the panel's bar
Every section had its own Save buttons — four on About, two on Home, one per record — and on a page
long enough to scroll, the button you wanted was the one that had just left the screen.

Now there is one, beside Publish, pinned above the scroll. The section announces whether it has unsaved
changes, whether it is busy, and how to save; the bar renders that. Neither knows what the other is,
which is what keeps DetailPanel a map of keys to components rather than a place that has to know what
each section can do.

── What this replaces ──

The per-card saves existed because the API treats an absent key as "leave it alone", so a Save carrying
everything meant a half-written manifesto shipping with a photograph somebody meant to change. With one
Save that is the model rather than an accident: one press writes the page you are looking at.

The cost, stated plainly: there is no longer a way to commit one part of a page and leave the rest.
Nothing here needed that, and if something ever does it wants its own verb rather than four buttons all
called Save.

── Dirty is a comparison, not a flag ──

Against what the server last returned, field by field. A flag says "something was typed" and stays true
after you undo it by hand; a comparison says "this differs from what is stored", which is the question
a Save button is actually answering — and it is what lets the button be dead when there is nothing to
do, which is the whole value of having one.

The gallery picker on Home is deliberately outside it and still writes on selection: a <select> that
needs a second press to mean anything is a control people believe they have already used.

Sections with nothing to save render no button rather than a dead one. A permanently disabled control
invites a press and teaches that pressing does nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 23:22:35 +01:00
pastilhasandClaude Opus 5 ccd4e23217 Home picks a gallery instead of managing one
Managing items on the page would be a second editor for something that already
has one, and the two would drift — a caption fixed in one place and not the
other. The page chooses; the Galleries section arranges; every page using that
gallery changes with it.

The list of galleries is read from the section that owns them rather than
embedded in the home payload, because a copy there would be stale the moment
somebody edited one.

While no gallery is chosen the old per-page rows still render, and the panel says
so — a carousel that appears to come from nowhere is worse than a sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 21:42:20 +01:00
pastilhasandClaude Opus 5 fc7bc5b8cf Hero is chosen by browsing originals, not typed
Nobody knows the path of a file they uploaded from a phone, and a text box
invites a typo that fails silently as a broken image on the live site. Browsing
`originals/` means the only values that can be stored are ones that exist.

The modal is the platform's file browser in pick mode — the same listing,
breadcrumb, sorting and thumbnails as everywhere else, with one branch changed.

It browses ORIGINALS and the site renders DERIVED, and that indirection is the
point of the pipeline: what somebody picks is the .mov or the 12-megapixel HEIC
that came off the camera, and what a browser can display is the ladder av-club
built beside it. Storing a derived file directly would pin the site to one
rendition, so the day the ladder changes every stored reference names a file that
is no longer the right one to serve.

The path is stored relative to `originals/`. The root is a setting and moves
between machines, so an absolute path would be a fact about one installation
written into content that outlives it.

No pathParam and no panelId on the modal's browser: its folder is not something
to put in a URL or remember, and both would leak a transient choice into shared
state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 20:30:33 +01:00
pastilhasandClaude Opus 5 95d5661e73 Intro and Hero are their own sections, and Intro comes first
The page reads hero-then-intro and this deliberately does not. The intro is the
thing that gets edited; the hero is a path that changes once a season. Ordering
by what somebody came to do beats mirroring the layout of the page they are
describing.

One PATCH per card, carrying only that card's field. `take()` on the server
treats an absent key as leave-it-alone, so sending the whole object from either
card would have each one saving the other's unsaved edits — a half-finished
intro riding along with a hero somebody meant to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 20:24:00 +01:00
pastilhasandClaude Opus 5 d57bd3e72f a real editor for the site's copy, and the media param stops following you
Two things.

`?media=` is Media's own folder and now goes when you leave it. It survived
every navigation, so `?section=home&media=GALLERY/CALAVERA+1` was an ordinary
URL — a parameter naming a folder in a section that is not open, carried into
every link and shared or bookmarked that way.

And the intro is a tiptap editor rather than a textarea of raw HTML. HTML in and
HTML out, because that is already what the site's database holds and renders —
introducing a second format would buy a migration and a round-tripping bug class
for nothing.

Declared as the PLUGIN's npmDependencies, not added to the platform's
package.json. That lockfile is frozen and shared, and npm-deps.ts exists for
exactly this: everything lands in plugins/node_modules/, so a plugin's editor
cannot move a version the platform depends on.

The toolbar is only what the site's stylesheet can render. Anything more would
produce copy that looks right while editing and wrong when published, which the
person only discovers after saying it was done.

The content effect compares against getHTML() before writing back, and that
comparison is the whole thing: while somebody types the two already agree, so
nothing is pushed in and the caret stays put. Without it this is the classic
controlled-contentEditable bug where the cursor jumps to the end on every
keystroke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 20:15:55 +01:00
pastilhasandClaude Opus 5 3ae8c960e5 the Home section, built against the site's own admin API
Hero, intro and an ordered gallery — the shape the old htmx admin had, as a
panel here.

Every write returns the WHOLE payload and this never patches its own state from
what it sent. That matters more than it looks: the server clears the hero crop
when the hero changes, because a rectangle describing a region of a different
picture would crop the new one somewhere arbitrary. A client keeping its own
copy would go on showing a framing the server had already discarded.

Ordering sends every id rather than one item's position, which is what makes it
impossible to end up with two things claiming the same place.

Thumbnails load through this plugin's proxy rather than straight from the dev
server, so they arrive with the session instead of requiring the browser to
reach a port on the host.

It edits DEVELOPMENT, because production is a directory of static files with no
API to write to. An edit lands on dev immediately and becomes public when
somebody presses Build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 20:09:29 +01:00