"Saved" is a moment, top-right, and gone in five seconds

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>
This commit is contained in:
2026-08-29 00:44:46 +01:00
co-authored by Claude Opus 5
parent df52045eaa
commit 9cbbc28c82
6 changed files with 141 additions and 58 deletions
+11 -47
View File
@@ -1,62 +1,26 @@
import { Database, FolderRoot } from 'lucide-react';
import { RootLocationBlock } from './RootLocationBlock';
import { DatabaseBlock } from './DatabaseBlock';
/**
* Administration — this plugin's own machinery, not the client's site.
*
* ── It is a list of blocks ──
* A list of blocks, and nothing else. Each one owns its own heading, its own state and its own status
* message; this file decides only which of them exist and in what order.
*
* Not one form. Administration holds several unrelated settings, and giving each its own titled block
* means adding the next one moves nothing — as against a single form that has to be broken up the
* first time anything joins it. The second block cost one entry, which is the point.
*
* The order is the order things are needed in: where the site is on disk, then how to read its data.
* The order is the order things are needed in: where the site is on this machine, then how to read its
* data.
*
* ── What used to be here ──
*
* An address and a token: where fofos is, over HTTP. That is gone. It has NOT been replaced yet, so
* nothing can currently configure this plugin — which is why `useReady` stays hardcoded false and
* every other section stays locked. The two are the same change and neither is finished.
*
* The server half is untouched: `/_config` still stores a URL and a token, and the proxy in
* `api/router.ts` still reads them. Only the way in from the screen is gone.
* An address and a token: where fofos is, over HTTP. That is gone from the screen. 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, which is why `useReady` stays hardcoded false and every other
* section stays locked. The two are the same unfinished change.
*/
/** One titled block. The shape every Administration setting gets, so the second one costs nothing. */
const Block = ({
icon: Icon,
title,
hint,
children,
}: {
icon: typeof Database;
title: string;
hint: string;
children?: React.ReactNode;
}) => (
<section className="flex flex-col gap-3 rounded-lg border border-border p-4">
<div className="space-y-1">
<h3 className="flex items-center gap-2 text-sm font-medium">
<Icon className="size-4" />
{title}
</h3>
<p className="text-xs text-muted-foreground">{hint}</p>
</div>
{children}
</section>
);
export const AdministrationPanel = () => (
<div className="flex size-full flex-col gap-4 overflow-auto p-5">
<Block icon={FolderRoot} title="Root location" hint="Where the fofos site is on this machine.">
<RootLocationBlock />
</Block>
<Block icon={Database} title="Database" hint="Where the fofos site's own data lives.">
{/* Deliberately empty. The block exists so the shape is settled and so the screen says what is
coming rather than looking like a section nobody finished — but nothing is decided about what
goes in it, and guessing at fields would be a decision made in the wrong place. */}
<p className="text-xs text-muted-foreground/70">Nothing to set yet.</p>
</Block>
<RootLocationBlock />
<DatabaseBlock />
</div>
);