Commit Graph
10 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 0dce8fcb3c About Us
Four bands in the order the page shows them, which is the sitemap's shape exactly: a HEADER of a
photograph and a phrase, then ABOUT US, then PRODUTO with a video beside its text, then MANIFESTO.

No schema change and no API change — `about_content` already carries every one of those columns and
`aboutRouter` already reads and writes them. This is the screen that was missing, not the data.

One save per band, the rule Home follows and for the same reason: `take()` treats an absent key as
"leave it alone", so a single Save carrying every field would let a half-finished manifesto ride along
with a photograph somebody meant to change.

Both media slots go through MediaPicker rather than a text field. A path somebody types is a path that
is wrong by one character and renders as a broken image. One picker serves both, with `picking` naming
which slot asked — two would be two copies of a modal that already knows how to browse originals/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 22:33:45 +01:00
pastilhasandClaude Opus 5 ccf5768a3d Gallery, Drops and Collabs
Three sections, one panel, configured — the same split the site's API already makes. Each is a titled
record with an ordered list of media hanging off it, and the only real differences are what the title
is called and which extra fields the record carries.

The htmx admin had three screens and that was defensible there, because each carried its own markup as
well. There is no markup to differ here, so three panels would be three places to fix one ordering bug.

The `fields` list in records.ts is the FORM for what the server allows, not a second authority over it.
What may actually be written is decided by `valuesFrom` in the site's sections.ts, per section, as an
allowlist — a key added here that the server does not accept is an input that silently saves nothing.

Which record is open lives in `?record=<id>` beside `?section=`, per docs/navigation-audit.md, and the
detail is loaded from that id rather than carried over from the list. That is what makes a typed or
reloaded URL work, and it is guarded against a slow response for a record nobody is looking at any
more overwriting a fast one for the record they opened next.

Ordering sends every id in the order it should be, so two items cannot claim one position. Media is
added one path at a time because the server appends, and a parallel add would land in whatever order
the requests happened to finish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 00:40:45 +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 9c62777f5b thumbnails need the token in the query string
An <img> cannot send an Authorization header, which is why user-middleware and
the permission gate both already accept `?token=` — it is exactly what the file
browser's own thumbnails do.

Built without it, every picture 401'd while the layout looked perfectly correct:
the space was reserved, the rows were right, and nothing appeared. The route was
fine and had been tested with a bearer token, which is precisely the case the
browser cannot reproduce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 21:26:19 +01:00
pastilhasandClaude Opus 5 9fd5f1b3ab gallery items get a thumbnail and a caption
The thumbnail comes from the DERIVED ladder, which is the point of the whole
pipeline: most of these are HEIC and a browser cannot display the original at
all. 320 is the smallest rung and still sharp at this size.

Served by this plugin from disk rather than by the site. The panel talks to the
plugin and the catch-all forwards everything to the site's /api/admin, which is a
different route — and more importantly, looking at a gallery should not require
the development server to be running. Editing the site is not the same as
reading it.

Not under /_config, because that prefix is owner-only and a thumbnail is not a
credential: a member who can open the section can see the pictures in it, which
is what the section is. Registered before the catch-all, or the catch-all would
forward it to fofos.

Path traversal is refused by resolving first and comparing against the directory
plus a separator — a bare startsWith accepts `derived-old` for a root of
`derived`.

An item whose conversion has not run has no rung, and the thumbnail hides rather
than showing a broken-image glyph, which reads as corrupt rather than pending.

Captions save on blur. A caption is a sentence somebody is in the middle of, and
a request per keystroke would be wasteful and a race with itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 21:23:40 +01:00
pastilhasandClaude Opus 5 f61653f7af Galleries: a panel, and Create Gallery in the file browser
The section reads the site's own admin API, so it requires a CONNECTION where
Media beside it requires only a root. The difference is real rather than
copy-paste: Media is a file browser over a folder on this disk, while a gallery
is a row in the site's database — opening it without the site answering would be
a screen where every action fails.

The context action hands its selection over in the URL and sends you to the
panel rather than opening a dialog of its own. Naming a gallery, seeing the slug
it will get and watching the items land belongs on the screen that owns
galleries, not in a modal over a file browser that would then have to explain
where the result went.

It offers itself only inside originals/, because a gallery item is a path
relative to that folder and an entry that appears everywhere while working in one
place is worse than one that appears where it works. The test is on the PATH, not
on which panel is open — the same browser is mounted in several places and only
the path says where it is pointed.

A mixed selection is refused. A folder among files is either a mistake or a
request to recurse, and neither is something to guess from a right-click.

The handoff is consumed once and stripped from the URL. Left there, a refresh
would create the gallery again, which somebody discovers as four copies.

Nothing is written until Create: the folder's name is a suggestion in the field,
not a decision already taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 21:08:25 +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