a copy button on the console, and the failure case spelled out
a failure is something somebody PASTES — into a search, into a message, back to whoever wrote the thing that broke. selecting text inside a scrolled box, on a phone, is the point at which people screenshot an error instead, and a screenshot of a stack trace helps nobody. the button is pinned to the corner of the BOX and not scrolled with the text: one that leaves the viewport when the transcript is long is missing exactly when the transcript is worth copying. `pr-10` keeps the output from running underneath it, and it carries its own dark background because it sits over the console rather than beside it. `copyToClipboard` from helpers, the same one CopyField uses, rather than reaching for navigator.clipboard here. the no-timer-on-failure rule was already the behaviour; the comment now says the part that was only implied. the half-done case is the one that matters and it is named: a clone that worked followed by an install that did not is a failure, `ok` is false, and the transcript of BOTH stays. the only thing that clears it is starting the next attempt — the one moment it stops being true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ import type { PluginManifest } from '@@/plugins/manifest';
|
|||||||
*/
|
*/
|
||||||
export const manifest: PluginManifest = {
|
export const manifest: PluginManifest = {
|
||||||
publisher: 'officerdev',
|
publisher: 'officerdev',
|
||||||
version: '2026.08.29.12',
|
version: '2026.08.29.13',
|
||||||
platform: '>=1.0.0',
|
platform: '>=1.0.0',
|
||||||
|
|
||||||
label: 'Fofos',
|
label: 'Fofos',
|
||||||
|
|||||||
+34
-6
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { CircleAlert, CircleCheck, CircleDashed, DownloadCloud, Loader2, RefreshCw } from 'lucide-react';
|
import { Check, CircleAlert, CircleCheck, CircleDashed, Copy, DownloadCloud, Loader2, RefreshCw } from 'lucide-react';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
|
import { copyToClipboard } from 'helpers/clipboard';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { readableError } from './errors';
|
import { readableError } from './errors';
|
||||||
import { useFlash } from './useFlash';
|
import { useFlash } from './useFlash';
|
||||||
@@ -26,8 +27,16 @@ import { useFlash } from './useFlash';
|
|||||||
* been read by the time it finishes, and left on screen it becomes furniture that outlives its own
|
* 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.
|
* 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
|
* A failure is the opposite. It gets no timer at all rather than a long one — the only thing that
|
||||||
* why, and a transcript that scrolled past is one somebody has to reproduce to read.
|
* takes it off the screen is starting the next attempt, which is the one moment it stops being true.
|
||||||
|
* That includes the half-done case, which is the most important of the three: a clone that worked
|
||||||
|
* followed by an install that did not is a failure, `ok` is false, and the transcript of both stays.
|
||||||
|
*
|
||||||
|
* ── And it can be copied ──
|
||||||
|
*
|
||||||
|
* A failure is something somebody pastes — into a search, into a message, back to whoever wrote the
|
||||||
|
* thing that broke. Selecting text inside a scrolled box, on a phone, is the point at which people
|
||||||
|
* take a screenshot of an error message instead, and a screenshot of a stack trace helps nobody.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type Status = {
|
type Status = {
|
||||||
@@ -53,6 +62,7 @@ export const WebsiteCheckout = ({ rootPath }: { rootPath: string }) => {
|
|||||||
const [output, setOutput] = useState<string | null>(null);
|
const [output, setOutput] = useState<string | null>(null);
|
||||||
const [failed, setFailed] = useState(false);
|
const [failed, setFailed] = useState(false);
|
||||||
const [showing, show] = useFlash();
|
const [showing, show] = useFlash();
|
||||||
|
const [copied, flashCopied] = useFlash(1500);
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -147,9 +157,27 @@ export const WebsiteCheckout = ({ rootPath }: { rootPath: string }) => {
|
|||||||
rather than like a paragraph of the form. Scrolled, not truncated: the useful line of a
|
rather than like a paragraph of the form. Scrolled, not truncated: the useful line of a
|
||||||
failure is usually the last one. */}
|
failure is usually the last one. */}
|
||||||
{output && (failed || showing) && (
|
{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">
|
<div className="relative">
|
||||||
<code className={failed ? 'text-red-400' : 'text-neutral-200'}>{output}</code>
|
<pre className="max-h-40 overflow-auto rounded-md border border-border bg-black p-3 pr-10 font-mono text-[11px] leading-relaxed whitespace-pre-wrap">
|
||||||
</pre>
|
<code className={failed ? 'text-red-400' : 'text-neutral-200'}>{output}</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
{/* Pinned to the corner of the BOX, not scrolled with the text — a copy button that leaves
|
||||||
|
the viewport when the transcript is long is missing exactly when the transcript is worth
|
||||||
|
copying. `pr-10` above keeps the text out from under it. Its own dark background because
|
||||||
|
it sits over the output rather than beside it. */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
if (await copyToClipboard(output)) flashCopied();
|
||||||
|
}}
|
||||||
|
aria-label="Copy this output"
|
||||||
|
title="Copy"
|
||||||
|
className="absolute top-1.5 right-1.5 rounded border border-border/60 bg-black/80 p-1.5 text-neutral-400 transition-colors hover:text-neutral-100"
|
||||||
|
>
|
||||||
|
{copied ? <Check className="size-3.5 text-success" /> : <Copy className="size-3.5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user