From eb13be415e99534c7494cfe2c8af29439afac998 Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:28:48 -0400 Subject: [PATCH 1/5] Rebuild UI in the bench-instrument design language Ground-up visual rebuild toward the decided direction: QRH procedural grammar, Japanese density, and skeuomorphic material (bone keycap chrome over a dark charcoal chassis). - globals.css: single token spine swapped to the bench palette with keycap/recess material tokens; keycap buttons with machined depth and one orange commit action; link creator restyled as a numbered five-step operations card with a carbon-transfer output frame and READY/CAUTION/ WARNING states; samples become an indexed sidebar; viewer toolbar gets an LED plus filename-primary layout; content bodies (code, diff, csv, json) stay dark charcoal in both themes, including the vendored diff palette; self-narrating chrome labels are hidden - layout.tsx: fonts swapped to Manrope / SUSE / Spline Sans Mono via next/font, same CSS variables - viewer-shell.tsx: marketing hero, trust pills, and bento sections removed; the link creator is the first viewport in a two-column layout with the samples sidebar; real chassis footer with wordmark, zero-retention tagline, and nav links - fragment-details-disclosure.tsx: expanded by default; its unit test updated to match the intentional change No payload, codec, or renderer behavior changes. Lint, typecheck, 324 unit tests, build budgets, and the behavioral e2e suite all pass; visual snapshots are deliberately not regenerated yet. Co-Authored-By: Claude Fable 5 --- src/app/globals.css | 1853 +++++++++++------ src/app/layout.tsx | 13 +- src/components/viewer-shell.tsx | 348 ++-- .../viewer/fragment-details-disclosure.tsx | 2 +- .../fragment-details-disclosure.test.tsx | 6 +- 5 files changed, 1390 insertions(+), 832 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 4c3d819..d9c5797 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,116 +1,99 @@ @import "tailwindcss"; :root { - /* ── Surface hierarchy — warm ivory with sunset undertone ── */ - --page-bg: #faf8f4; - --surface: rgba(250, 248, 244, 0.88); - --surface-strong: rgba(255, 255, 255, 0.96); - --surface-muted: rgba(40, 30, 50, 0.03); - --surface-elevated: rgba(245, 241, 235, 0.86); - /* ── Ghost borders — warm taupe ── */ - --border: rgba(160, 140, 130, 0.14); - --border-strong: rgba(160, 140, 130, 0.24); - /* ── Text — warm charcoal ── */ - --text-primary: #2a2028; - --text-muted: #6b5e68; - --text-soft: #8a7e86; - /* ── Accent — sunset orange ── */ - --accent: #cc6633; - --accent-strong: #a8502a; - --accent-secondary: #8b5ca8; - /* ── Semantic ── */ - --success: #4a8c6f; - --warning: #b8863a; - --danger: #c44b3f; - /* ── Ambient shadows — warm-tinted ── */ - --shadow-lg: 0 26px 72px rgba(42, 32, 40, 0.07); - --shadow-md: 0 20px 40px rgba(42, 32, 40, 0.035); - /* ── Radius — sharp, architectural ── */ + /* ── Bench-instrument spine: bone chrome, hairline borders, sharp radii ── */ + --page-bg: #ebe7dd; + --surface: #e7e2d4; + --surface-strong: #f1ede1; + --surface-muted: #14110d0a; + --surface-elevated: #efeadf; + --border: #3a332a26; + --border-strong: #3a332a4d; + --text-primary: #211c15; + --text-muted: #57503f; + --text-soft: #6e6656; + --accent: #c65a1e; + --accent-strong: #a1470f; + --accent-secondary: #57503f; + --success: #25714f; + --warning: #c65a1e; + --danger: #a8352a; + --shadow-lg: none; + --shadow-md: none; --radius-xl: 2px; --radius-lg: 2px; --radius-md: 2px; --radius-sm: 2px; --duration-base: 280ms; - /* ── Code surfaces (light default) ── */ - --surface-code: #f8f6fa; - --surface-code-raised: rgba(240, 236, 244, 0.95); - --surface-code-text: #1e1a22; + /* ── Keycap material ── */ + --key-face: #f2eee2; + --key-hi: #fbf8f0; + --key-lo: #d9d3c3; + --key-foot: #aaa28e; + --key-edge: #57503f; + --key-ink: #211c15; + /* ── Recessed wells, engraved labels, LED rings ── */ + --well: #e0dbcc; + --well-shadow: #14110d2b; + --well-lip: #14110d14; + --engrave: #ffffff66; + --led-ring: #00000033; + /* ── Code surfaces: dark charcoal in BOTH themes ── */ + --surface-code: #1c1915; + --surface-code-raised: #26221b; + --surface-code-text: #e6dfcf; --surface-code-chrome-border: color-mix(in srgb, var(--surface-code-text) 12%, transparent); --surface-code-toolbar-shine: color-mix(in srgb, var(--surface-code-text) 5%, transparent); --surface-code-gutter-fg: color-mix(in srgb, var(--surface-code-text) 42%, transparent); --surface-code-gutter-border: color-mix(in srgb, var(--surface-code-text) 10%, transparent); --surface-code-active-line: color-mix(in srgb, var(--surface-code-text) 7%, transparent); --surface-code-active-gutter: color-mix(in srgb, var(--surface-code-text) 9%, transparent); - /* ── Code toolbar “is-code” button (mirrors light code chrome) ── */ --surface-code-is-code-border: var(--surface-code-chrome-border); --surface-code-is-code-bg: color-mix(in srgb, var(--surface-code-text) 6%, transparent); --surface-code-is-code-fg: var(--surface-code-text); --surface-code-selection-bg: rgba(105, 209, 221, 0.18); - /* ── CodeMirror rainbow brackets (saturated for light code bg) ── */ - --rb-0: #c06820; - --rb-1: #b08828; - --rb-2: #2a8e9a; - --rb-3: #3a8a55; - --rb-4: #4466cc; - --rb-5: #8844bb; - /* ── Print ── */ - --print-paper: #ffffff; - --print-ink: #2a2028; - --print-muted: #6b5e68; - --print-border: rgba(160, 140, 130, 0.14); -} - -.dark { - /* ── Surface hierarchy — deep dusk ── */ - --page-bg: #0e0b14; - --surface: rgba(18, 14, 24, 0.92); - --surface-strong: rgba(14, 11, 20, 0.98); - --surface-muted: rgba(180, 160, 200, 0.05); - --surface-elevated: rgba(24, 20, 32, 0.96); - /* ── Ghost borders — twilight violet ── */ - --border: rgba(100, 80, 120, 0.16); - --border-strong: rgba(100, 80, 120, 0.28); - /* ── Text — warm pearl ── */ - --text-primary: #ece6f0; - --text-muted: #b8afc2; - --text-soft: #8a7e96; - /* ── Accent — sunset ember ── */ - --accent: #e08c50; - --accent-strong: #c87a42; - --accent-secondary: #b08ed0; - /* ── Semantic ── */ - --success: #78c5a8; - --warning: #deb06a; - --danger: #e8a098; - /* ── Shadows — purple-black ── */ - --shadow-lg: 0 28px 72px rgba(14, 11, 20, 0.5); - --shadow-md: 0 20px 40px rgba(14, 11, 20, 0.25); - /* ── Radius — zero, architectural ── */ - --radius-xl: 0px; - --radius-lg: 0px; - --radius-md: 0px; - --radius-sm: 0px; - /* ── Code ── */ - --surface-code: rgba(10, 8, 16, 0.98); - --surface-code-raised: rgba(18, 14, 26, 0.95); - --surface-code-text: #ece6f0; - --surface-code-chrome-border: rgba(239, 243, 247, 0.08); - --surface-code-toolbar-shine: rgba(255, 255, 255, 0.03); - --surface-code-gutter-fg: rgba(239, 243, 247, 0.5); - --surface-code-gutter-border: rgba(239, 243, 247, 0.08); - --surface-code-active-line: rgba(255, 255, 255, 0.045); - --surface-code-active-gutter: rgba(255, 255, 255, 0.05); - --surface-code-is-code-border: rgba(239, 243, 247, 0.12); - --surface-code-is-code-bg: rgba(255, 255, 255, 0.04); - --surface-code-is-code-fg: rgba(239, 243, 247, 0.86); - --surface-code-selection-bg: rgba(105, 209, 221, 0.18); - /* ── CodeMirror rainbow brackets (bright for dark code bg) ── */ + /* ── CodeMirror rainbow brackets (bright for the always-dark code bg) ── */ --rb-0: #f08d5e; --rb-1: #efb360; --rb-2: #69d1dd; --rb-3: #80c193; --rb-4: #9eb3ff; --rb-5: #d799ff; + /* ── Print ── */ + --print-paper: #ffffff; + --print-ink: #211c15; + --print-muted: #57503f; + --print-border: #3a332a26; +} + +.dark { + --page-bg: #14110d; + --surface: #181510; + --surface-strong: #1d1913; + --surface-muted: #cfc6b40d; + --surface-elevated: #1a1611; + --border: #cfc6b422; + --border-strong: #cfc6b447; + --text-primary: #e4ddcb; + --text-muted: #b0a794; + --text-soft: #8b8271; + --accent: #e0722c; + --accent-strong: #b1541a; + --accent-secondary: #b0a794; + --success: #63bb92; + --danger: #d96a5c; + --key-face: #cfc6b4; + --key-hi: #dcd5c5; + --key-lo: #b8ae9c; + --key-foot: #6e6656; + --key-edge: #0a0806; + --key-ink: #1c1915; + --well: #0d0b08; + --well-shadow: #000000b3; + --well-lip: #00000080; + --engrave: #00000099; + --led-ring: #00000080; + /* Code surfaces stay identical in dark; see :root */ } * { @@ -126,27 +109,18 @@ body { margin: 0; color: var(--text-primary); font-family: var(--font-sans), sans-serif; - background: - radial-gradient(ellipse 60% 45% at 0% 0%, #f1e0d2, transparent 70%), - radial-gradient(ellipse 45% 45% at 100% 5%, #e6dee8, transparent 70%), - radial-gradient(ellipse 40% 35% at 65% 100%, #ede2d6, transparent 70%), - var(--page-bg); -} - -.dark body { - background: - radial-gradient(ellipse 70% 50% at 0% 0%, #2a1a10, transparent 70%), - radial-gradient(ellipse 60% 60% at 100% 20%, #1a142a, transparent 70%), - radial-gradient(ellipse 50% 40% at 50% 100%, #201018, transparent 70%), - var(--page-bg); + background: var(--page-bg); } - a { - color: inherit; + color: var(--text-primary); text-decoration: none; } +a:hover { + color: var(--accent); +} + button, input, textarea, @@ -160,12 +134,12 @@ select { } .dark ::selection { - background: rgba(224, 140, 80, 0.30); - color: #ece6f0; + background: rgba(224, 114, 44, 0.30); + color: #e4ddcb; } .font-display { - font-family: var(--font-display), serif; + font-family: var(--font-display), sans-serif; } .font-mono { @@ -243,7 +217,6 @@ select { gap: 1rem; } -.home-samples-section, .home-inspector-section, .home-stage-section { padding-top: 2.5rem; @@ -251,13 +224,51 @@ select { border-bottom: 1px solid var(--border); } -@media (min-width: 768px) { - .home-samples-section, - .home-inspector-section, - .home-stage-section { - padding-top: 4rem; - padding-bottom: 4rem; - } +/* ── Samples: compact indexed sidebar ── */ +.home-samples-section { + padding: 0; + margin: 0; + border: 0; +} + +.home-samples-section .section-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px; + border-bottom: 1px solid var(--border-strong); + padding-bottom: 8px; + margin-bottom: 0; +} + +.home-samples-section .section-header .section-kicker { + display: none; +} + +.home-samples-section .section-header h3 { + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-muted); + text-shadow: 0 1px 0 var(--engrave); + margin: 0; +} + +.home-samples-section .section-header .mono-pill { + border: 0; + background: none; + padding: 0; + font-family: var(--font-mono), monospace; + font-size: 0.58rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-soft); +} + +.home-samples-section > p { + display: none; } .site-footer { @@ -281,43 +292,31 @@ select { color: var(--text-primary); } -/* ── Bento grid — tonal separation via 1px gaps ── */ +/* ── Limits table (metadata grid) ── */ .bento-grid { display: grid; - grid-template-columns: 1fr; - gap: 1px; - background: var(--border); - border: 1px solid var(--border); - border-radius: var(--radius-xl); + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + gap: 0; + border: 1px solid var(--border-strong); + border-radius: 2px; overflow: hidden; } -@media (min-width: 768px) { - .bento-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (min-width: 1280px) { - .bento-grid { - grid-template-columns: repeat(3, 1fr); - } - - .bento-wide { - grid-column: span 2; - } -} - .bento-grid-compact { grid-template-columns: repeat(2, 1fr); } .bento-card { - background: var(--page-bg); + border: 0; + border-left: 1px solid var(--border); + border-radius: 0; + background: none; + box-shadow: none; + padding: 9px 12px !important; } -.dark .bento-card { - background: rgba(14, 11, 20, 0.98); +.bento-card:first-child { + border-left: 0; } .bento-link { @@ -343,12 +342,15 @@ select { overflow: hidden; } +/* ── Engraved label register ── */ .section-kicker { - color: var(--text-soft); + color: var(--text-muted); font-family: var(--font-mono), monospace; - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.02em; + font-size: 0.6rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + text-shadow: 0 1px 0 var(--engrave); } .mono-pill { @@ -373,10 +375,25 @@ select { background: color-mix(in srgb, var(--surface-strong) 88%, transparent); } -/* Dark data chips */ -.dark .mono-pill { - background: rgba(24, 20, 32, 0.8); - border-color: rgba(100, 80, 120, 0.22); +/* Interactive pills (e.g. theme toggle) read as keycaps */ +button.mono-pill { + font-family: var(--font-mono), monospace; + font-weight: 600; + font-size: 0.6rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--key-ink); + background: var(--key-face); + border: 1px solid var(--key-edge); + border-radius: 2px; + box-shadow: inset 0 1px 0 var(--key-hi), inset 0 -1px 0 var(--key-lo), 0 2px 0 var(--key-foot); + padding: 0.34rem 0.6rem; + cursor: pointer; +} + +button.mono-pill:active { + transform: translateY(2px); + box-shadow: inset 0 1px 0 var(--key-hi), 0 0 0 var(--key-foot); } .metric-grid { @@ -398,8 +415,11 @@ select { } .sample-link-grid { - display: grid; - gap: 0.75rem; + display: flex; + flex-direction: column; + gap: 0; + margin: 0; + counter-reset: idx; } .hero-link-card { @@ -458,18 +478,21 @@ select { } .metric-label { - color: var(--text-soft); + color: var(--text-muted); font-family: var(--font-mono), monospace; - font-size: 0.72rem; + font-size: 0.6rem; font-weight: 600; - letter-spacing: 0.08em; + letter-spacing: 0.14em; text-transform: uppercase; + text-shadow: 0 1px 0 var(--engrave); } .metric-value { margin-top: 0.45rem; - font-size: 1.1rem; - font-weight: 600; + color: var(--text-primary); + font-family: var(--font-mono), monospace; + font-size: 0.78rem; + font-weight: 500; line-height: 1.3; } @@ -489,90 +512,190 @@ select { .sample-link { display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; - min-height: 100%; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 1.25rem 1.5rem; - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); - transition: - transform var(--duration-base) ease, - border-color var(--duration-base) ease, - background-color var(--duration-base) ease, - box-shadow var(--duration-base) ease; + align-items: center; + gap: 10px; + min-height: 0; + border: 0; + border-bottom: 1px solid var(--border); + border-radius: 0; + padding: 8px 4px; + background: none; + text-decoration: none; + box-shadow: none; +} + +.sample-link::before { + counter-increment: idx; + content: counter(idx, decimal-leading-zero); + font-family: var(--font-mono), monospace; + font-size: 0.6rem; + font-weight: 600; + color: var(--text-soft); + text-shadow: 0 1px 0 var(--engrave); + flex: none; } .sample-link:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); + background: var(--surface-muted); + border-color: var(--border); } .sample-link.is-active { - border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); - background: color-mix(in srgb, var(--accent) 5%, var(--surface-strong)); + background: var(--well); + box-shadow: inset 0 2px 4px var(--well-shadow); + border-color: var(--border-strong); } -.home-generator-section { - padding-top: 2.5rem; - padding-bottom: 2.5rem; - border-bottom: 1px solid var(--border); +.sample-link .mono-pill { + display: none; } -@media (min-width: 768px) { - .home-generator-section { - padding-top: 4rem; - padding-bottom: 4rem; - } +.sample-link > div:first-child { + display: flex; + flex-direction: column-reverse; + gap: 2px; } -.home-generator-grid, -.creator-form-grid, -.creator-result-metrics, -.creator-result-actions { - display: grid; - gap: 0.8rem; +.sample-link h4 { + font-family: var(--font-sans), sans-serif; + font-size: 0.76rem; + font-weight: 600; + line-height: 1.3; + margin: 0; + color: var(--text-primary); +} + +.sample-link h4 + p { + display: none; +} + +.sample-link .section-kicker { + font-size: 0.54rem; + letter-spacing: 0.1em; +} + +.sample-link > svg { + display: none; +} + +/* ── Link creator: five-step ops card ── */ +.home-generator-section { + counter-reset: op; + padding: 0; + margin: 0; + background: var(--surface-strong); + border: 1px solid var(--border-strong); + border-radius: 2px; } .home-generator-grid { - gap: 1.5rem; + display: flex; + flex-direction: column; + gap: 0; +} + +.home-generator-grid > div:first-child { + padding: 14px 16px 18px; } .home-generator-heading { - display: grid; - gap: 0.75rem; + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + border-bottom: 1px solid var(--border-strong); + padding-bottom: 10px; + margin-bottom: 14px; +} + +.home-generator-heading .section-kicker { + display: none; +} + +.home-generator-heading h3 { + font-family: var(--font-display), sans-serif; + font-size: 1.35rem; + font-weight: 800; + letter-spacing: -0.02em; + margin: 0; + line-height: 1.1; +} + +.home-generator-heading p { + font-size: 0.78rem; + line-height: 1.45; + color: var(--text-muted); + margin: 0; + max-width: 34rem; +} + +.home-generator-heading > div { + display: flex; + flex-direction: column; + gap: 4px; +} + +.home-generator-heading > div::after { + content: "PROC LNK-01 / REV C"; + font-family: var(--font-mono), monospace; + font-size: 0.56rem; + letter-spacing: 0.16em; + color: var(--text-soft); + text-shadow: 0 1px 0 var(--engrave); } .creator-kind-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(7.4rem, 1fr)); - gap: 0.65rem; - margin-top: 1rem; + grid-template-columns: repeat(5, 1fr); + gap: 0; + border: 1px solid var(--border-strong); + border-radius: 2px; + margin: 0 0 14px; +} + +.creator-kind-grid::before { + grid-column: 1 / -1; + counter-increment: op; + content: "0" counter(op) " / FORMAT"; + font-family: var(--font-mono), monospace; + font-weight: 600; + font-size: 0.58rem; + letter-spacing: 0.14em; + color: var(--text-muted); + text-shadow: 0 1px 0 var(--engrave); + padding: 6px 8px; + border-bottom: 1px solid var(--border); } .creator-kind-card { display: flex; align-items: center; - gap: 0.7rem; - padding: 0.82rem 0.88rem; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-elevated) 90%, transparent); - color: var(--text-primary); - transition: - border-color 150ms ease, - background-color 150ms ease; + justify-content: center; + gap: 0.4rem; + border: 0; + border-left: 1px solid var(--border); + border-radius: 0; + background: none; + padding: 0.5rem 0.35rem; + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); + cursor: pointer; + box-shadow: none; } -.creator-kind-card:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); +.creator-kind-card:first-of-type { + border-left: 0; } .creator-kind-card.is-active { - border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); + background: var(--well); + color: var(--text-primary); + box-shadow: inset 0 2px 4px var(--well-shadow); } .creator-kind-card:focus-visible, @@ -583,70 +706,79 @@ select { } .creator-kind-icon { - display: grid; - place-items: center; - width: 2.2rem; - height: 2.2rem; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--surface-muted) 90%, transparent); - color: var(--accent); + display: inline-flex; + width: auto; + height: auto; + border: 0; + background: none; + color: var(--text-soft); } -.creator-kind-label { - font-size: 0.92rem; - font-weight: 600; - line-height: 1.35; - text-transform: capitalize; +.creator-kind-card.is-active .creator-kind-icon { + color: var(--text-primary); } .creator-form-grid { - margin-top: 1rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; } .creator-field { - display: grid; - gap: 0.45rem; + display: flex; + flex-direction: column; + gap: 5px; + margin: 0; } .creator-field-full { grid-column: 1 / -1; } +/* Step counters engraved onto field labels */ +.creator-form-grid .creator-field > .metric-label::before, +.creator-field-head > .metric-label::before { + counter-increment: op; + content: "0" counter(op) " /\00a0"; +} + .creator-field-head { display: flex; - flex-wrap: wrap; align-items: baseline; justify-content: space-between; - gap: 0.5rem 1rem; + gap: 10px; } .creator-field-hint { - color: var(--text-muted); - font-size: 0.86rem; - line-height: 1.5; + color: var(--text-soft); + font-size: 0.68rem; } +/* ── Recessed input slots ── */ .creator-input, .creator-textarea, -.creator-link-output { +select.creator-input { width: 100%; - border: none; - border-bottom: 2px solid transparent; - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); + font-family: var(--font-mono), monospace; + font-size: 0.78rem; color: var(--text-primary); - transition: border-color var(--duration-base) ease; + background: var(--well); + border: 1px solid var(--border-strong); + border-radius: 2px; + box-shadow: inset 0 2px 4px var(--well-shadow), inset 0 1px 0 var(--well-lip); + padding: 0.5rem 0.6rem; + outline: none; } -.creator-input:focus-visible, -.creator-textarea:focus-visible { - border-bottom-color: var(--accent); +.creator-input:focus, +.creator-textarea:focus { + border-color: var(--border-strong); + box-shadow: inset 0 2px 4px var(--well-shadow), inset 0 -2px 0 var(--accent); } -.creator-input, -.creator-textarea { - padding: 0.88rem 0.95rem; +.dark .creator-input, +.dark .creator-textarea { + color: #e4ddcb; } .creator-input::placeholder, @@ -654,163 +786,387 @@ select { color: var(--text-soft); } -.creator-input { - min-height: 3rem; -} - .creator-textarea { - min-height: 17rem; + min-height: 190px; resize: vertical; - font-family: var(--font-mono), monospace; - font-size: 0.86rem; line-height: 1.65; } .creator-form-footer { + grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; - justify-content: space-between; - gap: 0.85rem; - padding-top: 0.2rem; + gap: 10px; + border-top: 1px solid var(--border-strong); + padding-top: 12px; + margin-top: 2px; +} + +.creator-form-footer > button[type="submit"]::before { + counter-increment: op; + content: "0" counter(op) " /\00a0"; +} + +.creator-codec-row { + order: -1; + margin-right: auto; + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.creator-codec-row .artifact-action { + padding: 0.3rem 0.5rem; + font-size: 0.58rem; +} + +/* ── Output bay ── */ +.creator-result-shell { + min-width: 0; + width: 100%; + box-sizing: border-box; + border-top: 1px solid var(--border-strong); + background: var(--surface); + padding: 14px 16px 16px; + position: static; +} + +.creator-result-card { + position: relative; + width: 100%; + box-sizing: border-box; + height: auto; + border: 0; + padding: 0; + background: none; + box-shadow: none; + display: flex; + flex-direction: column; + gap: 12px; +} + +.creator-result-head { + display: flex; + align-items: baseline; + gap: 10px; + margin: 0; +} + +.creator-result-head .section-kicker { + display: none; +} + +.creator-result-head .mono-pill { + display: none; +} + +.creator-result-head h4 { + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-muted); + text-shadow: 0 1px 0 var(--engrave); + margin: 0; +} + +/* Carbon-transfer motif: generated link output only */ +.creator-link-frame { + position: relative; + background: #1c1915; + border: 1px solid #0a0806; + border-radius: 2px; + padding: 24px 12px 10px; + box-shadow: inset 0 2px 6px #000000a6; +} + +.creator-link-frame::before { + content: ""; + position: absolute; + top: 8px; + left: 12px; + right: 12px; + height: 4px; + background: repeating-linear-gradient(90deg, #cfc6b4 0 9px, transparent 9px 16px); + opacity: 0.55; +} + +.creator-link-frame .metric-label { + color: #8b8271; + text-shadow: 0 1px 0 #000; +} + +.creator-link-output { + width: 100%; + box-sizing: border-box; + min-height: 7.75rem; + margin-top: 4px; + padding: 0; + border: 0; + border-radius: 0; + resize: none; + outline: none; + background: none; + color: #e6dfcf; + font-family: var(--font-mono), monospace; + font-size: 0.72rem; + line-height: 1.5; + word-break: break-all; +} + +.creator-result-card:has(.creator-link-output)::after { + content: "ENCODED\00a0\00b7\00a0NO UPLOAD"; + position: absolute; + top: -4px; + right: 4px; + transform: rotate(-7deg); + font-family: var(--font-mono), monospace; + font-size: 0.6rem; + font-weight: 600; + letter-spacing: 0.18em; + color: var(--danger); + border: 2px solid currentColor; + border-radius: 2px; + padding: 0.28rem 0.55rem; + opacity: 0.7; + pointer-events: none; +} + +.creator-result-metrics { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0; + border: 1px solid var(--border-strong); + border-top: 2px dashed var(--border-strong); + border-radius: 2px; +} + +.creator-result-metrics .metric-card { + border: 0; + border-left: 1px solid var(--border); + border-radius: 0; + background: none; + padding: 8px 10px; + box-shadow: none; +} + +.creator-result-metrics .metric-card:first-child { + border-left: 0; } -.creator-codec-row { +.creator-result-actions { display: flex; flex-wrap: wrap; - align-items: center; - gap: 0.4rem; + gap: 8px; } -.creator-result-shell { - min-width: 0; +.creator-result-note { + border-top: 1px solid var(--border); + padding-top: 10px; } -.creator-result-card { - display: grid; - gap: 1rem; - height: 100%; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface-elevated) 80%, transparent); - padding: 1.5rem; +.creator-result-note p { + margin: 0; } -.creator-result-head { +.creator-result-note p + p { + margin-top: 4px; + font-size: 0.76rem; + color: var(--text-muted); +} + +/* READY / NOTE / CAUTION / WARNING states */ +.creator-empty-state { display: flex; - flex-wrap: wrap; - align-items: flex-start; - justify-content: space-between; - gap: 0.75rem; + align-items: center; + gap: 9px; + border: 1px solid var(--border-strong); + border-radius: 2px; + padding: 12px 14px; + background: none; } -.creator-link-frame, -.creator-result-note, -.creator-empty-state, -.creator-error-state, -.creator-warning-state { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 92%, transparent); - padding: 0.95rem 1rem; +.creator-empty-state::before { + content: ""; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--success); + box-shadow: inset 0 -1px 1px #00000073, 0 0 0 1px var(--led-ring); + flex: none; } -.creator-link-output { - min-height: 7.75rem; - margin-top: 0.6rem; - padding: 0; - border: none; - border-radius: 0; - resize: none; - background: transparent; +.creator-empty-state::after { + content: "READY"; + order: 2; font-family: var(--font-mono), monospace; - font-size: 0.76rem; - line-height: 1.65; - word-break: break-all; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.16em; + color: var(--success); + text-shadow: 0 1px 0 var(--engrave); } -.creator-result-actions { - grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); +.creator-empty-state p { + order: 3; + margin: 0; + font-size: 0.74rem; + color: var(--text-soft); } .creator-inline-status { - margin-top: 0.7rem; - color: var(--warning); - font-size: 0.86rem; - line-height: 1.55; + display: flex; + gap: 6px; + margin-top: 6px; + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-muted); +} + +.creator-inline-status::before { + content: "NOTE /"; + font-weight: 600; + color: var(--text-soft); } +.creator-warning-state, .artifact-share-warning { + display: flex; + align-items: baseline; + gap: 8px; margin: 0; - padding: 0.85rem 1rem; - border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--warning) 8%, var(--surface-strong)); - color: var(--warning); - font-size: 0.86rem; - line-height: 1.55; + border: 1px solid var(--accent); + border-radius: 2px; + padding: 9px 12px; + font-size: 0.74rem; + color: var(--text-primary); + background: none; +} + +.creator-warning-state::before, +.artifact-share-warning::before { + content: "CAUTION"; + font-family: var(--font-mono), monospace; + font-size: 0.6rem; + font-weight: 600; + letter-spacing: 0.14em; + color: var(--accent); + flex: none; } .creator-error-state { - color: var(--danger); - font-size: 0.9rem; - line-height: 1.55; + display: flex; + align-items: baseline; + gap: 8px; + border: 1px solid var(--danger); + border-radius: 2px; + padding: 9px 12px; + font-size: 0.74rem; + color: var(--text-primary); + background: none; } -.creator-warning-state { - color: var(--warning); - font-size: 0.9rem; - line-height: 1.55; - border-color: color-mix(in srgb, var(--warning) 35%, var(--border)); - background: color-mix(in srgb, var(--warning) 8%, var(--surface-strong)); +.creator-error-state::before { + content: "WARNING"; + font-family: var(--font-mono), monospace; + font-size: 0.6rem; + font-weight: 600; + letter-spacing: 0.14em; + color: var(--danger); + flex: none; } .artifact-first-layout { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 0; + display: flex; + flex-direction: column; + gap: 12px; scroll-margin-top: 4.5rem; } -/* ── Compact toolbar ribbon ── */ +/* ── Instrument toolbar bar: LED + filename + keycaps ── */ .artifact-toolbar-bar { display: flex; flex-wrap: wrap; align-items: center; - justify-content: space-between; - gap: 0.75rem; - padding: 0.75rem 0; - border-bottom: 1px solid var(--border); + gap: 8px 12px; + border: 1px solid var(--border-strong); + border-radius: 2px; + background: var(--surface-strong); + padding: 8px 12px; + margin: 0; } .artifact-toolbar-left { display: flex; - flex-wrap: wrap; align-items: center; - gap: 0.75rem 1.5rem; + gap: 10px; + min-width: 0; + margin-right: auto; +} + +.artifact-toolbar-left > .mono-pill { + display: none; +} + +.artifact-toolbar-left::before { + content: ""; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--success); + box-shadow: inset 0 -1px 1px #00000073, 0 0 0 1px var(--led-ring); + flex: none; +} + +/* nth-of-type counts the hidden status mono-pill span; keep it in the DOM */ +.artifact-toolbar-left > span:nth-of-type(2) { + font-family: var(--font-mono), monospace; + font-size: 0.82rem; + font-weight: 600; + color: var(--text-primary); + letter-spacing: 0.01em; +} + +.artifact-toolbar-left > span:nth-of-type(3) { + font-family: var(--font-mono), monospace; + font-size: 0.6rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-soft); } -/* ── Editorial content area ── */ .artifact-content-section { - padding-top: 2rem; - padding-bottom: 2rem; + margin: 0; + padding-top: 0; + padding-bottom: 0; } -@media (min-width: 768px) { - .artifact-content-section { - padding-top: 3rem; - padding-bottom: 3rem; - } +.artifact-content-section .section-kicker { + display: none; +} + +.artifact-content-section h3 { + font-family: var(--font-display), sans-serif; + font-size: 1.3rem; + font-weight: 800; + letter-spacing: -0.02em; + line-height: 1.15; + margin: 0; } .artifact-selector-row { display: flex; - gap: 0.75rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - scroll-padding-inline: 1rem; + gap: 0; overflow-x: auto; - border-bottom: 1px solid var(--border); + border: 1px solid var(--border-strong); + border-radius: 2px; + background: var(--surface); + scrollbar-width: thin; scroll-snap-type: x proximity; } @@ -825,25 +1181,22 @@ select { .artifact-switcher { display: flex; - align-items: flex-start; - gap: 0.85rem; - flex: 0 0 min(18rem, calc(100vw - 5rem)); - min-height: 4.5rem; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 0.85rem 0.95rem; - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); + align-items: center; + gap: 8px; + flex: 0 0 auto; + min-width: 150px; + border: 0; + border-left: 1px solid var(--border); + border-radius: 0; + padding: 7px 12px; + background: none; + cursor: pointer; + box-shadow: none; scroll-snap-align: start; - transition: - transform var(--duration-base) ease, - border-color var(--duration-base) ease, - background-color var(--duration-base) ease, - box-shadow var(--duration-base) ease; } -.artifact-switcher:hover { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); - background: color-mix(in srgb, var(--accent) 3%, var(--surface-elevated)); +.artifact-switcher:first-child { + border-left: 0; } .artifact-switcher-content { @@ -851,13 +1204,8 @@ select { } .artifact-switcher.is-active { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent); -} - -.dark .artifact-switcher.is-active { - box-shadow: inset 3px 0 0 var(--accent); + background: var(--well); + box-shadow: inset 0 2px 4px var(--well-shadow); } .artifact-switcher:focus-visible { @@ -865,43 +1213,55 @@ select { outline-offset: 2px; } -.artifact-switcher-icon { - display: grid; - place-items: center; - width: 2.35rem; - height: 2.35rem; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: color-mix(in srgb, var(--surface-muted) 90%, transparent); - color: var(--accent); +.artifact-switcher-title { + font-family: var(--font-sans), sans-serif; + font-size: 0.74rem !important; + font-weight: 600; } -.artifact-switcher.is-active .artifact-switcher-icon { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 8%, var(--surface-strong)); +.artifact-switcher-meta { + margin-top: 1px !important; + font-size: 0.6rem !important; } -.viewer-frame { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface-strong) 97%, transparent); - overflow: hidden; +.artifact-switcher-meta .section-kicker { + display: none; +} + +.artifact-switcher-meta span { + font-family: var(--font-mono), monospace; + color: var(--text-soft); +} + +.artifact-switcher-icon { + display: inline-flex; + width: auto; + height: auto; + flex-shrink: 0; + border: 0; + background: none; + color: var(--text-soft); } -.dark .viewer-frame { - background: color-mix(in srgb, var(--surface-strong) 96%, #0b1016 4%); - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02); +.viewer-frame { + border: 1px solid var(--border-strong); + border-radius: 2px; + background: var(--surface-strong); + box-shadow: none; + margin-top: 8px !important; + overflow: hidden; } .viewer-frame-primary { min-height: 0; } +/* Content bodies: dark charcoal in BOTH themes; markdown stays on bone */ .artifact-preview { min-height: 0; padding: 1.5rem; - background: color-mix(in srgb, var(--surface-strong) 96%, transparent); + background: #1c1915; + color: #e6dfcf; } @media (min-width: 768px) { @@ -910,55 +1270,61 @@ select { } } -.dark .artifact-preview { - background: color-mix(in srgb, var(--surface-elevated) 96%, transparent); +.artifact-preview.is-markdown { + padding: 0.95rem; + background: var(--surface-strong); + color: var(--text-primary); } .artifact-preview pre { margin: 0; white-space: pre-wrap; word-break: break-word; - color: var(--text-muted); + color: inherit; font-family: var(--font-mono), monospace; font-size: 0.86rem; line-height: 1.7; } -.artifact-preview.is-markdown { - padding: 0.95rem; -} - .viewer-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; - gap: 0.55rem; + gap: 8px; } +/* ── Keycap actions ── */ .artifact-action { display: inline-flex; align-items: center; justify-content: center; - gap: 0.45rem; - min-height: 2.7rem; - padding: 0.58rem 0.82rem; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - background: color-mix(in srgb, var(--surface-elevated) 88%, transparent); - color: var(--text-primary); + gap: 0.4rem; + min-height: 0; + padding: 0.4rem 0.7rem; + border: 1px solid var(--key-edge); + border-radius: 2px; + background: var(--key-face); + color: var(--key-ink); font-family: var(--font-mono), monospace; - font-size: 0.74rem; - font-weight: 500; - letter-spacing: 0.01em; - transition: - border-color 150ms ease, - background-color 150ms ease; + font-size: 0.64rem; + font-weight: 600; + letter-spacing: 0.09em; + text-transform: uppercase; + box-shadow: inset 0 1px 0 var(--key-hi), inset 0 -1px 0 var(--key-lo), 0 2px 0 var(--key-foot); + transition: transform 60ms, box-shadow 60ms, filter 60ms; + cursor: pointer; } .artifact-action:hover { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 5%, var(--surface-elevated)); + filter: brightness(1.04); + background: var(--key-face); + border-color: var(--key-edge); +} + +.artifact-action:active { + transform: translateY(2px); + box-shadow: inset 0 1px 0 var(--key-hi), inset 0 -1px 0 var(--key-lo), 0 0 0 var(--key-foot); } .artifact-action:focus-visible { @@ -967,24 +1333,46 @@ select { } .artifact-action.is-primary { - border-color: transparent; - background: linear-gradient(135deg, var(--accent), var(--accent-strong)); - color: #ffffff; + background: var(--well); + color: var(--text-primary); + border-color: var(--key-edge); + box-shadow: inset 0 2px 4px var(--well-shadow), 0 1px 0 var(--key-foot); +} + +.artifact-action.is-primary:hover { + background: var(--well); + box-shadow: inset 0 2px 4px var(--well-shadow), 0 1px 0 var(--key-foot); } .dark .artifact-action.is-primary { + color: #e4ddcb; +} + +/* The one orange COMMIT key: Generate link + viewer Download */ +.creator-form-footer > button[type="submit"].artifact-action, +.viewer-toolbar > button.artifact-action.is-primary:last-child { background: var(--accent); - color: #1a0e08; + color: #fff6ea; + border-color: var(--accent-strong); + box-shadow: inset 0 1px 0 #ffffff4d, inset 0 -1px 0 #00000026, 0 2px 0 var(--accent-strong); } -.artifact-action.is-primary:hover { - box-shadow: 0 14px 30px color-mix(in srgb, var(--accent) 20%, transparent); +.creator-form-footer > button[type="submit"].artifact-action { + padding: 0.55rem 1.1rem; + font-size: 0.72rem; +} + +.creator-form-footer > button[type="submit"].artifact-action:active, +.viewer-toolbar > button.artifact-action.is-primary:last-child:active { + transform: translateY(2px); + box-shadow: inset 0 1px 0 #ffffff4d, 0 0 0 var(--accent-strong); } .artifact-action.is-code { border-color: var(--surface-code-is-code-border); background: var(--surface-code-is-code-bg); color: var(--surface-code-is-code-fg); + box-shadow: none; } .markdown-document { @@ -1013,7 +1401,7 @@ select { .markdown-article h6 { margin-top: 1.5em; color: var(--text-primary); - font-family: var(--font-display), serif; + font-family: var(--font-display), sans-serif; font-weight: 600; line-height: 1.08; letter-spacing: -0.04em; @@ -1088,7 +1476,7 @@ select { border-radius: 0; background: color-mix(in srgb, var(--surface-elevated) 60%, transparent); color: var(--text-primary); - font-family: var(--font-display), serif; + font-family: var(--font-display), sans-serif; font-style: italic; } @@ -1106,28 +1494,19 @@ select { font-size: 0.88em; } +/* Markdown code blocks: dark charcoal in both themes */ .markdown-code-frame { overflow: hidden; - border: 1px solid color-mix(in srgb, var(--border-strong) 88%, transparent); - border-radius: var(--radius-lg); - background: linear-gradient(180deg, color-mix(in srgb, var(--surface-code-raised) 84%, transparent), color-mix(in srgb, var(--surface-code) 100%, transparent)); - box-shadow: var(--shadow-md); -} - -.dark .markdown-code-frame { - border-color: rgba(100, 80, 120, 0.18); + border: 1px solid #0a0806; + border-radius: 2px; + background: #1c1915; } .markdown-mermaid-frame { overflow: hidden; - border: 1px solid color-mix(in srgb, var(--border-strong) 88%, transparent); - border-radius: var(--radius-lg); - background: var(--surface-primary); - box-shadow: var(--shadow-md); -} - -.dark .markdown-mermaid-frame { - border-color: rgba(100, 80, 120, 0.18); + border: 1px solid var(--border-strong); + border-radius: 2px; + background: var(--surface-strong); } .mermaid-container { @@ -1170,12 +1549,8 @@ select { justify-content: space-between; gap: 0.75rem; padding: 0.8rem 1rem; - border-bottom: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent 58%); -} - -.dark .markdown-code-head { - background: linear-gradient(90deg, rgba(224, 140, 80, 0.05), transparent 58%); + border-bottom: 1px solid #cfc6b41f; + background: #26221b; } .markdown-code-chip, @@ -1220,22 +1595,6 @@ select { box-shadow var(--duration-base) ease; } -.code-renderer-toolbar { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - padding: 0.9rem 1rem; - border-bottom: 1px solid var(--surface-code-chrome-border); - background: - linear-gradient(180deg, var(--surface-code-toolbar-shine), transparent), - var(--surface-code-raised); - transition: - background var(--duration-base) ease, - border-color var(--duration-base) ease; -} - .code-renderer-meta { display: flex; flex-wrap: wrap; @@ -1243,14 +1602,18 @@ select { gap: 0.6rem; } -.code-renderer-language-pill { - border-color: var(--surface-code-chrome-border) !important; - background: color-mix(in srgb, var(--surface-code-text) 6%, transparent) !important; - color: var(--surface-code-text) !important; +/* No self-narrating renderer chrome */ +.code-renderer-language-pill, +.code-renderer-readonly-label { + display: none; +} + +.diff-renderer-shell[data-diff-state="rich"] .code-renderer-meta { + display: none; } -.code-renderer-readonly-label { - color: color-mix(in srgb, var(--surface-code-text) 56%, transparent) !important; +.json-renderer-toolbar > .mono-pill { + display: none; } .code-renderer-host { @@ -1273,31 +1636,109 @@ select { gap: 1rem; } +/* Dark instrument toolbars over charcoal content */ +.code-renderer-toolbar, .diff-renderer-toolbar, -.csv-renderer-toolbar, .json-renderer-toolbar { display: flex; flex-wrap: wrap; align-items: center; - justify-content: space-between; - gap: 0.75rem; + justify-content: flex-end; + gap: 8px; + background: #26221b; + border-bottom: 1px solid #cfc6b41f; + padding: 7px 10px; +} + +.csv-renderer-shell { + background: #1c1915; + color: #e6dfcf; +} + +.csv-renderer-toolbar { + display: flex; + flex-wrap: wrap; + gap: 14px; + padding: 8px 12px; + background: #26221b; + border-bottom: 1px solid #cfc6b426; +} + +.csv-renderer-toolbar .mono-pill { + border: 0; + background: none; + padding: 0; + font-family: var(--font-mono), monospace; + font-size: 0.58rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: #8b8271; + display: inline-flex; + gap: 0.35rem; + align-items: center; } .diff-view-toggle { display: flex; flex-wrap: wrap; - gap: 0.6rem; + gap: 0; +} + +.diff-view-toggle .artifact-action { + border-radius: 0; +} + +.diff-view-toggle .artifact-action:first-child { + border-radius: 2px 0 0 2px; +} + +.diff-view-toggle .artifact-action:last-child { + border-radius: 0 2px 2px 0; + margin-left: -1px; } .diff-mobile-note { min-height: 2.1rem; } +/* Diff bodies: dark charcoal in BOTH themes (also supplies the vendor palette) */ .diff-renderer-frame { overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); + border: 1px solid #0a0806; + border-radius: 2px; + background: #1c1915; +} + +.diff-tailwindcss-wrapper, +.diff-renderer-frame [data-theme="light"], +.diff-renderer-frame [data-theme="dark"] { + color-scheme: dark; + color: #e6dfcf; + --diff-bgColor-default: #1c1915; + --diff-add-content--: #1e2f24; + --diff-del-content--: #3a201b; + --diff-add-content-highlight--: #2c4a38; + --diff-del-content-highlight--: #57271f; + --diff-border--: #cfc6b41f; + --diff-add-lineNumber--: #22352b; + --diff-del-lineNumber--: #41231e; + --diff-plain-content--: #1c1915; + --diff-expand-content--: #181510; + --diff-plain-lineNumber-color--: #8b8271; + --diff-expand-lineNumber-color--: #6e6656; + --diff-plain-lineNumber--: #181510; + --diff-expand-lineNumber--: #14110d; + --diff-hunk-content--: #26221b; + --diff-hunk-content-color--: #b0a794; + --diff-hunk-lineNumber--: #26221b; + --diff-add-widget--: #26221b; + --diff-add-widget-color--: #b0a794; + --diff-empty-content--: #181510; +} + +.diff-tailwindcss-wrapper { + font-family: var(--font-mono), monospace; + font-size: 0.74rem; } .patch-bundle-shell { @@ -1310,23 +1751,37 @@ select { flex-direction: column; gap: 0.65rem; padding: 1rem; - border-right: 1px solid var(--border); - background: color-mix(in srgb, var(--surface) 96%, transparent); + border-right: 1px solid #cfc6b41f; + background: #181510; } .patch-bundle-link { display: grid; gap: 0.45rem; - border: 1px solid var(--border); - border-radius: var(--radius-md); + border: 1px solid #cfc6b426; + border-radius: 2px; padding: 0.85rem 0.9rem; - background: color-mix(in srgb, var(--surface-strong) 90%, transparent); + background: #26221b; + color: #e6dfcf; text-align: left; - } +} -.patch-bundle-link.is-active { - border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); - background: color-mix(in srgb, var(--accent) 6%, var(--surface-strong)); +.patch-bundle-link.is-active, +.patch-bundle-link.is-active:hover { + background: #0d0b08; + border-color: #cfc6b447; + box-shadow: inset 0 2px 4px #000c; +} + +.patch-bundle-link .mono-pill { + border: 0; + background: none; + padding: 0; + font-family: var(--font-mono), monospace; + font-size: 0.56rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #8b8271; } .patch-bundle-files { @@ -1335,7 +1790,7 @@ select { } .patch-file-section + .patch-file-section { - border-top: 1px solid var(--border); + border-top: 1px solid #cfc6b41f; } .patch-file-header { @@ -1344,7 +1799,10 @@ select { align-items: center; justify-content: space-between; gap: 0.75rem; - padding: 1rem 1rem 0.8rem; + padding: 8px 12px; + background: #26221b; + border-bottom: 1px solid #cfc6b41f; + color: #e6dfcf; } .patch-file-header h4 { @@ -1353,19 +1811,22 @@ select { font-weight: 600; } -.diff-renderer-frame [data-theme="light"] { - --diff-bgColor-default: color-mix(in srgb, var(--surface-strong) 96%, white 4%); -} - -.diff-renderer-frame [data-theme="dark"] { - --diff-bgColor-default: #0f1721; +.patch-file-header .mono-pill { + border: 0; + background: none; + padding: 0; + font-family: var(--font-mono), monospace; + font-size: 0.58rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #8b8271; } .csv-table-wrap { overflow: auto; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 92%, transparent); + border: 0; + border-radius: 0; + background: transparent; max-height: min(68vh, 42rem); } @@ -1374,12 +1835,12 @@ select { min-width: 36rem; border-collapse: separate; border-spacing: 0; + font-family: var(--font-mono), monospace; + font-size: 0.72rem; } .csv-table th, .csv-table td { - padding: 0.85rem 1rem; - border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; } @@ -1388,17 +1849,21 @@ select { position: sticky; top: 0; z-index: 1; - background: color-mix(in srgb, var(--surface) 96%, white 4%); - color: var(--text-soft); + background: #26221b; + color: #b0a794; font-family: var(--font-mono), monospace; - font-size: 0.72rem; + font-size: 0.6rem; font-weight: 600; - letter-spacing: 0.08em; + letter-spacing: 0.12em; text-transform: uppercase; + padding: 6px 12px; + border-bottom: 1px solid #cfc6b433; } .csv-table td { - color: var(--text-muted); + padding: 5px 12px; + border-bottom: 1px solid #cfc6b414; + color: #e6dfcf; white-space: nowrap; } @@ -1407,18 +1872,19 @@ select { } .artifact-empty-state { - border: 1px solid color-mix(in srgb, var(--danger) 38%, var(--border)); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--danger) 8%, var(--surface-strong)); - padding: 1rem 1.1rem; - color: var(--text-primary); + border: 1px solid var(--danger); + border-radius: 2px; + background: none; + padding: 10px 12px; + font-size: 0.74rem; + color: inherit; } .json-tree-shell { overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-strong) 94%, transparent); + border: 1px solid #cfc6b41f; + border-radius: 2px; + background: transparent; padding: 0.75rem 0; } @@ -1450,7 +1916,13 @@ select { gap: 0.65rem; min-height: 2rem; padding-right: 1rem; - color: var(--text-primary); + color: #e6dfcf; +} + +.json-leaf-row { + font-family: var(--font-mono), monospace; + font-size: 0.74rem; + line-height: 1.55; } .json-summary { @@ -1472,16 +1944,16 @@ select { .json-key { font-family: var(--font-mono), monospace; - color: var(--text-soft); + color: #cfc6b4; + margin-right: 0.5em; } .json-key::after { content: ":"; - margin-left: 0.2rem; } .json-structure { - color: var(--text-muted); + color: #b0a794; } .json-value { @@ -1489,19 +1961,20 @@ select { } .json-string { - color: #7fd7a4; + color: #9ccfae; } .json-number { - color: #f29c8f; + color: #e0955e; } .json-boolean { - color: #69d1dd; + color: #e0955e; } .json-null { - color: #9eb3ff; + color: #8b8271; + font-style: italic; } .json-children { @@ -1576,37 +2049,42 @@ select { .artifact-meta-value { margin-top: 0.45rem; - font-size: 0.98rem; - font-weight: 600; + color: var(--text-primary); + font-family: var(--font-mono), monospace; + font-size: 0.78rem; + font-weight: 500; line-height: 1.45; word-break: break-word; } +/* ── Fragment inspector ── */ .artifact-disclosure { - margin-top: 0.75rem; + margin-top: 12px; overflow: hidden; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: color-mix(in srgb, var(--surface) 94%, transparent); + border: 1px solid var(--border-strong); + border-radius: 2px; + background: none; } .artifact-disclosure-summary { display: flex; - align-items: center; + align-items: baseline; justify-content: space-between; - gap: 1rem; - padding: 0.9rem 1rem; + gap: 10px; + padding: 9px 12px; cursor: pointer; list-style: none; } .artifact-disclosure-summary-copy { - display: grid; - gap: 0.24rem; + display: flex; + align-items: baseline; + gap: 12px; min-width: 0; } .artifact-disclosure-title { + font-size: 0.74rem !important; line-height: 1.45; } @@ -1622,36 +2100,62 @@ select { .artifact-disclosure-summary::after { content: "+"; color: var(--text-soft); - font-size: 1.15rem; + font-family: var(--font-mono), monospace; + font-size: 0.8rem; line-height: 1; - transition: transform var(--duration-base) ease; } .artifact-disclosure[open] .artifact-disclosure-summary::after { - transform: rotate(45deg); + content: "\2212"; +} + +.artifact-disclosure[open] .artifact-disclosure-summary { + border-bottom: 1px solid var(--border); } .artifact-disclosure-body { - display: grid; - gap: 0.9rem; - padding: 0 1rem 1rem; - border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 10px; + padding: 10px 12px 12px; +} + +.artifact-disclosure-status { + margin: 0; + font-size: 0.74rem !important; } .artifact-disclosure-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 0; + border: 1px solid var(--border); + border-radius: 2px; +} + +.artifact-disclosure-grid .metric-card { + border: 0; + border-left: 1px solid var(--border); + border-radius: 0; + background: none; + box-shadow: none; + padding: 8px 10px; +} + +.artifact-disclosure-grid .metric-card:first-child { + border-left: 0; } .artifact-hash-preview { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: color-mix(in srgb, var(--surface-muted) 94%, transparent); - padding: 0.95rem 1rem; + border: 0; + border-top: 1px dashed var(--border-strong); + border-radius: 0; + background: none; + padding: 8px 0 0; } .artifact-hash-preview-code { + margin-top: 6px !important; margin-bottom: 0; } @@ -1673,13 +2177,6 @@ select { gap: 1.5rem; } - .panel, - .viewer-frame, - .artifact-disclosure { - border-radius: var(--radius-md); - box-shadow: 0 12px 28px rgba(28, 28, 24, 0.06); - } - .mono-pill { gap: 0.3rem; padding: 0.34rem 0.62rem; @@ -1693,8 +2190,6 @@ select { } .metric-grid, - .artifact-disclosure-grid, - .sample-link-grid, .artifact-first-layout { gap: 0.6rem; } @@ -1709,49 +2204,11 @@ select { font-size: 0.95rem; } - .hero-link-card, - .sample-link { - gap: 0.65rem; - padding: 0.78rem 0.82rem; - } - - .home-generator-grid, - .creator-form-grid, - .creator-result-metrics, - .creator-result-actions { + .hero-link-card { gap: 0.65rem; - } - - .creator-kind-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - margin-top: 0.85rem; - } - - .creator-kind-card, - .creator-result-card { - padding: 0.8rem; - } - - .creator-kind-label { - font-size: 0.88rem; - } - - .creator-field-hint, - .creator-inline-status, - .creator-error-state { - font-size: 0.82rem; - line-height: 1.45; - } - - .creator-input, - .creator-textarea { padding: 0.78rem 0.82rem; } - .creator-textarea { - min-height: 14rem; - } - .creator-link-output { min-height: 8.5rem; font-size: 0.72rem; @@ -1782,25 +2239,10 @@ select { } .artifact-selector-row { - gap: 0.5rem; margin-top: 0.65rem; - padding-top: 0.65rem; - padding-bottom: 0.1rem; scroll-padding-inline: 0.7rem; } - .artifact-switcher { - gap: 0.58rem; - flex-basis: min(12.75rem, calc(100vw - 2.2rem)); - min-height: 3.75rem; - padding: 0.68rem 0.74rem; - } - - .artifact-switcher-icon { - width: 1.9rem; - height: 1.9rem; - } - .artifact-switcher-title { display: -webkit-box; overflow: hidden; @@ -2043,7 +2485,6 @@ select { .artifact-meta-value, .metric-value { margin-top: 0.34rem; - font-size: 0.94rem; line-height: 1.32; } @@ -2107,21 +2548,233 @@ select { } } -@media (min-width: 768px) { - .home-generator-grid { - grid-template-columns: minmax(0, 1.1fr) minmax(20rem, 0.9fr); - align-items: start; + + +/* ── App chassis: header strip, dark footer, two-column homepage ── */ +.ar-page-pad { + max-width: 1280px; + margin-inline: auto; + padding: 14px 20px 40px; +} + +.ar-site-header { + background: var(--surface-strong); + border-bottom: 1px solid var(--border-strong); +} + +.ar-header-inner { + display: flex; + align-items: center; + gap: 14px; + max-width: 1280px; + margin-inline: auto; + padding: 9px 20px; +} + +.ar-wordmark { + font-family: var(--font-mono), monospace; + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: lowercase; + color: var(--text-primary); +} + +.ar-tagline { + font-family: var(--font-mono), monospace; + font-size: 0.56rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-soft); + text-shadow: 0 1px 0 var(--engrave); +} + +.ar-header-spacer { + flex: 1; +} + +.ar-nav-link { + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-muted); +} + +.ar-nav-link:hover { + color: var(--accent); +} + +/* Footer: real dark chassis in BOTH themes */ +.ar-site-footer { + background: #14110d; + color: #b8ae9c; +} + +.ar-footer-inner { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 10px 20px; + max-width: 1280px; + margin-inline: auto; + padding: 16px 20px; +} + +.ar-site-footer .ar-wordmark { + color: #dcd5c5; +} + +.ar-site-footer .ar-tagline { + color: #8b8271; + text-shadow: 0 1px 0 #00000099; +} + +.ar-site-footer a, +.ar-site-footer .ar-nav-link { + color: #b8ae9c; +} + +.ar-site-footer a:hover, +.ar-site-footer .ar-nav-link:hover { + color: #dcd5c5; +} + +.ar-footer-nav { + display: flex; + flex-wrap: wrap; + gap: 14px; +} + +.ar-footer-strip { + border-top: 1px solid #cfc6b41f; +} + +.ar-footer-strip > * , +.ar-footer-strip { + color: #6e6656; + font-family: var(--font-mono), monospace; + font-size: 0.56rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.ar-footer-strip-inner { + max-width: 1280px; + margin-inline: auto; + padding: 8px 20px; +} + +/* Decode fault placard (viewer error promotion) */ +.ar-fault-placard { + display: flex; + align-items: baseline; + gap: 9px; + border: 1px solid var(--danger); + border-radius: 2px; + padding: 12px 14px; + background: none; +} + +.ar-fault-placard::before { + content: ""; + align-self: center; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--danger); + box-shadow: inset 0 -1px 1px #00000073, 0 0 0 1px var(--led-ring); + flex: none; +} + +.ar-fault-label { + font-family: var(--font-mono), monospace; + font-size: 0.62rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--danger); + text-shadow: 0 1px 0 var(--engrave); +} + +.ar-fault-placard p { + margin: 0; + font-size: 0.74rem; + color: var(--text-primary); +} + +/* Homepage: creator + samples sidebar */ +.ar-two-col { + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + gap: 0 20px; + align-items: start; +} + +.ar-samples-col { + border-left: 1px solid var(--border-strong); + padding-left: 16px; + position: sticky; + top: 14px; +} + +@media (max-width: 880px) { + .ar-two-col { + grid-template-columns: 1fr; } + .ar-samples-col { + border-left: 0; + padding-left: 0; + border-top: 1px solid var(--border-strong); + padding-top: 14px; + margin-top: 16px; + position: static; + } +} + +@media (max-width: 720px) { .creator-form-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: 1fr; + gap: 10px; } - .sample-link-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .creator-kind-grid { + grid-template-columns: repeat(3, 1fr); + } + + .creator-kind-card { + border-top: 1px solid var(--border); + } + + .home-generator-grid > div:first-child { + padding: 10px 10px 14px; + } + + .creator-result-shell { + padding: 10px 10px 12px; + } + + .creator-result-metrics { + grid-template-columns: 1fr 1fr; + } + + .artifact-toolbar-bar { + padding: 6px 8px; + } + + .bento-grid { + grid-template-columns: 1fr 1fr; } -} + .ar-page-pad { + padding: 10px 10px 32px !important; + } +} @media print { @page { @@ -2170,7 +2823,7 @@ select { body[data-print-mode="markdown"] .markdown-print-heading h1 { margin: 0.45rem 0 0; color: var(--print-ink); - font-family: var(--font-display), serif; + font-family: var(--font-display), sans-serif; font-size: 1.9rem; line-height: 1.1; letter-spacing: -0.04em; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 03556de..2f8b3fd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,25 +1,26 @@ import type { Metadata } from "next"; -import { Fraunces, IBM_Plex_Mono, IBM_Plex_Sans } from "next/font/google"; +import { Manrope, Spline_Sans_Mono, SUSE } from "next/font/google"; import type { ReactNode } from "react"; import { getCanonicalSiteUrl, getMetadataBase } from "@/lib/site/canonical-base"; import "./globals.css"; -const display = Fraunces({ +const display = Manrope({ subsets: ["latin"], variable: "--font-display", - weight: ["500", "600", "700"], + weight: ["500", "600", "700", "800"], }); -const sans = IBM_Plex_Sans({ +const sans = SUSE({ subsets: ["latin"], variable: "--font-sans", - weight: ["400", "500", "600", "700"], + weight: ["400", "500", "600"], }); -const mono = IBM_Plex_Mono({ +const mono = Spline_Sans_Mono({ subsets: ["latin"], variable: "--font-mono", weight: ["400", "500", "600"], + style: ["normal", "italic"], }); export const metadata: Metadata = { diff --git a/src/components/viewer-shell.tsx b/src/components/viewer-shell.tsx index 56a9dcc..620350f 100644 --- a/src/components/viewer-shell.tsx +++ b/src/components/viewer-shell.tsx @@ -5,29 +5,19 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { CSSProperties } from "react"; import { MAX_FRAGMENT_LENGTH, - artifactKinds, type ArtifactPayload, type ParsedPayload, type PayloadEnvelope, } from "@/lib/payload/schema"; import { getHashPreview } from "@/components/viewer/hash-preview"; +import { FragmentDetailsDisclosure } from "@/components/viewer/fragment-details-disclosure"; import { numberFormatter } from "@/lib/format"; import { withBasePath } from "@/lib/site/base-path"; -const iconPath = withBasePath("/icon.svg"); const securityPath = withBasePath("/security/"); const urlExplainerPath = withBasePath("/url-explainer/"); -const iconImageStyle: CSSProperties = { - backgroundImage: `url(${iconPath})`, - backgroundPosition: "center", - backgroundRepeat: "no-repeat", - backgroundSize: "contain", -}; -const heroAnimationStyle: CSSProperties = { animationDelay: "80ms" }; -const bentoAnimationStyle: CSSProperties = { animationDelay: "120ms" }; -const sampleAnimationStyle: CSSProperties = { animationDelay: "180ms" }; -const inspectorAnimationStyle: CSSProperties = { animationDelay: "220ms" }; -const initializeAnimationStyle: CSSProperties = { animationDelay: "260ms" }; +const sampleAnimationStyle: CSSProperties = { animationDelay: "120ms" }; +const inspectorAnimationStyle: CSSProperties = { animationDelay: "180ms" }; // Intentionally a local copy of getVisibleFragmentLength (src/lib/payload/fragment.ts). // Importing that helper statically would pull the fragment/codec module (lz-string, fflate) @@ -42,37 +32,29 @@ function getVisibleHashLength(hash: string): number { } } -const ecosystemLinks = [ +const footerNavLinks = [ + { href: securityPath, label: "Security", external: false, srPrefix: "" }, + { href: urlExplainerPath, label: "URL explainer", external: false, srPrefix: "" }, { href: "https://github.com/baanish/agent-render", - kicker: "Source", - title: "GitHub", - description: "Source code, issues, releases, and self-hosting notes.", + label: "GitHub", + external: true, + srPrefix: "", }, { href: "https://github.com/baanish/agent-render/blob/main/docs/payload-format.md", - kicker: "Protocol", - title: "Payload format docs", - description: "Fragment key, codecs, envelope fields, and size limits.", + label: "Payload format docs", + external: true, + srPrefix: "", }, { + // srPrefix keeps the /safety.*security page/i accessible-name contract from the old bento card. href: "https://github.com/baanish/agent-render/blob/main/docs/architecture.md#security-posture", - kicker: "Safety", - title: "Security page", - description: "The current security posture and zero-retention boundaries.", + label: "Security page", + external: true, + srPrefix: "Safety ", }, - { - href: "https://openclaw.ai", - kicker: "Ecosystem", - title: "OpenClaw", - description: "The agent ecosystem this viewer was built to support.", - }, -] as const; - -const emptyStateSteps = [ - "Pick a sample fragment below.", - "The payload decodes client-side from the URL hash.", - "The renderer displays the artifact without contacting a server.", + { href: "https://openclaw.ai", label: "OpenClaw", external: true, srPrefix: "" }, ] as const; const ThemeToggle = dynamic( @@ -166,22 +148,22 @@ function getEmptyParsedPayload(): ParsedPayload { function getStatusTone(parsed: ParsedPayload) { if (parsed.ok) { return { - label: "Decoded", + label: "READY", color: "var(--success)", - message: "Fragment decoded successfully.", + message: "Fragment decoded client-side. No copy retained.", }; } if (parsed.code === "empty") { return { - label: "Empty", + label: "EMPTY", color: "var(--accent-secondary)", message: parsed.message, }; } return { - label: "Error", + label: "FAULT", color: "var(--danger)", message: parsed.message, }; @@ -370,44 +352,41 @@ export function ViewerShell() { return (
-
- { - e.preventDefault(); - handleGoHome(); - }} - className="flex items-center gap-2.5 sm:gap-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2 rounded-[var(--radius-lg)] -m-1 p-1" - aria-label="Go to homepage" - > -
-
-

- Agent Render -

-
- -
- +
+
-
+
{activeArtifact && envelope ? ( + ) : viewerState === "error" ? ( +
+
+
+ +
) : (
- {/* ── Editorial hero ── */} -
-

Artifact viewer

-

- Zero-retention artifact viewer for AI outputs. -

-

+

+

Zero-retention artifact viewer for AI outputs.

+

Artifact content lives in the URL fragment, so in static mode the static host does not receive artifact content on the page request.

-

+

Fragment links can still appear in browser history, screenshots, copied messages, extensions, and other places you share or run your browser.

-
- static export - 5 renderers - zero retention -
-
- - {/* ── Bento feature grid ── */} -
-
-

Protocol shape

-

- #<codec-tag><payload> -

- - Why does this URL look weird? - -
-
-

Static boundary

-

- The browser decodes markdown, code, diffs, CSV, and JSON - locally from the fragment after the shell loads. -

-
- {ecosystemLinks.map((link) => ( - - {link.kicker} - {link.title} -

- {link.description} -

-
- ))} -
- Try it - - Load a sample below - -

- Click any sample to populate the viewer from the URL hash. -

-
-
- - {/* ── Link creator ── */} - - - {/* ── Samples + Inspector — full-bleed sections ── */} - +
+ + {/* ── First viewport: link creator + samples sidebar ── */} +
+ + +

Fragment inspector

-

+

Current URL state

@@ -567,91 +512,48 @@ export function ViewerShell() {
- - {/* ── Initialize section ── */} -
-
-
-

Viewer shell

-

- Initialize your Artifact -

-

- Select a fragment above to render it here. Payloads stay off - the host request path, but links still need care. -

-
- -
- {artifactKinds.map((kind) => ( - - {kind} - - ))} -
-
- -
-
-

Getting started

-

- Pick a sample or paste your own content above. -

-

- {parsed.ok - ? "Fragment decoded — select an artifact to render it." - : "No fragment in the URL yet."} -

-
- {emptyStateSteps.map((step, index) => ( -
-

Step {index + 1}

-

- {step} -

-
- ))} -
-

Security

- - Read the security page - -

- Fragment payloads stay out of the static host request path, - but links are not secret-safe. -

- - Read the privacy tradeoff - -
-
-

Hosting

-

- Single static route. Works on any static host. -

-
-
-
)} - + +
); } diff --git a/src/components/viewer/fragment-details-disclosure.tsx b/src/components/viewer/fragment-details-disclosure.tsx index 10b823d..c9cb8c8 100644 --- a/src/components/viewer/fragment-details-disclosure.tsx +++ b/src/components/viewer/fragment-details-disclosure.tsx @@ -21,7 +21,7 @@ export function FragmentDetailsDisclosure({ hashPreview, }: FragmentDetailsDisclosureProps) { return ( -
+
Fragment details diff --git a/tests/components/fragment-details-disclosure.test.tsx b/tests/components/fragment-details-disclosure.test.tsx index de28524..497d450 100644 --- a/tests/components/fragment-details-disclosure.test.tsx +++ b/tests/components/fragment-details-disclosure.test.tsx @@ -6,7 +6,7 @@ import { FragmentDetailsDisclosure } from "@/components/viewer/fragment-details- import { MAX_FRAGMENT_LENGTH } from "@/lib/payload/schema"; describe("FragmentDetailsDisclosure", () => { - it("reveals metadata when expanded", async () => { + it("shows metadata expanded by default and collapses on summary click", async () => { render( { ); const summary = screen.getByText(/Codec, budget, and hash preview/i); - await userEvent.click(summary); expect(screen.getByText("Decoded")).toBeVisible(); expect(screen.getByText("lz")).toBeVisible(); expect(screen.getByText(/#agent-render=v1.lz.abc/i)).toBeVisible(); + + await userEvent.click(summary); + expect(screen.getByText("Decoded")).not.toBeVisible(); }); }); From 1dde4ad4bab823885e512fe08a9d4fb8c34ec399 Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:28:48 -0400 Subject: [PATCH 2/5] Add design-sync inputs for the Claude Design system project Sync configuration, conventions, authored component previews, and the distilled design vision that back the agent-render-skeumorphism project on claude.ai/design. Machine state (.ds-sync, ds-bundle, caches) stays gitignored; eslint ignores the generated bundle output. Co-Authored-By: Claude Fable 5 --- .design-sync/NOTES.md | 49 ++++++++++++ .design-sync/config.json | 47 ++++++++++++ .design-sync/conventions.md | 50 +++++++++++++ .design-sync/ds-entry.ts | 19 +++++ .design-sync/harvest-css.mjs | 24 ++++++ .design-sync/previews/ArtifactSelector.tsx | 74 +++++++++++++++++++ .design-sync/previews/ArtifactStage.tsx | 54 ++++++++++++++ .design-sync/previews/CodeRenderer.tsx | 37 ++++++++++ .design-sync/previews/CsvRenderer.tsx | 10 +++ .design-sync/previews/DiffRenderer.tsx | 14 ++++ .../previews/FragmentDetailsDisclosure.tsx | 51 +++++++++++++ .design-sync/previews/JsonRenderer.tsx | 10 +++ .design-sync/previews/LinkCreator.tsx | 33 +++++++++ .design-sync/previews/MarkdownRenderer.tsx | 10 +++ .design-sync/previews/MermaidBlock.tsx | 26 +++++++ .design-sync/previews/SampleLinks.tsx | 14 ++++ .design-sync/previews/ThemeToggle.tsx | 4 + .design-sync/previews/ViewerShell.tsx | 18 +++++ .design-sync/process-shim.ts | 5 ++ .gitignore | 7 ++ docs/design-vision.md | 53 +++++++++++++ eslint.config.mjs | 2 +- 22 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 .design-sync/NOTES.md create mode 100644 .design-sync/config.json create mode 100644 .design-sync/conventions.md create mode 100644 .design-sync/ds-entry.ts create mode 100644 .design-sync/harvest-css.mjs create mode 100644 .design-sync/previews/ArtifactSelector.tsx create mode 100644 .design-sync/previews/ArtifactStage.tsx create mode 100644 .design-sync/previews/CodeRenderer.tsx create mode 100644 .design-sync/previews/CsvRenderer.tsx create mode 100644 .design-sync/previews/DiffRenderer.tsx create mode 100644 .design-sync/previews/FragmentDetailsDisclosure.tsx create mode 100644 .design-sync/previews/JsonRenderer.tsx create mode 100644 .design-sync/previews/LinkCreator.tsx create mode 100644 .design-sync/previews/MarkdownRenderer.tsx create mode 100644 .design-sync/previews/MermaidBlock.tsx create mode 100644 .design-sync/previews/SampleLinks.tsx create mode 100644 .design-sync/previews/ThemeToggle.tsx create mode 100644 .design-sync/previews/ViewerShell.tsx create mode 100644 .design-sync/process-shim.ts create mode 100644 docs/design-vision.md diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md new file mode 100644 index 0000000..15e0c3e --- /dev/null +++ b/.design-sync/NOTES.md @@ -0,0 +1,49 @@ +# design-sync notes for agent-render + +## Build mechanics (learned on first sync, Aug 2026) + +- The converter needs `cfg.entry` = `.design-sync/ds-entry.ts` (authored re-export list). Without it, PKG_DIR resolves to the nonexistent `node_modules/agent-render` and the build crashes. The entry also exports `sampleEnvelopes` (repo sample payloads) for previews and for the design agent. +- `.design-sync/process-shim.ts` must stay the first import in ds-entry.ts: components read `process.env.NEXT_PUBLIC_*` at module init and `next/dynamic` expects `process`; without the shim every preview throws `ReferenceError: process is not defined` and window.AgentRender ends up non-component. +- `.design-sync/harvest-css.mjs` (wired into buildCmd) turns the hashed Next CSS output into the stable cssEntry at `.design-sync/.cache/ds-css/main.css`: rewrites `/_next/static/media/` font urls to repo-relative, lifts the next/font `--font-*` variable classes to `:root`. Re-syncs must run `npm run build` first or the harvest fails loudly. +- Props auto-extraction can't work here (no shipped .d.ts tree; props types are file-local), so `cfg.dtsPropsFor` carries hand-written bodies for all 13 components. If a component's props change in src, update the config body too; nothing will fail automatically. +- Preview pattern for `
` components (FragmentDetailsDisclosure): open it post-mount via a ref effect (`querySelector("details").setAttribute("open","")`), exactly like a user click. Hover/drag states stay unauthored. +- tokens/ is empty by design: all custom properties live in the compiled globals.css inside `_ds_bundle.css`. + +## Preview-authoring learnings (folded from the first-sync wave) + +- App bug found while previewing (surface in any rebuild): DiffRenderer's `oldContent`/`newContent` fallback renders an empty diff body. `src/components/renderers/diff-renderer.tsx` (~line 501) builds `new DiffFile(..., [])` with an empty hunk list; `@git-diff-view/core` never diffs raw contents and `@git-diff-view/file` isn't installed. Previews only use the `patch` path. +- brotli-wasm cannot load under the static capture server (its `.wasm` fetch 404s), so arx-family encode/decode rejects with "Failed to fetch". Previews drive flows with the deflate codec / `#d...` sample hashes. Any preview using codec "auto" or an arx hash will hit this. +- Mermaid renders reliably headless, but font metrics clip the last glyph of labels: quote labels and append ` `. Keep `flowchart TD` to <= 5 ranks for the ~536px cell. +- Native `button.click()` drives React delegated handlers in previews (LinkCreator generates a real link); sequence dependent clicks with ~50ms setTimeout. +- ViewerShell decoded state: set `window.location.hash` to a deflate sample hash in an effect, then mount behind a ready flag. +- `lucide-react` bare imports compile in preview-rebuild. Kind-icon map: `src/components/artifact-kind-icons.ts`; getHeading/getSupportingLabel live in `artifact-stage.tsx`. +- ArtifactSelector's 5-tab strip is ~1490px intrinsic and clips at its own scroll edge (no escape); previews scroll to the active tab post-mount instead of a viewport override (an explicit viewport re-keys and clears grades). +- ViewerShell has `overrides.ViewerShell = {cardMode: single, primaryStory: EmptyHomepage, viewport: 1280x900}` for a fuller single-card capture. + +## Known render warns + +- (none recorded yet; add any warn triaged as legitimate here so re-syncs don't chase it) + +## Re-sync risks (watch-list for the next run) + +- The claude.ai/design agent (Aug 6 redesign run) applied lint fixes directly to the uploaded `_ds_bundle.css`: folded the next-font `__variable_*` classes into `:root`, added `@kind` annotations to unclassifiable tokens, inlined utility-scoped `--tw-*` state vars, and re-scoped the diff theme vars to plain `[data-theme=...]` scopes. A re-sync will overwrite these with the locally-built CSS and re-introduce the adherence flags; ideally teach harvest-css.mjs the same transforms (or accept the flags as known). + +- `dtsPropsFor` bodies are hand-copied from file-local src types; a props change in src updates nothing automatically. When any component's props change, update the config body in the same PR. +- `.design-sync/ds-entry.ts` is the export list; adding or removing a component in src requires editing it (and `componentSrcMap`, `dtsPropsFor`). +- `harvest-css.mjs` assumes the Next build emits exactly one CSS file and `__variable_*` font classes; a Next major upgrade can change both. It throws loudly rather than shipping stale CSS, but the fix will be manual. +- Previews index into `sampleEnvelopes` by position (0, 2, 3, 4, 5); reordering `src/lib/payload/examples.ts` silently changes preview content without failing anything. +- The ViewerShell/LinkCreator/SampleLinks previews run real decode/generation flows against deflate sample hashes from `sample-link-data.ts`; edits there shift what the cards show. +- Fonts are fetched by next/font at `npm run build` time (network dependency of the buildCmd). +- Render check ran against the playwright/chromium-1228 cache matching the repo pin; a repo playwright bump needs a matching browser install. +- Verified: everything except hover/drag states and the DiffRenderer `oldContent`/`newContent` path (broken in the app, see above). Grades carry via the uploaded `_ds_sync.json`. + + + +- Purpose of this sync (Aug 2026): the user is rebuilding agent-render's UI in claude.ai/design. Three prior Kimi K3 sessions failed at it. The decided design direction (from the cursor session, `~/Downloads/kimi-failed-cursor.md`) is "fused bench-instrument world": skeuomorphism + Japanese high density + airplane QRH card. Target project: agent-render-skeumorphism. +- This repo is an app, not a library: no dist entry, converter runs in synth-entry mode from `src/` with tsconfig `@/*` path aliases. +- Fonts come from `next/font/google` (Fraunces display, IBM Plex Sans body, IBM Plex Mono). There are no @font-face files in source; harvest compiled CSS + woff2 from `out/_next/static/` after `npm run build`. Font families are exposed as `--font-display`/`--font-sans`/`--font-mono` variables set by Next-generated classes on ``, so the shipped styles need a hand-authored bridge that defines those variables at `:root`. +- `src/app/globals.css` starts with `@import "tailwindcss"` (Tailwind 4 via PostCSS); components mostly use bespoke classes defined in globals.css, but the compiled CSS from the Next build is the honest cssEntry source. +- Component set (13): ViewerShell, ThemeToggle, ArtifactSelector, ArtifactStage, FragmentDetailsDisclosure, MarkdownRenderer, CodeRenderer, DiffRenderer, CsvRenderer, JsonRenderer, MermaidBlock, LinkCreator, SampleLinks. `use-theme-controller` is a hook, `hash-preview.ts` a utility; neither is a component. +- Heavy renderers (CodeMirror, mermaid, @git-diff-view/react, papaparse) are dynamically imported in the app; previews must allow them time to mount (`onReady` callbacks exist on renderer props). +- Vision guideline docs are user-approved to ship in `guidelines/` (skeuomorphism + Japanese density + QRH direction, 16 UI criticisms as hard rules). +- Agent feedback the user endorsed (Aug 6, from "brodie" and "Orion"): do NOT average the three aesthetics; assign each one a job. QRH supplies procedural grammar (numbered steps, WARN/CAUTION/NOTE callouts, limits, revision/state markers). Japanese UI supplies density. Skeuomorphism supplies material (laminated cards, recessed fields, molded keys, stamped labels, dividers, hardware; never generic shadows). Kill the hero; the link generator is the first viewport; whole viewport a fixed instrument grid; sections become numbered procedure/status blocks; orange reserved for CAUTION/action states; samples become a compact indexed sidebar; CREATE A LINK becomes a five-step operations card with explicit READY/error states. "QRH first, Japanese density second, marketing page never." diff --git a/.design-sync/config.json b/.design-sync/config.json new file mode 100644 index 0000000..cf2f666 --- /dev/null +++ b/.design-sync/config.json @@ -0,0 +1,47 @@ +{ + "projectId": "cadc747e-85f1-42c6-8cc7-230f2bf35887", + "pkg": "agent-render", + "globalName": "AgentRender", + "shape": "package", + "buildCmd": "npm run build && node .design-sync/harvest-css.mjs", + "cssEntry": ".design-sync/.cache/ds-css/main.css", + "srcDir": "src", + "tsconfig": "tsconfig.json", + "entry": ".design-sync/ds-entry.ts", + "guidelinesGlob": ["docs/*.md"], + "readmeHeader": ".design-sync/conventions.md", + "overrides": { + "ViewerShell": { "cardMode": "single", "primaryStory": "EmptyHomepage", "viewport": "1280x900" }, + "LinkCreator": { "cardMode": "column" } + }, + "dtsPropsFor": { + "MarkdownRenderer": "artifact: { id: string; kind: \"markdown\"; content: string; title?: string; filename?: string }; onReady?: () => void;", + "CodeRenderer": "artifact: { id: string; kind: \"code\"; content: string; language?: string; title?: string; filename?: string }; compact?: boolean; onReady?: () => void;", + "DiffRenderer": "artifact: { id: string; kind: \"diff\"; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }; onReady?: () => void;", + "CsvRenderer": "artifact: { id: string; kind: \"csv\"; content: string; title?: string; filename?: string }; onReady?: () => void;", + "JsonRenderer": "artifact: { id: string; kind: \"json\"; content: string; title?: string; filename?: string }; onReady?: () => void;", + "MermaidBlock": "code: string; onReady?: () => void;", + "ArtifactSelector": "artifacts: Array<{ id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }>; activeArtifactId: string; onSelect: (artifactId: string) => void; kindIcons: Record<\"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\", React.ComponentType<{ className?: string }>>; getHeading: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string; getSupportingLabel: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string;", + "ArtifactStage": "activeArtifact: { id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }; envelope: { v: 1; codec: \"plain\" | \"lz\" | \"deflate\" | \"arx\" | \"arx2\" | \"arx3\" | \"arx4\"; title?: string; activeArtifactId?: string; artifacts: Array<{ id: string; kind: string; content?: string; title?: string; filename?: string }> }; fragmentLength: number; hash: string; onArtifactSelect: (artifactId: string) => void; onRendererReady: (readyKey: string) => void; rendererReadyKey: string; statusTone: { color: string; label: string; message: string };", + "FragmentDetailsDisclosure": "statusLabel: string; statusMessage: string; fragmentLength: string; maxLength: string; codec: string; hashPreview: string;", + "LinkCreator": "onPreviewHash: (hash: string) => void;", + "SampleLinks": "activeHash: string; animationStyle: React.CSSProperties;", + "ThemeToggle": "className?: string;", + "ViewerShell": "" + }, + "componentSrcMap": { + "ViewerShell": "src/components/viewer-shell.tsx", + "ThemeToggle": "src/components/theme-toggle.tsx", + "ArtifactSelector": "src/components/viewer/artifact-selector.tsx", + "ArtifactStage": "src/components/viewer/artifact-stage.tsx", + "FragmentDetailsDisclosure": "src/components/viewer/fragment-details-disclosure.tsx", + "MarkdownRenderer": "src/components/renderers/markdown-renderer.tsx", + "CodeRenderer": "src/components/renderers/code-renderer.tsx", + "DiffRenderer": "src/components/renderers/diff-renderer.tsx", + "CsvRenderer": "src/components/renderers/csv-renderer.tsx", + "JsonRenderer": "src/components/renderers/json-renderer.tsx", + "MermaidBlock": "src/components/renderers/mermaid-block.tsx", + "LinkCreator": "src/components/home/link-creator.tsx", + "SampleLinks": "src/components/home/sample-links.tsx" + } +} diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md new file mode 100644 index 0000000..36ffd13 --- /dev/null +++ b/.design-sync/conventions.md @@ -0,0 +1,50 @@ +# agent-render conventions + +This library is the component set of a zero-retention artifact viewer app, not a generic UI kit. Read `guidelines/docs/design-vision.md` before designing: it is the owner's target direction (bench-instrument skeuomorphism, QRH procedural grammar, Japanese density) for a ground-up rebuild. The current components carry the old look; keep their behavior and data contracts, push the visuals toward the vision. + +## Setup + +No provider is required. All styling comes from the stylesheet closure (`styles.css` imports `_ds_bundle.css` and `fonts/fonts.css`). Theme: light is default; dark mode is the `dark` class on the root element (``). Never use `prefers-color-scheme` alone; the app toggles the class. + +## Styling idiom: CSS custom properties + +Style your own layout glue with the token variables, never hardcoded colors. The vocabulary (defined at `:root` and overridden in `.dark` inside `_ds_bundle.css`): + +- Surfaces: `--page-bg`, `--surface`, `--surface-strong`, `--surface-muted`, `--surface-elevated` +- Borders: `--border`, `--border-strong` (hairline 1px borders are the separation grammar; avoid shadows) +- Text: `--text-primary`, `--text-muted`, `--text-soft` +- Accent and semantic: `--accent`, `--accent-strong`, `--accent-secondary`, `--success`, `--warning`, `--danger` +- Depth and shape: `--shadow-lg`, `--shadow-md`, `--radius-xl`, `--radius-lg` (radii are deliberately sharp, 2px) +- Fonts: `--font-display` (Fraunces), `--font-sans` (IBM Plex Sans), `--font-mono` (IBM Plex Mono). Mono is for data and labels, uppercase with letter-spacing for the label register. + +Component chrome classes ship in `_ds_bundle.css` (`.artifact-action`, `.mono-pill`, `.creator-input`, `.sample-link`, `.viewer-frame`, `.artifact-disclosure`); prefer composing the exported components over reaching for their internal classes. + +## Data contracts + +Renderers take an `artifact` object: `{ id, kind: "markdown"|"code"|"diff"|"csv"|"json", content, title?, filename?, language? }` (diff uses `patch` instead of `content`). Realistic ready-made payloads ship on `window.AgentRender.sampleEnvelopes` (6 envelopes; index 4 holds one artifact of every kind). Use them instead of inventing content. + +## Gotchas + +- Heavy renderers (CodeMirror, mermaid, diff) mount through dynamic imports; they appear a beat after first paint. Each renderer accepts `onReady`. +- DiffRenderer needs a real git `patch` string. The `oldContent`/`newContent` fallback currently renders an empty body (known app gap). +- arx-family codecs need `brotli-wasm`, which may not load in sandboxed frames. For `ViewerShell`/`SampleLinks` hashes, use deflate-coded `#d...` samples. +- `ViewerShell` is the whole page and reads `window.location.hash`; render it alone, not inside a layout. + +## Idiomatic composition + +```jsx +const { ArtifactStage, sampleEnvelopes } = window.AgentRender; +const env = sampleEnvelopes[4]; +
+ {}} + onRendererReady={() => {}} + rendererReadyKey={env.artifacts[0].id} + statusTone={{ label: "Decoded", color: "var(--success)", message: "Fragment decoded successfully." }} + /> +
+``` diff --git a/.design-sync/ds-entry.ts b/.design-sync/ds-entry.ts new file mode 100644 index 0000000..2b335cb --- /dev/null +++ b/.design-sync/ds-entry.ts @@ -0,0 +1,19 @@ +// design-sync bundle entry: the library view of this app's components. +// The app has no dist; this is what window.AgentRender exposes. +import "./process-shim"; +export { ViewerShell } from "../src/components/viewer-shell"; +export { ThemeToggle } from "../src/components/theme-toggle"; +export { ArtifactSelector } from "../src/components/viewer/artifact-selector"; +export { ArtifactStage } from "../src/components/viewer/artifact-stage"; +export { FragmentDetailsDisclosure } from "../src/components/viewer/fragment-details-disclosure"; +export { MarkdownRenderer } from "../src/components/renderers/markdown-renderer"; +export { CodeRenderer } from "../src/components/renderers/code-renderer"; +export { DiffRenderer } from "../src/components/renderers/diff-renderer"; +export { CsvRenderer } from "../src/components/renderers/csv-renderer"; +export { JsonRenderer } from "../src/components/renderers/json-renderer"; +export { MermaidBlock } from "../src/components/renderers/mermaid-block"; +export { LinkCreator } from "../src/components/home/link-creator"; +export { SampleLinks } from "../src/components/home/sample-links"; +// Data, not components: the repo's canonical sample payloads, for previews and +// for building realistic screens with the renderers. +export { sampleEnvelopes } from "../src/lib/payload/examples"; diff --git a/.design-sync/harvest-css.mjs b/.design-sync/harvest-css.mjs new file mode 100644 index 0000000..a991c52 --- /dev/null +++ b/.design-sync/harvest-css.mjs @@ -0,0 +1,24 @@ +// Harvests the Next build's compiled CSS into a stable cssEntry for design-sync. +// Next emits hashed filenames and absolute /_next/ font URLs, and sets the +// --font-* variables via generated classes on that previews never get; +// this rewrites all three into .design-sync/.cache/ds-css/main.css. +import { readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const repo = join(dirname(fileURLToPath(import.meta.url)), ".."); +const cssDir = join(repo, "out/_next/static/css"); +const files = readdirSync(cssDir).filter((f) => f.endsWith(".css")); +if (files.length !== 1) throw new Error(`expected 1 compiled css, found: ${files.join(", ") || "none"} — run npm run build first`); + +let css = readFileSync(join(cssDir, files[0]), "utf8"); +css = css.replaceAll("url(/_next/static/media/", "url(../../../out/_next/static/media/"); + +const rootVars = []; +for (const m of css.matchAll(/\.__variable_[a-f0-9]+\{([^}]+)\}/g)) rootVars.push(m[1]); +if (rootVars.length === 0) throw new Error("no __variable_ font classes found in compiled css"); + +const outDir = join(repo, ".design-sync/.cache/ds-css"); +mkdirSync(outDir, { recursive: true }); +writeFileSync(join(outDir, "main.css"), `:root{${rootVars.join(";")}}\n` + css); +console.log(`harvested ${files[0]} -> .design-sync/.cache/ds-css/main.css (${rootVars.length} font vars lifted to :root)`); diff --git a/.design-sync/previews/ArtifactSelector.tsx b/.design-sync/previews/ArtifactSelector.tsx new file mode 100644 index 0000000..7ece45c --- /dev/null +++ b/.design-sync/previews/ArtifactSelector.tsx @@ -0,0 +1,74 @@ +import { useEffect, useRef } from "react"; +import { ArtifactSelector, sampleEnvelopes } from "agent-render"; +import { FileCode2, FileDiff, FileJson2, FileSpreadsheet, FileText } from "lucide-react"; + +/** Scrolls the strip to its active tab after mount, as the browser would on tab switch. */ +const ScrolledToActive = ({ children }: { children?: unknown }) => { + const ref = useRef(null); + useEffect(() => { + const strip = ref.current?.querySelector(".artifact-selector-row"); + const active = strip?.querySelector(".is-active"); + if (strip && active) strip.scrollLeft = active.offsetLeft; + }, []); + return
{children as never}
; +}; + +// Mirrors src/components/artifact-kind-icons.ts and the heading/label helpers +// that ArtifactStage passes at the real call site (src/components/viewer/artifact-stage.tsx). +const kindIcons = { + markdown: FileText, + code: FileCode2, + diff: FileDiff, + csv: FileSpreadsheet, + json: FileJson2, +}; + +type SelectorArtifact = { id: string; kind: string; title?: string; filename?: string }; + +const getHeading = (artifact: SelectorArtifact) => + artifact.title ?? artifact.filename ?? artifact.id; +const getSupportingLabel = (artifact: SelectorArtifact) => + artifact.filename && artifact.filename !== getHeading(artifact) + ? artifact.filename + : artifact.id; + +const showcase = sampleEnvelopes[4]; +const noop = () => {}; + +/** Full five-kind bundle (arx showcase): markdown, code, diff, csv, json tabs with the markdown active. */ +export const FiveArtifactShowcase = () => ( + +); + +/** Same bundle scrolled to the active diff tab, with the csv and json tabs in view. */ +export const DiffTabActive = () => ( + + + +); + +/** Minimal two-artifact bundle: a roadmap doc plus its release patch. */ +export const TwoArtifactBundle = () => ( + +); diff --git a/.design-sync/previews/ArtifactStage.tsx b/.design-sync/previews/ArtifactStage.tsx new file mode 100644 index 0000000..e6e19fc --- /dev/null +++ b/.design-sync/previews/ArtifactStage.tsx @@ -0,0 +1,54 @@ +import { ArtifactStage, sampleEnvelopes } from "agent-render"; + +// The five-artifact "arx showcase" envelope, presented as an arx4-coded fragment +// the way the viewer shell would after decoding a real shared link. +const showcase = { ...sampleEnvelopes[4], codec: "arx4" as const }; + +const decodedTone = { + label: "Decoded", + color: "var(--success)", + message: "Fragment decoded successfully.", +}; + +const hash = "#e𐊀㮕ヅ𐤈⽂₣ᚔ𝍖ᛞ㹨ヸ⽇㮖𐤉₪ᚕ𝍗ᛟ㹩𐊕ヅ⽂㮕𐤈₣ᚔ𝍖ᛞ㹨𐊀ヸ"; +const noop = () => {}; + +function pickArtifact(id: string) { + const artifact = showcase.artifacts.find((candidate) => candidate.id === id); + if (!artifact) throw new Error(`missing showcase artifact: ${id}`); + return artifact; +} + +/** Markdown artifact active: toolbar, five-tab selector, rendered release notes, metadata grid. */ +export const MarkdownActive = () => { + const active = pickArtifact("release-notes"); + return ( + + ); +}; + +/** Diff artifact active: same bundle switched to the migration patch with the diff renderer. */ +export const DiffActive = () => { + const active = pickArtifact("migration-diff"); + return ( + + ); +}; diff --git a/.design-sync/previews/CodeRenderer.tsx b/.design-sync/previews/CodeRenderer.tsx new file mode 100644 index 0000000..e3f764e --- /dev/null +++ b/.design-sync/previews/CodeRenderer.tsx @@ -0,0 +1,37 @@ +import { CodeRenderer, sampleEnvelopes } from "agent-render"; + +const codecExcerpt = sampleEnvelopes[4].artifacts[1]; + +/** Canonical: a TypeScript source file with the full toolbar chrome. */ +export const TypeScriptSource = () => ; + +/** Compact variant, as used for code fences embedded inside markdown. */ +export const CompactSnippet = () => ( + Fragment-powered artifact viewer shell;\n}', + }} + /> +); + +/** Non-TS language for the language chip + highlighter sweep. */ +export const PythonScript = () => ( + dict[str, int]:\n """Load the codec benchmark baseline committed by npm run bench:codecs."""\n data = json.loads(path.read_text())\n return {row["codec"]: row["visible_chars"] for row in data["corpus"]}\n\n\nif __name__ == "__main__":\n baseline = load_baseline(Path("scripts/bench-baseline.json"))\n for codec, chars in sorted(baseline.items()):\n print(f"{codec:>8}: {chars:,} visible chars")', + }} + /> +); diff --git a/.design-sync/previews/CsvRenderer.tsx b/.design-sync/previews/CsvRenderer.tsx new file mode 100644 index 0000000..d27356f --- /dev/null +++ b/.design-sync/previews/CsvRenderer.tsx @@ -0,0 +1,10 @@ +import { CsvRenderer, sampleEnvelopes } from "agent-render"; + +const metricsSnapshot = sampleEnvelopes[3].artifacts[0]; +const codecBenchmarks = sampleEnvelopes[4].artifacts[3]; + +/** Canonical: small metrics table with quoted fields. */ +export const MetricsSnapshot = () => ; + +/** Numeric benchmark table from the arx showcase bundle. */ +export const CodecBenchmarks = () => ; diff --git a/.design-sync/previews/DiffRenderer.tsx b/.design-sync/previews/DiffRenderer.tsx new file mode 100644 index 0000000..fadf2d4 --- /dev/null +++ b/.design-sync/previews/DiffRenderer.tsx @@ -0,0 +1,14 @@ +import { DiffRenderer, sampleEnvelopes } from "agent-render"; + +const releasePatch = sampleEnvelopes[2].artifacts[0]; +const migrationDiff = sampleEnvelopes[4].artifacts[2]; + +/** Canonical: multi-file git patch in split view (edit + new file). */ +export const SplitMultiFilePatch = () => ; + +/** Same patch pipeline rendered unified, from the arx showcase migration. */ +export const UnifiedMigration = () => ; + +// No oldContent/newContent cell: that path renders an empty diff body (DiffFile is +// constructed with no hunks and @git-diff-view/file is not installed), so there is +// no complete composition to capture. See .design-sync/learnings/batch-renderers.md. diff --git a/.design-sync/previews/FragmentDetailsDisclosure.tsx b/.design-sync/previews/FragmentDetailsDisclosure.tsx new file mode 100644 index 0000000..fb2cb2d --- /dev/null +++ b/.design-sync/previews/FragmentDetailsDisclosure.tsx @@ -0,0 +1,51 @@ +import { useEffect, useRef } from "react"; +import { FragmentDetailsDisclosure } from "agent-render"; + +/** Opens the component's own
after mount, exactly as a click would. */ +const Open = ({ children }: { children?: unknown }) => { + const ref = useRef(null); + useEffect(() => { + ref.current?.querySelector("details")?.setAttribute("open", ""); + }, []); + return
{children as never}
; +}; + +/** Success state, expanded: codec, budget, and hash preview rows visible. */ +export const Decoded = () => ( + + + +); + +/** Decode-failure state, expanded: the viewer promotes this to the top of the page. */ +export const DecodeError = () => ( + + + +); + +/** Default collapsed state, as first seen under an artifact. */ +export const Collapsed = () => ( + +); diff --git a/.design-sync/previews/JsonRenderer.tsx b/.design-sync/previews/JsonRenderer.tsx new file mode 100644 index 0000000..f449c9c --- /dev/null +++ b/.design-sync/previews/JsonRenderer.tsx @@ -0,0 +1,10 @@ +import { JsonRenderer, sampleEnvelopes } from "agent-render"; + +const manifest = sampleEnvelopes[4].artifacts[4]; +const brokenManifest = sampleEnvelopes[5].artifacts[0]; + +/** Canonical: deeply nested valid manifest rendered as a tree. */ +export const ArtifactManifest = () => ; + +/** Malformed JSON (truncated object) exercising the parse-error state. */ +export const MalformedManifest = () => ; diff --git a/.design-sync/previews/LinkCreator.tsx b/.design-sync/previews/LinkCreator.tsx new file mode 100644 index 0000000..5059d85 --- /dev/null +++ b/.design-sync/previews/LinkCreator.tsx @@ -0,0 +1,33 @@ +import { useEffect, useRef } from "react"; +import { LinkCreator } from "agent-render"; + +const noop = () => {}; + +/** Default markdown draft ("Product brief") with the result panel in its empty state. */ +export const DraftForm = () => ; + +/** + * Picks the deflate codec, then submits the default draft, exactly as clicks would. + * Deflate is chosen because "auto" tries the arx codecs, whose brotli-wasm asset + * cannot be fetched from the static preview server. + */ +export const GeneratedLink = () => { + const ref = useRef(null); + useEffect(() => { + const root = ref.current; + if (!root) return; + const deflatePill = Array.from( + root.querySelectorAll(".creator-codec-row button"), + ).find((button) => button.textContent?.trim() === "deflate"); + deflatePill?.click(); + const timer = setTimeout(() => { + root.querySelector('button[type="submit"]')?.click(); + }, 50); + return () => clearTimeout(timer); + }, []); + return ( +
+ +
+ ); +}; diff --git a/.design-sync/previews/MarkdownRenderer.tsx b/.design-sync/previews/MarkdownRenderer.tsx new file mode 100644 index 0000000..fdc2cc6 --- /dev/null +++ b/.design-sync/previews/MarkdownRenderer.tsx @@ -0,0 +1,10 @@ +import { MarkdownRenderer, sampleEnvelopes } from "agent-render"; + +const roadmap = sampleEnvelopes[0].artifacts[0]; +const releaseNotes = sampleEnvelopes[4].artifacts[0]; + +/** Canonical document: headings, blockquote, task list, table, code fence. */ +export const SprintRoadmap = () => ; + +/** Long-form release notes: dense tables, ordered lists, ascii diagram fence. */ +export const ReleaseNotes = () => ; diff --git a/.design-sync/previews/MermaidBlock.tsx b/.design-sync/previews/MermaidBlock.tsx new file mode 100644 index 0000000..6a3e26c --- /dev/null +++ b/.design-sync/previews/MermaidBlock.tsx @@ -0,0 +1,26 @@ +import { MermaidBlock } from "agent-render"; + +// Labels carry a trailing nbsp: headless font metrics under-measure node width +// and clip the last glyph otherwise. +const encodePipeline = `flowchart TD + A["Artifact payload "] --> B["Tuple envelope + dictionary substitution "] + B --> C["Brotli q11 "] + C --> D{"Wire selection "} + D -->|"smallest visible "| E["baseBMP fragment "] + D -->|"max compat "| F["base76 fragment "]`; + +const decodeSequence = `sequenceDiagram + participant U as Browser + participant V as Viewer shell + participant C as arx3 codec + U->>V: open #agent-render=v1 fragment + V->>C: decodeEnvelope(fragment) + C->>C: brotli inflate + dictionary expand + C-->>V: PayloadEnvelope (5 artifacts) + V-->>U: render active artifact`; + +/** Canonical: flowchart of the arx3 encode pipeline. */ +export const EncodePipelineFlow = () => ; + +/** Sequence diagram: fragment decode handshake between browser and viewer. */ +export const DecodeSequence = () => ; diff --git a/.design-sync/previews/SampleLinks.tsx b/.design-sync/previews/SampleLinks.tsx new file mode 100644 index 0000000..d76b5c0 --- /dev/null +++ b/.design-sync/previews/SampleLinks.tsx @@ -0,0 +1,14 @@ +import { SampleLinks } from "agent-render"; + +// "Viewer bootstrap" preset hash, copied verbatim from the sample card data so is-active matches. +const bootstrapHash = "#dbY7BCsIwDIZfJeSksCle5_DoCwhenIe6ZVrs2tJlczD27mYdgqCXQL785PtH9JjtEuzjLDHDimqjmDBBlu2s6UUBbs5xy0F5wUpwH3HaPsgYQQGzy4j69_AUVLrq8-37uuF2EFz_x3MTGrwLDHVnS9bOwtLlNKdWaxgLCxCIu2Ahb5S2h2NQ94Ysp95JjipQgXWtSoZFAFGQb2N4X9hJPEY8s3G6Tm8"; + +/** Homepage preset grid, no selection: all six sample envelopes in their rest state. */ +export const PresetGrid = () => ( + +); + +/** Same grid with the "Viewer bootstrap" preset highlighted as the active fragment. */ +export const ActivePreset = () => ( + +); diff --git a/.design-sync/previews/ThemeToggle.tsx b/.design-sync/previews/ThemeToggle.tsx new file mode 100644 index 0000000..de0a6e6 --- /dev/null +++ b/.design-sync/previews/ThemeToggle.tsx @@ -0,0 +1,4 @@ +import { ThemeToggle } from "agent-render"; + +/** Header pill control; in the capture's light theme it offers "Dark mode". */ +export const HeaderPill = () => ; diff --git a/.design-sync/previews/ViewerShell.tsx b/.design-sync/previews/ViewerShell.tsx new file mode 100644 index 0000000..7cb5c9c --- /dev/null +++ b/.design-sync/previews/ViewerShell.tsx @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; +import { ViewerShell } from "agent-render"; + +// "Maintainer kickoff" sample fragment (deflate codec) from the homepage sample cards. +const kickoffHash = "#dVVLbThsxEP2VkXkBiUTiNVVbtRUtUqFCBbUPLBKOPZtY67UtezYhBf69x5sL8LBeey5nzpmZJ5XU7OxUrcbTqJmy3HotrE6V4HWlXRB8nKlzpottC4eGI0dte53wymp296TcO1uHV69zZ-M67JBuUgYUvca0rxnT3sJQqx_R-7gmNOET3S6Z0jD3zlBZsvcU4poCsy2kKXOwnEFQllqoZfaFvOsYrtblIhPjdUFgFtdqI6dIlurT3s-16ShlXjleT2upo0P9MyomJq7GCd093tPvsQztVZF1mY34DbmAykwVg_M--mZIKWahH04uhjl993oVM1u62qeLnnsGq2BxLR0YFyn77J_MibJe08Xt1SUqlRpsab4hTEcPXvaBX6x9wwiHR9tG0K2K1sc1UEdhl3oIZklmyaar1ar5GUQzusKEm2gZCi6_ooDZc_VOJhMY3pzV-GdUupvEMxqj7Qb_c-skZqc9SuTYMxXRm4L2CLq-zfwWLWNCwVT8Q-J1jnYw4v5BYpt178KC1k6W5HVYDHrBAHRpx-h61BUHSQNADxhfM4SC1FZ2DH5T50wwrapVAw3ptQ9HdP6o--R3RNBVH01XfQ8PD3UE_DhOrkW3xEW0lQ1on4cVeyzEMRgueg4ygz4UW5zQUxMIWyhDDpX_6J1CfJbyFzKOGwUNQSbbRf3YqBP6TI3KlXqjaIZ770oBVqM-NOFlZFIJjWtQd-sw4sOum-xKgk7q49xBSx05lKMMKGME8CSdOE_Vy_3Lfw"; + +/** Canonical empty-state homepage: nav bar, editorial hero, bento grid, inspector. */ +export const EmptyHomepage = () => ; + +/** Decoded state: the sample hash is set pre-mount, so the shell decodes and renders the artifact stage. */ +export const DecodedFragment = () => { + const [ready, setReady] = useState(false); + useEffect(() => { + window.location.hash = kickoffHash; + setReady(true); + }, []); + return ready ? : null; +}; diff --git a/.design-sync/process-shim.ts b/.design-sync/process-shim.ts new file mode 100644 index 0000000..c502511 --- /dev/null +++ b/.design-sync/process-shim.ts @@ -0,0 +1,5 @@ +// Browser shim: Next injects process.env at build time; the ds bundle runs raw, +// so give module-init reads of process.env.NEXT_PUBLIC_* an empty env instead +// of a ReferenceError. Must stay the first import in ds-entry.ts. +const g = globalThis as Record; +if (!("process" in g)) g.process = { env: {} }; diff --git a/.gitignore b/.gitignore index b1efc58..8dc0c52 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,10 @@ final-light*.png # Self-hosted server runtime database (local default ./data/agent-render.db; see selfhosted/db.ts) /data/*.db* + +# design-sync machine state +.ds-sync/ +ds-bundle/ +.design-sync/.cache/ +.design-sync/learnings/ +.design-sync/node_modules diff --git a/docs/design-vision.md b/docs/design-vision.md new file mode 100644 index 0000000..c71afd9 --- /dev/null +++ b/docs/design-vision.md @@ -0,0 +1,53 @@ +# Agent-Render rebuild: target design vision + +This design system snapshot is the CURRENT agent-render app (warm ivory, sunset orange, Fraunces/IBM Plex). The owner is rebuilding it toward a decided target world. When designing new screens, keep the components' behavior and data contracts, but push every visual decision toward this target, not toward the current look. + +## The fused world: "Bench Instrument x Carbon Transfer" + +Three influences. Give each ONE job; never average them into a blur: + +1. **Airplane QRH card supplies the procedural grammar.** Numbered procedures, WARN/CAUTION/NOTE callouts, limits tables, revision and state markers. Sections read as numbered procedure and status blocks, not as marketing sections. +2. **Japanese high-density UI supplies the density.** Hairline 1px borders, a 9 to 12 column bench rig, dense stat rows instead of floating cards. Blocks dock against engraved dividers; nothing floats above a 16px gap. No dead space, no decorative empty grid rows. +3. **Skeuomorphism supplies the material.** Laminated cards, recessed input slots, molded keycaps, stamped and engraved labels, real hardware details. Depth is functional state, never generic drop shadows or glow. + +"QRH first, Japanese density second, marketing page never." + +## Palette + +- Chrome: off-white bone keycap surfaces over a dark-engraved charcoal chassis. Reference tokens from the prior attempt: page bg `#ebe7dd` light / `#14110d` dark; dark-mode bone chrome lifted toward aged bone `#cfc6b4` (hi `#dcd5c5`, lo `#b8ae9c`), never muddy taupe. +- Content surfaces (code, diff, csv, json bodies) stay **dark charcoal in BOTH themes** (`#1c1915` family). Chrome stays bone; content reads on dark. +- Exactly one action orange, reserved for COMMIT actions and CAUTION states. One confirmation mint green. One alert brick red. Status is engraved uppercase mono caps plus one tiny real LED dot; never a glowing halo or a pill. +- No radial-gradient washes, no glass, no cream rounded cards, no shadow-glow. + +## Type + +- Target faces: Manrope (display), SUSE (body), Spline Sans Mono (mono). Aspirational picks were GT Eesti and Berkeley Mono. Space Grotesk is banned as overused. +- The current bundle ships Fraunces and IBM Plex Sans/Mono via `--font-display` / `--font-sans` / `--font-mono`; design against the variables, not the faces. +- Wordmark is lowercase mono `agent-render`. Buttons, pills, and labels are uppercase mono with letter-spacing: the engraved-label register. Mono is for data and labels only, never costume. + +## Component grammar + +- Buttons are keycaps: 1px strong border, sharp 2px radius, 2px machined bottom-edge depth (lit top edge, solid foot), press-down `:active` translate. Primary action is solid orange, no gradient. +- Inputs recess into the chassis: inset shadow or an engraved bottom rule that raises to orange on focus. +- Panels are hairline etched boxes on the bone chassis, no card fill, no per-card shadows. +- Carbon-transfer motif (dashed carbon strip, rotated red stamp, perforated tear line) belongs ONLY to the generated-link output; never tint artifact bodies. + +## Layout and UX rules (owner-established, non-negotiable) + +- No self-narrating chrome: never render labels like "Artifact viewer", "read-only", "Decoded", a bare kind kicker (`tsx`, `CSV`), or library names. Show the real filename or nothing. Filename is the primary toolbar text. +- The link creator is the most important surface: full width, first viewport, output below, structured as a five-step operations card with explicit READY and error states. Kill the marketing hero. +- Samples become a compact indexed sidebar, not showcase cards. +- The fragment inspector renders only when a fragment exists, and promotes to the very top of the page on decode error. Fragment details are expanded by default. +- A real footer: wordmark, zero-retention tagline, inline nav (Security / URL explainer / GitHub). Never a one-line stub. +- Mobile stays dense: compressed paddings, no wasted vertical space. + +## Voice and claims + +Terse, technical, honest. Headline register: "Share AI output as one link." Zero-retention is a host-design truth, not a privacy-anonymity promise; never overstate safety. Trust facts worth surfacing: open source, self-hostable, no database. + +## Lessons from three failed rebuild attempts + +- A token reskin is not a rebuild. The owner wants the world rebuilt from the ground up in this grammar. +- Do not build a second parallel token system; one token spine only. +- Do not brighten dark mode into flatness: keep depth, keep contrast (text must clear WCAG AA on the pale bone surfaces). +- Every visible surface counts: renderer toolbars, disclosure panels, footer, favicon, mobile paddings. diff --git a/eslint.config.mjs b/eslint.config.mjs index 8752a43..bf1a35c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,7 +12,7 @@ const compat = new FlatCompat({ const config = [...compat.extends("next/core-web-vitals")]; config.push({ - ignores: [".next/**", "out/**", "playwright-report/**", "test-results/**"], + ignores: [".next/**", "out/**", "playwright-report/**", "test-results/**", "ds-bundle/**", ".design-sync/**"], }); export default config; From dbb5bda38d10378bdf3324e154c2dbf7997cf01f Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:06:47 -0400 Subject: [PATCH 3/5] Restyle artifact metadata and fragment disclosure into the QRH register Both surfaces had only been re-tinted by the rebuild and still read as the old design. Now they follow the bench-instrument grammar: - Metadata becomes a dense LIMITS strip: engraved header row, cells flow horizontally on desktop via auto-fit (still exactly two columns at the 390px contract width), tighter engraved labels and mono values - Disclosure summary becomes an engraved strip with a mono-caps title; the status message reads as a NOTE line; the metric grid tightens to instrument density - Hash preview becomes an amber-on-charcoal recessed readout slot (the design contract's VFD register for machine data) - Dead utility-class padding dropped from the restyled markup Lint, component tests, and the chromium e2e suite (28/28) pass. Co-Authored-By: Claude Fable 5 --- src/app/globals.css | 76 +++++++++++++++++-- src/components/viewer/artifact-stage.tsx | 5 +- .../viewer/fragment-details-disclosure.tsx | 4 +- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index d9c5797..99cb54a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -302,8 +302,26 @@ select { overflow: hidden; } +/* QRH limits strip: one dense horizontal row on desktop, 2-up on phones + (auto-fit at 150px computes exactly 2 tracks at 390px, which the mobile + e2e contract pins). */ .bento-grid-compact { - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); +} + +.bento-grid-compact::before { + content: "Limits"; + grid-column: 1 / -1; + padding: 4px 10px; + border-bottom: 1px solid var(--border); + background: var(--surface-muted); + font-family: var(--font-mono), monospace; + font-size: 0.54rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-muted); + text-shadow: 0 1px 0 var(--engrave); } .bento-card { @@ -312,7 +330,15 @@ select { border-radius: 0; background: none; box-shadow: none; - padding: 9px 12px !important; + padding: 6px 10px !important; +} + +.bento-card .metric-label { + font-size: 0.54rem; +} + +.bento-card .artifact-meta-value { + font-size: 0.72rem; } .bento-card:first-child { @@ -2071,9 +2097,10 @@ select.creator-input { align-items: baseline; justify-content: space-between; gap: 10px; - padding: 9px 12px; + padding: 6px 10px; cursor: pointer; list-style: none; + background: var(--surface-muted); } .artifact-disclosure-summary-copy { @@ -2084,8 +2111,13 @@ select.creator-input { } .artifact-disclosure-title { - font-size: 0.74rem !important; - line-height: 1.45; + font-family: var(--font-mono), monospace; + font-size: 0.62rem !important; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.6; + color: var(--text-muted) !important; } .artifact-disclosure-summary::-webkit-details-marker { @@ -2122,7 +2154,19 @@ select.creator-input { .artifact-disclosure-status { margin: 0; - font-size: 0.74rem !important; + font-size: 0.7rem !important; + font-family: var(--font-mono), monospace; + letter-spacing: 0.02em; +} + +.artifact-disclosure-status::before { + content: "Note / "; + font-weight: 600; + font-size: 0.6rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-soft); + text-shadow: 0 1px 0 var(--engrave); } .artifact-disclosure-grid { @@ -2139,7 +2183,15 @@ select.creator-input { border-radius: 0; background: none; box-shadow: none; - padding: 8px 10px; + padding: 6px 10px; +} + +.artifact-disclosure-grid .metric-card .metric-label { + font-size: 0.54rem; +} + +.artifact-disclosure-grid .metric-card .metric-value { + font-size: 0.72rem; } .artifact-disclosure-grid .metric-card:first-child { @@ -2154,9 +2206,19 @@ select.creator-input { padding: 8px 0 0; } +/* Hash readout: amber mono on a recessed dark slot (the design contract's + VFD-readout register, reserved for machine data like this). */ .artifact-hash-preview-code { margin-top: 6px !important; margin-bottom: 0; + padding: 7px 10px; + border: 1px solid #0a0806; + border-radius: 2px; + background: var(--surface-code); + box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.55); + color: var(--rb-1) !important; + font-size: 0.66rem !important; + line-height: 1.65; } .fade-up { diff --git a/src/components/viewer/artifact-stage.tsx b/src/components/viewer/artifact-stage.tsx index fdcacfe..11d9277 100644 --- a/src/components/viewer/artifact-stage.tsx +++ b/src/components/viewer/artifact-stage.tsx @@ -601,10 +601,7 @@ export function ArtifactStage({ data-testid="artifact-metadata-grid" > {artifactDetailRows.map((row) => ( -
+

{row.label}

{row.value}

diff --git a/src/components/viewer/fragment-details-disclosure.tsx b/src/components/viewer/fragment-details-disclosure.tsx index c9cb8c8..f2862ee 100644 --- a/src/components/viewer/fragment-details-disclosure.tsx +++ b/src/components/viewer/fragment-details-disclosure.tsx @@ -25,13 +25,13 @@ export function FragmentDetailsDisclosure({ Fragment details - + Codec, budget, and hash preview
-

{statusMessage}

+

{statusMessage}

Status

From 9973cd3c867dec512c33c864471b83b03c37a5a0 Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:39:48 -0400 Subject: [PATCH 4/5] Remove self-narrating chrome labels Sweep of leftover narration the redesign contract bans: - markdown code fences dropped the "premium fence" caption (the language chip stays) - json toolbar dropped its "read-only" pill; code toolbar dropped the hidden "read-only codemirror" kicker element - the fragment disclosure dropped its status message line entirely: the STATUS readout already carries the state, and on decode faults the placard above the disclosure shows the actual failure reason, so the line was redundant in every state (statusMessage prop removed from the component, call sites, and the design-sync contract) - dead CSS for all of the above removed Co-Authored-By: Claude Fable 5 --- .design-sync/config.json | 2 +- .../previews/FragmentDetailsDisclosure.tsx | 3 -- src/app/globals.css | 37 +------------------ src/components/renderers/code-renderer.tsx | 1 - src/components/renderers/json-renderer.tsx | 1 - .../renderers/markdown-renderer.tsx | 1 - src/components/viewer-shell.tsx | 1 - src/components/viewer/artifact-stage.tsx | 1 - .../viewer/fragment-details-disclosure.tsx | 3 -- .../fragment-details-disclosure.test.tsx | 1 - 10 files changed, 3 insertions(+), 48 deletions(-) diff --git a/.design-sync/config.json b/.design-sync/config.json index cf2f666..d06f34b 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -23,7 +23,7 @@ "MermaidBlock": "code: string; onReady?: () => void;", "ArtifactSelector": "artifacts: Array<{ id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }>; activeArtifactId: string; onSelect: (artifactId: string) => void; kindIcons: Record<\"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\", React.ComponentType<{ className?: string }>>; getHeading: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string; getSupportingLabel: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string;", "ArtifactStage": "activeArtifact: { id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }; envelope: { v: 1; codec: \"plain\" | \"lz\" | \"deflate\" | \"arx\" | \"arx2\" | \"arx3\" | \"arx4\"; title?: string; activeArtifactId?: string; artifacts: Array<{ id: string; kind: string; content?: string; title?: string; filename?: string }> }; fragmentLength: number; hash: string; onArtifactSelect: (artifactId: string) => void; onRendererReady: (readyKey: string) => void; rendererReadyKey: string; statusTone: { color: string; label: string; message: string };", - "FragmentDetailsDisclosure": "statusLabel: string; statusMessage: string; fragmentLength: string; maxLength: string; codec: string; hashPreview: string;", + "FragmentDetailsDisclosure": "statusLabel: string; fragmentLength: string; maxLength: string; codec: string; hashPreview: string;", "LinkCreator": "onPreviewHash: (hash: string) => void;", "SampleLinks": "activeHash: string; animationStyle: React.CSSProperties;", "ThemeToggle": "className?: string;", diff --git a/.design-sync/previews/FragmentDetailsDisclosure.tsx b/.design-sync/previews/FragmentDetailsDisclosure.tsx index fb2cb2d..825ce3f 100644 --- a/.design-sync/previews/FragmentDetailsDisclosure.tsx +++ b/.design-sync/previews/FragmentDetailsDisclosure.tsx @@ -15,7 +15,6 @@ export const Decoded = () => ( ( ( export const Collapsed = () => ( .mono-pill { - display: none; -} - .code-renderer-host { min-height: 0; } @@ -2152,23 +2142,6 @@ select.creator-input { padding: 10px 12px 12px; } -.artifact-disclosure-status { - margin: 0; - font-size: 0.7rem !important; - font-family: var(--font-mono), monospace; - letter-spacing: 0.02em; -} - -.artifact-disclosure-status::before { - content: "Note / "; - font-weight: 600; - font-size: 0.6rem; - letter-spacing: 0.14em; - text-transform: uppercase; - color: var(--text-soft); - text-shadow: 0 1px 0 var(--engrave); -} - .artifact-disclosure-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); @@ -2286,7 +2259,6 @@ select.creator-input { } .hero-link-body, - .artifact-disclosure-status, .sample-link p { line-height: 1.48; } @@ -2564,10 +2536,6 @@ select.creator-input { line-height: 1.35; } - .artifact-disclosure-status { - font-size: 0.88rem; - } - .artifact-disclosure-body { gap: 0.75rem; padding: 0 0.84rem 0.84rem; @@ -2931,7 +2899,6 @@ select.creator-input { } body[data-print-mode="markdown"] .markdown-code-chip, - body[data-print-mode="markdown"] .markdown-code-caption, body[data-print-mode="markdown"] .markdown-table th, body[data-print-mode="markdown"] .markdown-article strong, body[data-print-mode="markdown"] .markdown-article h1, diff --git a/src/components/renderers/code-renderer.tsx b/src/components/renderers/code-renderer.tsx index c585b13..3edc001 100644 --- a/src/components/renderers/code-renderer.tsx +++ b/src/components/renderers/code-renderer.tsx @@ -336,7 +336,6 @@ export function CodeRenderer({ artifact, compact = false, onReady }: CodeRendere
{language} - read-only codemirror
- read-only
{view === "tree" ? ( }> diff --git a/src/components/renderers/markdown-renderer.tsx b/src/components/renderers/markdown-renderer.tsx index 82019cb..82a184f 100644 --- a/src/components/renderers/markdown-renderer.tsx +++ b/src/components/renderers/markdown-renderer.tsx @@ -249,7 +249,6 @@ export function MarkdownRenderer({ artifact, onReady }: MarkdownRendererProps) {
{language} - premium fence
) : ( diff --git a/src/components/viewer/artifact-stage.tsx b/src/components/viewer/artifact-stage.tsx index 11d9277..d9d0ec0 100644 --- a/src/components/viewer/artifact-stage.tsx +++ b/src/components/viewer/artifact-stage.tsx @@ -614,7 +614,6 @@ export function ArtifactStage({ hashPreview={getHashPreview(hash)} maxLength={numberFormatter.format(MAX_FRAGMENT_LENGTH)} statusLabel={statusTone.label} - statusMessage={statusTone.message} /> diff --git a/src/components/viewer/fragment-details-disclosure.tsx b/src/components/viewer/fragment-details-disclosure.tsx index f2862ee..fc57643 100644 --- a/src/components/viewer/fragment-details-disclosure.tsx +++ b/src/components/viewer/fragment-details-disclosure.tsx @@ -1,6 +1,5 @@ type FragmentDetailsDisclosureProps = { statusLabel: string; - statusMessage: string; fragmentLength: string; maxLength: string; codec: string; @@ -14,7 +13,6 @@ type FragmentDetailsDisclosureProps = { */ export function FragmentDetailsDisclosure({ statusLabel, - statusMessage, fragmentLength, maxLength, codec, @@ -31,7 +29,6 @@ export function FragmentDetailsDisclosure({
-

{statusMessage}

Status

diff --git a/tests/components/fragment-details-disclosure.test.tsx b/tests/components/fragment-details-disclosure.test.tsx index 497d450..099d67a 100644 --- a/tests/components/fragment-details-disclosure.test.tsx +++ b/tests/components/fragment-details-disclosure.test.tsx @@ -14,7 +14,6 @@ describe("FragmentDetailsDisclosure", () => { hashPreview="#agent-render=v1.lz.abc" maxLength={String(MAX_FRAGMENT_LENGTH)} statusLabel="Decoded" - statusMessage="Fragment decoded successfully." />, ); From afc0872ab6066864100b1832ed8f9cc9b4fc071d Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:35:12 -0400 Subject: [PATCH 5/5] Syntax-highlight the raw JSON source view The raw view rendered an uncolored text dump. A single-pass regex tokenizer now colors strings, keys, numbers, and literals with the same token classes the tree view uses, so both views share one vocabulary. The source text is preserved byte-for-byte (keys keep their literal colons; the tree's ::after colon glyph is suppressed in the raw scope), and unmatched text stays uncolored so the parse-error raw view is safe. Co-Authored-By: Claude Fable 5 --- src/app/globals.css | 10 +++++ src/components/renderers/json-renderer.tsx | 43 +++++++++++++++++++++- tests/components/json-renderer.test.tsx | 12 ++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index 048866e..7da3c64 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1912,6 +1912,16 @@ select.creator-input { border: 1px solid var(--surface-code-chrome-border); border-radius: var(--radius-lg); background: var(--surface-code); +} + +/* Raw source keeps its literal colons; the tree's .json-key::after glyph and + spacing must not double them here. */ +.json-raw-source .json-key { + margin-right: 0; +} + +.json-raw-source .json-key::after { + content: none; color: var(--surface-code-text); padding: 1rem 1.05rem 1.15rem; font-family: var(--font-mono), monospace; diff --git a/src/components/renderers/json-renderer.tsx b/src/components/renderers/json-renderer.tsx index 09de223..9997890 100644 --- a/src/components/renderers/json-renderer.tsx +++ b/src/components/renderers/json-renderer.tsx @@ -76,10 +76,51 @@ function JsonNode({ label, value, level = 0 }: { label?: string; value: JsonValu ); } +// One alternation pass: strings (key vs value decided by a trailing colon), +// numbers, and literals. Anything unmatched (punctuation, malformed text) +// stays uncolored, so this is safe for the parse-error raw view too. +const JSON_TOKEN_RX = /("(?:[^"\\\n]|\\.)*")(\s*:)?|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|\btrue\b|\bfalse\b|\bnull\b/g; + +function highlightJsonSource(content: string): ReactNode[] { + const nodes: ReactNode[] = []; + let last = 0; + for (const match of content.matchAll(JSON_TOKEN_RX)) { + const [full, string, colon] = match; + if (match.index > last) { + nodes.push(content.slice(last, match.index)); + } + const tokenClass = + string !== undefined + ? colon + ? "json-key" + : "json-string" + : full === "true" || full === "false" + ? "json-boolean" + : full === "null" + ? "json-null" + : "json-number"; + nodes.push( + + {string ?? full} + , + ); + if (string !== undefined && colon) { + nodes.push(colon); + } + last = match.index + full.length; + } + if (last < content.length) { + nodes.push(content.slice(last)); + } + return nodes; +} + function JsonRawSource({ content }: { content: string }) { + const highlighted = useMemo(() => highlightJsonSource(content), [content]); + return (
-      {content}
+      {highlighted}
     
); } diff --git a/tests/components/json-renderer.test.tsx b/tests/components/json-renderer.test.tsx index c8d901d..5f49c70 100644 --- a/tests/components/json-renderer.test.tsx +++ b/tests/components/json-renderer.test.tsx @@ -53,6 +53,18 @@ describe("JsonRenderer", () => { expect(document.querySelector(".cm-editor")).not.toBeInTheDocument(); }); + it("syntax-highlights the raw source with the tree's token classes", async () => { + render(); + + await userEvent.click(screen.getByRole("button", { name: "Raw" })); + + const raw = screen.getByTestId("renderer-json-raw"); + expect(raw.querySelector(".json-key")?.textContent).toBe('"name"'); + expect(raw.querySelector(".json-string")?.textContent).toBe('"agent-render"'); + // Colons come from the source text, never from the tree's ::after glyph. + expect(raw).toHaveTextContent('"name": "agent-render"'); + }); + it("renders array nodes with numeric child labels", () => { render();