Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .design-sync/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# design-sync notes for agent-render

## Build mechanics (learned on first sync, Aug 2026)

- The converter needs `cfg.entry` = `.design-sync/ds-entry.ts` (authored re-export list). Without it, PKG_DIR resolves to the nonexistent `node_modules/agent-render` and the build crashes. The entry also exports `sampleEnvelopes` (repo sample payloads) for previews and for the design agent.
- `.design-sync/process-shim.ts` must stay the first import in ds-entry.ts: components read `process.env.NEXT_PUBLIC_*` at module init and `next/dynamic` expects `process`; without the shim every preview throws `ReferenceError: process is not defined` and window.AgentRender ends up non-component.
- `.design-sync/harvest-css.mjs` (wired into buildCmd) turns the hashed Next CSS output into the stable cssEntry at `.design-sync/.cache/ds-css/main.css`: rewrites `/_next/static/media/` font urls to repo-relative, lifts the next/font `--font-*` variable classes to `:root`. Re-syncs must run `npm run build` first or the harvest fails loudly.
- Props auto-extraction can't work here (no shipped .d.ts tree; props types are file-local), so `cfg.dtsPropsFor` carries hand-written bodies for all 13 components. If a component's props change in src, update the config body too; nothing will fail automatically.
- Preview pattern for `<details>` components (FragmentDetailsDisclosure): open it post-mount via a ref effect (`querySelector("details").setAttribute("open","")`), exactly like a user click. Hover/drag states stay unauthored.
- tokens/ is empty by design: all custom properties live in the compiled globals.css inside `_ds_bundle.css`.

## Preview-authoring learnings (folded from the first-sync wave)

- App bug found while previewing (surface in any rebuild): DiffRenderer's `oldContent`/`newContent` fallback renders an empty diff body. `src/components/renderers/diff-renderer.tsx` (~line 501) builds `new DiffFile(..., [])` with an empty hunk list; `@git-diff-view/core` never diffs raw contents and `@git-diff-view/file` isn't installed. Previews only use the `patch` path.
- brotli-wasm cannot load under the static capture server (its `.wasm` fetch 404s), so arx-family encode/decode rejects with "Failed to fetch". Previews drive flows with the deflate codec / `#d...` sample hashes. Any preview using codec "auto" or an arx hash will hit this.
- Mermaid renders reliably headless, but font metrics clip the last glyph of labels: quote labels and append `&nbsp;`. Keep `flowchart TD` to <= 5 ranks for the ~536px cell.
- Native `button.click()` drives React delegated handlers in previews (LinkCreator generates a real link); sequence dependent clicks with ~50ms setTimeout.
- ViewerShell decoded state: set `window.location.hash` to a deflate sample hash in an effect, then mount behind a ready flag.
- `lucide-react` bare imports compile in preview-rebuild. Kind-icon map: `src/components/artifact-kind-icons.ts`; getHeading/getSupportingLabel live in `artifact-stage.tsx`.
- ArtifactSelector's 5-tab strip is ~1490px intrinsic and clips at its own scroll edge (no escape); previews scroll to the active tab post-mount instead of a viewport override (an explicit viewport re-keys and clears grades).
- ViewerShell has `overrides.ViewerShell = {cardMode: single, primaryStory: EmptyHomepage, viewport: 1280x900}` for a fuller single-card capture.

## Known render warns

- (none recorded yet; add any warn triaged as legitimate here so re-syncs don't chase it)

## Re-sync risks (watch-list for the next run)

- The claude.ai/design agent (Aug 6 redesign run) applied lint fixes directly to the uploaded `_ds_bundle.css`: folded the next-font `__variable_*` classes into `:root`, added `@kind` annotations to unclassifiable tokens, inlined utility-scoped `--tw-*` state vars, and re-scoped the diff theme vars to plain `[data-theme=...]` scopes. A re-sync will overwrite these with the locally-built CSS and re-introduce the adherence flags; ideally teach harvest-css.mjs the same transforms (or accept the flags as known).

- `dtsPropsFor` bodies are hand-copied from file-local src types; a props change in src updates nothing automatically. When any component's props change, update the config body in the same PR.
- `.design-sync/ds-entry.ts` is the export list; adding or removing a component in src requires editing it (and `componentSrcMap`, `dtsPropsFor`).
- `harvest-css.mjs` assumes the Next build emits exactly one CSS file and `__variable_*` font classes; a Next major upgrade can change both. It throws loudly rather than shipping stale CSS, but the fix will be manual.
- Previews index into `sampleEnvelopes` by position (0, 2, 3, 4, 5); reordering `src/lib/payload/examples.ts` silently changes preview content without failing anything.
- The ViewerShell/LinkCreator/SampleLinks previews run real decode/generation flows against deflate sample hashes from `sample-link-data.ts`; edits there shift what the cards show.
- Fonts are fetched by next/font at `npm run build` time (network dependency of the buildCmd).
- Render check ran against the playwright/chromium-1228 cache matching the repo pin; a repo playwright bump needs a matching browser install.
- Verified: everything except hover/drag states and the DiffRenderer `oldContent`/`newContent` path (broken in the app, see above). Grades carry via the uploaded `_ds_sync.json`.



- Purpose of this sync (Aug 2026): the user is rebuilding agent-render's UI in claude.ai/design. Three prior Kimi K3 sessions failed at it. The decided design direction (from the cursor session, `~/Downloads/kimi-failed-cursor.md`) is "fused bench-instrument world": skeuomorphism + Japanese high density + airplane QRH card. Target project: agent-render-skeumorphism.
- This repo is an app, not a library: no dist entry, converter runs in synth-entry mode from `src/` with tsconfig `@/*` path aliases.
- Fonts come from `next/font/google` (Fraunces display, IBM Plex Sans body, IBM Plex Mono). There are no @font-face files in source; harvest compiled CSS + woff2 from `out/_next/static/` after `npm run build`. Font families are exposed as `--font-display`/`--font-sans`/`--font-mono` variables set by Next-generated classes on `<html>`, so the shipped styles need a hand-authored bridge that defines those variables at `:root`.

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

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.

- `src/app/globals.css` starts with `@import "tailwindcss"` (Tailwind 4 via PostCSS); components mostly use bespoke classes defined in globals.css, but the compiled CSS from the Next build is the honest cssEntry source.
- Component set (13): ViewerShell, ThemeToggle, ArtifactSelector, ArtifactStage, FragmentDetailsDisclosure, MarkdownRenderer, CodeRenderer, DiffRenderer, CsvRenderer, JsonRenderer, MermaidBlock, LinkCreator, SampleLinks. `use-theme-controller` is a hook, `hash-preview.ts` a utility; neither is a component.
- Heavy renderers (CodeMirror, mermaid, @git-diff-view/react, papaparse) are dynamically imported in the app; previews must allow them time to mount (`onReady` callbacks exist on renderer props).
- Vision guideline docs are user-approved to ship in `guidelines/` (skeuomorphism + Japanese density + QRH direction, 16 UI criticisms as hard rules).
- Agent feedback the user endorsed (Aug 6, from "brodie" and "Orion"): do NOT average the three aesthetics; assign each one a job. QRH supplies procedural grammar (numbered steps, WARN/CAUTION/NOTE callouts, limits, revision/state markers). Japanese UI supplies density. Skeuomorphism supplies material (laminated cards, recessed fields, molded keys, stamped labels, dividers, hardware; never generic shadows). Kill the hero; the link generator is the first viewport; whole viewport a fixed instrument grid; sections become numbered procedure/status blocks; orange reserved for CAUTION/action states; samples become a compact indexed sidebar; CREATE A LINK becomes a five-step operations card with explicit READY/error states. "QRH first, Japanese density second, marketing page never."
47 changes: 47 additions & 0 deletions .design-sync/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"projectId": "cadc747e-85f1-42c6-8cc7-230f2bf35887",
"pkg": "agent-render",
"globalName": "AgentRender",
"shape": "package",
"buildCmd": "npm run build && node .design-sync/harvest-css.mjs",
"cssEntry": ".design-sync/.cache/ds-css/main.css",
"srcDir": "src",
"tsconfig": "tsconfig.json",
"entry": ".design-sync/ds-entry.ts",
"guidelinesGlob": ["docs/*.md"],
"readmeHeader": ".design-sync/conventions.md",
"overrides": {
"ViewerShell": { "cardMode": "single", "primaryStory": "EmptyHomepage", "viewport": "1280x900" },
"LinkCreator": { "cardMode": "column" }
},
"dtsPropsFor": {
"MarkdownRenderer": "artifact: { id: string; kind: \"markdown\"; content: string; title?: string; filename?: string }; onReady?: () => void;",
"CodeRenderer": "artifact: { id: string; kind: \"code\"; content: string; language?: string; title?: string; filename?: string }; compact?: boolean; onReady?: () => void;",
"DiffRenderer": "artifact: { id: string; kind: \"diff\"; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }; onReady?: () => void;",
"CsvRenderer": "artifact: { id: string; kind: \"csv\"; content: string; title?: string; filename?: string }; onReady?: () => void;",
"JsonRenderer": "artifact: { id: string; kind: \"json\"; content: string; title?: string; filename?: string }; onReady?: () => void;",
"MermaidBlock": "code: string; onReady?: () => void;",
"ArtifactSelector": "artifacts: Array<{ id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }>; activeArtifactId: string; onSelect: (artifactId: string) => void; kindIcons: Record<\"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\", React.ComponentType<{ className?: string }>>; getHeading: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string; getSupportingLabel: (artifact: { id: string; kind: string; title?: string; filename?: string }) => string;",
"ArtifactStage": "activeArtifact: { id: string; kind: \"markdown\" | \"code\" | \"diff\" | \"csv\" | \"json\"; content?: string; patch?: string; oldContent?: string; newContent?: string; language?: string; view?: \"unified\" | \"split\"; title?: string; filename?: string }; envelope: { v: 1; codec: \"plain\" | \"lz\" | \"deflate\" | \"arx\" | \"arx2\" | \"arx3\" | \"arx4\"; title?: string; activeArtifactId?: string; artifacts: Array<{ id: string; kind: string; content?: string; title?: string; filename?: string }> }; fragmentLength: number; hash: string; onArtifactSelect: (artifactId: string) => void; onRendererReady: (readyKey: string) => void; rendererReadyKey: string; statusTone: { color: string; label: string; message: string };",
"FragmentDetailsDisclosure": "statusLabel: string; fragmentLength: string; maxLength: string; codec: string; hashPreview: string;",
"LinkCreator": "onPreviewHash: (hash: string) => void;",
"SampleLinks": "activeHash: string; animationStyle: React.CSSProperties;",
"ThemeToggle": "className?: string;",
"ViewerShell": ""
},
"componentSrcMap": {
"ViewerShell": "src/components/viewer-shell.tsx",
"ThemeToggle": "src/components/theme-toggle.tsx",
"ArtifactSelector": "src/components/viewer/artifact-selector.tsx",
"ArtifactStage": "src/components/viewer/artifact-stage.tsx",
"FragmentDetailsDisclosure": "src/components/viewer/fragment-details-disclosure.tsx",
"MarkdownRenderer": "src/components/renderers/markdown-renderer.tsx",
"CodeRenderer": "src/components/renderers/code-renderer.tsx",
"DiffRenderer": "src/components/renderers/diff-renderer.tsx",
"CsvRenderer": "src/components/renderers/csv-renderer.tsx",
"JsonRenderer": "src/components/renderers/json-renderer.tsx",
"MermaidBlock": "src/components/renderers/mermaid-block.tsx",
"LinkCreator": "src/components/home/link-creator.tsx",
"SampleLinks": "src/components/home/sample-links.tsx"
}
}
50 changes: 50 additions & 0 deletions .design-sync/conventions.md
Original file line number Diff line number Diff line change
@@ -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.

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 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.

Suggested change
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.


## Setup

No provider is required. All styling comes from the stylesheet closure (`styles.css` imports `_ds_bundle.css` and `fonts/fonts.css`). Theme: light is default; dark mode is the `dark` class on the root element (`<html class="dark">`). Never use `prefers-color-scheme` alone; the app toggles the class.

## Styling idiom: CSS custom properties

Style your own layout glue with the token variables, never hardcoded colors. The vocabulary (defined at `:root` and overridden in `.dark` inside `_ds_bundle.css`):

- Surfaces: `--page-bg`, `--surface`, `--surface-strong`, `--surface-muted`, `--surface-elevated`
- Borders: `--border`, `--border-strong` (hairline 1px borders are the separation grammar; avoid shadows)
- Text: `--text-primary`, `--text-muted`, `--text-soft`
- Accent and semantic: `--accent`, `--accent-strong`, `--accent-secondary`, `--success`, `--warning`, `--danger`
- Depth and shape: `--shadow-lg`, `--shadow-md`, `--radius-xl`, `--radius-lg` (radii are deliberately sharp, 2px)
- Fonts: `--font-display` (Fraunces), `--font-sans` (IBM Plex Sans), `--font-mono` (IBM Plex Mono). Mono is for data and labels, uppercase with letter-spacing for the label register.

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]: 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/.

Suggested change
- 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.


Component chrome classes ship in `_ds_bundle.css` (`.artifact-action`, `.mono-pill`, `.creator-input`, `.sample-link`, `.viewer-frame`, `.artifact-disclosure`); prefer composing the exported components over reaching for their internal classes.

## Data contracts

Renderers take an `artifact` object: `{ id, kind: "markdown"|"code"|"diff"|"csv"|"json", content, title?, filename?, language? }` (diff uses `patch` instead of `content`). Realistic ready-made payloads ship on `window.AgentRender.sampleEnvelopes` (6 envelopes; index 4 holds one artifact of every kind). Use them instead of inventing content.

## Gotchas

- Heavy renderers (CodeMirror, mermaid, diff) mount through dynamic imports; they appear a beat after first paint. Each renderer accepts `onReady`.
- DiffRenderer needs a real git `patch` string. The `oldContent`/`newContent` fallback currently renders an empty body (known app gap).
- arx-family codecs need `brotli-wasm`, which may not load in sandboxed frames. For `ViewerShell`/`SampleLinks` hashes, use deflate-coded `#d...` samples.
- `ViewerShell` is the whole page and reads `window.location.hash`; render it alone, not inside a layout.

## Idiomatic composition

```jsx
const { ArtifactStage, sampleEnvelopes } = window.AgentRender;
const env = sampleEnvelopes[4];
<div style={{ background: "var(--page-bg)", padding: "2rem" }}>
<ArtifactStage
activeArtifact={env.artifacts[0]}
envelope={{ ...env, codec: "arx4" }}
fragmentLength={1982}
hash="#e..."
onArtifactSelect={() => {}}
onRendererReady={() => {}}
rendererReadyKey={env.artifacts[0].id}
statusTone={{ label: "Decoded", color: "var(--success)", message: "Fragment decoded successfully." }}
/>
</div>
```
19 changes: 19 additions & 0 deletions .design-sync/ds-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// design-sync bundle entry: the library view of this app's components.
// The app has no dist; this is what window.AgentRender exposes.
import "./process-shim";
export { ViewerShell } from "../src/components/viewer-shell";
export { ThemeToggle } from "../src/components/theme-toggle";
export { ArtifactSelector } from "../src/components/viewer/artifact-selector";
export { ArtifactStage } from "../src/components/viewer/artifact-stage";
export { FragmentDetailsDisclosure } from "../src/components/viewer/fragment-details-disclosure";
export { MarkdownRenderer } from "../src/components/renderers/markdown-renderer";
export { CodeRenderer } from "../src/components/renderers/code-renderer";
export { DiffRenderer } from "../src/components/renderers/diff-renderer";
export { CsvRenderer } from "../src/components/renderers/csv-renderer";
export { JsonRenderer } from "../src/components/renderers/json-renderer";
export { MermaidBlock } from "../src/components/renderers/mermaid-block";
export { LinkCreator } from "../src/components/home/link-creator";
export { SampleLinks } from "../src/components/home/sample-links";
// Data, not components: the repo's canonical sample payloads, for previews and
// for building realistic screens with the renderers.
export { sampleEnvelopes } from "../src/lib/payload/examples";
24 changes: 24 additions & 0 deletions .design-sync/harvest-css.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Harvests the Next build's compiled CSS into a stable cssEntry for design-sync.
// Next emits hashed filenames and absolute /_next/ font URLs, and sets the
// --font-* variables via generated classes on <html> that previews never get;
// this rewrites all three into .design-sync/.cache/ds-css/main.css.
import { readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const repo = join(dirname(fileURLToPath(import.meta.url)), "..");
const cssDir = join(repo, "out/_next/static/css");
const files = readdirSync(cssDir).filter((f) => f.endsWith(".css"));
if (files.length !== 1) throw new Error(`expected 1 compiled css, found: ${files.join(", ") || "none"} — run npm run build first`);

let css = readFileSync(join(cssDir, files[0]), "utf8");
css = css.replaceAll("url(/_next/static/media/", "url(../../../out/_next/static/media/");

const rootVars = [];
for (const m of css.matchAll(/\.__variable_[a-f0-9]+\{([^}]+)\}/g)) rootVars.push(m[1]);
if (rootVars.length === 0) throw new Error("no __variable_ font classes found in compiled css");

const outDir = join(repo, ".design-sync/.cache/ds-css");
mkdirSync(outDir, { recursive: true });
writeFileSync(join(outDir, "main.css"), `:root{${rootVars.join(";")}}\n` + css);
console.log(`harvested ${files[0]} -> .design-sync/.cache/ds-css/main.css (${rootVars.length} font vars lifted to :root)`);
Loading
Loading