Conversation
|
Board check (static Storybook, Chromium via Playwright), light and dark palettes:
One finding about the harness rather than this PR: the static Storybook build does not link the StyleX stylesheet ( 🤖 Generated with Claude Code |
|
Sidebar toggle reworked after review: the collapsed state no longer pastes a chevron in the content area. The open icon has a wider panel with three rows; collapsing slides the divider 10 → 6.5 and the rows shrink into the rail as dots (transform only), so nothing is added for a state and the static 🤖 Generated with Claude Code |
a112987 to
a393ade
Compare
a393ade to
8c0590b
Compare
An original 75-icon set drawn in the package on one 24 grid at a 1.5 stroke, following Nucleo's drawing conventions and none of its paths. `src/icons/registry.ts` holds each drawing as outline marks plus, where it has depth, a layer model (mass, mid, front, detail, outer, dots); `Icon` renders it in four treatments — outline, duotone, glyph and bulk — from those same paths, with a glyph's cuts made through a mask so they are holes rather than a painted panel colour. Nine stateful icons move between two states by one registered custom property, `--lody-icon-t`, that CSS transitions: transform, opacity and dash offset only, no path morphing, so at rest each state is the static drawing. Board section, tests, README row, AGENTS.md rule, spec section and a proposed Agent Note. No product caller migrates yet. Model: claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The draft-02 design board (`index.html`) the icon registry was generated from, and a proposed note from a parallel exploration of a wider icon family. Committed as found in the worktree so the exploration travels with the PR; neither is consumed by the package. Model: claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Model: claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`index.html` was not the board the registry was generated from, as the previous commit claimed: it was a 42-symbol snapshot of the draft in progress (33 icons plus the model candidates), copied to the worktree root by a parallel session. The registry was generated from the finished 75-icon board, which `test/icons.test.tsx` and the Agent Note describe; the board itself is a design artifact and stays out of the tree. Model: claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hevron The collapsed state of the sidebar toggle added a chevron in the content area, which is a symbol about the sidebar rather than the sidebar. Now nothing is added for a state: the open icon has a wider panel with three rows, and collapsing slides the divider from 10 to 6.5 while the rows shrink into the rail as dots — a narrow sidebar showing only its marks. The static `sidebar` and `sidebar-collapsed` icons are the two ends of the same drawing. Model: claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The set landed as four more rows on the token board, which is reached through `packages/components`' Storybook. That was wrong twice. The board's question is "what is this token's value", answered once per token off the rendered node; an icon has no token, and its question is asked 75 times — find the drawing, put it at the size the surface uses, in that surface's colour, on the rung it will sit on, and take it away as markup. Those are controls, not samples. And the set is `@lody/ui`'s, while Storybook belongs to a package that only consumes it: looking at an icon should not require building the product's component library. So `packages/ui/playground` is a Vite page of its own, `pnpm --filter @lody/ui playground`, with no Storybook under it: search, a size slider from 12 to 64, the four treatments, the semantic tones, the four rungs — the accent rung is where a glyph's mask shows, since the cut is a hole and the rung shows through — a per-icon panel with the drawing enlarged on the 24 grid and `copy svg`, and the stateful icons under a slow-motion switch that lets `--lody-icon-t` be watched rather than inferred from its two ends. `src/gallery/gallery.tsx` goes back to what it was. Nothing in `playground/` is exported and the package still depends on React, Base UI and StyleX alone at runtime; `vite` and `@vitejs/plugin-react` join its devDependencies. `playground.css` carries the page's own reset and the slow-motion override, and also gives the StyleX injection target a chunk to exist in: with no CSS of its own the bundle emits no `index-<hash>.css` and the built page comes out unstyled. Typecheck, the production build and the dev server were run. The page itself has not been opened in a browser; the note says so. Model: claude-opus-5[1m]
…arks The collapsed state put its divider at 6.5, which leaves a 3-unit rail. The two strokes bounding that rail take 1.5 of it, so the marks inside had 1.5 units to live in and were drawn 2 wide: they overlapped the frame on one side and the divider on the other, and the icon read as three lumps. The playground is what showed it. The animated state was wrong a second way. It shrank the rows with `scaleX`, and a horizontal scale leaves a stroke's thickness alone while squashing its round caps, so at t = 1 the marks were 0.8 wide against the static drawing's 2. The two ends of the transition were two different drawings, which is the one thing a stateful icon here may not be. The divider now collapses to 8.5: the rail is 5 units and the 3.5 between its strokes holds one mark of 2 with 0.75 of air either side. The rows retract by their dash instead of scaling — a dash shortened to zero length is a round cap and nothing else, which is a dot — and the uniform scale carrying it from 1.5 to 2 grows the stroke without touching its shape. Two marks, not three: at 2 wide a third leaves a 1-unit gap, and a size ladder shows three smearing into one vertical stroke by 20px while two stay separate. The middle row fades; the outer two land on (6, 9.5) and (6, 14.5), which is what `sidebar-collapsed` draws. Checked in the playground in Chromium. The animated t = 1 and the static drawing were screenshotted at 64px and compared pixel by pixel: 825 ink pixels against 833, differing only by a one-pixel fringe in one direction on every stroke, which is a sub-pixel crop offset and not a difference in shape. The transition was rendered at t = 0, 0.25, 0.5, 0.75 and 1 — the caps stay round throughout and no sliver appears. The note records the correction and retires the `translateX(-3px)` measurement, which was read off the broken drawing. Model: claude-opus-5[1m]
Drawn with an 11-wide dome over 6 units of straight side the bell was 14 across and 13 down: a tube with a cap on it, not a bell. The dome is 12 now and the side sweeps out to a mouth 16.4 across a 12.7-tall body. The mouth is the part worth reading. Widening it while it was still a flange — a straight flare from the side down to the mouth line — made the filled variants grow horns: the flare met the mouth at 39 degrees, and a 39-degree tip is a spike that `stroke-linejoin: round` hides in the outline and a fill cannot. Rounding the spike is not available either, since a 0.8 radius at that angle consumes 2.28 units of a 3.2-unit flare and there is no flange left. So the flange is gone. The side arrives at the mouth vertically through a curve, turns through a 0.7 corner, and the mouth is a line under it — a 90-degree corner, which a fill can hold. That is the general form of a rule the set already had for glyphs: what the outline's stroke rounds, the mass has to round itself. The clapper's arc is a segment rather than a half-circle, so it hangs a unit clear of the mouth and stops at the live area instead of 1.25 past it; `front` is that same silhouette filled, which is what gives bulk a solid clapper under the 35% body. A wider bell swings wider. The mouth is 16 units from the nub it pivots on, and at the old 14 degrees the corner furthest from the pivot put its stroke 0.03 from the canvas — inside, but not a margin. The swing is 12 degrees, which leaves 0.42 and reads the same. `BellRingIcon` no longer restates the path: it reads `ICONS.bell`, and a test holds it there. That is the guard the sidebar did not have, and both of this branch's drawing bugs were a stateful icon and a static one drifting apart. Seen in the playground in all four treatments at 12 through 48, and the swing rendered at -12, -6, 0, 6 and 12 degrees against the canvas edge. `pnpm --filter @lody/ui test` passes, 262 tests. Model: claude-opus-5[1m]
`packages/components` imports 239 distinct icons from `lucide-react` across 295 files, 2138 usages. Collapsed onto the drawings that would serve them, the set covered 78% of that. These sixteen take it to 91%: monitor, alert-circle, users, circle, shield-alert, folder-plus, arrow-up, mail, wrench, undo, quote, save, image, pin-off, fork and pull-request-closed. `monitor` alone answers 52 usages, which is what a product about machines looks like from the icon layer. Each takes its family's skeleton before it takes a better idea: `monitor` and `image` the 2px container `sidebar` and `terminal` already share, `alert-circle` the circle family's ring with `info-circle` turned over, `shield-alert` the shield, `users` `user` twice, `fork` and `pull-request-closed` the git family's two node columns and 2px nodes. `send` and `arrow-up`, and `pin` and `pin-off`, are one drawing under two names: the path is a module const rather than two copies, which is the lesson of the two drift bugs earlier on this branch. `package` was dropped from the batch rather than drawn — a box with a lid is `archive` and an isometric one is `model`. Three were redrawn after the first pass, all failing the same way, reading at 120px and not at 20. `quote` as a block with the tail notched out of a corner is a pair of counters by 20px; it is a hook now. `pin-off` broken into fragments is not a pin, so the pin stays whole and takes the line across it. `pull-request-closed` had its cross floating clear of the branch it closes. `folder-plus` and `users` ended up with no layer model, which is a rule worth having: **a drawing a fill would cost its meaning takes no layers.** A cross on a 100% front panel is currentColor on currentColor and what is left is `folder`; half a person as a stroke beside a filled one is two fragments off a silhouette. `folder-open` and `user` were already outline-only for the same reason. The playground's bulk column is where both showed. The icon rules move to `src/icons/AGENTS.md`: the parent was 33 bytes under its gate and these are now a scope of their own, with the grid, the four treatments, the two-state rules and what to check before calling a drawing done. Families are reordered so members line up in a list, which is what that file asks for. Seen in the playground at 32 and 52 in outline, glyph and bulk. `pnpm --filter @lody/ui test` passes, 262 tests. Model: claude-opus-5[1m]
A second reading of the board sent six back, and the reasons divide into three kinds. **A shape that is not the thing.** `quote`, twice: a notched block reads as a counter, and a ball with its tail swept too far round it reads as a lowercase e. What a quote mark is is a ball with a short tail, so that is what it is now. **A proportion that is not the thing's.** `save` at 17 by 15 is a letterbox; a disk is square, so it is 15 by 15, and its shutter and label centre on 12. `code` had 4.5 of chevron travel and a slash 4 wide, which bunched its mass in the middle and read narrow beside everything on its row; the chevrons reach the live area now and the slash leans across it. **A part that does not belong to the whole.** `folder-plus` carried the front-panel line as well as the cross, and that line splits the face the cross has to sit in — the line is gone and the cross centres on the face. `pull-request-closed` drew its cross at an arrowhead's size with the branch stopping two units short of it, so the cross and the branch read as two drawings sharing a column; the cross is a node's size now and the branch runs up to meet it. And `fork` put its heads on 6.5 and 17.5 while `branch`, `merge` and `pull-request` put theirs on 7 and 17 — symmetrical on its own, out of line the moment the family is read down a list, which is the only way a family is ever read. Four of the six read fine enlarged and failed at 20, which is the size they will be used at. No test sees any of this. Seen in the playground: all six at 112px on the grid, the git family side by side against its 7 and 17 columns, and `save` in all four treatments at 16 through 32. `pnpm --filter @lody/ui test` passes, 262 tests. Model: claude-opus-5[1m]
It was substituted into the batch when `package` turned out to be `archive`, and it went through four drawings without arriving: a cross capping the branch, which leaves two columns with nothing between them and reads as two lollipops rather than a pull request; the branch run up into the cross to close that gap, which came out a spear; `pull-request`'s arm restored with a cross for its arrowhead, drawn at the size that fit the room left above the arm rather than the size a cross is in this set; and the same again at `x-circle`'s 5. The last was defensible and still not good enough to keep. The lesson is about the batch rather than the drawing. It answers four usages; everything else in the fifteen answers seven or more, and none of them needed more than one redraw. A drawing that has to say a relation did not happen — not a thing, not a state — is a harder problem than four usages justify, and the honest move when `package` fell out was to draw fifteen, not to fill the slot from the ranked list because it was open. `packages/components` keeps `lucide-react`'s `GitPullRequestClosed` at those four sites, as it does for every icon this set has not taken over; no caller changes. 90 icons. Model: claude-opus-5[1m]
8c0590b to
352d51f
Compare
The gallery file was missing Toggle component definitions and rows during the rebase. Restored from origin/feat/ui-v2 to include all necessary Toggle, ToggleGroup, and Toolbar components and their gallery representations. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Related issue
Same-repository branch; no intake Issue.
Problem / pressure
Three sources of icons and no shared grid:
packages/ui/src/internal/glyphs.tsxdraws nine glyphs on a 16 grid at a 1.6 stroke because the package may depend on nothing but React, Base UI and StyleX;packages/componentsusesreact-iconsat sixteen sites (almost all brand marks) and inlines an<svg>in roughly four hundred files, each to whatever grid its author had. A menu row chevron and the sidebar toggle are two weights of two strokes on two grids, and every new surface draws a third. The product wants a set of its own, with the intermediate treatments (duotone and bulk) and with state: a sidebar that shows what is in it and whether it is open, animated between the two.Summary
packages/ui/src/icons/registry.ts: 75 original icons on one 24 grid (20 live area, 1.5 stroke, round caps and joins, 2px container corners, 2px git nodes). Follows Nucleo drawing conventions, copies none of its paths. Each icon ismarks(outline) plus, where it has depth,layers(mass / mid / front / detail / outer / dots).icon.tsx:<Icon name variant title>andcreateIcon. Four treatments from the same paths: outline, duotone (mass at 18%), glyph (filled, marks cut through an SVG mask so the cut is a hole and not a painted panel colour), bulk (no stroke; mass 35%, mid 55%, front 100%). An icon states no size and no colour: it fills its box and inheritscurrentColor, the contract the existing glyphs have. Decoration unlesstitleis given.stateful.tsx: nine two-state icons (sidebar, chevron, check, eye, play/pause, star, bell, refresh, folder) driven by one registered custom property--lody-icon-tthat CSS transitions; only transform, opacity and dash offset move, no path morphing;prefers-reduced-motionzeroes the duration.index.ts: 75 named components (SidebarIcon,BranchIcon, ...), exported as@lody/ui/icons.test/icons.test.tsx, README row,AGENTS.mdrule (existing bullets trimmed in wording only to stay under the 8 KiB gate),specs/ui-primitives.mdIcons section (draft), proposed Agent Note.Visual explanation
Before / after
@lody/ui,react-iconsand ~400 ad hoc inline SVGs in components@lody/ui/icons, four treatments per drawing, nine animated two-state iconsTest plan
pnpm --filter @lody/ui typecheck: clean.pnpm --filter @lody/ui test: 21 files, 261 tests pass (17 new intest/icons.test.tsx: registry shape and grid bounds, a11y contract, the four treatments, mask uniqueness and name-in-id, outer strokes surviving a glyph, bulk without outline, two states as one markup with one number changed).oxlinton the new and changed files: 0 errors.pnpm run docs check: only the four pre-existingacp-extension-dshbroken links remain.--lody-icon-ttransition interpolates live (see the PR comment for the computed values). jsdom applies no CSS, so tests cover only the two ends.check:public-boundaryfailed on uninitialised submodules in this worktree, unrelated to this diff.Context handoff
Instructions for reviewing agents
icon.tsxglyph mask (id fromuseIdplus the icon name; strokes outside the mass drawn outside the mask), andstateful.tsx(CSS.registerPropertyguard, StyleX values usingvar(--lody-icon-t), reduced-motion media query).sinof the same number rather than a keyframe; theAGENTS.mdtrims must not have changed any rule.sin()incalc()and registered-property transitions need Chromium, Safari 16.4 or Firefox 128; the 16px rendering is the 24 grid scaled, not an optically corrected small size.Authoring context
@lody/uidepends only on React, Base UI and StyleX; no Nucleo paths (paid library, no files in hand, redistribution understood to be forbidden); no product caller migrates;glyphs.tsxuntouched.@lody/ui/icons; a module-levelCSS.registerPropertycall guarded for SSR and repeat registration.Spinnerfromrefresh.🤖 Generated with Claude Code