diff --git a/.agents/docs/components-package.md b/.agents/docs/components-package.md index a370ca77c..5abb0ed09 100644 --- a/.agents/docs/components-package.md +++ b/.agents/docs/components-package.md @@ -58,6 +58,12 @@ button for a file the machine will refuse, and the user would lose the edit at s ## Theming and fields +Reusable visual primitives and semantic design tokens are owned by +[`@lody/ui`](../../packages/ui/README.md). `@lody/components` composes those +primitives into product surfaces and may add layout or interaction constraints, +while the primitive's props remain the owner of its visual variant, size, tone, +and shape. + `--input` is the theme's raw `input.background` and doubles as a muted chip/composer slab, so in a light theme it can sit below the page color and read as a disabled field. `--input-field` is derived in `lib/vscode-theme/vscode-theme-css.ts` as the lighter of diff --git a/.agents/notes/implemented/architecture/2026-09-08-ui-button-migration-takeover.md b/.agents/notes/implemented/architecture/2026-09-08-ui-button-migration-takeover.md new file mode 100644 index 000000000..30fc939bd --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-08-ui-button-migration-takeover.md @@ -0,0 +1,68 @@ +# UI Button migration takeover + +Status: implemented +Translation: pending +Issue: https://github.com/LodyAI/Lody/issues/304 +Original PR: https://github.com/LodyAI/Lody/pull/305 + +## Abstract + +PR #305 redesigns Lody buttons around `@lody/ui`, Base UI, and StyleX tokens, +but its September 2 branch no longer merged cleanly with current `main`. The +takeover keeps that redesign as the visual authority, preserves business behavior +added on `main`, and migrates newer Button consumers to the author's public API. +It does not provide a compatibility layer for the deleted Button or restore its +appearance through caller classes. Surface-specific accessibility and layout +requirements remain explicit at their owning call sites. + +## Decision + +Update the author's branch by merging current `main` into its existing commit. +Resolve overlapping product files by retaining current navigation, session, +onboarding, machine, and Skills behavior, then express their controls using the +new Button variants, sizes, tones, shapes, and icon flag. + +New Button consumers added after the PR base follow the same API used by the +author's migration. They do not select sizes by matching the removed component's +pixel dimensions. Mermaid viewer controls keep their existing 44 px touch target +because that surface has an independent mobile accessibility requirement; the +new Button still owns their appearance. + +`@lody/ui` owns primitive visuals and semantic tokens. Product packages compose +those primitives and may pass classes for layout or interaction constraints. +Caller classes must not recreate the deleted Button's colors, typography, +shadows, radii, or state styling. + +Composer submit and stop actions use the Button's `primary`, `medium`, `pill`, +and icon choices directly. Their former `ghost` variant plus Tailwind color, +size, radius, shadow, and press-state overrides bypassed the primitive and could +leave the stop glyph with insufficient contrast. Landing, session, mobile-sheet, +and Storybook compositions now select the same token-owned control treatment. + +## Integration evidence + +The original PR is commit `11ef421f86c29fe50773c6c101bf129839368c3c`, +based on `947affd0741e35b3644622c841e8457a26133a95`. Current `main` is merged as +the second parent so the author's commit and authorship remain intact. Conflict +resolution retains the current package dependencies and application handlers, +adds `@lody/ui` where needed, and regenerates the lockfile from the merged +manifests. + +Verification used the repository's supported Node 22 runtime. The complete +`pnpm check` suite passed after disabling commit signing for its synthetic Git +repository, and the focused `@lody/ui` test suite passed all eight tests. The +Electron application, documentation site, and component Storybook production +builds completed with StyleX compiling the package source. A source scan found no +remaining imports of the deleted Button module or uses of its old size and variant +props. + +Interactive browser screenshot comparison was unavailable in the takeover +environment. The production application and Storybook builds provide compiler +and integration coverage, but a maintainer should still inspect the PR preview as +the final visual acceptance step. + +## Limits + +This migration establishes the Button and token foundation described in the +linked draft Spec. Other primitives remain in `@lody/components` until a later +migration gives each one an explicit `@lody/ui` contract. diff --git a/.agents/notes/implemented/architecture/2026-09-17-ui-icons-package.md b/.agents/notes/implemented/architecture/2026-09-17-ui-icons-package.md new file mode 100644 index 000000000..24739a718 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-17-ui-icons-package.md @@ -0,0 +1,48 @@ +# Extract the icon system into `@lody/icons` + +Status: implemented +Translation: pending + +## Abstract + +The icon system is now an independently consumable workspace package. The +extraction removes its implementation dependencies on `@lody/ui` while keeping +the old UI subpath available during caller migration. + +## Decision + +The original Lody icon registry, SVG renderer, four visual treatments, and +stateful icons now live in the independent `@lody/icons` workspace package. +`@lody/ui` depends on `@lody/icons`; the icon package does not depend on +`@lody/ui`, Base UI, product components, or UI token modules. + +The old `@lody/ui/icons` subpath remains a compatibility re-export so existing +consumers do not need a flag-day import change. New code and the icon playground +use `@lody/icons` directly. The legacy `src/internal/glyphs.tsx` primitives +remain in `@lody/ui` because they are private marks owned by its controls, not +members of the independent icon set. + +## Implementation + +- Moved the icon registry, renderer, stateful icons, icon-specific rules, and + tests from `packages/ui` to `packages/icons`. +- Replaced the renderer's dependency on `@lody/ui/internal/class-name` with a + local helper. +- Replaced the stateful renderer's dependency on `@lody/ui` motion tokens with + its own fixed transition contract. +- Added public root and focused subpath exports to `@lody/icons` and added its + workspace importer and lockfile entry. +- Updated the existing icon playground to consume the new package and emit + `@lody/icons` examples. + +## Validation + +- `pnpm --filter @lody/icons typecheck` +- `pnpm --filter @lody/icons test` — 18 tests passed +- `pnpm --filter @lody/ui typecheck` +- `pnpm --filter @lody/ui test` — 258 tests passed + +The package extraction implements the boundary proposed in +`.agents/notes/proposed/feature/2026-09-14-ui-icon-set.md`; product-wide +migration from `lucide-react` and other product-local icons remains separate +work. diff --git a/.agents/notes/implemented/feature/2026-09-09-ui-field-primitives.md b/.agents/notes/implemented/feature/2026-09-09-ui-field-primitives.md new file mode 100644 index 000000000..7f9e6dcee --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-09-ui-field-primitives.md @@ -0,0 +1,266 @@ +# UI field primitives + +Status: implemented +Translation: pending + +## Abstract + +`@lody/ui` had proven one component and a token set, but every remaining control +in `packages/components/src/ui` still reads Tailwind field concepts — +`input-border`, `input-field`, `input-placeholder`, `ring`, `muted` — that the +new token rules define no equivalent for. Migrating Input, Textarea and Label +one at a time across that gap would have let each control invent its own focus, +invalid and disabled colours, forking the token system the migration exists to +unify. This note records adding one `field` component token group and a Base UI +Field composition — `Field.Root` with `Field.Label`, `Input`, `Textarea`, +`Field.Description` and `Field.Error` — so those states are defined once and read +from the field rather than passed to each control, and writing the state mapping +into the token rules and the gallery. Building it exposed that the product +shell's `*:focus, *:focus-visible { outline: none !important }` suppresses every +outline-based focus ring, including the migrated Button's, so the field ring is a +box-shadow instead; Button still carries the suppressed outline and is not fixed +here. Every in-repo caller then moved onto the composition and the three Radix +and Tailwind files were deleted, which is what took the mapping from a board +into the product. + +## Problem + +The package exported `Button` and the theme; `packages/components/src/ui` +still owned every other atom. Proving "the Button pattern works" is not the same +as proving the contract for a control that has states. + +The three files this pilot replaces read a vocabulary the new tokens do not have: + +- `input.tsx`: `border-input-border`, `bg-input-field`, `text-input-foreground`, + `placeholder:text-input-placeholder`, `focus-visible:ring-1 ring-ring`, + `disabled:bg-muted disabled:opacity-60` +- `textarea.tsx`: the same set, plus `ring-offset-background` and an explicitly + removed focus ring +- `label.tsx`: `@radix-ui/react-label` with `peer-disabled:opacity-70` + +Three different disabled treatments (`bg-muted` + 60%, 70%, and the rules' +45%) and two different focus rings already existed in three files. Without a +decided mapping, each migrated control would add a fourth. + +## Decision + +**One token group for the family, not one per component.** `field` covers the +label, the control, the help text and the error, and the selects, checkboxes and +switches that follow. `button` stays separate because a button is not a field. +The alternative — `input`, `textarea` and `label` groups — is exactly the fork +this change exists to prevent: the same focus colour would be declared three +times and drift on the first redesign. + +**Composition, not props.** `Field.Root` owns `name`, `disabled` and validity. +`Input` and `Textarea` read that state through Base UI's `className` callback and +pick their own classes from it. A control therefore has no `invalid` prop for a +caller to keep in sync with the field, and a label is associated with its control +without a hand-written `htmlFor`. + +**Base UI throughout.** `Input` is Base UI's `Input`, which is `Field.Control`. +Base UI has no textarea part, so `Textarea` is the same `Field.Control` rendered +as a `