Skip to content

Rebuild UI as bench instrument; migrate diff renderer to @pierre/diffs - #111

Open
baanish wants to merge 2 commits into
mainfrom
kimi-ui-redesign
Open

Rebuild UI as bench instrument; migrate diff renderer to @pierre/diffs#111
baanish wants to merge 2 commits into
mainfrom
kimi-ui-redesign

Conversation

@baanish

@baanish baanish commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Full chrome redesign fusing four directions into one design system ("bench instrument"):

  • C3 hardware-bench skeuomorphism: machined keycap controls, LED status lamps, VFD readouts
  • C4 Japanese density: compact spec rows, no dead space
  • D7 QRH placard grammar: letterspaced caps labels, dot-leader rows, hairline rules
  • D1 CR-39 carbonless slip: carbon-tint strip on the generated-link output only

Also migrates the diff renderer from @git-diff-view/react to @pierre/diffs.

Design

  • Themes: bone chrome in light; charcoal placard chrome in dark (QRH grammar). Content chassis (code/diff/csv/json) stays dark-charcoal in both themes.
  • Token spine: globals.css :root/.dark owns all tokens (--bench-* aliases, keycap/recess depth recipe); bench.css is grammar-only, defines no colors.
  • Fonts: Manrope display, SUSE body, Spline Sans Mono (self-hosted woff2 via next/font in layout.tsx).
  • Depth is functional only: keycap lighting on controls, recessed inputs. No glow halos, no cream cards, no radial washes, no eyebrow kickers, no self-describing chrome text ("Decoded", "read-only codemirror", kind kicker duplicating filename).

Key changes

New files

  • src/app/bench.css: grammar layer (lamp, readout, carbon strip, stamp, perf, board, cell, ledger row)
  • src/components/home/bench-hero.tsx, src/components/viewer/inspector-panel.tsx
  • src/lib/diff/pierre-react.ts: pierre react re-export so the deferred diff chunk stays isolated
  • src/fonts/*.woff2 (9 files), .impeccable/ design-contract + drafts, scripts/generate-sample-artifacts.mjs

Diff renderer (diff-renderer.tsx)

  • @git-diff-view/react@pierre/diffs/react: PatchDiff per parsed file section, MultiFileDiff for old/new pairs
  • Testids, .patch-file-section, .patch-bundle-link, binary empty-state preserved
  • diff-view-stylesheet.ts, public/vendor/diff-view-pure.css(.br), tests/diff-style-asset.test.ts deleted
  • next.config.ts: webpack alias for the ./react subpath (package exports only the import condition); @pierre/theme peer dep installed
  • Diff build budget raised to 900 KiB (measured ~862 KiB gzipped, Shiki engine)

Spec sheet (fragment-details-disclosure.tsx + artifact-stage.tsx)

  • Artifact metadata grid and fragment disclosure merged into one placard
  • Two-column dot-leader ledger; status lamp in its row; hash as one truncated mono line
  • Removed "Fragment decoded successfully." from both the disclosure summary and InspectorPanel (self-evident chrome text)

Docs updated

AGENTS.md, docs/architecture.md, docs/dependency-notes.md, README.md, .impeccable/design-contract.md aligned to the shipped world.

Verification

  • npm run lint pass, npm run typecheck pass
  • npm run test: 49 files, 313/313 pass
  • CI=1 npx playwright test tests/e2e/viewer.spec.ts --project=chromium: 28/28 pass
  • Visual QA in both themes (screenshots in .impeccable/screenshots/)

Known pre-existing failure

npm run bench:codecs gate fails: json-package:arx regressed 1.45% vs baseline etc. Confirmed pre-existing on main (baseline/dictionary drift), NOT caused by this change. Left as-is.

Summary by CodeRabbit

  • New Features

    • Added a redesigned bench-inspired interface for creating and viewing artifacts.
    • Added artifact status details, payload metrics, codec information, budget usage, and fragment diagnostics.
    • Added a compact artifact selector and refreshed sample-link presentation.
    • Added multiple responsive visual design drafts for artifact viewing experiences.
  • Enhancements

    • Improved diff rendering, including multi-file and binary-diff support with fallback handling.
    • Added self-hosted fonts for consistent, privacy-friendly presentation.
    • Improved theme-toggle accessibility and mobile layouts.
  • Documentation

    • Added product, architecture, design, and usage guidance.

Fuse C3 hardware-bench skeuomorphism, C4 Japanese density, D7 QRH
placard grammar, and D1 CR-39 carbonless slip into one design system.
Bone chrome in light theme, charcoal placard chrome in dark, dark
content chassis (code/diff/csv/json) in both. One token spine in
globals.css; bench.css is grammar-only.

Replace @git-diff-view/react with @pierre/diffs (lazy-loaded, Shiki,
no vendor CSS). Per-file PatchDiff inside sections; MultiFileDiff for
old/new pairs. Webpack alias for the ./react subpath; @pierre/theme
peer dep added. Diff budget raised to 900 KiB.

Merge artifact metadata and fragment disclosure into one spec-sheet
placard: two-column dot-leader ledger, status lamp in its row, hash
truncated to one line, no self-evident chrome text.
@socket-security

socket-security Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​pierre/​diffs@​1.3.58010099100100
Added@​pierre/​theme@​2.0.0801008995100

View full report

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR redesigns the artifact viewer with a bench-style interface, replaces the diff renderer with @pierre/diffs, adds product and design artifacts, removes mirrored diff assets, and updates scripts and tests.

Changes

Artifact viewer redesign

Layer / File(s) Summary
Product and design artifacts
.impeccable/*, PRODUCT.md, AGENTS.md, docs/*
Adds product requirements, design contracts, UI directives, draft interfaces, and updated diff-renderer documentation.
Bench shell and home controls
src/app/*, src/components/home/*, src/components/theme-toggle.tsx, src/components/renderers/*
Adds bench styling and local fonts. Redesigns link creation, samples, theme controls, and renderer accessibility labels.
Viewer layout and artifact metadata
src/components/viewer-shell.tsx, src/components/viewer/*
Replaces the former homepage layout with a bench shell, conditional inspector, console-style artifact stage, and ledger-style fragment details.
Pierre diff-renderer migration
src/components/renderers/diff-renderer.tsx, src/lib/diff/pierre-react.ts, package.json, scripts/*, public/*
Replaces @git-diff-view with @pierre/diffs, removes mirrored stylesheet handling, and updates bundle budgets and compression behavior.
Sample generation and validation
scripts/generate-sample-artifacts.mjs, tests/*
Adds sample artifact-link generation and updates unit, end-to-end, budget, header, and self-hosted asset coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ViewerShell
  participant LinkCreator
  participant ArtifactRenderer
  participant InspectorPanel
  ViewerShell->>LinkCreator: render artifact-link creation controls
  LinkCreator-->>ViewerShell: return generated fragment link state
  ViewerShell->>InspectorPanel: show fragment diagnostics when required
  ViewerShell->>ArtifactRenderer: render decoded artifact content
  ArtifactRenderer-->>ViewerShell: display markdown, code, JSON, CSV, or diff output
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: the bench-instrument UI rebuild and migration to @pierre/diffs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kimi-ui-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploying agent-render with  Cloudflare Pages  Cloudflare Pages

Latest commit: c21fecb
Status: ✅  Deploy successful!
Preview URL: https://de974585.agent-render.pages.dev
Branch Preview URL: https://kimi-ui-redesign.agent-render.pages.dev

View logs

@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown

Greptile Summary

The PR rebuilds the application chrome around the bench-instrument design system and replaces the deferred diff implementation with @pierre/diffs.

  • Adds self-hosted fonts, shared visual tokens, dense viewer metadata components, and redesigned creator/viewer controls.
  • Migrates patch and old/new-content rendering to PatchDiff and MultiFileDiff.
  • Removes the previous vendored diff stylesheet and updates build budgets, headers, tests, and documentation.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
src/components/renderers/diff-renderer.tsx Migrates patch and file-pair rendering to lazily loaded @pierre/diffs components while retaining binary and raw-text fallbacks.
src/lib/diff/pierre-react.ts Provides an isolated re-export boundary for the deferred Pierre React components.
src/app/globals.css Establishes the redesigned theme token spine and shared bench-instrument styling.
src/app/bench.css Adds the reusable visual grammar for lamps, readouts, boards, ledger rows, and carbon-transfer details.
src/app/layout.tsx Replaces remotely sourced font integration with locally bundled Manrope, SUSE, and Spline Sans Mono assets.
src/components/viewer/fragment-details-disclosure.tsx Reworks fragment and artifact metadata into the compact placard presentation.
public/_headers Removes the obsolete precompressed diff stylesheet header rule after the renderer migration.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  F[URL fragment] --> D[Client-side decode]
  D --> S[Viewer shell]
  S --> K{Artifact kind}
  K -->|Diff patch| P[Parse patch sections]
  P --> PD[Lazy-loaded PatchDiff]
  K -->|Old and new content| MD[Lazy-loaded MultiFileDiff]
  K -->|Other kinds| R[Existing artifact renderers]
Loading

Reviews (2): Last reviewed commit: "Add overnight redesign review notes and ..." | Re-trigger Greptile

@kilo-code-bot kilo-code-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review findings for PR #111.

];

for (const sample of SAMPLES) {
const link = createGeneratedArtifactLink(sample.draft, PREVIEW_BASE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The sync createGeneratedArtifactLink ignores draft.codec, so the intended arx3/arx4 samples never actually exercise those codecs.

createGeneratedArtifactLink (src/lib/payload/link-creator.ts:167) calls the sync encodeEnvelope, which only considers the sync candidates (deflate/lz/plain) and throws on the arx family (src/lib/payload/fragment.ts:149,231). The draft.codec value here (arx3 for the markdown sample at line 170, arx4 for the json sample at line 218, plus the lz/deflate choices) is never read, so every sample is silently encoded with the smallest sync codec instead. The Codec column written to SAMPLE_LINKS.md will therefore report deflate/lz/plain and the arx3/arx4 renderers are never exercised — contradicting the script's stated purpose (line 2-3) and the wire-size table in the markdown sample body.

Use the async variant (which honors draft.codec at link-creator.ts:194 and supports the arx family) and await it:

Suggested change
const link = createGeneratedArtifactLink(sample.draft, PREVIEW_BASE);
const link = await createGeneratedArtifactLinkAsync(sample.draft, PREVIEW_BASE);

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • tests/e2e/overnight.spec.ts - no new issues (RUN_OVERNIGHT-gated screenshot spec; sample-titles, viewer states, renderer selectors, and arx-showcase bundle switch all verify against current renderers and mirror existing passing visual/e2e patterns)
  • MORNING.md - documentation only, no code impact
Previous Review Summary (commit 59c2d3c)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 59c2d3c)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
scripts/generate-sample-artifacts.mjs 239 Sample generator uses the sync createGeneratedArtifactLink, which ignores draft.codec, so the intended arx3/arx4 (and lz/deflate) sample links never actually exercise those codecs; the emitted Codec column and generated fragments contradict the script's purpose. Use the async createGeneratedArtifactLinkAsync instead.
Files Reviewed (30 files)
  • src/components/renderers/diff-renderer.tsx - reviewed, no new issues (verified patch-bundle guards, mobile mode handling)
  • src/lib/diff/pierre-react.ts - reviewed
    The full redesign (shell, viewer stage/disclosure, home/link-creator, renderers, scripts, tests, docs, CSS) was reviewed. The @pierre/diffs migration, spec-sheet merge, sanitizer paths, and theme/link flows were verified clean of high-confidence runtime/security issues.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4-flash · Input: 40.7K · Output: 7.4K · Cached: 538.6K

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (17)
.impeccable.md (1)

74-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the remaining design context.

This file still specifies the previous editorial UI system, including Fraunces and IBM Plex fonts, ghost borders, and purple-black dark mode. .impeccable/design-contract.md defines the authoritative bench system with a different token, font, and component grammar.

Update or remove the obsolete sections with this renderer migration. Otherwise, future UI work can follow conflicting design instructions.

Based on learnings: Keep documentation, examples, and the OpenClaw skill contract aligned whenever fragment format, supported kinds, payload limits, zero-retention wording, renderer capabilities, commands, or deployment assumptions change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.impeccable.md at line 74, Update the remaining design context in
.impeccable.md to match the authoritative system defined by
.impeccable/design-contract.md, removing obsolete Fraunces/IBM Plex typography,
ghost-border styling, and purple-black dark-mode guidance. Ensure the renderer
migration’s documentation, examples, and OpenClaw skill contract consistently
reflect the current tokens, fonts, component grammar, renderer capabilities,
commands, and deployment assumptions.

Source: Learnings

package.json (2)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid npx tsx for an .mjs script.

npx tsx downloads tsx at run time when it is not installed. That makes the script non-deterministic and it fails without network access. The target is scripts/generate-sample-artifacts.mjs, so plain node runs it directly.

If the script needs TypeScript, add tsx to devDependencies and drop npx.

♻️ Proposed script fix
-    "generate:samples": "npx tsx scripts/generate-sample-artifacts.mjs",
+    "generate:samples": "node scripts/generate-sample-artifacts.mjs",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 42, Update the generate:samples script in package.json
to invoke scripts/generate-sample-artifacts.mjs with node instead of npx tsx,
since the target is a JavaScript module; only add tsx as a devDependency if the
script actually requires TypeScript execution.

61-62: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Pin @pierre/theme@2.0.0 if keeping this peer dependency.

@pierre/theme is listed but does not appear imported by the diff renderer; it is only provided by @pierre/diffs@1.3.5 as a dependency. If the app does not consume this peer dependency, remove it to avoid unnecessary resolution pressure or an installed dead dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 61 - 62, Update the package dependencies around
`@pierre/theme`: remove it if the application does not directly consume the theme
package, or pin the dependency exactly to version 2.0.0 if it must remain as a
peer dependency. Keep `@pierre/diffs` unchanged.
tests/components/diff-renderer.test.tsx (2)

8-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale FileDiff references in tests/components/diff-renderer.test.tsx and docs/architecture.md. src/lib/diff/pierre-react.ts re-exports only PatchDiff and MultiFileDiff, and src/components/renderers/diff-renderer.tsx renders only those two. Both sites still name a FileDiff component that the product does not use.

  • tests/components/diff-renderer.test.tsx#L8-L12: remove the FileDiff key from the vi.mock("@/lib/diff/pierre-react") factory.
  • docs/architecture.md#L63-L67: change the component list to PatchDiff and MultiFileDiff, and apply the same correction to the duplicate sentence under "## Diff choice".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/components/diff-renderer.test.tsx` around lines 8 - 12, Remove the
stale FileDiff mock from tests/components/diff-renderer.test.tsx lines 8-12,
leaving PatchDiff and MultiFileDiff in the vi.mock factory. In
docs/architecture.md lines 63-67, update the component list and duplicate “##
Diff choice” sentence to mention only PatchDiff and MultiFileDiff.

90-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore coverage for the parse-error fallback branch.

This suite covers the invalid-shape fallback only. diff-renderer.tsx still handles a separate parse-error branch at Lines 317-323, which emits a different message and a Parser detail: line. The removal of the malformed-patch test leaves that branch untested.

Add a case that makes parseGitPatchBundle throw, then assert the could not be rendered as a valid unified diff message and the parser detail text.

I can generate this test case. Do you want me to add it?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/components/diff-renderer.test.tsx` around lines 90 - 96, Add a
DiffRenderer test that forces parseGitPatchBundle to throw, covering the
separate parse-error fallback rather than the existing invalid-shape case.
Assert the renderer enters fallback state and displays the “could not be
rendered as a valid unified diff” message along with the “Parser detail:” text
and thrown error detail.
src/components/renderers/diff-renderer.tsx (3)

435-437: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Respect prefers-reduced-motion for the smooth scroll.

The file navigation always uses behavior: "smooth". Users who set prefers-reduced-motion: reduce receive an animated scroll they asked to suppress.

♿ Proposed fix
                     onClick={() => {
-                      document.getElementById(`patch-file-${file.id}`)?.scrollIntoView({ behavior: "smooth", block: "start" });
+                      const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+                      document.getElementById(`patch-file-${file.id}`)?.scrollIntoView({
+                        behavior: prefersReducedMotion ? "auto" : "smooth",
+                        block: "start",
+                      });
                     }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/renderers/diff-renderer.tsx` around lines 435 - 437, Update
the onClick handler in the diff renderer to detect the user’s
prefers-reduced-motion setting and use non-animated scrolling when it is set to
reduce, while retaining smooth scrolling otherwise. Keep the existing
patch-file-${file.id} target and scrollIntoView behavior unchanged apart from
the conditional scroll behavior.

20-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider carrying patchFiles in the patch state.

{ kind: "patch" } holds no data. The render path must therefore re-check parsedPatchBundle.kind === "parsed" at Line 426, and the : null branch at Line 468 becomes unreachable dead code that the compiler cannot prove. Putting patchFiles on the state removes the second check.

♻️ Proposed refactor
 type DiffRenderState =
-  | { kind: "patch" }
+  | { kind: "patch"; patchFiles: ReturnType<typeof parseGitPatchBundle> }
   | { kind: "filepair"; oldContent: string; newContent: string }

Then at the producer:

       if (parsedPatchBundle.kind === "parsed") {
-        return { kind: "patch" };
+        return { kind: "patch", patchFiles: parsedPatchBundle.patchFiles };
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/renderers/diff-renderer.tsx` around lines 20 - 34, Update
DiffRenderState so the "patch" variant carries patchFiles, and populate that
field when creating the patch state from a parsed bundle. In the render path,
use state.patchFiles directly instead of rechecking parsedPatchBundle.kind ===
"parsed"; remove the unreachable null branch while preserving existing handling
for non-patch states.

367-375: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize fileDiffOptions by mode.

fileDiffOptions changes only when mode changes, but it becomes a new object on every render and is passed to PatchDiff/MultiFileDiff. Memoize [mode] to avoid unnecessary prop churn on unrelated re-renders. The option names and values match @pierre/diffs usage.

♻️ Proposed refactor

Move the value into a memo declared with the other hooks, above the early returns:

+  const fileDiffOptions = useMemo(
+    () =>
+      ({
+        diffStyle: mode,
+        themeType: "dark",
+        overflow: "wrap",
+      }) as const,
+    [mode],
+  );

Then delete the inline definition under the existing fileDiffOptions assignment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/renderers/diff-renderer.tsx` around lines 367 - 375, Memoize
the fileDiffOptions object in the diff renderer using a hook keyed by mode,
declaring it with the other hooks before any early returns. Remove the inline
object construction while preserving its existing diffStyle, themeType, and
overflow values.
src/components/home/sample-links.tsx (1)

37-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Expose the active sample to assistive technology.

isActive only adds a class. A screen reader user cannot tell which sample is currently open. Add aria-current.

♻️ Proposed change
               className={cn("sample-link", "bench-cell", isActive && "is-active")}
+              aria-current={isActive ? "true" : undefined}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home/sample-links.tsx` at line 37, Update the sample link
element using the isActive state to include an aria-current attribute, marking
the active sample while leaving inactive samples unset. Preserve the existing
className behavior.
src/components/viewer/artifact-stage.tsx (2)

394-394: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

aria-label on a role-less div has no effect. Both sites put aria-label on a generic div. A div has no implicit role, so most assistive technology ignores the label and the grouping is never announced. The shared fix is to give the element a role, or to drop the label.

  • src/components/viewer/artifact-stage.tsx#L394-L394: the stat strip already labels each value inline (kind, size, codec, fragment). Remove aria-label="Artifact metadata", or add role="group" to make the label effective.
  • src/components/home/bench-hero.tsx#L19-L19: remove aria-label="Trust details"; the three pills read as text on their own.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/artifact-stage.tsx` at line 394, Remove the ineffective
aria-label attributes from the generic divs: delete “Artifact metadata” in
src/components/viewer/artifact-stage.tsx lines 394-394 and “Trust details” in
src/components/home/bench-hero.tsx lines 19-19, since both groups already expose
their content as text and require no replacement role.

394-413: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The console strip and the spec sheet show the same four values.

The console strip renders kind, size, codec, and fragment/max. FragmentDetailsDisclosure then renders Kind and Size (from artifactDetailRows), Codec, and Budget. The disclosure is always open, so a reader sees each value twice on one page.

The PR objective describes consolidating artifact metadata into a single two-column spec-sheet placard. Choose one surface for these four values. Keep the strip for the at-a-glance readout and drop the duplicated ledger rows, or keep the ledger and reduce the strip to the filename plus actions.

Also applies to: 563-571

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/artifact-stage.tsx` around lines 394 - 413, Consolidate
the duplicated artifact metadata shown by the console strip and
FragmentDetailsDisclosure into one surface. Prefer keeping the console strip’s
kind, size, codec, and fragment/max readout, and remove the corresponding Kind,
Size, Codec, and Budget rows from FragmentDetailsDisclosure and its
artifactDetailRows data; preserve the filename and action controls.
src/components/home/link-creator.tsx (1)

469-469: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hide the decorative stamp from assistive technology.

LINK TRANSFER is visual grammar, not information. Screen readers read it as content between the action row and the bundle row. Add aria-hidden="true".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home/link-creator.tsx` at line 469, Update the decorative
stamp div with className "bench-stamp" to include aria-hidden="true", keeping
the existing visual content and styling unchanged.
src/components/viewer/inspector-panel.tsx (1)

78-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Give the progress bar an accessible name.

role="progressbar" carries no name here. A screen reader announces the percentage without stating what it measures. Add aria-label.

♻️ Proposed change
           <div
             className="budget-track"
             role="progressbar"
+            aria-label="Fragment budget used"
             aria-valuemin={0}
             aria-valuemax={100}
             aria-valuenow={Math.round(budgetRatio * 100)}
           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/inspector-panel.tsx` around lines 78 - 86, Add an
aria-label to the progressbar div in the budget-track markup so screen readers
identify what the budgetRatio percentage measures, while preserving the existing
aria-valuemin, aria-valuemax, and aria-valuenow attributes.
src/components/viewer/fragment-details-disclosure.tsx (1)

46-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The lamp colour depends on free-form label strings.

statusLabel is a string. The mapping matches the exact values "Error" and "Ready", which getStatusTone produces in src/components/viewer-shell.tsx (lines 158-180). If a label changes there, this component silently falls back to amber and no compiler error appears.

Narrow the prop to a union, or pass a separate tone field.

♻️ Proposed change
-  statusLabel: string;
+  statusLabel: "Ready" | "Empty" | "Error";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/fragment-details-disclosure.tsx` around lines 46 - 47,
Update the statusLabel prop used by statusLampClass to a constrained union of
the supported status values, such as "Error" and "Ready" (or pass the
corresponding tone field), and align getStatusTone’s output with that type so
label changes are caught by the compiler instead of silently defaulting to
amber.
src/components/viewer-shell.tsx (1)

413-413: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Check the heading hierarchy of the empty state.

Line 413 renders a screen-reader h2, and BenchHero renders a second h2 at the same level. The page has no h1 in this state. Promote one of them to h1 so the document outline starts at the top level.

Also applies to: 427-427

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer-shell.tsx` at line 413, Update the empty-state heading
hierarchy in the component containing the “Zero-retention artifact viewer”
heading and the nearby BenchHero heading so one serves as an h1 instead of both
being h2 elements. Ensure the empty-state document outline begins with a single
top-level heading while preserving the other heading’s relative hierarchy.
src/components/home/bench-hero.tsx (1)

5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The optional animationStyle props now have no caller. The bench redesign removed the staggered entrance wrapper from the shell. src/components/viewer-shell.tsx renders <BenchHero /> (line 427) and <SampleLinks activeHash={hash} /> (line 434), so neither component receives animationStyle. The prop, the CSSProperties import, and the style binding are dead surface in both files.

  • src/components/home/bench-hero.tsx#L5-L7: remove animationStyle from BenchHeroProps, drop the CSSProperties import, and remove style={animationStyle} from the section.
  • src/components/home/sample-links.tsx#L12-L12: remove animationStyle from SampleLinksProps and remove style={animationStyle} from the section.

Keep the props only if a caller will pass them in a follow-up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home/bench-hero.tsx` around lines 5 - 7, Remove the unused
animationStyle prop and related styling from both components: in
src/components/home/bench-hero.tsx lines 5-7, update BenchHeroProps, remove the
CSSProperties import, and remove the section style binding; in
src/components/home/sample-links.tsx line 12, update SampleLinksProps and remove
the section style binding. No changes are needed in viewer-shell.tsx because its
callers already pass no animationStyle.
src/app/bench.css (1)

284-313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the dark-mode hero colors into the token spine.

The file header states that all tokens live in globals.css. These rules hardcode #ede7da, #b0a695, and #e8e0d4. .bench-chip.is-on also hardcodes #fff8ef. Define --bench-ink, --bench-ink-soft overrides under .dark in globals.css instead, so the two sheets stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/bench.css` around lines 284 - 313, Move the dark-mode color values
from the `.dark .bench-hero-headline`, `.dark .bench-hero .bench-hero-truth`,
`.dark .bench-hero .bench-hero-warning`, and bold-text rules into `.dark` token
overrides in `globals.css` for `--bench-ink` and `--bench-ink-soft`; remove the
corresponding hardcoded color declarations from `bench.css` and update
`.bench-chip.is-on` to use the appropriate token instead of `#fff8ef`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.impeccable/drafts/c1-crt-arcade.html:
- Line 132: Clarify the privacy boundary across all three sites: in
.impeccable/drafts/c1-crt-arcade.html lines 132-132, replace “The host never
receives it” with the scoped statement that static hosts do not receive artifact
content in the page request, plus the caveat that fragment links can leak
through browser history, copied URLs, screenshots, or analytics; in
.impeccable/drafts/c1-crt-arcade.html lines 172-175, remove or qualify “HOST
SEES NOTHING” with the same boundary; in .impeccable/drafts/d2-terminal-log.html
lines 117-120, add the link-leak caveat after the local-decoding statement.

In @.impeccable/drafts/c4-high-density.html:
- Around line 229-232: Update the SELF-HOST panel in the mini section to remove
the unsupported “Optional UUID server with SQLite TTL” persistence claim.
Replace it with the supported static-export behavior defined by the
architecture, and only label it as future work if a separate implementation
exists.

In `@docs/architecture.md`:
- Around line 63-67: Remove the duplicated paragraph and bullet from the earlier
architecture section, retaining them only under “## Diff choice.” Update the
`agent-render` component claim to mention only `PatchDiff` and `MultiFileDiff`,
matching the exports in `pierre-react.ts` and usage in `diff-renderer.tsx`; do
not claim that `FileDiff` is used.

In `@scripts/generate-sample-artifacts.mjs`:
- Around line 1-13: Update the usage documentation for
generate-sample-artifacts.mjs to invoke it through tsx, using “npx tsx
scripts/generate-sample-artifacts.mjs” or only documenting the existing npm
wrapper; do not advertise direct plain-Node execution because the script imports
the TypeScript module createGeneratedArtifactLink.

In `@src/components/home/link-creator.tsx`:
- Around line 203-207: Remove aria-live from the bench-readout element, or
otherwise ensure the live region contains only the settled generatedLink result
rather than the draft.content.length fallback. Preserve the displayed count and
codec behavior while preventing announcements on every textarea keystroke.

In `@src/components/theme-toggle.tsx`:
- Around line 35-36: Update the theme toggle’s accessibility semantics to use
one pattern: keep aria-pressed with a static aria-label describing the setting,
such as “Dark mode,” and preserve the existing mounted/loading handling; do not
retain the changing “Switch to … theme” action label alongside aria-pressed.

In `@src/components/viewer-shell.tsx`:
- Around line 378-389: Update the home anchor in the viewer shell to use the
base path produced by the existing withBasePath helper instead of href="#",
while preserving the current onClick handler and handleGoHome behavior.

In `@src/components/viewer/fragment-details-disclosure.tsx`:
- Around line 13-21: Update the LedgerRow type annotation to use a locally
imported ReactNode type instead of React.ReactNode. Add the appropriate
type-only ReactNode import and change the children property annotation, leaving
the component’s runtime imports and behavior unchanged.

In `@src/components/viewer/inspector-panel.tsx`:
- Around line 70-75: Update the artifact count rendering in the inspector panel
to display "—" when artifactCount is null, matching the unknown codec behavior,
while continuing to format non-null counts with numberFormatter.

In `@tests/e2e/viewer.spec.ts`:
- Around line 236-245: Update the viewport bounds assertion loop in the viewer
test to validate vertical visibility as well: obtain the viewport height and
assert each valueBox.top is at least -1 and valueBox.bottom is no greater than
viewport height plus 1. If the intended contract is only horizontal clipping,
instead narrow the test comment and assertions to explicitly describe horizontal
bounds.

---

Nitpick comments:
In @.impeccable.md:
- Line 74: Update the remaining design context in .impeccable.md to match the
authoritative system defined by .impeccable/design-contract.md, removing
obsolete Fraunces/IBM Plex typography, ghost-border styling, and purple-black
dark-mode guidance. Ensure the renderer migration’s documentation, examples, and
OpenClaw skill contract consistently reflect the current tokens, fonts,
component grammar, renderer capabilities, commands, and deployment assumptions.

In `@package.json`:
- Line 42: Update the generate:samples script in package.json to invoke
scripts/generate-sample-artifacts.mjs with node instead of npx tsx, since the
target is a JavaScript module; only add tsx as a devDependency if the script
actually requires TypeScript execution.
- Around line 61-62: Update the package dependencies around `@pierre/theme`:
remove it if the application does not directly consume the theme package, or pin
the dependency exactly to version 2.0.0 if it must remain as a peer dependency.
Keep `@pierre/diffs` unchanged.

In `@src/app/bench.css`:
- Around line 284-313: Move the dark-mode color values from the `.dark
.bench-hero-headline`, `.dark .bench-hero .bench-hero-truth`, `.dark .bench-hero
.bench-hero-warning`, and bold-text rules into `.dark` token overrides in
`globals.css` for `--bench-ink` and `--bench-ink-soft`; remove the corresponding
hardcoded color declarations from `bench.css` and update `.bench-chip.is-on` to
use the appropriate token instead of `#fff8ef`.

In `@src/components/home/bench-hero.tsx`:
- Around line 5-7: Remove the unused animationStyle prop and related styling
from both components: in src/components/home/bench-hero.tsx lines 5-7, update
BenchHeroProps, remove the CSSProperties import, and remove the section style
binding; in src/components/home/sample-links.tsx line 12, update
SampleLinksProps and remove the section style binding. No changes are needed in
viewer-shell.tsx because its callers already pass no animationStyle.

In `@src/components/home/link-creator.tsx`:
- Line 469: Update the decorative stamp div with className "bench-stamp" to
include aria-hidden="true", keeping the existing visual content and styling
unchanged.

In `@src/components/home/sample-links.tsx`:
- Line 37: Update the sample link element using the isActive state to include an
aria-current attribute, marking the active sample while leaving inactive samples
unset. Preserve the existing className behavior.

In `@src/components/renderers/diff-renderer.tsx`:
- Around line 435-437: Update the onClick handler in the diff renderer to detect
the user’s prefers-reduced-motion setting and use non-animated scrolling when it
is set to reduce, while retaining smooth scrolling otherwise. Keep the existing
patch-file-${file.id} target and scrollIntoView behavior unchanged apart from
the conditional scroll behavior.
- Around line 20-34: Update DiffRenderState so the "patch" variant carries
patchFiles, and populate that field when creating the patch state from a parsed
bundle. In the render path, use state.patchFiles directly instead of rechecking
parsedPatchBundle.kind === "parsed"; remove the unreachable null branch while
preserving existing handling for non-patch states.
- Around line 367-375: Memoize the fileDiffOptions object in the diff renderer
using a hook keyed by mode, declaring it with the other hooks before any early
returns. Remove the inline object construction while preserving its existing
diffStyle, themeType, and overflow values.

In `@src/components/viewer-shell.tsx`:
- Line 413: Update the empty-state heading hierarchy in the component containing
the “Zero-retention artifact viewer” heading and the nearby BenchHero heading so
one serves as an h1 instead of both being h2 elements. Ensure the empty-state
document outline begins with a single top-level heading while preserving the
other heading’s relative hierarchy.

In `@src/components/viewer/artifact-stage.tsx`:
- Line 394: Remove the ineffective aria-label attributes from the generic divs:
delete “Artifact metadata” in src/components/viewer/artifact-stage.tsx lines
394-394 and “Trust details” in src/components/home/bench-hero.tsx lines 19-19,
since both groups already expose their content as text and require no
replacement role.
- Around line 394-413: Consolidate the duplicated artifact metadata shown by the
console strip and FragmentDetailsDisclosure into one surface. Prefer keeping the
console strip’s kind, size, codec, and fragment/max readout, and remove the
corresponding Kind, Size, Codec, and Budget rows from FragmentDetailsDisclosure
and its artifactDetailRows data; preserve the filename and action controls.

In `@src/components/viewer/fragment-details-disclosure.tsx`:
- Around line 46-47: Update the statusLabel prop used by statusLampClass to a
constrained union of the supported status values, such as "Error" and "Ready"
(or pass the corresponding tone field), and align getStatusTone’s output with
that type so label changes are caught by the compiler instead of silently
defaulting to amber.

In `@src/components/viewer/inspector-panel.tsx`:
- Around line 78-86: Add an aria-label to the progressbar div in the
budget-track markup so screen readers identify what the budgetRatio percentage
measures, while preserving the existing aria-valuemin, aria-valuemax, and
aria-valuenow attributes.

In `@tests/components/diff-renderer.test.tsx`:
- Around line 8-12: Remove the stale FileDiff mock from
tests/components/diff-renderer.test.tsx lines 8-12, leaving PatchDiff and
MultiFileDiff in the vi.mock factory. In docs/architecture.md lines 63-67,
update the component list and duplicate “## Diff choice” sentence to mention
only PatchDiff and MultiFileDiff.
- Around line 90-96: Add a DiffRenderer test that forces parseGitPatchBundle to
throw, covering the separate parse-error fallback rather than the existing
invalid-shape case. Assert the renderer enters fallback state and displays the
“could not be rendered as a valid unified diff” message along with the “Parser
detail:” text and thrown error detail.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6e43e60-92b4-4807-95fd-fbf604009ed6

📥 Commits

Reviewing files that changed from the base of the PR and between 7354741 and 59c2d3c.

⛔ Files ignored due to path filters (23)
  • .impeccable/screenshots/c1-crt-arcade.png is excluded by !**/*.png
  • .impeccable/screenshots/c2-night-sixpack.png is excluded by !**/*.png
  • .impeccable/screenshots/c3-hardware-bench.png is excluded by !**/*.png
  • .impeccable/screenshots/c4-high-density.png is excluded by !**/*.png
  • .impeccable/screenshots/c6-rain-garden.png is excluded by !**/*.png
  • .impeccable/screenshots/d1-ncr-form.png is excluded by !**/*.png
  • .impeccable/screenshots/d2-terminal-log.png is excluded by !**/*.png
  • .impeccable/screenshots/d3-broadcast-chyron.png is excluded by !**/*.png
  • .impeccable/screenshots/d4-nautical-chart.png is excluded by !**/*.png
  • .impeccable/screenshots/d5-blueprint.png is excluded by !**/*.png
  • .impeccable/screenshots/d6-cd-catalog.png is excluded by !**/*.png
  • .impeccable/screenshots/d7-cockpit-placard.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
  • src/app/icon.svg is excluded by !**/*.svg
  • src/fonts/manrope-v20-latin-500.woff2 is excluded by !**/*.woff2
  • src/fonts/manrope-v20-latin-600.woff2 is excluded by !**/*.woff2
  • src/fonts/manrope-v20-latin-700.woff2 is excluded by !**/*.woff2
  • src/fonts/manrope-v20-latin-800.woff2 is excluded by !**/*.woff2
  • src/fonts/spline-sans-mono-v13-latin-500.woff2 is excluded by !**/*.woff2
  • src/fonts/spline-sans-mono-v13-latin-600.woff2 is excluded by !**/*.woff2
  • src/fonts/suse-v4-latin-500.woff2 is excluded by !**/*.woff2
  • src/fonts/suse-v4-latin-600.woff2 is excluded by !**/*.woff2
  • src/fonts/suse-v4-latin-700.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (57)
  • .gitignore
  • .impeccable.md
  • .impeccable/config.json
  • .impeccable/design-contract.md
  • .impeccable/drafts/_index.html
  • .impeccable/drafts/c1-crt-arcade.html
  • .impeccable/drafts/c2-night-sixpack.html
  • .impeccable/drafts/c3-hardware-bench.html
  • .impeccable/drafts/c4-high-density.html
  • .impeccable/drafts/c6-rain-garden.html
  • .impeccable/drafts/d1-ncr-form.html
  • .impeccable/drafts/d2-terminal-log.html
  • .impeccable/drafts/d3-broadcast-chyron.html
  • .impeccable/drafts/d4-nautical-chart.html
  • .impeccable/drafts/d5-blueprint.html
  • .impeccable/drafts/d6-cd-catalog.html
  • .impeccable/drafts/d7-cockpit-placard.html
  • AGENTS.md
  • PRODUCT.md
  • docs/architecture.md
  • docs/dependency-notes.md
  • package.json
  • public/_headers
  • public/vendor/diff-view-pure.css
  • public/vendor/diff-view-pure.css.br
  • scripts/bench-codecs.mjs
  • scripts/check-build-budgets.mjs
  • scripts/compress-dictionary.mjs
  • scripts/generate-sample-artifacts.mjs
  • src/app/bench.css
  • src/app/globals.css
  • src/app/layout.tsx
  • src/components/home/bench-hero.tsx
  • src/components/home/link-creator.tsx
  • src/components/home/sample-links.tsx
  • src/components/renderers/code-renderer.tsx
  • src/components/renderers/diff-renderer.tsx
  • src/components/renderers/diff-view-stylesheet.ts
  • src/components/renderers/json-renderer.tsx
  • src/components/renderers/markdown-renderer.tsx
  • src/components/theme-toggle.tsx
  • src/components/viewer-shell.tsx
  • src/components/viewer/artifact-selector.tsx
  • src/components/viewer/artifact-stage.tsx
  • src/components/viewer/fragment-details-disclosure.tsx
  • src/components/viewer/inspector-panel.tsx
  • src/lib/diff/pierre-react.ts
  • tests/build-budgets.test.ts
  • tests/components/diff-renderer.test.tsx
  • tests/components/fragment-details-disclosure.test.tsx
  • tests/components/viewer-shell.test.tsx
  • tests/diff-style-asset.test.ts
  • tests/e2e/viewer.spec.ts
  • tests/headers.test.ts
  • tests/selfhosted/api-catalog.test.ts
  • tests/selfhosted/static-headers.test.ts
  • tests/serve-export-headers.test.ts
💤 Files with no reviewable changes (10)
  • src/components/renderers/code-renderer.tsx
  • public/_headers
  • tests/diff-style-asset.test.ts
  • tests/headers.test.ts
  • src/components/renderers/json-renderer.tsx
  • src/components/renderers/diff-view-stylesheet.ts
  • tests/serve-export-headers.test.ts
  • tests/selfhosted/static-headers.test.ts
  • public/vendor/diff-view-pure.css
  • tests/selfhosted/api-catalog.test.ts

</div>

<marquee class="marquee" style="display:block">ZERO-RETENTION<br>ARTIFACT <span class="red">VIEWER</span></marquee>
<p class="sub-marquee">Payload lives in the URL fragment. <span class="glow">The host never receives it.</span> Five renderers loaded: markdown · code · diff · csv · json.</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

State the privacy boundary and the URL-leak risks.

Zero retention does not make a link secret-safe. State that static hosts do not receive artifact content on the page request. Also state that fragment URLs can leak through browser history, copied URLs, screenshots, and analytics.

  • .impeccable/drafts/c1-crt-arcade.html#L132-L132: replace “The host never receives it” with the scoped static-host statement and the link-leak caveat.
  • .impeccable/drafts/c1-crt-arcade.html#L172-L175: remove “HOST SEES NOTHING” or qualify it with the same boundary.
  • .impeccable/drafts/d2-terminal-log.html#L117-L120: add the link-leak caveat after the local-decoding statement.

Based on learnings: the product is zero-retention by host design but is not absolutely secret-safe because links can leak through browser history, copied URLs, screenshots, or analytics.

📍 Affects 2 files
  • .impeccable/drafts/c1-crt-arcade.html#L132-L132 (this comment)
  • .impeccable/drafts/c1-crt-arcade.html#L172-L175
  • .impeccable/drafts/d2-terminal-log.html#L117-L120
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.impeccable/drafts/c1-crt-arcade.html at line 132, Clarify the privacy
boundary across all three sites: in .impeccable/drafts/c1-crt-arcade.html lines
132-132, replace “The host never receives it” with the scoped statement that
static hosts do not receive artifact content in the page request, plus the
caveat that fragment links can leak through browser history, copied URLs,
screenshots, or analytics; in .impeccable/drafts/c1-crt-arcade.html lines
172-175, remove or qualify “HOST SEES NOTHING” with the same boundary; in
.impeccable/drafts/d2-terminal-log.html lines 117-120, add the link-leak caveat
after the local-decoding statement.

Source: Learnings

Comment on lines +229 to +232
<div class="mini">
<div class="mh">SELF-HOST<em>NEW</em></div>
<p>Optional UUID server with SQLite TTL when the team genuinely needs persistence.</p>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the unsupported persistence mode claim.

The supplied architecture defines a static shell with fragment-resident artifact data. “Optional UUID server with SQLite TTL” describes a backend persistence feature that is not part of that contract.

Replace this panel with the supported static-export behavior. Mark it as future work only if a separate implementation exists.

Based on learnings: do not describe roadmap ideas as shipped behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.impeccable/drafts/c4-high-density.html around lines 229 - 232, Update the
SELF-HOST panel in the mini section to remove the unsupported “Optional UUID
server with SQLite TTL” persistence claim. Replace it with the supported
static-export behavior defined by the architecture, and only label it as future
work if a separate implementation exists.

Source: Learnings

Comment thread docs/architecture.md
Comment on lines +63 to +67
The largest remaining deferred cost is still the diff renderer stack. After the full rebuild, that stack is `@pierre/diffs` (Shiki-based, dynamically imported, no vendor CSS mirror into `public/`). It lazy-loads only when a diff artifact mounts, keeping the empty shell and non-diff artifacts off that cost. The stack provides the review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement.

`agent-render` uses `@pierre/diffs` `PatchDiff`/`MultiFileDiff`/`FileDiff` components instead of `@codemirror/merge`.

- `@pierre/diffs` matches the product goal better because it is already shaped like a GitHub-style review surface

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicated paragraph and the FileDiff claim.

Lines 65-67 repeat lines 78-80 word for word, including the bullet. Keep the statement only under "## Diff choice".

Also, the product code does not use FileDiff. src/lib/diff/pierre-react.ts re-exports only PatchDiff and MultiFileDiff, and diff-renderer.tsx renders only those two.

📝 Proposed documentation fix
 The largest remaining deferred cost is still the diff renderer stack. After the full rebuild, that stack is `@pierre/diffs` (Shiki-based, dynamically imported, no vendor CSS mirror into `public/`). It lazy-loads only when a diff artifact mounts, keeping the empty shell and non-diff artifacts off that cost. The stack provides the review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement.
 
-`agent-render` uses `@pierre/diffs` `PatchDiff`/`MultiFileDiff`/`FileDiff` components instead of `@codemirror/merge`.
-
-- `@pierre/diffs` matches the product goal better because it is already shaped like a GitHub-style review surface
-
 The JSON and markdown paths are now substantially lighter because:

And in the "## Diff choice" section:

-`agent-render` uses `@pierre/diffs` `PatchDiff`/`MultiFileDiff`/`FileDiff` components instead of `@codemirror/merge`.
+`agent-render` uses the `@pierre/diffs` `PatchDiff` and `MultiFileDiff` components instead of `@codemirror/merge`.

Based on learnings: "Base changes on the current shipped product, trust code over stale guidance, and do not describe roadmap ideas as shipped behavior."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture.md` around lines 63 - 67, Remove the duplicated paragraph
and bullet from the earlier architecture section, retaining them only under “##
Diff choice.” Update the `agent-render` component claim to mention only
`PatchDiff` and `MultiFileDiff`, matching the exports in `pierre-react.ts` and
usage in `diff-renderer.tsx`; do not claim that `FileDiff` is used.

Source: Learnings

Comment on lines +1 to +13
#!/usr/bin/env node
// Generates fresh sample fragment links across all supported artifact kinds,
// exercises the app's own encoder (`createGeneratedArtifactLink`), and writes
// a clickable markdown index to `.impeccable/overnight-shots/SAMPLE_LINKS.md`.
//
// Usage: node scripts/generate-sample-artifacts.mjs
// Requires tsx (devDependency): `npx tsx` resolves this automatically when
// invoked via `npm run generate:samples`.

import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createGeneratedArtifactLink } from "../src/lib/payload/link-creator.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,15p' scripts/generate-sample-artifacts.mjs
rg -n -C 2 '"generate:samples"|tsx' package.json

Repository: baanish/agent-render

Length of output: 1654


🌐 Web query:

Node.js ESM TypeScript imports .ts files require loader tsx

💡 Result:

To use TypeScript files (.ts) within a Node.js ESM environment, the tsx package provides a seamless solution by leveraging esbuild for fast, on-demand compilation [1][2][3]. You can integrate tsx in two primary ways: 1. Using the tsx CLI as a drop-in replacement for node: You can run your entry point directly using the tsx command, which handles the necessary loader registration automatically [1][2][3]. tsx your-file.ts 2. Using Node.js with the --import flag (Recommended): For Node.js v20.6.0 and later, you can use the module customization API to register tsx as a loader [1][4]. This is often preferred for CI/CD or production-like environments as it uses the official Node.js binary [4][3]. node --import tsx your-file.ts Key considerations: - Module Resolution: When using ESM, Node.js strictly requires explicit file extensions in import statements (e.g., import { x } from './file.js') [5][6][4]. While tsx is sometimes more lenient and may resolve./file.ts automatically, it is best practice to use the.js extension in your source code to ensure portability and alignment with standard ESM behavior [4]. - TypeScript Features: tsx supports tsconfig.json features like path aliases [1][2][3]. However, if you require full TypeScript type-checking or support for advanced features (like legacy decorators or experimental syntax), it is recommended to use a formal build step (e.g., tsc) before execution [7][3]. - Loader Specificity: If you need to target specific module systems, you can use more granular imports, such as node --import tsx/esm for ESM-only or node --require tsx/cjs for CommonJS-only contexts [1][8][3]. For projects that require official, minimal runtime support without third-party dependencies, Node.js now includes experimental built-in type stripping [7]; however, this feature ignores tsconfig.json settings and does not perform type checking [7]. For full compatibility with existing TypeScript configurations, third-party loaders like tsx remain the standard approach [5][7].

Citations:


🌐 Web query:

TypeScript tsx npm GitHub README usage npx tsx node

💡 Result:

tsx (TypeScript Execute) is a tool designed to run TypeScript and ESM files directly in Node.js without requiring manual compilation [1][2]. It uses esbuild to provide seamless TypeScript execution with modern, sensible defaults [2][3]. Usage Instructions To use tsx in your project, follow these steps: 1. Installation Install tsx as a development dependency in your project: npm install --save-dev tsx [4][5] 2. Running with the tsx CLI You can use the tsx binary as a direct alias to the node command, allowing you to run TypeScript files immediately [2][3]: npx tsx your-file.ts [4][5] 3. Running with the node CLI Alternatively, you can integrate tsx directly into the standard node command using the --import flag [4][5]: node --import tsx your-file.ts [5][6] Key Features - Seamless TypeScript execution: Runs TypeScript code directly, handling both CommonJS and ESM modes [2][3]. - No configuration required: Works out-of-the-box with sensible defaults, though it supports tsconfig.json [2][3]. - Watch mode: Includes a built-in watch mode to re-run files automatically upon saving [3]. - ESM support: Resolves common issues encountered with ESM in Node.js [2][3]. Important Note tsx does not perform type checking. It is recommended to use the official TypeScript compiler (tsc) for type checking your code before running it with tsx [4][5].

Citations:


Use tsx for the direct script execution contract.

This script imports ../src/lib/payload/link-creator.ts; plain node scripts/generate-sample-artifacts.mjs cannot resolve that import, so the documented usage must require npx tsx scripts/generate-sample-artifacts.mjs or only advertise the wrapper script.

Proposed fix
-#!/usr/bin/env node
 // Generates fresh sample fragment links across all supported artifact kinds,
@@
-// Usage:  node scripts/generate-sample-artifacts.mjs
+// Usage:  npm run generate:samples
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/usr/bin/env node
// Generates fresh sample fragment links across all supported artifact kinds,
// exercises the app's own encoder (`createGeneratedArtifactLink`), and writes
// a clickable markdown index to `.impeccable/overnight-shots/SAMPLE_LINKS.md`.
//
// Usage: node scripts/generate-sample-artifacts.mjs
// Requires tsx (devDependency): `npx tsx` resolves this automatically when
// invoked via `npm run generate:samples`.
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createGeneratedArtifactLink } from "../src/lib/payload/link-creator.ts";
// Generates fresh sample fragment links across all supported artifact kinds,
// exercises the app's own encoder (`createGeneratedArtifactLink`), and writes
// a clickable markdown index to `.impeccable/overnight-shots/SAMPLE_LINKS.md`.
//
// Usage: npm run generate:samples
// Requires tsx (devDependency): `npx tsx` resolves this automatically when
// invoked via `npm run generate:samples`.
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createGeneratedArtifactLink } from "../src/lib/payload/link-creator.ts";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-sample-artifacts.mjs` around lines 1 - 13, Update the usage
documentation for generate-sample-artifacts.mjs to invoke it through tsx, using
“npx tsx scripts/generate-sample-artifacts.mjs” or only documenting the existing
npm wrapper; do not advertise direct plain-Node execution because the script
imports the TypeScript module createGeneratedArtifactLink.

Comment on lines +203 to 207
<div className="bench-readout" aria-live="polite">
{numberFormatter.format(
generatedLink?.fragmentLength ?? draft.content.length,
)} chars / {generatedLink?.codec ?? draft.codec ?? "auto"}
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not put aria-live on a readout that changes on every keystroke.

The readout value falls back to draft.content.length. That value changes on each character the user types in the content textarea. With aria-live="polite", a screen reader announces the new count after every keystroke, which floods the announcement queue while the user is typing.

Announce only the settled generation result, or drop the live region.

🛠️ Proposed fix
-          <div className="bench-readout" aria-live="polite">
+          <div className="bench-readout">
             {numberFormatter.format(
               generatedLink?.fragmentLength ?? draft.content.length,
             )} chars / {generatedLink?.codec ?? draft.codec ?? "auto"}
           </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="bench-readout" aria-live="polite">
{numberFormatter.format(
generatedLink?.fragmentLength ?? draft.content.length,
)} chars / {generatedLink?.codec ?? draft.codec ?? "auto"}
</div>
<div className="bench-readout">
{numberFormatter.format(
generatedLink?.fragmentLength ?? draft.content.length,
)} chars / {generatedLink?.codec ?? draft.codec ?? "auto"}
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/home/link-creator.tsx` around lines 203 - 207, Remove
aria-live from the bench-readout element, or otherwise ensure the live region
contains only the settled generatedLink result rather than the
draft.content.length fallback. Preserve the displayed count and codec behavior
while preventing announcements on every textarea keystroke.

Comment on lines +35 to 36
aria-pressed={mounted ? isDark : false}
aria-label={mounted ? `Switch to ${isDark ? "light" : "dark"} theme` : "Theme toggle loading"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Pick one toggle pattern: aria-pressed or a changing label.

aria-pressed describes the state of the control. The label describes the action. Together a screen reader announces "Switch to light theme, pressed", which is contradictory.

Use a static name plus aria-pressed (for example, name the control "Dark mode" and let aria-pressed report whether dark mode is on), or keep the action label and remove aria-pressed.

♻️ Proposed change (state pattern)
       aria-pressed={mounted ? isDark : false}
-      aria-label={mounted ? `Switch to ${isDark ? "light" : "dark"} theme` : "Theme toggle loading"}
+      aria-label={mounted ? "Dark mode" : "Theme toggle loading"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
aria-pressed={mounted ? isDark : false}
aria-label={mounted ? `Switch to ${isDark ? "light" : "dark"} theme` : "Theme toggle loading"}
aria-pressed={mounted ? isDark : false}
aria-label={mounted ? "Dark mode" : "Theme toggle loading"}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/theme-toggle.tsx` around lines 35 - 36, Update the theme
toggle’s accessibility semantics to use one pattern: keep aria-pressed with a
static aria-label describing the setting, such as “Dark mode,” and preserve the
existing mounted/loading handling; do not retain the changing “Switch to …
theme” action label alongside aria-pressed.

Comment on lines 378 to 389
<a
href="#"
onClick={(e) => {
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"
className="-m-1 flex items-center gap-2.5 rounded-[var(--radius-sm)] p-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
aria-label="Go to homepage"
>
<div className="grid h-8 w-8 place-items-center rounded-[var(--radius-lg)] border border-[color:var(--border)] bg-[color:var(--surface-strong)] sm:h-9 sm:w-9">
<span
aria-hidden="true"
className="h-4.5 w-4.5 sm:h-5 sm:w-5"
style={iconImageStyle}
/>
</div>
<h1 className="font-display text-lg font-semibold tracking-[-0.03em] sm:text-xl">
Agent Render
</h1>
<span aria-hidden="true" className="bench-lamp" />
<span className="nav-wordmark">agent-render</span>
</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Do not use href="#" for the home link; it can discard the artifact fragment.

handleGoHome clears the fragment correctly after hydration. Before the click handler is attached, or if the handler does not run, the browser follows href="#" and replaces the current fragment. The fragment is the only copy of the artifact content, so the artifact is lost from the address bar.

Point the anchor at the base path instead. withBasePath is already imported in this file.

🛠️ Proposed fix
         <a
-          href="#"
+          href={withBasePath("/")}
           onClick={(e) => {
             e.preventDefault();
             handleGoHome();
           }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
href="#"
onClick={(e) => {
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"
className="-m-1 flex items-center gap-2.5 rounded-[var(--radius-sm)] p-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
aria-label="Go to homepage"
>
<div className="grid h-8 w-8 place-items-center rounded-[var(--radius-lg)] border border-[color:var(--border)] bg-[color:var(--surface-strong)] sm:h-9 sm:w-9">
<span
aria-hidden="true"
className="h-4.5 w-4.5 sm:h-5 sm:w-5"
style={iconImageStyle}
/>
</div>
<h1 className="font-display text-lg font-semibold tracking-[-0.03em] sm:text-xl">
Agent Render
</h1>
<span aria-hidden="true" className="bench-lamp" />
<span className="nav-wordmark">agent-render</span>
</a>
<a
href={withBasePath("/")}
onClick={(e) => {
e.preventDefault();
handleGoHome();
}}
className="-m-1 flex items-center gap-2.5 rounded-[var(--radius-sm)] p-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
aria-label="Go to homepage"
>
<span aria-hidden="true" className="bench-lamp" />
<span className="nav-wordmark">agent-render</span>
</a>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer-shell.tsx` around lines 378 - 389, Update the home
anchor in the viewer shell to use the base path produced by the existing
withBasePath helper instead of href="#", while preserving the current onClick
handler and handleGoHome behavior.

Comment on lines +13 to +21
function LedgerRow({
label,
children,
className,
}: {
label: string;
children: React.ReactNode;
className?: string;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the React import and the tsconfig UMD setting for the disclosure component.
set -euo pipefail

f=$(fd -t f 'fragment-details-disclosure.tsx' src)
echo "== head of $f =="
sed -n '1,25p' "$f"

echo
echo "== React imports in the file =="
rg -n '^\s*import .*"react"' "$f" || echo "no react import found"

echo
echo "== allowUmdGlobalAccess / jsx settings =="
fd -t f 'tsconfig*.json' . --max-depth 2 --exec rg -n 'allowUmdGlobalAccess|"jsx"' {}

Repository: baanish/agent-render

Length of output: 951


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate tsconfig files =="
fd -t f 'tsconfig.*json' . -H

echo
echo "== tsconfig snippets =="
while IFS= read -r f; do
  echo "--- $f"
  rg -n 'allowUmdGlobalAccess|"jsx"|"types"|compilerOptions|include' "$f" || true
done < <(fd -t f 'tsconfig.*json' . -H)

echo
echo "== full disclosure file imports and relevant React references =="
f=$(fd -t f 'fragment-details-disclosure.tsx' src | head -n 1)
rg -n 'import|React\.React|ReactNode|react' "$f"

Repository: baanish/agent-render

Length of output: 307


Import ReactNode locally instead of using React.ReactNode.

src/components/viewer/fragment-details-disclosure.tsx is a module that uses React.ReactNode without importing React. Add a type import for ReactNode and replace this annotation with children: ReactNode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/fragment-details-disclosure.tsx` around lines 13 - 21,
Update the LedgerRow type annotation to use a locally imported ReactNode type
instead of React.ReactNode. Add the appropriate type-only ReactNode import and
change the children property annotation, leaving the component’s runtime imports
and behavior unchanged.

Comment on lines +70 to +75
<span className="stat-item">
artifacts{" "}
<span className="stat-value">
{artifactCount !== null ? numberFormatter.format(artifactCount) : "0"}
</span>
</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show when the artifact count is unknown.

artifactCount is null when decoding fails. src/components/viewer-shell.tsx line 419 passes null on the error path. The panel then prints 0, which states that the bundle contains zero artifacts. The count is unknown, not zero. Line 68 already uses for an unknown codec.

🛠️ Proposed fix
               <span className="stat-value">
-                {artifactCount !== null ? numberFormatter.format(artifactCount) : "0"}
+                {artifactCount !== null ? numberFormatter.format(artifactCount) : "—"}
               </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="stat-item">
artifacts{" "}
<span className="stat-value">
{artifactCount !== null ? numberFormatter.format(artifactCount) : "0"}
</span>
</span>
<span className="stat-item">
artifacts{" "}
<span className="stat-value">
{artifactCount !== null ? numberFormatter.format(artifactCount) : ""}
</span>
</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/viewer/inspector-panel.tsx` around lines 70 - 75, Update the
artifact count rendering in the inspector panel to display "—" when
artifactCount is null, matching the unknown codec behavior, while continuing to
format non-null counts with numberFormatter.

Comment thread tests/e2e/viewer.spec.ts
Comment on lines +236 to +245
// every row label and value stays on-screen
const viewportWidth = page.viewportSize()?.width ?? 0;
const valueBoxes = await specSheet
.locator(".artifact-meta-value, .artifact-hash-preview-code")
.evaluateAll((elements) => elements.map((element) => element.getBoundingClientRect()));
expect(valueBoxes.length).toBeGreaterThan(0);
for (const valueBox of valueBoxes) {
expect(valueBox.width).toBeGreaterThan(0);
expect(valueBox.right).toBeLessThanOrEqual(viewportWidth + 1);
expect(valueBox.left).toBeGreaterThanOrEqual(-1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check vertical viewport bounds, or narrow the test claim.

Lines 236-245 only check left and right bounds. A value box below the phone viewport passes this test. If full viewport visibility is required, also check top and bottom against the viewport height. Otherwise, state that this test checks horizontal clipping only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/viewer.spec.ts` around lines 236 - 245, Update the viewport bounds
assertion loop in the viewer test to validate vertical visibility as well:
obtain the viewport height and assert each valueBox.top is at least -1 and
valueBox.bottom is no greater than viewport height plus 1. If the intended
contract is only horizontal clipping, instead narrow the test comment and
assertions to explicitly describe horizontal bounds.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59c2d3c9d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Guards the rich diff library (@pierre/diffs), the heaviest deferred
// chunk. Shiki's engine makes it ~860 KiB gzipped; always lazy-loaded.
// A meaningful jump means a version bump pulled in more — confirm first.
importKeyParts: ["components/renderers/diff-renderer", "@pierre/diffs"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the budget against the renderer loadable entry

After a build, check:build-budgets cannot find this entry: @pierre/diffs is now a static import through pierre-react.ts, while the relevant loadable-manifest key comes from artifact-stage.tsx dynamically importing diff-renderer.tsx. No key therefore contains both configured substrings, so getLoadableFiles throws No react-loadable entry matched, breaking both npm run check and npm run test:ci; match the artifact-stage/diff-renderer loadable entry instead.

Useful? React with 👍 / 👎.

Comment on lines +395 to +397
<span className="bench-cell stat-item">
kind <span className="stat-value">{activeArtifact.kind}</span>
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the artifact-kind narration from viewer chrome

Whenever an artifact is open, this adds self-evident chrome such as kind markdown or kind csv, even though the owner-established UI contract explicitly prohibits artifact-kind narration and requires showing the real filename or nothing. The filename is already rendered immediately above, so omit this stat rather than duplicating the renderer identity.

AGENTS.md reference: AGENTS.md:L258-L260

Useful? React with 👍 / 👎.

Comment on lines +238 to +239
for (const sample of SAMPLES) {
const link = createGeneratedArtifactLink(sample.draft, PREVIEW_BASE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Await the codec-aware sample encoder

For samples requesting arx3, arx4, or any other explicit codec, this synchronous helper ignores draft.codec and calls the sync encoder without options, which can only select plain/lz/deflate. Consequently npm run generate:samples does not generate the codecs declared by its sample drafts; use the async link creator so those requested codec fixtures are actually exercised.

Useful? React with 👍 / 👎.

Comment thread package.json
"check": "npm run lint && npm run test && npm run bench:codecs && npm run typecheck && npm run build && npm run check:build-budgets",
"check:build-budgets": "node scripts/check-build-budgets.mjs",
"generate:samples": "npx tsx scripts/generate-sample-artifacts.mjs",
"shots:overnight": "RUN_OVERNIGHT=1 playwright test tests/e2e/overnight.spec.ts",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point the overnight script at an existing spec

Running npm run shots:overnight always fails because tests/e2e/overnight.spec.ts does not exist; a repository-wide search finds only viewer.spec.ts, visual.spec.ts, and arx4-determinism.spec.ts under tests/e2e. Add the intended spec or change this command to target the actual screenshot suite.

Useful? React with 👍 / 👎.

Comment thread scripts/bench-codecs.mjs
dependencies: {
"@codemirror/view": "^6.38.2",
"@git-diff-view/react": "^0.1.1",
"@pierre/diffs": "^1.3.3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refresh the codec baseline with the changed fixture

Changing the benchmark corpus from @git-diff-view/react to this string without updating scripts/bench-baseline.json makes npm run bench:codecs fail deterministically: the json-package rows regress by 1.45–2.49% and the command exits 1, whereas the parent version of this script passes against the same dictionaries and baseline. Regenerate the committed baseline for the intended corpus—or leave the benchmark fixture stable—so the documented npm run check validation can complete.

AGENTS.md reference: AGENTS.md:L185-L193

Useful? React with 👍 / 👎.

Comment on lines +463 to +466
<MultiFileDiff
oldFile={{ name: fileName, contents: renderedDiff.oldContent }}
newFile={{ name: fileName, contents: renderedDiff.newContent }}
options={fileDiffOptions}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the diff language hint to Pierre

For diff artifacts with an extensionless or missing filename, or where language intentionally overrides the extension, the migration now drops the public DiffArtifact.language hint entirely: neither the patch component nor these old/new file descriptors receives it. Those valid payloads therefore lose the syntax-aware highlighting that the previous renderer obtained through detectCodeLanguage; thread the explicit hint into Pierre's file/options metadata for both rendering paths.

Useful? React with 👍 / 👎.

Comment thread src/app/layout.tsx
const display = Fraunces({
subsets: ["latin"],
const display = localFont({
src: [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ship the font license notices with the binaries

The newly vendored Manrope, SUSE, and Spline Sans Mono WOFF2 files are SIL OFL fonts, but a repository-wide search finds no OFL or upstream copyright notice—only the project's MIT LICENSE. Redistributing these binaries without their accompanying OFL and copyright terms does not satisfy the fonts' redistribution conditions; add the upstream license/notices and document their provenance before release.

AGENTS.md reference: AGENTS.md:L206-L214

Useful? React with 👍 / 👎.

- Document the overnight UI redesign and review risks
- Add gated Playwright screenshots across artifact kinds, themes, and viewports

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/e2e/overnight.spec.ts (1)

73-73: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use one deterministic screenshot-readiness barrier.

tests/e2e/helpers.ts Lines [31]-[61] already wait for renderer readiness, fonts, and two animation frames. captureSpotCheck then adds a fixed 350 ms delay at Line [73], while the JSON path screenshots immediately after readiness at Lines [123]-[124]. If the delay is needed for CSS transitions, JSON captures can record an intermediate state. If it is not needed, generic captures pay an unnecessary delay. Centralize the transition-completion check and use it for both paths.

Also applies to: 123-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/overnight.spec.ts` at line 73, Remove the fixed 350 ms wait from
captureSpotCheck and centralize a deterministic transition-completion readiness
barrier in the shared screenshot flow. Apply the same barrier after
renderer/font/animation readiness for both captureSpotCheck and the JSON-path
screenshots, ensuring neither path captures an intermediate CSS-transition state
or incurs an unnecessary unconditional delay.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MORNING.md`:
- Around line 60-62: Update the “Lint / typecheck / unit / bench” section in
MORNING.md beside npm run check to document that the pre-existing codec
benchmark failure on main is expected, while lint, typecheck, unit tests, and
other checks must still pass.
- Line 3: Update the scope summary in MORNING.md to accurately mention the
migration from `@git-diff-view/react` to `@pierre/diffs`, related dependency
changes, and AGENTS.md/docs updates, or explicitly limit the note to the
bench-chrome cohort so it does not claim there was no renderer rewrite or
documentation work.
- Around line 49-50: Update the light-mode --text-soft token in globals.css to a
darker value that achieves at least 4.5:1 contrast against --page-bg for normal
text, then revise the corresponding MORNING.md note to document the corrected
contrast and remove the deferred follow-up language before calling the redesign
ship-ready.

In `@tests/e2e/overnight.spec.ts`:
- Around line 111-128: Add a mobile 390x844 case to the “json bundle (arx
showcase) desktop” test, matching the existing mobile artifact checks and
capturing the JSON artifact screenshot for each theme; if mobile coverage is
intentionally excluded, explicitly document that decision instead.

---

Nitpick comments:
In `@tests/e2e/overnight.spec.ts`:
- Line 73: Remove the fixed 350 ms wait from captureSpotCheck and centralize a
deterministic transition-completion readiness barrier in the shared screenshot
flow. Apply the same barrier after renderer/font/animation readiness for both
captureSpotCheck and the JSON-path screenshots, ensuring neither path captures
an intermediate CSS-transition state or incurs an unnecessary unconditional
delay.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fd0e023-9cc6-4ac2-a53e-bd7f311d9420

📥 Commits

Reviewing files that changed from the base of the PR and between 59c2d3c and c21fecb.

📒 Files selected for processing (2)
  • MORNING.md
  • tests/e2e/overnight.spec.ts

Comment thread MORNING.md
@@ -0,0 +1,85 @@
# Overnight redesign — morning review

The fused world ("Creator Hardware Bench" skeuomorphism + Japanese density + QRH/CR‑39 engraved-panel grammar) is now inked across the app. Payload protocol, renderers, colors-in-code (rainbow brackets), and `.dark` theme still work — this is a *chrome* redesign with functional content tweaks, not a renderer rewrite.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the scope summary for the renderer migration.

The supplied PR scope includes migration from @git-diff-view/react to @pierre/diffs, plus dependency and documentation updates. This conflicts with the statements that the change is “not a renderer rewrite,” that there are “no dependency changes,” and that no AGENTS.md or docs/ updates are required. Scope this note to the bench-chrome cohort or list the migration and documentation changes.

Also applies to: 82-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MORNING.md` at line 3, Update the scope summary in MORNING.md to accurately
mention the migration from `@git-diff-view/react` to `@pierre/diffs`, related
dependency changes, and AGENTS.md/docs updates, or explicitly limit the note to
the bench-chrome cohort so it does not claim there was no renderer rewrite or
documentation work.

Comment thread MORNING.md
Comment on lines +49 to +50
1. **Contrast, light mode.** `--text-soft #7d7466` on `--page-bg #ebe7dd` is right at WCAG AA for 14px regular. Mono-pill labels pass (they now use `--text-primary`), but the small uppercase kicker `--text-soft` only just clears 4.5:1.
2. **`#ebe7dd` vs `#14110d`.** Verify light/dark transition doesn't look like a blinding flash on system-dark defaults.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat the light-mode contrast failure as a release issue.

#7d7466 on #ebe7dd is approximately 3.7:1, below the 4.5:1 WCAG AA threshold for normal text. Line [80] defers the darker token to an optional follow-up while Line [78] calls the redesign ship-ready. Update src/app/globals.css and this note before release, or restrict --text-soft to large text.

Suggested wording
-1. **Contrast, light mode.** `--text-soft `#7d7466`` on `--page-bg `#ebe7dd`` is right at WCAG AA for 14px regular.
+1. **Contrast, light mode.** `--text-soft `#7d7466`` on `--page-bg `#ebe7dd`` is approximately 3.7:1 and fails WCAG AA for normal text. Apply a darker token before release.

Also applies to: 78-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MORNING.md` around lines 49 - 50, Update the light-mode --text-soft token in
globals.css to a darker value that achieves at least 4.5:1 contrast against
--page-bg for normal text, then revise the corresponding MORNING.md note to
document the corrected contrast and remove the deferred follow-up language
before calling the redesign ship-ready.

Comment thread MORNING.md
Comment on lines +60 to +62
# 2. Lint / typecheck / unit / bench
npm run check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the expected benchmark exception beside npm run check.

This step includes bench, while the PR scope reports a pre-existing codec benchmark failure on main. State the expected failure and the checks that must still pass. Otherwise, a failed check can look like a redesign regression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MORNING.md` around lines 60 - 62, Update the “Lint / typecheck / unit /
bench” section in MORNING.md beside npm run check to document that the
pre-existing codec benchmark failure on main is expected, while lint, typecheck,
unit tests, and other checks must still pass.

Comment on lines +111 to +128
test("json bundle (arx showcase) desktop", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
for (const theme of ["light", "dark"] as const) {
await setTheme(page, theme);
await goToHash(page);
await stabilizePage(page);
await waitForViewerState(page, "empty");
await page.getByRole("link", { name: /arx showcase/i }).click();
await waitForViewerState(page, "artifact");
await page
.getByRole("button", { name: /Open artifact Artifact manifest/i })
.click();
await waitForRendererReady(page, "json");
await page.screenshot({
fullPage: true,
path: path.join(SHOT_DIR, `json-${theme}-desktop.png`),
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add mobile coverage for the JSON artifact.

The other artifact checks include a mobile case at Lines [106]-[108], but the JSON test runs only at the desktop viewport. Add a 390x844 JSON capture, matching the other mobile checks, or state that JSON is intentionally excluded from mobile coverage.

Suggested addition
+test("json bundle (arx showcase) mobile", async ({ page }) => {
+  await page.setViewportSize({ width: 390, height: 844 });
+  await setTheme(page, "light");
+  await goToHash(page);
+  await stabilizePage(page);
+  await waitForViewerState(page, "empty");
+  await page.getByRole("link", { name: /arx showcase/i }).click();
+  await waitForViewerState(page, "artifact");
+  await page.getByRole("button", { name: /Open artifact Artifact manifest/i }).click();
+  await waitForRendererReady(page, "json");
+  await page.screenshot({
+    fullPage: true,
+    path: path.join(SHOT_DIR, "json-light-mobile.png"),
+  });
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("json bundle (arx showcase) desktop", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
for (const theme of ["light", "dark"] as const) {
await setTheme(page, theme);
await goToHash(page);
await stabilizePage(page);
await waitForViewerState(page, "empty");
await page.getByRole("link", { name: /arx showcase/i }).click();
await waitForViewerState(page, "artifact");
await page
.getByRole("button", { name: /Open artifact Artifact manifest/i })
.click();
await waitForRendererReady(page, "json");
await page.screenshot({
fullPage: true,
path: path.join(SHOT_DIR, `json-${theme}-desktop.png`),
});
}
test("json bundle (arx showcase) desktop", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
for (const theme of ["light", "dark"] as const) {
await setTheme(page, theme);
await goToHash(page);
await stabilizePage(page);
await waitForViewerState(page, "empty");
await page.getByRole("link", { name: /arx showcase/i }).click();
await waitForViewerState(page, "artifact");
await page
.getByRole("button", { name: /Open artifact Artifact manifest/i })
.click();
await waitForRendererReady(page, "json");
await page.screenshot({
fullPage: true,
path: path.join(SHOT_DIR, `json-${theme}-desktop.png`),
});
}
});
test("json bundle (arx showcase) mobile", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await setTheme(page, "light");
await goToHash(page);
await stabilizePage(page);
await waitForViewerState(page, "empty");
await page.getByRole("link", { name: /arx showcase/i }).click();
await waitForViewerState(page, "artifact");
await page.getByRole("button", { name: /Open artifact Artifact manifest/i }).click();
await waitForRendererReady(page, "json");
await page.screenshot({
fullPage: true,
path: path.join(SHOT_DIR, "json-light-mobile.png"),
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/overnight.spec.ts` around lines 111 - 128, Add a mobile 390x844
case to the “json bundle (arx showcase) desktop” test, matching the existing
mobile artifact checks and capturing the JSON artifact screenshot for each
theme; if mobile coverage is intentionally excluded, explicitly document that
decision instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant