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>
"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>
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>
Four sections could not be scrolled: About, and Gallery, Drops and Collabs, which are three
configurations of one panel. Both of those panels were written yesterday with `overflow-auto` and no
height, so they grew to fit their content and ran off the bottom of the workspace instead of scrolling
inside it.
`overflow-auto` only does something on an element with a height to overflow. DetailPanel gives its
child `min-h-0 flex-1` inside a `size-full` column, so a panel that says `size-full` fills exactly that
box and scrolls within it — which is what Home, Galleries and Administration were already doing and
what these two were copied from without.
Messages had the same fault in a different shape. Its list is a flex child with `flex-1 overflow-auto`,
and a flex child defaults to `min-height: auto` — it refuses to shrink below its content, so the list
grew the panel rather than scrolling. `min-h-0` is what lets it shrink, and therefore what makes the
overflow mean anything.
Audited the rest rather than fixing the reported ones: every panel with an `overflow-auto` now pairs it
with a height, and the two that have none are centred single screens with nothing to scroll.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>