feat(editor): console copy button + collapsible DOM-floor console + one blue family
The first prod blue screen (finally!) also surfaced the console UX gaps: the in-app log had to be hand-copied (and arrived truncated — the flight-recorder dump missing), and the DOM-floor screen's log was a fixed block. - React console: a copy button beside the toggle (writes the full log to the clipboard, confirms in the log); stays collapsible on a fatal as in normal editing. - DOM-floor screen: its console mirrors the editor one — toggle bar (▾/▸ console) + copy, collapsible — and its content is ring + trace dump. - One blue family: both fatal screens now use the boot overlay's #1a1a2e, so every full-screen state (boot, consent, fatal, floor) is coherent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
This commit is contained in:
parent
210b079ed9
commit
875195f721
2 changed files with 66 additions and 15 deletions
|
|
@ -2373,7 +2373,7 @@ export function WasmTool({
|
||||||
{fatal && (
|
{fatal && (
|
||||||
<div
|
<div
|
||||||
data-testid="fatal-overlay"
|
data-testid="fatal-overlay"
|
||||||
className="absolute inset-0 z-[35] flex flex-col items-center justify-center gap-3 bg-[#1e3a8a] text-white"
|
className="absolute inset-0 z-[35] flex flex-col items-center justify-center gap-3 bg-[#1a1a2e] text-white"
|
||||||
>
|
>
|
||||||
<p className="font-mono text-4xl text-white/90">:(</p>
|
<p className="font-mono text-4xl text-white/90">:(</p>
|
||||||
<p className="font-mono text-sm text-white">
|
<p className="font-mono text-sm text-white">
|
||||||
|
|
@ -2403,13 +2403,28 @@ export function WasmTool({
|
||||||
visible on a fatal even with chrome hidden, for the same reason. */}
|
visible on a fatal even with chrome hidden, for the same reason. */}
|
||||||
{(!effectiveChromeHidden || fatal) && (
|
{(!effectiveChromeHidden || fatal) && (
|
||||||
<div className="absolute bottom-0 left-0 right-0 z-40">
|
<div className="absolute bottom-0 left-0 right-0 z-40">
|
||||||
<button
|
<div className="flex items-center bg-black/70">
|
||||||
className="flex items-center gap-1 bg-black/70 px-3 py-1 font-mono text-xs text-white"
|
<button
|
||||||
onClick={() => setShowLog((s) => !s)}
|
className="flex items-center gap-1 px-3 py-1 font-mono text-xs text-white"
|
||||||
>
|
onClick={() => setShowLog((s) => !s)}
|
||||||
{showLog ? <ChevronDown size={14} /> : <ChevronUp size={14} />} console
|
>
|
||||||
({logs.length})
|
{showLog ? <ChevronDown size={14} /> : <ChevronUp size={14} />} console
|
||||||
</button>
|
({logs.length})
|
||||||
|
</button>
|
||||||
|
{showLog && (
|
||||||
|
<button
|
||||||
|
className="ml-auto px-3 py-1 font-mono text-xs text-white/70 hover:text-white"
|
||||||
|
onClick={() => {
|
||||||
|
void navigator.clipboard.writeText(logs.join("\n")).then(
|
||||||
|
() => append("[console] copied to clipboard"),
|
||||||
|
() => append("[console] clipboard copy failed"),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
copy
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{showLog && (
|
{showLog && (
|
||||||
<pre className="max-h-64 overflow-auto bg-black/85 p-3 font-mono text-[11px] leading-tight text-green-300">
|
<pre className="max-h-64 overflow-auto bg-black/85 p-3 font-mono text-[11px] leading-tight text-green-300">
|
||||||
{logs.join("\n")}
|
{logs.join("\n")}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,10 @@ function buildDom(): void {
|
||||||
const root = doc.createElement("div");
|
const root = doc.createElement("div");
|
||||||
root.id = "pcbjam-fatal-screen";
|
root.id = "pcbjam-fatal-screen";
|
||||||
root.setAttribute("data-testid", "fatal-screen-dom");
|
root.setAttribute("data-testid", "fatal-screen-dom");
|
||||||
|
// Same blue as the boot overlay / React fatal screen (#1a1a2e) — one
|
||||||
|
// coherent family across every full-screen state.
|
||||||
root.style.cssText =
|
root.style.cssText =
|
||||||
"position:fixed;inset:0;z-index:2147483000;background:#1e3a8a;color:#fff;" +
|
"position:fixed;inset:0;z-index:2147483000;background:#1a1a2e;color:#fff;" +
|
||||||
"display:flex;flex-direction:column;align-items:center;justify-content:center;" +
|
"display:flex;flex-direction:column;align-items:center;justify-content:center;" +
|
||||||
"gap:12px;font-family:ui-monospace,Menlo,monospace;padding:24px;box-sizing:border-box;";
|
"gap:12px;font-family:ui-monospace,Menlo,monospace;padding:24px;box-sizing:border-box;";
|
||||||
|
|
||||||
|
|
@ -67,14 +69,48 @@ function buildDom(): void {
|
||||||
"padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;";
|
"padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;";
|
||||||
reload.onclick = () => window.location.reload();
|
reload.onclick = () => window.location.reload();
|
||||||
|
|
||||||
|
// Console block mirroring the in-editor one: toggle bar + copy, collapsible.
|
||||||
|
const consoleWrap = doc.createElement("div");
|
||||||
|
consoleWrap.style.cssText = "position:absolute;bottom:0;left:0;right:0;text-align:left;";
|
||||||
|
|
||||||
|
const bar = doc.createElement("div");
|
||||||
|
bar.style.cssText =
|
||||||
|
"display:flex;align-items:center;background:#000000b3;font-size:11px;";
|
||||||
|
|
||||||
|
const logText = () => [...ring, "", dump()].join("\n");
|
||||||
|
|
||||||
|
const toggle = doc.createElement("button");
|
||||||
|
toggle.textContent = "▾ console";
|
||||||
|
toggle.style.cssText =
|
||||||
|
"border:0;background:transparent;color:#fff;padding:4px 12px;cursor:pointer;font:inherit;";
|
||||||
|
|
||||||
|
const copy = doc.createElement("button");
|
||||||
|
copy.textContent = "copy";
|
||||||
|
copy.style.cssText =
|
||||||
|
"border:0;background:transparent;color:#ffffffb3;padding:4px 12px;cursor:pointer;" +
|
||||||
|
"font:inherit;margin-left:auto;";
|
||||||
|
copy.onclick = () => {
|
||||||
|
navigator.clipboard.writeText(logText()).then(
|
||||||
|
() => { copy.textContent = "copied ✓"; },
|
||||||
|
() => { copy.textContent = "copy failed"; },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const log = doc.createElement("pre");
|
const log = doc.createElement("pre");
|
||||||
log.style.cssText =
|
log.style.cssText =
|
||||||
"position:absolute;bottom:0;left:0;right:0;max-height:38vh;overflow:auto;" +
|
"max-height:38vh;overflow:auto;margin:0;padding:10px 12px;background:#000000d9;" +
|
||||||
"margin:0;padding:10px 12px;background:#000000d9;color:#86efac;" +
|
"color:#86efac;font-size:11px;line-height:1.35;";
|
||||||
"font-size:11px;line-height:1.35;text-align:left;";
|
log.textContent = logText();
|
||||||
log.textContent = [...ring, "", dump()].join("\n");
|
|
||||||
|
|
||||||
root.append(face, title, err, hint, reload, log);
|
toggle.onclick = () => {
|
||||||
|
const hidden = log.style.display === "none";
|
||||||
|
log.style.display = hidden ? "" : "none";
|
||||||
|
toggle.textContent = hidden ? "▾ console" : "▸ console";
|
||||||
|
};
|
||||||
|
|
||||||
|
bar.append(toggle, copy);
|
||||||
|
consoleWrap.append(bar, log);
|
||||||
|
root.append(face, title, err, hint, reload, consoleWrap);
|
||||||
doc.body.appendChild(root);
|
doc.body.appendChild(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue