a band chooses whether a rule follows it

The site separates bands with an edge-to-edge line, and it was `index < sections.length - 1` — not a
decision anybody could make. Two bands that belong together, a heading beside its picture and the
carousel that continues it, read as two pages with a rule between them.

Putting both in ONE band does not solve it: a band is one row, so a full-width strip inside a
two-column band overlaps the other column. What was wanted was never one section; it was no line.

A checkbox rather than a field in the draft, because it is a switch — there is nothing to debounce, and
waiting 600ms to watch a line disappear reads as the control not working. Labelled by what it does
rather than as `divider`, which is the column name and says nothing about what you will see.

Still never on the last band: a line above the footer is the footer's own border, twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 15:20:06 +01:00
co-authored by Claude Opus 5
parent 7081e1139d
commit 6781fd5875
2 changed files with 31 additions and 1 deletions
+2
View File
@@ -68,6 +68,8 @@ export type CanvasComponent = {
export type CanvasSection = { export type CanvasSection = {
id: number; id: number;
name: string; name: string;
/** Whether the page draws a rule beneath this band. */
divider?: boolean;
/** The heading the page draws above the band, or null. Not `name` — see the site's schema. */ /** The heading the page draws above the band, or null. Not `name` — see the site's schema. */
title?: string | null; title?: string | null;
layout: 'one' | 'two'; layout: 'one' | 'two';
+29 -1
View File
@@ -26,7 +26,7 @@ export const BandSettings = ({
blocks, blocks,
}: { }: {
pageId: number; pageId: number;
band: { id: number; name: string; title?: string | null }; band: { id: number; name: string; title?: string | null; divider?: boolean };
/** The builder re-reads its sections: the nav's row is this name, and the page draws this title. */ /** The builder re-reads its sections: the nav's row is this name, and the page draws this title. */
onSaved: () => void; onSaved: () => void;
/** This band's blocks — `BandBlocks`, built by the panel that has the handlers. */ /** This band's blocks — `BandBlocks`, built by the panel that has the handlers. */
@@ -37,6 +37,7 @@ export const BandSettings = ({
clientRef.current = client; clientRef.current = client;
const [draft, setDraft] = useState({ name: band.name, title: band.title ?? '' }); const [draft, setDraft] = useState({ name: band.name, title: band.title ?? '' });
const [busyDivider, setBusyDivider] = useState(false);
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [saved, flashSaved] = useFlash(); const [saved, flashSaved] = useFlash();
@@ -93,6 +94,33 @@ export const BandSettings = ({
/> />
</div> </div>
{/*
Written on its own rather than through the draft, because it is a switch: there is nothing to
debounce and waiting 600ms to see a line disappear reads as the control not working.
Phrased as what it DOES — a rule under this band — rather than as `divider`, which is the column
name and tells somebody nothing about what they will see.
*/}
<label className="flex cursor-pointer items-center gap-2 text-xs">
<input
type="checkbox"
className="size-3.5 accent-primary"
checked={band.divider !== false}
disabled={busyDivider}
onChange={async (event) => {
const next = event.currentTarget.checked;
setBusyDivider(true);
try {
await client.patch(`/fofos-admin/pages/${pageId}/sections/${band.id}`, { divider: next });
onSaved();
} finally {
setBusyDivider(false);
}
}}
/>
A rule beneath this band
</label>
{saved && <p className="text-xs text-success">Saved</p>} {saved && <p className="text-xs text-success">Saved</p>}
{error && <p className="text-xs text-destructive">{error}</p>} {error && <p className="text-xs text-destructive">{error}</p>}