the output is a console, and it clears itself when it worked

pre/code on black. it is quoting a terminal, so it should look like one — as a
tinted paragraph it read as part of the form, which is exactly what it is not.

IT CLOSES ITSELF ON SUCCESS, after five seconds, and NEVER on failure.

a successful clone or update is worth watching and not worth keeping. "74 packages
installed" has been read by the time it finishes, and left on screen it becomes
furniture that outlives its own truth — still sitting there after the root has been
changed underneath it.

a failure is the opposite, and gets no timer at all rather than a long one. it is
the only copy of why, and a transcript that scrolled past is one somebody has to
reproduce in order to read. nothing takes it off the screen except the next attempt.

same `useFlash` as the Saved indicator, so there is one timer implementation and one
number, and the clear-the-previous-timeout property comes along with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 00:57:36 +01:00
co-authored by Claude Opus 5
parent bab47526f7
commit 91aae3e981
2 changed files with 21 additions and 10 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ import type { PluginManifest } from '@@/plugins/manifest';
*/
export const manifest: PluginManifest = {
publisher: 'officerdev',
version: '2026.08.29.11',
version: '2026.08.29.12',
platform: '>=1.0.0',
label: 'Fofos',
+20 -9
View File
@@ -3,6 +3,7 @@ import { CircleAlert, CircleCheck, CircleDashed, DownloadCloud, Loader2, Refresh
import { useClient } from 'hooks/useClient';
import { Button } from '@/components/ui/button';
import { readableError } from './errors';
import { useFlash } from './useFlash';
/**
* The website checkout under the root location: is it there, clone it, update it.
@@ -18,6 +19,15 @@ import { readableError } from './errors';
* "Permission denied (publickey)" says exactly what is wrong. Anything this could write in its place
* would be a guess at which of a dozen causes it was, and the guess is what sends somebody looking in
* the wrong direction.
*
* ── The console closes itself on success, and never on failure ──
*
* A successful clone or update is worth WATCHING and not worth keeping: "74 packages installed" has
* been read by the time it finishes, and left on screen it becomes furniture that outlives its own
* truth — still sitting there after the root has been changed underneath it.
*
* A failure is the opposite. It stays until something else happens, because it is the only copy of
* why, and a transcript that scrolled past is one somebody has to reproduce to read.
*/
type Status = {
@@ -42,6 +52,7 @@ export const WebsiteCheckout = ({ rootPath }: { rootPath: string }) => {
const [busy, setBusy] = useState<'clone' | 'update' | null>(null);
const [output, setOutput] = useState<string | null>(null);
const [failed, setFailed] = useState(false);
const [showing, show] = useFlash();
const load = useCallback(async () => {
try {
@@ -68,6 +79,9 @@ export const WebsiteCheckout = ({ rootPath }: { rootPath: string }) => {
setStatus(result.status);
setOutput(result.output || null);
setFailed(!result.ok);
// Only on the way out. A failure has no timer at all rather than a long one — nothing should be
// able to take it off the screen except the next attempt.
if (result.ok) show();
} catch (failure) {
// A 400 carries git's own words in the body; `readableError` surfaces them.
setOutput(readableError(failure));
@@ -129,15 +143,12 @@ export const WebsiteCheckout = ({ rootPath }: { rootPath: string }) => {
</div>
</div>
{/* git's own words, in git's own shape. Scrolled rather than truncated — the useful line of a
clone failure is usually the last one. */}
{output && (
<pre
className={`max-h-32 overflow-auto rounded-md border border-border p-2 font-mono text-[11px] whitespace-pre-wrap ${
failed ? 'text-destructive' : 'text-muted-foreground'
}`}
>
{output}
{/* git's own words, in git's own shape — a console, so it looks like the thing it is quoting
rather than like a paragraph of the form. Scrolled, not truncated: the useful line of a
failure is usually the last one. */}
{output && (failed || showing) && (
<pre className="max-h-40 overflow-auto rounded-md border border-border bg-black p-3 font-mono text-[11px] leading-relaxed whitespace-pre-wrap">
<code className={failed ? 'text-red-400' : 'text-neutral-200'}>{output}</code>
</pre>
)}
</div>