show what the commands are actually saying
Install, uninstall and purge narrated what the platform was doing and hid what it was running. Every spawn captured stdout and stderr, held them, and surfaced the last 600 characters only if the exit code was non-zero. That is enough to explain a crash and useless for the two things people want while watching an install: knowing a slow step is still moving, and seeing the warning a command prints on its way to exiting 0. The second is not hypothetical — `db:push` reported success on a machine where it created no tables. Its output was captured, discarded because the exit code was fine, and the failure surfaced minutes later as 42P01 in a sidecar log. Whatever drizzle said about that push, nobody ever read it. `runLogged` streams both pipes as they arrive, so they interleave the way they do in a terminal. Following only stdout would have been silent for exactly the commands worth watching: git writes everything to stderr, drizzle-kit writes progress there and results to stdout. Two pieces of noise control, because a log nobody can read is not an improvement: `git clone` gets `--progress` instead of `--quiet` (git says nothing when it thinks it has no terminal, and that is the step where a slow link looks like a hang) with per-percent rewrites collapsed to the last fragment per chunk; and drizzle's spinner, which is eight lines saying the same thing with a different braille glyph, collapses to the first — otherwise "Changes applied" scrolls past underneath it. Steps and output share ONE ordered list rather than two. Their order relative to each other is the whole information: "which step was this output from" is the only question the log exists to answer. Output renders dimmer and indented under the step that produced it. Verified by running a real purge and reinstall: the clone's object counts, the barrel generation naming both plugins, drizzle's push, then "schema: applied in 2029ms (1 table verified)". tsgo clean, frontend builds, 873 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
type AvailablePlugin,
|
||||
type PluginActionResult,
|
||||
type PluginDependencies,
|
||||
type ActionEntry,
|
||||
type PluginItem,
|
||||
type PluginVerb,
|
||||
} from './usePlugins';
|
||||
@@ -136,7 +137,7 @@ const ActionLog = ({
|
||||
result,
|
||||
running,
|
||||
}: {
|
||||
steps: string[];
|
||||
steps: ActionEntry[];
|
||||
result: PluginActionResult | null;
|
||||
running: PluginVerb | null;
|
||||
}) => {
|
||||
@@ -154,9 +155,14 @@ const ActionLog = ({
|
||||
<span className={result?.ok ? 'text-emerald-600' : 'text-red-600'}>{result?.ok ? 'Done' : 'Failed'}</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="space-y-0.5 font-mono text-xs text-duck-dark/70">
|
||||
{steps.map((s, i) => (
|
||||
<li key={i}>· {s}</li>
|
||||
{/* Raw output is dimmer and indented under the step that produced it — present when you want it,
|
||||
not competing with the narration when you do not. `break-all` because a clone URL or a drizzle
|
||||
error is one long token and would otherwise stretch the panel. */}
|
||||
<ul className="max-h-64 space-y-0.5 overflow-y-auto font-mono text-xs">
|
||||
{steps.map((entry, i) => (
|
||||
<li key={i} className={entry.kind === 'step' ? 'text-duck-dark/70' : 'break-all pl-3 text-duck-dark/35'}>
|
||||
{entry.kind === 'step' ? `· ${entry.text}` : entry.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{result?.error ? <div className="mt-2 text-xs text-red-600">{result.error}</div> : null}
|
||||
@@ -190,7 +196,7 @@ const AvailableDetail = ({
|
||||
plugin: AvailablePlugin;
|
||||
busy: boolean;
|
||||
onInstall: () => void;
|
||||
steps: string[];
|
||||
steps: ActionEntry[];
|
||||
result: PluginActionResult | null;
|
||||
running: PluginVerb | null;
|
||||
}) => (
|
||||
|
||||
@@ -77,6 +77,15 @@ export type PluginActionResult = { ok: boolean; appName: string; steps: string[]
|
||||
|
||||
export type PluginVerb = 'install' | 'uninstall' | 'enable' | 'disable' | 'purge';
|
||||
|
||||
/**
|
||||
* One line in the action log.
|
||||
*
|
||||
* `step` is the platform narrating what it is doing. `log` is the raw output of whatever that step
|
||||
* spawned — git, drizzle-kit, a package manager. Kept apart because they deserve different weight on
|
||||
* screen, and kept in ONE list because their order relative to each other is the information.
|
||||
*/
|
||||
export type ActionEntry = { kind: 'step' | 'log'; text: string };
|
||||
|
||||
export type BrokenPlugin = { appName: string; error: string };
|
||||
|
||||
/**
|
||||
@@ -133,7 +142,7 @@ export function usePlugins() {
|
||||
queryClient.invalidateQueries({ queryKey: ['DOCK'] });
|
||||
}, [queryClient]);
|
||||
|
||||
const [steps, setSteps] = useState<string[]>([]);
|
||||
const [steps, setSteps] = useState<ActionEntry[]>([]);
|
||||
const [result, setResult] = useState<PluginActionResult | null>(null);
|
||||
const [running, setRunning] = useState<PluginVerb | null>(null);
|
||||
const abort = useRef<AbortController | null>(null);
|
||||
@@ -188,8 +197,12 @@ export function usePlugins() {
|
||||
else if (line.startsWith('data:')) data += line.slice(5).trim();
|
||||
}
|
||||
if (!data) continue;
|
||||
const parsed = JSON.parse(data) as { step?: string } & PluginActionResult;
|
||||
if (event === 'step' && parsed.step) setSteps((prev) => [...prev, parsed.step!]);
|
||||
const parsed = JSON.parse(data) as { step?: string; line?: string } & PluginActionResult;
|
||||
if (event === 'step' && parsed.step) setSteps((prev) => [...prev, { kind: 'step', text: parsed.step! }]);
|
||||
// One list, in arrival order. Two lists would be re-interleaved by render order rather than by
|
||||
// when things happened, and "which step was this output from" is the only question the log
|
||||
// exists to answer.
|
||||
else if (event === 'log' && parsed.line) setSteps((prev) => [...prev, { kind: 'log', text: parsed.line! }]);
|
||||
else if (event === 'done') setResult(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user