b9f8ce4ee4b671e5e9bad6cc3f06b9d8d28bf8a8
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
003613872d |
provision <root>/media, beside the checkout
where the images and the files will live. same row shape as the checkout above it,
because it is the same kind of fact: a thing that either is or is not at a known
path under the root, with one button that puts it there.
BESIDE `website`, never inside it, and that is the point of it being here rather
than a folder in the repository:
- it must SURVIVE the checkout. deleting `website` and cloning it again is a
normal thing to do when something is wrong with it, and it must never be the
thing that destroys the client's photographs.
- it must never be TRACKED. media in git is a repository nobody can clone a year
from now, and a .gitignore is a promise somebody eventually breaks with
`git add -f`.
both under one root, so a backup of the root is the whole site — the code and the
things it serves. that is the property that makes the root worth being a setting.
NOTHING ABOUT ITS INSIDES IS DECIDED. this creates the directory and reports
whether it is there; the layout, who writes to it and what goes where are still
being described. inventing a folder tree now would settle that in the file least
able to change its mind, because directories on disk are not a migration you can
run.
`mkdir -p`, so it also creates a root that is still only a default — saving a root
creates nothing, and provisioning into a path that does not exist is the ordinary
first run. idempotent by the same flag, which is what makes the button safe to
offer next to a status that might be stale.
no console for this one: making a directory either works or gives one line about
why not, and a black transcript box for a single sentence makes a small thing look
like a large one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
df394e29a4 |
the website checkout under the root: clone it, update it, say which
`<root>/website` is now checked, and the block says which of three things it is: not there, there but not a checkout, or a checkout with its branch and its head commit. the middle one is the state worth being loud about — cloning will refuse, and the reason is something already sitting in the way. SSH, hardcoded. the repo is private, so https asks for a username with nobody at a terminal to answer, and the SSH remote is already authenticated by the owner's key. hardcoded because this plugin administers exactly one website: a field for it would be a setting nobody changes and one more thing that can point somewhere wrong. when the checkout's origin is NOT that URL, the block says so — shown always it is noise, shown on a mismatch it answers why an update pulled something unexpected. NOTHING HERE MAY HANG. these run inside an HTTP request, so git gets GIT_TERMINAL_PROMPT=0 and ssh gets BatchMode=yes. without them a key that lost access does not fail, it BLOCKS forever waiting for a password at a terminal that does not exist, and the panel spins until somebody restarts the server. there is a five-minute kill on top for everything else that can wedge. update is `--ff-only`, never a merge. a merge commit made by a button in a checkout nobody is watching is a thing somebody finds days later; refusing is recoverable and legible. it also refuses on a dirty tree rather than failing halfway — and dirty is `--untracked-files=no`, because a build artefact or an uncommitted .env is not a reason to make Update permanently unavailable. clone takes FULL history, not --depth 1. this is a working checkout somebody will pull into and read the log of, and shallow turns both into a support question. it sits inside Root location rather than in a block of its own: the path and the thing at the path are one question, and split apart somebody could change the root in one place and read a status about the old one in the other, both looking equally current. hidden while editing, because it describes what is SAVED. git's own output is shown verbatim on failure. "Permission denied (publickey)" says exactly what is wrong; anything written in its place would be a guess at which of a dozen causes it was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9cbbc28c82 |
"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> |
||
|
|
df52045eaa |
the path gets the row, the actions get their own
Root location is two states now. reading, it is a path and one Edit. editing, it is a field and the things you can do to it. the actions moved to the block's BOTTOM row, off the value's line. an absolute path is long, and it was being truncated to make room for buttons that are not needed most of the time — the thing somebody opened this to read was the thing squeezed. shown, it wraps with `break-all` rather than truncating, because a path is read from both ends: the machine at the front, the folder at the back, and hiding either is worse than two lines. Edit is a mode and not an always-live field. this is looked at far more often than it is changed, and a live input invites a stray keystroke into a path everything else will depend on. Cancel restores what is stored, so leaving the mode cannot leave half a path on screen looking like the setting. "back to the default" is gone, as asked — and `clearRootPath` and its DELETE route with it, since that button was the only caller. an unreferenced endpoint is the same rot as an unreferenced component, and this one was a second, quieter Save that wrote a value nobody chose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |