Files
fofos-admin/web/AdministrationPanel.tsx
T
pastilhasandClaude Opus 5 d5f2d8f370 Root location: an absolute path, with a picker that can make folders
defaults to `<owner home>/fofos`, and stores the FULL path — never the tilde. a
tilde means "whoever is asking", and the people asking will not all be the same
person: this plugin is grantable, and the site is in one place regardless of who
is looking at it. stored as `~/fofos` it would give every user a different answer
to a question that has one.

THE DEFAULT IS COMPUTED, NOT WRITTEN. no row means the default is worked out on
every read, so it follows the machine. writing it in at install freezes whichever
home existed on install day, and a restore onto a different machine then points at
a directory that is not there while looking deliberately chosen. a saved path is a
decision and is stored exactly as given; only its absence is derived. the form
says which of the two it is showing, because "not set" reading identically to "set
to the default" is how somebody comes to believe they configured something they
did not.

THE PICKER USES THE PLATFORM'S FILE API — /file-browser/ls and /mkdir — not routes
of this plugin's own. they already refuse to leave home, and they already tell a
missing directory from a forbidden one, which is a distinction that cost somebody
an afternoon and would have been re-guessed here. shipping a second listing and
mkdir would be a second filesystem surface to audit for the sake of one form.

that API is an execution capability and never grantable, so for anybody but the
owner it 403s — said in a sentence rather than shown as an empty tree, because an
empty listing is data and must never be how a refusal looks. it also browses the
owner's home and nothing above it; a path outside home is still reachable by
typing, since the picker fills the field and the FIELD is what saves. one value on
screen, one Save, and picking a folder commits nothing by itself.

"Use this folder" takes the one that is OPEN rather than one highlighted in the
list. that makes looking inside and choosing one gesture, and it is the only way
to choose a folder with nothing in it — which a freshly made one always is.

a SECOND table, not a column on fofos_admin_connection, whose url and token are
both notNull: a root path could not be saved there without also having an HTTP
connection, and that connection is being replaced rather than extended. when it
goes, its table is a clean drop instead of surgery on columns that outlived it.
the rule against tables in this plugin is unbent — what it forbids is the CLIENT'S
work, and this row holds a path somebody can retype.

needs `bun db:push`.

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

63 lines
2.4 KiB
TypeScript

import { Database, FolderRoot } from 'lucide-react';
import { RootLocationBlock } from './RootLocationBlock';
/**
* Administration — this plugin's own machinery, not the client's site.
*
* ── It is a list of blocks ──
*
* 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.
*
* ── 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.
*/
/** 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>
</div>
);