Rebuild UI in the bench-instrument design language - #109
Conversation
Ground-up visual rebuild toward the decided direction: QRH procedural grammar, Japanese density, and skeuomorphic material (bone keycap chrome over a dark charcoal chassis). - globals.css: single token spine swapped to the bench palette with keycap/recess material tokens; keycap buttons with machined depth and one orange commit action; link creator restyled as a numbered five-step operations card with a carbon-transfer output frame and READY/CAUTION/ WARNING states; samples become an indexed sidebar; viewer toolbar gets an LED plus filename-primary layout; content bodies (code, diff, csv, json) stay dark charcoal in both themes, including the vendored diff palette; self-narrating chrome labels are hidden - layout.tsx: fonts swapped to Manrope / SUSE / Spline Sans Mono via next/font, same CSS variables - viewer-shell.tsx: marketing hero, trust pills, and bento sections removed; the link creator is the first viewport in a two-column layout with the samples sidebar; real chassis footer with wordmark, zero-retention tagline, and nav links - fragment-details-disclosure.tsx: expanded by default; its unit test updated to match the intentional change No payload, codec, or renderer behavior changes. Lint, typecheck, 324 unit tests, build budgets, and the behavioral e2e suite all pass; visual snapshots are deliberately not regenerated yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sync configuration, conventions, authored component previews, and the distilled design vision that back the agent-render-skeumorphism project on claude.ai/design. Machine state (.ds-sync, ds-bundle, caches) stays gitignored; eslint ignores the generated bundle output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
💤 Files with no reviewable changes (7)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe change adds design-sync tooling and preview fixtures, rebuilds the viewer homepage and renderer styling, updates typography and responsive layouts, and changes fragment metadata disclosure to start expanded with updated test coverage. ChangesAgent-render redesign
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ViewerShell
participant LinkCreator
participant SampleLinks
participant FragmentDetailsDisclosure
participant ThemeToggle
ViewerShell->>LinkCreator: render link creator
ViewerShell->>SampleLinks: render sample links
ViewerShell->>FragmentDetailsDisclosure: render fragment metadata
ViewerShell->>ThemeToggle: render theme toggle
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Deploying agent-render with
|
| Latest commit: |
afc0872
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://18e3a535.agent-render.pages.dev |
| Branch Preview URL: | https://claude-design-rebuild.agent-render.pages.dev |
|
| Filename | Overview |
|---|---|
| src/components/viewer-shell.tsx | Rebuilds the empty, fault, artifact, navigation, and footer layouts without changing the fragment decode pipeline. |
| src/app/globals.css | Replaces the visual token system and component styling with bone-and-charcoal bench-instrument presentation. |
| src/components/renderers/json-renderer.tsx | Adds source-preserving syntax highlighting to valid and malformed JSON raw views. |
| src/components/viewer/fragment-details-disclosure.tsx | Keeps fragment metadata expanded by default and simplifies duplicated status content. |
| src/app/layout.tsx | Replaces the application font families while retaining the existing theme initialization and static layout. |
| .design-sync/harvest-css.mjs | Harvests the current Next export stylesheet and lifts generated font variables for external design previews. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Open application] --> B{Fragment present?}
B -- No --> C[Link creator and sample index]
C --> D[Generate or select fragment]
D --> E[Decode fragment locally]
B -- Yes --> E
E --> F{Decode result}
F -- Fault --> G[Fault placard and fragment details]
F -- Ready --> H[Artifact selector]
H --> I[Markdown, code, diff, CSV, or JSON renderer]
Reviews (4): Last reviewed commit: "Syntax-highlight the raw JSON source vie..." | Re-trigger Greptile
| - Text: `--text-primary`, `--text-muted`, `--text-soft` | ||
| - Accent and semantic: `--accent`, `--accent-strong`, `--accent-secondary`, `--success`, `--warning`, `--danger` | ||
| - Depth and shape: `--shadow-lg`, `--shadow-md`, `--radius-xl`, `--radius-lg` (radii are deliberately sharp, 2px) | ||
| - Fonts: `--font-display` (Fraunces), `--font-sans` (IBM Plex Sans), `--font-mono` (IBM Plex Mono). Mono is for data and labels, uppercase with letter-spacing for the label register. |
There was a problem hiding this comment.
[WARNING]: Stale font references that contradict this PR's own changes.
layout.tsx in this same PR replaces Fraunces/IBM Plex with Manrope/SUSE/Spline Sans Mono (and globals.css swaps the warm-ivory palette for the bench-instrument bone/charcoal spine). This doc is the source-of-truth consumption contract for the design-sync agent (guidelinesGlob: ["docs/*.md"]), but it still tells that agent the current app ships Fraunces/IBM Plex, and docs/design-vision.md:3/:25 and .design-sync/NOTES.md repeat the same stale claim. As a result the freshly-shipped design inputs describe an app state that no longer exists in src/.
| - Fonts: `--font-display` (Fraunces), `--font-sans` (IBM Plex Sans), `--font-mono` (IBM Plex Mono). Mono is for data and labels, uppercase with letter-spacing for the label register. | |
| - Fonts: `--font-display` (Manrope), `--font-sans` (SUSE), `--font-mono` (Spline Sans Mono). Mono is for data and labels, uppercase with letter-spacing for the label register. |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous Review Summaries (3 snapshots, latest commit 9973cd3)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 9973cd3)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (10 files)
Fix these issues in Kilo Cloud Previous review (commit dbb5bda)Status: No Issues Found | Recommendation: Merge Review scope: incremental commit Files Reviewed (3 files)
Previous review (commit 1dde4ad)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (27 files)
Reviewed by deepseek-v4-flash · Input: 26.3K · Output: 9.6K · Cached: 470.5K |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (10)
src/app/globals.css (7)
553-557: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
column-reversemakes visual order differ from DOM order.
.sample-link > div:first-childreverses the children of the sample card. Insrc/components/home/sample-links.tsxthe DOM order is kicker row, thenh4, thenp. The rendered order becomes title above kicker. Assistive technology follows DOM order, so the announced order no longer matches the visual order. Reorder the markup inSampleLinksinstead of reversing it in CSS.🤖 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/globals.css` around lines 553 - 557, Remove flex-direction: column-reverse from .sample-link > div:first-child and reorder the children in the SampleLinks markup so the DOM order matches the intended visual order: title, kicker row, then description.
115-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUnclassed inline links lose every non-hover affordance.
The global rule sets link color to
--text-primary, which is the body text color, and removes the underline. An inline link inside prose is then visually identical to the surrounding text until the pointer hovers it. Keyboard users and touch users get no cue. Chrome links such as.ar-nav-linkand.markdown-linkcarry their own styling, so the risk is limited to unclassed links, but the default should still be distinguishable. Consider keeping an underline (or an accent color) for links that are not inside a nav or toolbar.🤖 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/globals.css` around lines 115 - 122, Update the global unclassed `a` rule so inline links remain visibly distinguishable without hover, by preserving an underline or applying an accent color. Keep existing specialized styles for `.ar-nav-link` and `.markdown-link` unaffected, and retain the current hover behavior.
69-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
--warningis not redefined in.dark.
:rootsets--warning:#c65a1e``, which equals the light--accent. `.dark` overrides `--accent` to `#e0722c` but leaves `--warning` at the light value. Any dark-mode surface that uses `--warning` therefore renders the darker light-mode orange on the charcoal chassis. Add the dark override to keep the single action orange consistent.♻️ Proposed token addition
--accent: `#e0722c`; --accent-strong: `#b1541a`; --accent-secondary: `#b0a794`; --success: `#63bb92`; + --warning: `#e0722c`; --danger: `#d96a5c`;🤖 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/globals.css` around lines 69 - 96, Add a --warning declaration within the .dark theme block alongside --accent, using the dark-mode action orange value so warning surfaces remain consistent with the dark accent color while leaving the existing light-theme token unchanged.
1002-1011: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStatus words live only in CSS generated content.
READY,NOTE /,CAUTION, andWARNINGare produced bycontent:on pseudo-elements. Generated content is not translatable, it is exposed inconsistently by screen readers, and it disappears if the stylesheet fails to load. These words carry the severity of the state, so they belong in the DOM next to the message.Render the label as a real element in the component and keep the pseudo-element only for the LED dot and other decoration.
Also applies to: 1051-1082
🤖 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/globals.css` around lines 1002 - 1011, Move the status labels READY, NOTE /, CAUTION, and WARNING from CSS pseudo-element content into real DOM elements rendered by the associated component, placing each label next to its state message with appropriate accessible text. Remove the corresponding status-word content declarations from the pseudo-elements while preserving their LED dots and other decorative styling.
1112-1141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPositional selectors couple styling to component DOM order.
.artifact-toolbar-left > span:nth-of-type(2)and:nth-of-type(3)select the filename and the meta text by index. The inline comment states that a hiddenmono-pillspan must stay in the DOM only to keep the count correct. Any markup reorder in the toolbar component silently restyles the wrong element, and no test catches it. The same pattern appears at.sample-link > div:first-child(Line 553),.home-generator-grid > div:first-child(Line 597), and.creator-kind-card:first-of-type(Line 691).Prefer explicit hooks, for example
data-role="filename"or a.artifact-toolbar-filenameclass emitted by the component. This keeps the CSS stable and removes the need to keep a hidden element only for counting.🤖 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/globals.css` around lines 1112 - 1141, Replace the positional selectors in the artifact toolbar, especially .artifact-toolbar-left > span:nth-of-type(2) and :nth-of-type(3), with explicit filename and metadata hooks emitted by the component, then remove the hidden-element counting dependency and update the CSS to target those hooks. Apply the same explicit-hook approach to .sample-link > div:first-child, .home-generator-grid > div:first-child, and .creator-kind-card:first-of-type.
2554-2572: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe chassis styling is defined twice: as
.ar-*classes and as inline Tailwind utilities. Both files express the same visual contract, so a palette or spacing change needs two edits and the two definitions can drift. The mobile override atsrc/app/globals.cssLine 2775 needs!importantonly because the inline utilities win the cascade, and.ar-header-inner,.ar-footer-inner,.ar-footer-nav, and.ar-footer-strip-innerare defined but never applied.
src/app/globals.css#L2554-L2572: keep.ar-page-pad,.ar-site-header, and.ar-header-inneras the single source of truth, or delete the rules that the shell never applies. Then remove the!importantat Line 2775.src/components/viewer-shell.tsx#L520-L555: apply.ar-footer-inner,.ar-footer-nav,.ar-wordmark,.ar-tagline, and.ar-footer-strip-inner, and delete the duplicatedbg-[#14110d],text-[#dcd5c5],text-[#6e6656],text-[#b8ae9c], andborder-[#cfc6b41f]utilities.🤖 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/globals.css` around lines 2554 - 2572, The chassis styles are duplicated between globals.css and inline utilities. In src/app/globals.css lines 2554-2572, keep .ar-page-pad, .ar-site-header, and .ar-header-inner as the source of truth or remove unused rules, then remove the !important from the mobile override at line 2775. In src/components/viewer-shell.tsx lines 520-555, apply .ar-footer-inner, .ar-footer-nav, .ar-wordmark, .ar-tagline, and .ar-footer-strip-inner, and remove the duplicated bg-[`#14110d`], text-[`#dcd5c5`], text-[`#6e6656`], text-[`#b8ae9c`], and border-[`#cfc6b41f`] utilities.
244-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winChrome is removed with
display: noneinstead of at the source.Fourteen rules hide existing markup: the samples kicker and description, the sample pill, icon and description, the generator kicker, the result-head kicker and pill, the toolbar status pill, the content-section kicker, the switcher kicker, the code renderer language pill and read-only label, and the JSON toolbar pill. The elements still render, still ship in the HTML, and still need maintenance in two places.
The design brief states that this chrome must not exist. Delete the markup in the owning components and drop these rules. This also removes the need for the hidden-span workaround noted at Line 1126.
Also applies to: 1149-1151, 1605-1616
🤖 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/globals.css` around lines 244 - 246, Remove the source markup for the samples section kicker and all other listed chrome elements from their owning components, rather than hiding them with CSS. Delete the corresponding rules including .home-samples-section .section-header .section-kicker and the rules at the additional referenced locations, along with the hidden-span workaround near the noted area.src/components/viewer-shell.tsx (2)
364-374: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winThe wordmark link has no real fallback destination.
href="#"combined withpreventDefault()means the anchor only works after hydration. Before hydration, or if the client bundle fails, a click appends#to the URL and adds a history entry instead of returning to the homepage. Point the anchor at the real home path and keep the handler for the in-page path.♻️ Proposed change
+const homePath = withBasePath("/");<a - href="#" + href={homePath} onClick={(e) => { e.preventDefault(); handleGoHome(); }}🤖 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 364 - 374, Update the wordmark anchor in the viewer shell to use the real homepage path instead of `href="#"`, while preserving the existing `onClick` handler and `handleGoHome()` behavior for the in-page navigation path.
35-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTwo footer links resolve to near-identical accessible names.
footerNavLinkscontains an internal "Security" entry pointing atsecurityPathand an external entry whose accessible name becomes "Safety Security page", pointing atarchitecture.md#security-posture. In a screen-reader link list the two read almost the same but lead to different destinations. The comment at Line 51 states the prefix exists only to preserve a/safety.*security page/iname contract from a removed bento card.Choose a label that describes the destination, for example "Security posture (docs)", and update the e2e expectation. Shaping user-visible names around a test regex is fragile.
srPrefix: ""on four entries is also dead data. Make the field optional and omit it where it is empty.♻️ Proposed shape
-const footerNavLinks = [ - { href: securityPath, label: "Security", external: false, srPrefix: "" }, - { href: urlExplainerPath, label: "URL explainer", external: false, srPrefix: "" }, +const footerNavLinks: readonly { + href: string; + label: string; + external: boolean; + srPrefix?: string; +}[] = [ + { href: securityPath, label: "Security", external: false }, + { href: urlExplainerPath, label: "URL explainer", external: false },🤖 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 35 - 57, Update footerNavLinks so the external architecture documentation entry uses a destination-specific label such as “Security posture (docs)” instead of the “Safety ” srPrefix workaround, and update the related e2e accessible-name expectation. Make srPrefix optional and remove it from entries where it is empty, preserving it only where required by an existing accessibility contract.tests/components/fragment-details-disclosure.test.tsx (1)
21-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe variable named
summaryis not the<summary>element.
screen.getByText(/Codec, budget, and hash preview/i)returns the inner<span class="artifact-disclosure-title">. The click still toggles the disclosure because the event bubbles to the<summary>ancestor, so the test passes today. The name is misleading, and the test depends on the current nesting: if the title span moves out of the<summary>, the click stops toggling and the failure message points at a text query rather than at the real cause.Target the
<summary>element directly. Also assert that the codec and the hash preview collapse, so the assertion set matches the expanded set at Lines 23–25.💚 Proposed test change
- const summary = screen.getByText(/Codec, budget, and hash preview/i); + const disclosure = screen.getByTestId("fragment-disclosure"); + const summary = disclosure.querySelector("summary"); + expect(summary).not.toBeNull(); expect(screen.getByText("Decoded")).toBeVisible(); expect(screen.getByText("lz")).toBeVisible(); expect(screen.getByText(/#agent-render=v1.lz.abc/i)).toBeVisible(); - await userEvent.click(summary); + await userEvent.click(summary as HTMLElement); expect(screen.getByText("Decoded")).not.toBeVisible(); + expect(screen.getByText("lz")).not.toBeVisible(); + expect(screen.getByText(/#agent-render=v1.lz.abc/i)).not.toBeVisible();🤖 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/fragment-details-disclosure.test.tsx` around lines 21 - 28, Update the disclosure test to query the actual HTML summary element rather than assigning the inner title span to summary, using an appropriate role or selector tied to the disclosure text. Keep the existing expanded-state assertions, then after clicking assert that “Decoded,” “lz,” and the hash preview are all not visible.
🤖 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 @.design-sync/conventions.md:
- Line 3: Update the design-vision reference in the conventions documentation to
use docs/design-vision.md instead of guidelines/docs/design-vision.md,
preserving the surrounding design guidance and behavior/data-contract
requirements.
In @.design-sync/NOTES.md:
- Line 44: Update the stale design-sync guidance across
.design-sync/NOTES.md:44-44, .design-sync/conventions.md:18-18, and
.design-sync/previews/ViewerShell.tsx:7-7: replace the old Fraunces/IBM Plex
font descriptions with the current Manrope, SUSE, and Spline Sans Mono mappings
and variables, and revise the ViewerShell description to cover the five-step
link creator, indexed samples sidebar, and chassis-style footer instead of the
removed hero and bento grid.
In @.design-sync/previews/FragmentDetailsDisclosure.tsx:
- Around line 4-10: Update the collapsed fixture in FragmentDetailsDisclosure so
it renders closed despite the component’s hardcoded open attribute: remove the
redundant Open helper and unsupported fixture, or replace it with a clearly
named forced-collapsed preview that removes open after mount. Preserve the
existing children rendering behavior.
In @.design-sync/previews/ViewerShell.tsx:
- Around line 13-16: Update the useEffect in DecodedFragment to capture the
current URL hash before assigning kickoffHash, then restore that original hash
in the effect cleanup on unmount; preserve the existing setReady behavior.
In `@src/app/globals.css`:
- Line 931: Update the CSS declarations in globals.css to satisfy all three
Stylelint errors: lowercase the currentColor keyword in the border declaration,
and replace both word-break: break-word declarations with overflow-wrap:
anywhere. Verify the hash preview and artifact pre block retain the intended
sizing and wrapping behavior.
- Around line 2069-2077: Update the .artifact-disclosure-summary styles to hide
Safari/WebKit’s native summary marker by adding the appropriate
::-webkit-details-marker rule, while preserving the existing flex layout and
::after plus/minus glyph behavior.
In `@src/app/layout.tsx`:
- Around line 2-23: Update the font imports and SUSE initialization in the
layout module: keep Manrope and Spline_Sans_Mono from next/font/google, replace
the unavailable Google SUSE import with next/font/local (or the existing
self-hosted Fontsource equivalent), and preserve the existing SUSE variable,
weights, and styling configuration.
In `@src/components/viewer-shell.tsx`:
- Around line 428-441: Update ViewerShell so the artifact and fault states each
render an appropriate top-level h1, including adjusting ArtifactStage’s initial
h3 hierarchy. Make the zero-retention security copy visible to sighted users or
link it into the viewer flow, while preserving its accessibility and avoiding
duplicate hidden-only content.
---
Nitpick comments:
In `@src/app/globals.css`:
- Around line 553-557: Remove flex-direction: column-reverse from .sample-link >
div:first-child and reorder the children in the SampleLinks markup so the DOM
order matches the intended visual order: title, kicker row, then description.
- Around line 115-122: Update the global unclassed `a` rule so inline links
remain visibly distinguishable without hover, by preserving an underline or
applying an accent color. Keep existing specialized styles for `.ar-nav-link`
and `.markdown-link` unaffected, and retain the current hover behavior.
- Around line 69-96: Add a --warning declaration within the .dark theme block
alongside --accent, using the dark-mode action orange value so warning surfaces
remain consistent with the dark accent color while leaving the existing
light-theme token unchanged.
- Around line 1002-1011: Move the status labels READY, NOTE /, CAUTION, and
WARNING from CSS pseudo-element content into real DOM elements rendered by the
associated component, placing each label next to its state message with
appropriate accessible text. Remove the corresponding status-word content
declarations from the pseudo-elements while preserving their LED dots and other
decorative styling.
- Around line 1112-1141: Replace the positional selectors in the artifact
toolbar, especially .artifact-toolbar-left > span:nth-of-type(2) and
:nth-of-type(3), with explicit filename and metadata hooks emitted by the
component, then remove the hidden-element counting dependency and update the CSS
to target those hooks. Apply the same explicit-hook approach to .sample-link >
div:first-child, .home-generator-grid > div:first-child, and
.creator-kind-card:first-of-type.
- Around line 2554-2572: The chassis styles are duplicated between globals.css
and inline utilities. In src/app/globals.css lines 2554-2572, keep .ar-page-pad,
.ar-site-header, and .ar-header-inner as the source of truth or remove unused
rules, then remove the !important from the mobile override at line 2775. In
src/components/viewer-shell.tsx lines 520-555, apply .ar-footer-inner,
.ar-footer-nav, .ar-wordmark, .ar-tagline, and .ar-footer-strip-inner, and
remove the duplicated bg-[`#14110d`], text-[`#dcd5c5`], text-[`#6e6656`],
text-[`#b8ae9c`], and border-[`#cfc6b41f`] utilities.
- Around line 244-246: Remove the source markup for the samples section kicker
and all other listed chrome elements from their owning components, rather than
hiding them with CSS. Delete the corresponding rules including
.home-samples-section .section-header .section-kicker and the rules at the
additional referenced locations, along with the hidden-span workaround near the
noted area.
In `@src/components/viewer-shell.tsx`:
- Around line 364-374: Update the wordmark anchor in the viewer shell to use the
real homepage path instead of `href="#"`, while preserving the existing
`onClick` handler and `handleGoHome()` behavior for the in-page navigation path.
- Around line 35-57: Update footerNavLinks so the external architecture
documentation entry uses a destination-specific label such as “Security posture
(docs)” instead of the “Safety ” srPrefix workaround, and update the related e2e
accessible-name expectation. Make srPrefix optional and remove it from entries
where it is empty, preserving it only where required by an existing
accessibility contract.
In `@tests/components/fragment-details-disclosure.test.tsx`:
- Around line 21-28: Update the disclosure test to query the actual HTML summary
element rather than assigning the inner title span to summary, using an
appropriate role or selector tied to the disclosure text. Keep the existing
expanded-state assertions, then after clicking assert that “Decoded,” “lz,” and
the hash preview are all not visible.
🪄 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: 7a569247-8b02-4c9b-bd67-39e7baa8bccf
📒 Files selected for processing (27)
.design-sync/NOTES.md.design-sync/config.json.design-sync/conventions.md.design-sync/ds-entry.ts.design-sync/harvest-css.mjs.design-sync/previews/ArtifactSelector.tsx.design-sync/previews/ArtifactStage.tsx.design-sync/previews/CodeRenderer.tsx.design-sync/previews/CsvRenderer.tsx.design-sync/previews/DiffRenderer.tsx.design-sync/previews/FragmentDetailsDisclosure.tsx.design-sync/previews/JsonRenderer.tsx.design-sync/previews/LinkCreator.tsx.design-sync/previews/MarkdownRenderer.tsx.design-sync/previews/MermaidBlock.tsx.design-sync/previews/SampleLinks.tsx.design-sync/previews/ThemeToggle.tsx.design-sync/previews/ViewerShell.tsx.design-sync/process-shim.ts.gitignoredocs/design-vision.mdeslint.config.mjssrc/app/globals.csssrc/app/layout.tsxsrc/components/viewer-shell.tsxsrc/components/viewer/fragment-details-disclosure.tsxtests/components/fragment-details-disclosure.test.tsx
| @@ -0,0 +1,50 @@ | |||
| # agent-render conventions | |||
|
|
|||
| This library is the component set of a zero-retention artifact viewer app, not a generic UI kit. Read `guidelines/docs/design-vision.md` before designing: it is the owner's target direction (bench-instrument skeuomorphism, QRH procedural grammar, Japanese density) for a ground-up rebuild. The current components carry the old look; keep their behavior and data contracts, push the visuals toward the vision. | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the design-vision path.
guidelines/docs/design-vision.md is not the documented path in this PR. Use docs/design-vision.md so design-sync users can load the visual requirements.
Proposed fix
-Read `guidelines/docs/design-vision.md` before designing
+Read `docs/design-vision.md` before designing📝 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.
| This library is the component set of a zero-retention artifact viewer app, not a generic UI kit. Read `guidelines/docs/design-vision.md` before designing: it is the owner's target direction (bench-instrument skeuomorphism, QRH procedural grammar, Japanese density) for a ground-up rebuild. The current components carry the old look; keep their behavior and data contracts, push the visuals toward the vision. | |
| This library is the component set of a zero-retention artifact viewer app, not a generic UI kit. Read `docs/design-vision.md` before designing: it is the owner's target direction (bench-instrument skeuomorphism, QRH procedural grammar, Japanese density) for a ground-up rebuild. The current components carry the old look; keep their behavior and data contracts, push the visuals toward the vision. |
🤖 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 @.design-sync/conventions.md at line 3, Update the design-vision reference in
the conventions documentation to use docs/design-vision.md instead of
guidelines/docs/design-vision.md, preserving the surrounding design guidance and
behavior/data-contract requirements.
|
|
||
| - Purpose of this sync (Aug 2026): the user is rebuilding agent-render's UI in claude.ai/design. Three prior Kimi K3 sessions failed at it. The decided design direction (from the cursor session, `~/Downloads/kimi-failed-cursor.md`) is "fused bench-instrument world": skeuomorphism + Japanese high density + airplane QRH card. Target project: agent-render-skeumorphism. | ||
| - This repo is an app, not a library: no dist entry, converter runs in synth-entry mode from `src/` with tsconfig `@/*` path aliases. | ||
| - Fonts come from `next/font/google` (Fraunces display, IBM Plex Sans body, IBM Plex Mono). There are no @font-face files in source; harvest compiled CSS + woff2 from `out/_next/static/` after `npm run build`. Font families are exposed as `--font-display`/`--font-sans`/`--font-mono` variables set by Next-generated classes on `<html>`, so the shipped styles need a hand-authored bridge that defines those variables at `:root`. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update stale design-sync visual guidance.
These files describe the pre-redesign font system and homepage. A re-sync can use obsolete visual requirements instead of the new bench-instrument interface.
.design-sync/NOTES.md#L44-L44: Replace the Fraunces and IBM Plex font description with the current Manrope, SUSE, and Spline Sans Mono mapping..design-sync/conventions.md#L18-L18: Replace the old font-family claims with the current font variables and families..design-sync/previews/ViewerShell.tsx#L7-L7: Describe the current five-step link creator, indexed samples sidebar, and chassis-style footer instead of the removed hero and bento grid.
📍 Affects 3 files
.design-sync/NOTES.md#L44-L44(this comment).design-sync/conventions.md#L18-L18.design-sync/previews/ViewerShell.tsx#L7-L7
🤖 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 @.design-sync/NOTES.md at line 44, Update the stale design-sync guidance
across .design-sync/NOTES.md:44-44, .design-sync/conventions.md:18-18, and
.design-sync/previews/ViewerShell.tsx:7-7: replace the old Fraunces/IBM Plex
font descriptions with the current Manrope, SUSE, and Spline Sans Mono mappings
and variables, and revise the ViewerShell description to cover the five-step
link creator, indexed samples sidebar, and chassis-style footer instead of the
removed hero and bento grid.
| /** Opens the component's own <details> after mount, exactly as a click would. */ | ||
| const Open = ({ children }: { children?: unknown }) => { | ||
| const ref = useRef<HTMLDivElement>(null); | ||
| useEffect(() => { | ||
| ref.current?.querySelector("details")?.setAttribute("open", ""); | ||
| }, []); | ||
| return <div ref={ref}>{children as never}</div>; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the collapsed fixture render closed.
FragmentDetailsDisclosure hardcodes open in src/components/viewer/fragment-details-disclosure.tsx:15-62. Therefore, Collapsed renders expanded. Open is also redundant.
Remove the unsupported fixture, or explicitly remove the open attribute after mount and name it as a forced collapsed preview.
Also applies to: 41-50
🤖 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 @.design-sync/previews/FragmentDetailsDisclosure.tsx around lines 4 - 10,
Update the collapsed fixture in FragmentDetailsDisclosure so it renders closed
despite the component’s hardcoded open attribute: remove the redundant Open
helper and unsupported fixture, or replace it with a clearly named
forced-collapsed preview that removes open after mount. Preserve the existing
children rendering behavior.
| useEffect(() => { | ||
| window.location.hash = kickoffHash; | ||
| setReady(true); | ||
| }, []); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restore the previous URL hash on unmount.
DecodedFragment changes global browser state and leaves the sample hash active. A later ViewerShell preview can then render a decoded fragment instead of the empty state.
Proposed fix
useEffect(() => {
+ const previousHash = window.location.hash;
window.location.hash = kickoffHash;
setReady(true);
+ return () => {
+ window.location.hash = previousHash;
+ };
}, []);📝 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.
| useEffect(() => { | |
| window.location.hash = kickoffHash; | |
| setReady(true); | |
| }, []); | |
| useEffect(() => { | |
| const previousHash = window.location.hash; | |
| window.location.hash = kickoffHash; | |
| setReady(true); | |
| return () => { | |
| window.location.hash = previousHash; | |
| }; | |
| }, []); |
🤖 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 @.design-sync/previews/ViewerShell.tsx around lines 13 - 16, Update the
useEffect in DecodedFragment to capture the current URL hash before assigning
kickoffHash, then restore that original hash in the effect cleanup on unmount;
preserve the existing setReady behavior.
| font-weight: 600; | ||
| letter-spacing: 0.18em; | ||
| color: var(--danger); | ||
| border: 2px solid currentColor; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the three Stylelint errors.
Stylelint reports errors at these lines:
- Line 931:
currentColormust be lowercasecurrentcolor(value-keyword-case). - Line 1282 and Line 2057:
word-break: break-wordis deprecated (declaration-property-value-keyword-no-deprecated). Useoverflow-wrap: anywhere.
Note that word-break: break-word and overflow-wrap: anywhere are not exactly equivalent for table and flex sizing. Check the hash preview and the artifact pre block after the change.
🔧 Proposed fixes
- border: 2px solid currentColor;
+ border: 2px solid currentcolor; .artifact-preview pre {
margin: 0;
white-space: pre-wrap;
- word-break: break-word;
+ overflow-wrap: anywhere; .artifact-meta-value {
...
line-height: 1.45;
- word-break: break-word;
+ overflow-wrap: anywhere;
}Also applies to: 1279-1287, 2050-2058
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 931-931: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 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/globals.css` at line 931, Update the CSS declarations in globals.css
to satisfy all three Stylelint errors: lowercase the currentColor keyword in the
border declaration, and replace both word-break: break-word declarations with
overflow-wrap: anywhere. Verify the hash preview and artifact pre block retain
the intended sizing and wrapping behavior.
Source: Linters/SAST tools
| .artifact-disclosure-summary { | ||
| display: flex; | ||
| align-items: center; | ||
| align-items: baseline; | ||
| justify-content: space-between; | ||
| gap: 1rem; | ||
| padding: 0.9rem 1rem; | ||
| gap: 10px; | ||
| padding: 9px 12px; | ||
| cursor: pointer; | ||
| list-style: none; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Safari still shows the native disclosure triangle.
.artifact-disclosure-summary sets list-style: none, and the ::after at Line 2101 supplies the + and − glyphs. Safari and older iOS WebKit do not honor list-style on summary. They render the native triangle in addition to your glyph. Add the WebKit pseudo-element rule and display: flex already present keeps the layout.
🔧 Proposed fix
cursor: pointer;
list-style: none;
}
+
+.artifact-disclosure-summary::-webkit-details-marker {
+ display: none;
+}Also applies to: 2100-2110
🤖 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/globals.css` around lines 2069 - 2077, Update the
.artifact-disclosure-summary styles to hide Safari/WebKit’s native summary
marker by adding the appropriate ::-webkit-details-marker rule, while preserving
the existing flex layout and ::after plus/minus glyph behavior.
| import { Manrope, Spline_Sans_Mono, SUSE } from "next/font/google"; | ||
| import type { ReactNode } from "react"; | ||
| import { getCanonicalSiteUrl, getMetadataBase } from "@/lib/site/canonical-base"; | ||
| import "./globals.css"; | ||
|
|
||
| const display = Fraunces({ | ||
| const display = Manrope({ | ||
| subsets: ["latin"], | ||
| variable: "--font-display", | ||
| weight: ["500", "600", "700"], | ||
| weight: ["500", "600", "700", "800"], | ||
| }); | ||
|
|
||
| const sans = IBM_Plex_Sans({ | ||
| const sans = SUSE({ | ||
| subsets: ["latin"], | ||
| variable: "--font-sans", | ||
| weight: ["400", "500", "600", "700"], | ||
| weight: ["400", "500", "600"], | ||
| }); | ||
|
|
||
| const mono = IBM_Plex_Mono({ | ||
| const mono = Spline_Sans_Mono({ | ||
| subsets: ["latin"], | ||
| variable: "--font-mono", | ||
| weight: ["400", "500", "600"], | ||
| style: ["normal", "italic"], |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Confirm the font families, weights, and styles exist in the installed next/font/google typings and in the Google Fonts metadata.
set -euo pipefail
fd -t f 'index.d.ts' node_modules/next/font/google 2>/dev/null | head -n 5 | while IFS= read -r f; do
rg -n -A 6 'declare function (SUSE|Manrope|Spline_Sans_Mono)' "$f" | head -n 120
done
for family in Manrope SUSE "Spline+Sans+Mono"; do
echo "== ${family} =="
curl -sSfL --max-time 20 "https://fonts.googleapis.com/css2?family=${family}:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&display=swap" \
-H 'User-Agent: Mozilla/5.0' | head -n 20 || echo "request failed for ${family}"
doneRepository: baanish/agent-render
Length of output: 158
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package files =="
fd -t f '^(package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$' . | sed 's#^\./##'
echo "== next/font/google declarations =="
git ls-files 'node_modules/next/font/**' 'node_modules/next/types/**' 2>/dev/null | head -n 50 || true
node - <<'JS'
const fs = require('fs');
const path = require('path');
for (const name of ['SUSE', 'Manrope', 'Spline_Sans_Mono']) {
let found = false;
for (const root of [path.join(process.cwd(),'node_modules')]) {
for (const file of Object.values(require('module').findSourceMaps ? require('module').findSourceMaps : {}).filter(Boolean)) {})
}
}
JS
echo "== dependency versions =="
if [ -f package.json ]; then
node - <<'JS'
const p = require('./package.json');
console.log(JSON.stringify({
dependencies: p.dependencies,
devDependencies: p.devDependencies,
}, null, 2));
JS
fi
echo "== direct manifest search =="
rg -n '"next"|next/font/google|SUSE|Spline_Sans_Mono' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true
echo "== Google Fonts requests =="
for family in Manrope SUSE "Spline+Sans+Mono"; do
echo "== ${family} =="
curl -sSfL --max-time 20 "https://fonts.googleapis.com/css2?family=${family}:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&display=swap" \
-H 'User-Agent: Mozilla/5.0' | tail -n 3 || echo "request failed for ${family}"
doneRepository: baanish/agent-render
Length of output: 1064
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package-lock next entries =="
python3 - <<'PY'
import json, re
with open('package-lock.json', encoding='utf-8') as f:
data=json.load(f)
for path, info in data.get('packages', {}).items():
if '/next' in path or 'next/font/google' in path:
print(path, info.get('version'), info.get('resolved',''))
PY
echo "== manifest deps =="
node - <<'JS'
const p=require('./package.json');
console.log('dependencies:', p.dependencies&&JSON.stringify(p.dependencies,null,2));
console.log('devDependencies:', p.devDependencies&&JSON.stringify(p.devDependencies,null,2));
JS
echo "== google font metadata requests =="
python3 - <<'PY'
import urllib.request
for name in ['Manrope','SUSE','Spline Sans Mono']:
url='https://fonts.google.com/metadata/fonts?key=fonts.googleapis.com&family=' + urllib.parse.quote(name.replace(' ','+'))
try:
with urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'}), timeout=20) as r:
data=r.read().decode('utf-8')[:500].replace('\n',' ')
weights=[]
weights_re=re.search(r'"weight"\s*:\s*\[(.*?)\]', data)
if weights_re: weights=weights_re.group(1).split(',')[:10]
print(name,'OK', 'weights=', weights[:8])
except Exception as e:
print(name,'ERR', e)
PY
echo "== CSS request status for italic weights =="
for family in Manrope SUSE "Spline+Sans+Mono"; do
echo "== ${family} =="
curl -sSfL --max-time 20 "https://fonts.googleapis.com/css2?family=${family}:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&display=swap" \
-H 'User-Agent: Mozilla/5.0' | sed -n '1,8p' || echo "request failed for ${family}"
doneRepository: baanish/agent-render
Length of output: 2940
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lock resolved package metadata for each font =="
python3 - <<'PY'
import json, urllib.request
with open('package-lock.json', encoding='utf-8') as f:
lock=json.load(f)
for key, pkg in lock.get('packages', {}).items():
if 'next/font/google' in key.lower() or key == 'node_modules/next/font/google' or key.endswith('/next/font/google'):
print(key, pkg.get('version'))
if pkg.get('resolved'):
try:
data=json.load(urllib.request.urlopen(pkg['resolved'].replace('/'+pkg['version']+'',''), timeout=20))
except Exception as e:
print(' fetch_err', e)
data=None
print(' export declarations:')
for name in ['SUSE','Manrope','Spline_Sans_Mono']:
print(' ' + name, 'declared=', data and name in data.get('declarations',{}) or False)
PY
echo "== exact import request for `@next/font/google` typings source =="
python3 - <<'PY'
import urllib.request
for name in ['SUSE','Manrope','Spline_Sans_Mono']:
url='https://unpkg.com/next@15.5.18/font/google.d.ts?path=declarations/index.d.ts'
# unpkg file listing unavailable; instead request package tarball and scan relevant file.
base='https://codeload.github.com/vercel/next.js/tar.gz/v15.5.18'
import urllib.request, tarfile, io
print('skipping full next tarball scan')
PYRepository: baanish/agent-render
Length of output: 305
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== next 15.5.18 Google font declarations in exported package =="
python3 - <<'PY'
import tarfile, io, urllib.request, os
url='https://codeload.github.com/vercel/next.js/tar.gz/v15.5.18'
data=urllib.request.urlopen(url, timeout=60).read()
tar=tarfile.open(fileobj=io.BytesIO(data), mode='r:gz')
names=tar.getnames()
matches=[n for n in names if 'next/font/google' in n or n.endswith('/declarations/index.d.ts') or 'SUSE' in n or 'Spline_Sans_Mono' in n or n.endswith('next.d.ts')]
print('matched_names_count', len(matches))
print('\n'.join(matches[:50]))
PY
echo "== tarball declaration file names containing font/google or declarations =="
python3 - <<'PY'
import tarfile, io, urllib.request
url='https://codeload.github.com/vercel/next.js/tar.gz/v15.5.18'
data=urllib.request.urlopen(url, timeout=60).read()
tar=tarfile.open(fileobj=io.BytesIO(data), mode='r:gz')
fonts=[]
for n in tar.getnames():
s=n.lower()
if ('font' in s and 'google' in s) or 'next/font' in s or n.endswith('next.d.ts'):
fonts.append(n)
print('\n'.join(fonts))
PY
echo "== search relevant exported files for SUSE/Manrope/Spline declarations =="
python3 - <<'PY'
import tarfile, io, urllib.request
url='https://codeload.github.com/vercel/next.js/tar.gz/v15.5.18'
data=urllib.request.urlopen(url, timeout=60).read()
tar=tarfile.open(fileobj=io.BytesIO(data), mode='r:gz')
for name in tar.getnames():
lower=name.lower()
if 'font' not in lower and 'SUSE' not in name and 'Spline_Sans_Mono' not in name and 'Manrope' not in name and not name.endswith('next.d.ts'):
continue
if 'node_modules' in name or name.startswith('../'):
continue
try:
f=tar.extractfile(name)
except:
continue
if f is None:
continue
text=f.read().decode('utf-8', 'replace')
if any(token in text for token in ['export declare const SUSE', 'export const SUSE', 'SUSE', 'export declare const Manrope', 'export const Manrope', 'Spline_Sans_Mono']):
print('FILE', name)
for i,line in enumerate(text.splitlines(),1):
if any(token in line for token in ['SUSE', 'Manrope', 'Spline_Sans_Mono', 'GoogleFontFamily']):
print(f'{i}: {line}')
PYRepository: baanish/agent-render
Length of output: 2508
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== next package metadata via npm registry =="
npm view next@15.5.18 dist.tarball types engines --json
echo "== Google font italic variable weights from CSS response parsing =="
python3 - <<'PY'
import urllib.request, re
for family in ['Manrope','SUSE','Spline+Sans+Mono']:
url=f"https://fonts.googleapis.com/css2?family={family}:ital,wght@0,400;0,500;0,600;1,400;1,500;1,600&display=swap"
css=urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'}), timeout=20, context=None).read().decode('utf-8')
print(f'== {family} request status =="')
print(re.findall(r"font-style:\s*(\w+);\s*font-weight:\s*([^;]+);|font-weight:\s*([^;]+);\s*font-style:\s*(\w+);", css))
PY
echo "== package-lock font-related entries empty because node_modules/next is package root; no need font specific =="
python3 - <<'PY'
import json
with open('package-lock.json') as f:
lock=json.load(f)
print('next version:', lock['packages'].get('node_modules/next',{}).get('version'))
flags=[]
for k,v in lock.get('packages',{}).items():
p=v.get('packageFiles',[])+v.get('peerDependencies',{})
if k=='node_modules/next' or 'SUSE' in k or 'Spline' in k or 'Manrope' in k:
flags.append(k)
print('font_related_packages=', flags[:20])
PYRepository: baanish/agent-render
Length of output: 3031
🌐 Web query:
Next.js 15.5.18 next/font/google SUSE Spline_Sans_Mono manual font family exports
💡 Result:
As of August 7, 2026, the SUSE and SUSE Mono font families are not available as direct exports within the next/font/google module in Next.js 15.5.18 [1]. While they are popular open-source typefaces [2], they have not been integrated into the automated Google Fonts package maintained by Next.js. There are no manual font family exports for these specific fonts in next/font/google because they are not part of the package's underlying registry [3]. To use the SUSE or SUSE Mono families in a Next.js project, you should use the local font approach [1][4]: 1. Download the font files (OTF or TTF) from the official repository or Google Fonts [2][5]. 2. Place the files in your project's public directory (e.g., public/fonts/). 3. Use next/font/local to load them [4]: import localFont from 'next/font/local'; const suse = localFont({ src: './public/fonts/SUSE-Regular.ttf', variable: '--font-suse', }); Alternatively, you can use the Fontsource NPM packages, which provide a convenient way to self-host these fonts with CSS support [6]: npm install @fontsource-variable/suse Then, import the CSS in your root layout: import '@fontsource-variable/suse/wght.css'; Note: "Spline Sans Mono" is available in next/font/google and can be imported directly as Spline_Sans_Mono [7][8][9]. However, the SUSE and SUSE Mono families remain distinct from this and must be handled via the local or Fontsource methods described above [1][5].
Citations:
- 1: Add SUSE font to next/font/google vercel/next.js#69963
- 2: https://github.com/SUSE/suse-font
- 3: https://github.com/vercel/next.js/blob/7a66008c/packages/font/src/google/index.ts
- 4: https://nextjs.org/docs/app/getting-started/fonts
- 5: Update SUSE + SUSE Mono google/fonts#9739
- 6: https://fontsource.org/fonts/suse/install
- 7: https://makeswift.mintlify.app/developer/reference/makeswift-api-handler
- 8: https://docs.makeswift.com/developer/docs/reference/makeswiftruntime/makeswift-api-handler
- 9: https://github.com/CopilotKit/CopilotKit/blob/d2cf0289/docs/app/layout.tsx
Use next/font/local for SUSE.
SUSE is present on Google Fonts, but the Next.js 15.5.18 next/font/google registry does not export it as SUSE, so this import will fail at build time. Keep Manrope and Spline_Sans_Mono as Google Font imports, and load SUSE with next/font/local or a self-hosted Fontsource package instead.
🤖 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/layout.tsx` around lines 2 - 23, Update the font imports and SUSE
initialization in the layout module: keep Manrope and Spline_Sans_Mono from
next/font/google, replace the unavailable Google SUSE import with
next/font/local (or the existing self-hosted Fontsource equivalent), and
preserve the existing SUSE variable, weights, and styling configuration.
| <section className="empty-state-layout"> | ||
| {/* ── Editorial hero ── */} | ||
| <section | ||
| className="home-hero-section fade-up" | ||
| style={heroAnimationStyle} | ||
| > | ||
| <p className="section-kicker">Artifact viewer</p> | ||
| <h2 className="font-display mt-4 max-w-4xl text-[2.5rem] font-bold leading-[0.92] tracking-[-0.04em] sm:mt-6 sm:text-6xl sm:leading-[0.92] lg:text-[4.5rem]"> | ||
| Zero-retention artifact viewer for AI outputs. | ||
| </h2> | ||
| <p className="mt-5 max-w-2xl text-base leading-[1.7] text-[color:var(--text-muted)] sm:mt-8 sm:text-lg sm:leading-8"> | ||
| <div className="sr-only"> | ||
| <h1>Zero-retention artifact viewer for AI outputs.</h1> | ||
| <p> | ||
| Artifact content lives in the URL fragment, so in static mode | ||
| the static host does not receive artifact content on the page | ||
| request. | ||
| </p> | ||
| <p className="mt-4 max-w-2xl text-sm leading-7 text-[color:var(--text-muted)] sm:text-base sm:leading-8"> | ||
| <p> | ||
| Fragment links can still appear in browser history, screenshots, | ||
| copied messages, extensions, and other places you share or run | ||
| your browser. | ||
| </p> | ||
| <div className="mt-6 flex flex-wrap gap-2 sm:mt-10 sm:gap-3"> | ||
| <span className="mono-pill">static export</span> | ||
| <span className="mono-pill">5 renderers</span> | ||
| <span className="mono-pill">zero retention</span> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* ── Bento feature grid ── */} | ||
| <section | ||
| className="bento-grid fade-up" | ||
| style={bentoAnimationStyle} | ||
| > | ||
| <div className="bento-card bento-wide px-5 py-6 sm:px-8 sm:py-8"> | ||
| <p className="section-kicker">Protocol shape</p> | ||
| <p className="font-mono mt-4 text-base leading-8 text-[color:var(--text-muted)] sm:text-lg"> | ||
| #<codec-tag><payload> | ||
| </p> | ||
| <a | ||
| href={urlExplainerPath} | ||
| className="mt-5 inline-flex items-center gap-2 text-sm font-semibold text-[color:var(--accent)]" | ||
| > | ||
| Why does this URL look weird? | ||
| </a> | ||
| </div> | ||
| <div className="bento-card px-5 py-6 sm:px-8 sm:py-8"> | ||
| <p className="section-kicker">Static boundary</p> | ||
| <p className="mt-4 text-sm leading-7 text-[color:var(--text-muted)] sm:text-base sm:leading-8"> | ||
| The browser decodes markdown, code, diffs, CSV, and JSON | ||
| locally from the fragment after the shell loads. | ||
| </p> | ||
| </div> | ||
| {ecosystemLinks.map((link) => ( | ||
| <a | ||
| key={link.href} | ||
| href={link.href} | ||
| target="_blank" | ||
| rel="noreferrer" | ||
| className="bento-card bento-link px-5 py-6 sm:px-8 sm:py-8" | ||
| > | ||
| <span className="hero-link-eyebrow">{link.kicker}</span> | ||
| <span className="hero-link-title">{link.title}</span> | ||
| <p className="mt-2 text-sm leading-7 text-[color:var(--text-muted)]"> | ||
| {link.description} | ||
| </p> | ||
| </a> | ||
| ))} | ||
| <div className="bento-card px-5 py-6 sm:px-8 sm:py-8"> | ||
| <span className="hero-link-eyebrow">Try it</span> | ||
| <span className="mt-3 block text-base font-semibold leading-6"> | ||
| Load a sample below | ||
| </span> | ||
| <p className="mt-2 text-sm leading-7 text-[color:var(--text-muted)]"> | ||
| Click any sample to populate the viewer from the URL hash. | ||
| </p> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* ── Link creator ── */} | ||
| <LinkCreator onPreviewHash={setFragmentHash} /> | ||
|
|
||
| {/* ── Samples + Inspector — full-bleed sections ── */} | ||
| <SampleLinks | ||
| activeHash={hash} | ||
| animationStyle={sampleAnimationStyle} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Locate every h1 in the app to confirm each viewer state has exactly one top-level heading.
set -euo pipefail
rg -n -C 4 '<h1' --type=tsx src
printf '\n== artifact stage headings ==\n'
fd -t f 'artifact-stage.tsx' src --exec ast-grep outline {} --items allRepository: baanish/agent-render
Length of output: 190
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== h1 usage in src ==\n'
rg -n -C 4 '<h1' src -g '*.tsx' || true
printf '\n== artifact stage candidates ==\n'
fd -i 'artifact-stage\.tsx$|ArtifactStage' src -t f
printf '\n== viewer shell outline/section =='
ast-grep outline src/components/viewer-shell.tsx --items all || true
printf '\n== relevant viewer-shell lines 360-460 =='
sed -n '360,460p' src/components/viewer-shell.tsx | nl -ba -v360Repository: baanish/agent-render
Length of output: 5314
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== viewer-shell relevant lines 170-460 ==\n'
sed -n '170,460p' src/components/viewer-shell.tsx
printf '\n== artifact-stage outline and h1 usage ==\n'
ast-grep outline src/components/viewer/artifact-stage.tsx --items all || true
rg -n -C 4 '<h[1-6]' src/components/viewer/artifact-stage.tsx --glob '*.tsx' || trueRepository: baanish/agent-render
Length of output: 12748
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== viewer shell h1-only in viewer shell branch excerpt =='
rg -n -C 2 '<h1' src/components/viewer-shell.tsx --glob '*.tsx' || true
printf '\n== viewer artifact stage heading section excerpt =='
sed -n '530,560p' src/components/viewer/artifact-stage.tsx
printf '\n== security/URL explainer page headings nearby privacy/retention copy =='
sed -n '1,85p' src/app/security/page.tsx
printf '\n'
sed -n '1,55p' src/app/url-explainer/page.tsxRepository: baanish/agent-render
Length of output: 9167
Add an <h1> for the artifact and fault states and keep the retention copy accessible to sighted users.
In ViewerShell, the only <h1> sits inside .sr-only on the empty branch. ArtifactStage starts with an <h3> and the fault branch has no <h1>, so those states can have no top-level page heading. The zero-retention copy is equivalent to visible security copy already present on the /url-explainer/ page; expose it visibly or link it into the viewer flow instead of hiding it screen-reader-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 `@src/components/viewer-shell.tsx` around lines 428 - 441, Update ViewerShell
so the artifact and fault states each render an appropriate top-level h1,
including adjusting ArtifactStage’s initial h3 hierarchy. Make the
zero-retention security copy visible to sighted users or link it into the viewer
flow, while preserving its accessibility and avoiding duplicate hidden-only
content.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dde4ad4ba
ℹ️ 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".
| <span className="font-mono text-[0.6rem] font-semibold uppercase tracking-[0.14em] text-[#6e6656]"> | ||
| Zero retention · the link is the payload. No database, no | ||
| uploads. |
There was a problem hiding this comment.
Avoid claiming zero retention in UUID mode
When this shared shell is served at /{uuid}, selfhosted/server.ts injects a payload retrieved from SQLite, so both “Zero retention · the link is the payload. No database” here and the repeated “no database” footer strip are false for a supported deployment mode. Detect the server-injected path and show UUID-specific retention wording, or omit these claims there, because users currently receive the opposite of the documented storage behavior.
AGENTS.md reference: AGENTS.md:L30-L31
Useful? React with 👍 / 👎.
| Zero-retention artifact viewer for AI outputs. | ||
| </h2> | ||
| <p className="mt-5 max-w-2xl text-base leading-[1.7] text-[color:var(--text-muted)] sm:mt-8 sm:text-lg sm:leading-8"> | ||
| <div className="sr-only"> |
There was a problem hiding this comment.
Keep the privacy caveat visible on the empty state
With no fragment, wrapping the product explanation and leak warning in sr-only makes them unavailable to sighted users; the visible footer still advertises “Zero retention,” but users are no longer told that links can leak through history, screenshots, copied messages, or extensions. Keep a compact visible explanation so the empty state does not imply that generated links are secret-safe.
AGENTS.md reference: AGENTS.md:L30-L31
Useful? React with 👍 / 👎.
| position: sticky; | ||
| top: 14px; |
There was a problem hiding this comment.
Offset the sticky samples below the sticky header
On desktop widths above 880px, scrolling the long link creator makes this sidebar stick at 14px while the opaque site header remains sticky at the top with z-30 and is taller than 14px. The header therefore covers the sample heading and first rows; set the sidebar offset to clear the header or remove its sticky positioning, and cover the scrolled desktop state in the visual check.
AGENTS.md reference: AGENTS.md:L211-L214
Useful? React with 👍 / 👎.
| /* ── Code surfaces: dark charcoal in BOTH themes ── */ | ||
| --surface-code: #1c1915; | ||
| --surface-code-raised: #26221b; |
There was a problem hiding this comment.
Align CodeMirror's theme facet with its dark surface
In the default light app theme, these tokens now force CodeMirror onto #1c1915, but CodeRenderer still passes dark: false and the indentation-marker extension consequently selects its low-opacity dark-blue light colors. Those markers are nearly indistinguishable from the new charcoal background, and other facet-driven editor rendering still behaves as though the surface were light; derive the editor facet from the always-dark content surface rather than the shell theme.
AGENTS.md reference: AGENTS.md:L213-L214
Useful? React with 👍 / 👎.
| :root { | ||
| /* ── Surface hierarchy — warm ivory with sunset undertone ── */ | ||
| --page-bg: #faf8f4; | ||
| --surface: rgba(250, 248, 244, 0.88); | ||
| --surface-strong: rgba(255, 255, 255, 0.96); | ||
| --surface-muted: rgba(40, 30, 50, 0.03); | ||
| --surface-elevated: rgba(245, 241, 235, 0.86); | ||
| /* ── Ghost borders — warm taupe ── */ | ||
| --border: rgba(160, 140, 130, 0.14); | ||
| --border-strong: rgba(160, 140, 130, 0.24); | ||
| /* ── Text — warm charcoal ── */ | ||
| --text-primary: #2a2028; | ||
| --text-muted: #6b5e68; | ||
| --text-soft: #8a7e86; | ||
| /* ── Accent — sunset orange ── */ | ||
| --accent: #cc6633; | ||
| --accent-strong: #a8502a; | ||
| --accent-secondary: #8b5ca8; | ||
| /* ── Semantic ── */ | ||
| --success: #4a8c6f; | ||
| --warning: #b8863a; | ||
| --danger: #c44b3f; | ||
| /* ── Ambient shadows — warm-tinted ── */ | ||
| --shadow-lg: 0 26px 72px rgba(42, 32, 40, 0.07); | ||
| --shadow-md: 0 20px 40px rgba(42, 32, 40, 0.035); | ||
| /* ── Radius — sharp, architectural ── */ | ||
| /* ── Bench-instrument spine: bone chrome, hairline borders, sharp radii ── */ | ||
| --page-bg: #ebe7dd; |
There was a problem hiding this comment.
Regenerate the intentional visual baselines
The non-CI npm run test:e2e path runs all eight screenshot assertions in tests/e2e/visual.spec.ts, including the empty shell and every renderer. This commit replaces the fonts, palette, layout, and renderer styling but updates none of the committed PNG baselines, so the prescribed local browser suite will report widespread snapshot failures even when the new output is correct; verify the rebuilt screens and commit the intentional baselines with this change.
AGENTS.md reference: AGENTS.md:L213-L214
Useful? React with 👍 / 👎.
| <p className="mx-auto w-full max-w-[1280px] px-5 py-3 font-mono text-[0.58rem] font-semibold uppercase tracking-[0.16em] text-[#6e6656]"> | ||
| Open source · self-hostable · no database |
There was a problem hiding this comment.
Raise the footer text contrast
The footer strip uses approximately 9px text in #6e6656 against the fixed #14110d background, a contrast ratio of only about 3.31:1; the tagline directly above uses the same color at 0.6rem. Both are below the 4.5:1 requirement for normal-sized text in either theme, making the retention and project-status copy difficult to read for low-vision users; use the already-defined lighter footer color or another AA-compliant value.
Useful? React with 👍 / 👎.
| .diff-tailwindcss-wrapper, | ||
| .diff-renderer-frame [data-theme="light"], | ||
| .diff-renderer-frame [data-theme="dark"] { | ||
| color-scheme: dark; | ||
| color: #e6dfcf; |
There was a problem hiding this comment.
Switch the diff highlighter to its dark palette
In the default light shell theme, DiffRenderer still initializes the library with light and passes diffViewTheme="light"; the vendor stylesheet therefore applies light-theme syntax colors such as #24292e while this override forces the diff background to #1c1915. Ordinary highlighted text has only about 1.19:1 contrast and several other tokens are similarly unreadable, because setting inherited color and diff variables does not override the vendor’s more-specific .hljs rules. Initialize the diff library with its dark theme whenever the body is always dark, or explicitly replace the complete syntax palette.
AGENTS.md reference: AGENTS.md:L213-L214
Useful? React with 👍 / 👎.
Both surfaces had only been re-tinted by the rebuild and still read as the old design. Now they follow the bench-instrument grammar: - Metadata becomes a dense LIMITS strip: engraved header row, cells flow horizontally on desktop via auto-fit (still exactly two columns at the 390px contract width), tighter engraved labels and mono values - Disclosure summary becomes an engraved strip with a mono-caps title; the status message reads as a NOTE line; the metric grid tightens to instrument density - Hash preview becomes an amber-on-charcoal recessed readout slot (the design contract's VFD register for machine data) - Dead utility-class padding dropped from the restyled markup Lint, component tests, and the chromium e2e suite (28/28) pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sweep of leftover narration the redesign contract bans: - markdown code fences dropped the "premium fence" caption (the language chip stays) - json toolbar dropped its "read-only" pill; code toolbar dropped the hidden "read-only codemirror" kicker element - the fragment disclosure dropped its status message line entirely: the STATUS readout already carries the state, and on decode faults the placard above the disclosure shows the actual failure reason, so the line was redundant in every state (statusMessage prop removed from the component, call sites, and the design-sync contract) - dead CSS for all of the above removed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
||
| .markdown-code-chip, | ||
| .markdown-code-caption { | ||
| .markdown-code-chip { |
There was a problem hiding this comment.
[WARNING]: Removing .markdown-code-caption leaves a still-rendered element unstyled.
This change drops .markdown-code-caption from the shared .markdown-code-chip, .markdown-code-caption rule and deletes its dedicated rule. However, the mermaid branch in src/components/renderers/markdown-renderer.tsx:236 still renders <span className="markdown-code-caption">diagram</span>. There is now no CSS rule for .markdown-code-caption, so the "diagram" caption renders with default browser typography instead of the intended mono chip styling. Either restore a .markdown-code-caption rule or remove the orphaned element in the mermaid branch.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
The raw view rendered an uncolored text dump. A single-pass regex tokenizer now colors strings, keys, numbers, and literals with the same token classes the tree view uses, so both views share one vocabulary. The source text is preserved byte-for-byte (keys keep their literal colons; the tree's ::after colon glyph is suppressed in the raw scope), and unmatched text stays uncolored so the parse-error raw view is safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| margin-right: 0; | ||
| } | ||
|
|
||
| .json-raw-source .json-key::after { |
There was a problem hiding this comment.
[WARNING]: The new .json-raw-source .json-key::after rule absorbs the raw-source container's styling properties, stripping them from .artifact-raw-source and .json-raw-source.
This change inserts } followed by the two new .json-raw-source rules before the closing brace of the .artifact-raw-source, .json-raw-source block. As a result, color, padding, font-family, font-size, line-height, and white-space (lines 1925-1931) were moved out of the container rule and now sit inside the .json-raw-source .json-key::after pseudo-element rule.
Consequences:
.artifact-raw-source(used atsrc/components/viewer/artifact-stage.tsx:183) and.json-raw-source(used atsrc/components/renderers/json-renderer.tsx:122) no longer receive the mono font, padding, font-size, line-height, or text color they previously had.- The
::afterpseudo-element now carriespadding/font-size/line-height/white-space/color, which are no-ops because itscontent: nonesuppresses rendering.
Keep only content: none; in the .json-raw-source .json-key::after rule (plus the margin-right: 0 on .json-key), and restore color/padding/font-family/font-size/line-height/white-space to the .artifact-raw-source, .json-raw-source block.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
What
Ground-up visual rebuild of the app toward the decided design direction, fusing three influences with one job each: airplane QRH supplies the procedural grammar (numbered operation steps, READY/CAUTION/WARNING states), Japanese high-density UI supplies the density (hairline borders, docked blocks, no dead space), and skeuomorphism supplies the material (bone keycap chrome over a dark charcoal chassis, molded keycaps, recessed input slots, engraved mono labels).
What did not change
Payload protocol, codecs, renderer behavior, dynamic-import structure, data-testids, and the print-markdown path are untouched.
Verification
CI=1, viewer spec) 28/28 on chromium; webkit blocked by pre-existing missing host libraries, unrelated to this changenpm run test:e2e:updateonce the look is blessedOpen question
The fragment inspector still renders (in EMPTY state) on the fragment-less homepage because the mobile e2e ordering test requires both sections measurable without a fragment. Removing it per the strict design rule means changing that test's premise; left for a follow-up decision.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Style
Documentation