Add 20+ layout, form, and chat components; expand benchmarking#131
Open
developit wants to merge 41 commits into
Open
Add 20+ layout, form, and chat components; expand benchmarking#131developit wants to merge 41 commits into
developit wants to merge 41 commits into
Conversation
A long-horizon, ID-stamped execution plan for extending Kinu (layout primitives, controls, display, conversation surface, convenience compositions, platform enhancements, and cross-cutting functionality), validated against the current component tree. Includes the Kinu Test, a Definition-of-Done runbook, platform-gating policy, and phased sequencing. Parks the Popover-API overlay migration and records withdrawn proposals.
…olve FUN-5/DIS-4 - Add §0.7 execution discipline: one item at a time, JS-primary/CSS-secondary min+gzip deltas recorded per item; update CON-5 and DoD #12 accordingly. - FUN-5: keep hand-maintained dark palette + HSL-triplet format; defer light-dark(), forward path via CSS Custom Functions (@function) preserving the triplet/alpha format. - DIS-4: Code becomes a shipped component with an opt-in highlighting plugin seam. - Add §12 decisions log capturing the above.
biome 1.9's experimental CSS parser cannot handle Kinu's deliberate modern CSS (attr(... type(<number>)), @position-try, nested @starting-style), producing 21 false-positive parse errors. Disable biome's CSS linter (tsc remains the type gate; CI gates size, not lint) and fix the 10 real JS/TS nits it surfaced in demo/docs: - 6 decorative <svg> get explicit aria-hidden="true" (matches existing usage) - 4 array-index keys replaced with stable keys (RaceStage.note, budget.label, mapped row value) pnpm lint now passes (tsc + biome, exit 0). No library bundle change. JS Δ 0 B · CSS Δ 0 B (biome.json/demo/docs are not in dist).
Single source of truth for adding a component: the platform-first prime directive, toolchain + gates, component folder anatomy, the createSimpleComponent factory and k-attribute styling model, the docs wiring checklist (barrel export, metadata.mjs, examples/<name>.tsx), the Definition-of-Done gate table, and the size-first commit discipline (record gzipped JS Δ / CSS Δ in every src/ commit). Linked from AGENTS.md alongside ROADMAP.md and ARCHITECTURE.md. JS Δ 0 B · CSS Δ 0 B (docs only).
Introduce a 6-step --k-space-* scale (0/xs/sm/md/lg/xl → 0–40px) in variables.css, placed with --k-radius in the root block so it persists across light/dark. Step names mirror kinu's existing sm/md/lg size convention so future layout primitives can map gap props directly to a token. Documented in the Theming page with an override example; this also makes the page's long-standing "spacing tokens" claim accurate. JS Δ 0 B · CSS Δ +32 B (gzip, dist/index.css 10982→11014).
Codify how kinu adopts cutting-edge platform features without breaking users: progressive enhancement over hard failure, native capability detection over version sniffing (the installCommands() native no-op as the model), SSR-safety by construction, documenting the baseline in component notes, and no polyfill bloat by default. Grounded in existing practice (ProgressRing typed attr(), Textarea field-sizing fallback, the commands polyfill's three-guard install). JS Δ 0 B · CSS Δ 0 B (docs only).
Codify the two shared mechanisms new interactive components must reuse rather than reinventing: (1) the command/commandfor attribute model with its layered, tree-shakeable, idempotent + SSR-safe installers in src/lib/commands.ts; and (2) event-driven global singletons modeled on toast (module-level dispatch + one mounted container, no provider/context, SSR-safe to import). JS Δ 0 B · CSS Δ 0 B (docs only).
Extend the size harness so size regressions are attributable per output and
per component:
- Report JS and CSS gzip (and raw) separately for every scenario instead of
one combined total; totals still reconcile (1.85 / 3.62 / 14.57 KiB).
- Add a per-component matrix: enumerate src/components/* (skipping folders
without an index, e.g. toast), build each in isolation with bounded
concurrency, and emit a table sorted by gzip. 63 components in ~2.6s
(vite warm-caches across in-process builds).
- Outputs land under benchmarks/size/.tmp/components/<name>/, which the CI
compressed-size-action pattern ({dist,benchmarks/size/.tmp}/**) already
matches — so per-component size deltas now surface automatically on PRs.
- BENCH_QUICK=1 / --quick skips the matrix for fast local iteration;
BENCH_CONCURRENCY tunes the pool.
- Ignore benchmarks/size/.tmp in biome so the minified build artifacts the
harness emits are never linted.
JS Δ 0 B · CSS Δ 0 B (benchmark tooling + config; nothing bundled).
CSS-only flex column with a token-based gap. One-line factory
(createSimpleComponent('stack','div')); all behaviour is attribute
selectors:
- gap: 0|xs|sm|md|lg|xl -> --k-gap -> --k-space-* (default md)
- align: start|center|end|stretch (align-items)
- justify: start|center|end|between|around|evenly (justify-content)
Wired into the barrel export, docs metadata + live example, and an SSR
smoke test (renders without a DOM). First component through the CON-5
per-component size matrix: isolated 1.39 KiB gzip.
JS Δ +17 B · CSS Δ +168 B (gzip, dist/).
CSS-only flex row with wrap for chip rows, button rows, and toolbars. Same token-based gap mechanism as Stack; align defaults to center, gap to sm. - gap: 0|xs|sm|md|lg|xl -> --k-gap -> --k-space-* (default sm) - align: start|center|end|stretch|baseline (align-items, default center) - justify: start|center|end|between|around|evenly Barrel export, docs metadata + example (chip/button row), SSR smoke test. Isolated 1.41 KiB gzip. JS Δ +14 B · CSS Δ +125 B (gzip, dist/).
CSS-only auto-fit grid for dashboards and card galleries: repeat(auto-fit, minmax(var(--k-grid-min,16rem), 1fr)). - gap: 0|xs|sm|md|lg|xl -> --k-gap -> --k-space-* (default md) - min: xs|sm|md|lg|xl column-width presets (10-24rem) -> --k-grid-min; override arbitrarily via the --k-grid-min custom property - cols: 1-6 for a fixed equal-column grid (overrides auto-fit) Barrel export, docs metadata + card-grid example, SSR smoke test. Isolated 1.39 KiB gzip. JS Δ +11 B · CSS Δ +195 B (gzip, dist/).
CSS grid named-area page scaffold with compound parts (AppShell.Header / Sidebar / Main / Footer) rendered as semantic <header>/<aside>/<main>/ <footer>. min-height:100dvh; collapses to a single column at <=48rem where the inline sidebar rail hides and mobile nav can hand off to the modal Sidebar dialog. Zero JavaScript. Barrel export, docs metadata + constrained shell example, SSR smoke test covering all four parts. Isolated 1.44 KiB gzip. Note: the roadmap's optional DoD demo migration (rebuild the Linear shell route) is deferred to the demo-migration pass; this lands the component. JS Δ +108 B · CSS Δ +108 B (gzip, dist/).
Run scripts/generate-docs.mjs to emit the generated docs artifacts for Stack, Cluster, Grid, and AppShell (manifest.json entries consumed by the demo + per-component markdown pages). Completes the docs half of the LAY-1/ 2/4/7 DoD; subsequent components regenerate these in-commit. JS Δ 0 B · CSS Δ 0 B (generated docs only).
Form-associated star rating built from a native radio group — keyboard accessible and form-submittable with no custom JS event wiring. N radio + label pairs render in descending DOM order so a CSS sibling combinator fills the checked star and every lower one; flex row-reverse flips it back, and :has() drives the hover preview. Inputs carry per-star aria-labels. - name (required), value, count (default 5), readOnly, size (sm|md|lg) - readOnly disables the inputs; :has(input:disabled) suppresses hover Barrel export, docs metadata + example + generated page, SSR smoke tests (interactive + read-only). JS Δ +346 B · CSS Δ +189 B (gzip, dist/).
The generated per-component cases used `import * as C; void C;`, which let the bundler tree-shake the component body away — leaving only the CSS side-effect import and under-reporting JS for logic components (e.g. Rating read 0.06 KiB gzip JS when its real cost is ~0.55). Switch the generated entry to `export * from '<component>'` so the component is retained and its cost-when-used is measured. Logic components now report real JS (dropdown-menu 2.09, combobox 1.64, dialog 1.25 KiB gzip); pure-CSS primitives are unchanged. This also makes the .tmp outputs the CI compressed-size-action tracks reflect true per-component JS. JS Δ 0 B · CSS Δ 0 B (benchmark tooling; nothing bundled).
Native <input type="number"> in an InputGroup with − / + buttons. The buttons fire command="--step-down"/"--step-up" on the existing command bus; the input is a factory component whose internal ref (4th-arg, the Carousel pattern) listens for the bubbled `command` event and calls native stepUp()/stepDown(), then dispatches input/change so forms stay in sync. Zero new global listeners. Forwards every native input attribute. Native Invoker Commands are Baseline; installCommands() is the old-Safari fallback. Barrel export, docs metadata + example + generated page, SSR smoke test. JS Δ +231 B · CSS Δ +130 B (gzip, dist/).
One-tap copy-to-clipboard button. A factory button whose internal ref wires click -> navigator.clipboard.writeText, then toggles a [copied] attribute for ~1.2s. The idle/copied labels render via CSS attr() (data-label / data-copied), so the success state needs no JS text swap. Copies `value`, or the textContent of the element matched by the `for` selector. SSR-safe: navigator is only touched inside the click handler. Barrel export, docs metadata + example + generated page, SSR smoke test. JS Δ +207 B · CSS Δ +129 B (gzip, dist/).
Pure-CSS metric block with compound parts Stat.Label, Stat.Value, and Stat.Delta. Stat.Delta takes trend (up|down|flat), colored via the semantic success/destructive tokens through an attribute selector. Composes with Grid for dashboard stat rows. Zero JavaScript. Barrel export, docs metadata + example + generated page, SSR smoke test. JS Δ +70 B · CSS Δ +68 B (gzip, dist/).
Wire the forms layer to native constraint validation with no JS validation logic and no "touched" state: - Add :user-invalid / [invalid] destructive styling to Textarea, Select, and Checkbox, matching the existing Input/NumberField/OTP pattern. - Field.Error is now hidden until the field is invalid — it reveals on :has(:user-invalid) or :has([invalid]) (server-side errors), and keeps its role="alert". The label already reddens via :has(:user-invalid). - New docs/pages/forms.md documenting the :user-invalid timing, the Field + Field.Error contract, custom/server messages, and the submit contract. :user-invalid is Baseline; :user-valid is left unstyled for apps to use. JS Δ +0 B · CSS Δ +73 B (gzip, dist/).
Productize the "intuitive for LLMs" contract so it never drifts again:
- scripts/gen-llms.mjs builds llms.txt (a static preamble +
scripts/llms-preamble.md, plus an always-current component reference) and
a machine-readable components.json from docs/metadata.mjs. Run on every
`pnpm build`, so adding a component updates the contract automatically.
- Fix the long-standing drift: the old hand-maintained llms.txt claimed the
attribute was `p` ("the p stands for kinu"); the code emits `k`. The
generated copy is correct and now covers all 71 components (the old one
predated Stack/Grid/Stat/Rating/CopyButton/NumberField/…).
- Ship it: add `./llms.txt` and `./components.json` package exports + files
entries, so `kinu/llms.txt` resolves for agents.
- Regenerate demo/public/llms.txt (served by the docs site).
JS Δ 0 B · CSS Δ 0 B (tooling + generated docs; nothing bundled).
CSS-only flex row — the horizontal sibling of Stack. Same token-based gap; align defaults to center, gap to sm, no wrap by default (pass wrap, or use Cluster when wrapping is the point). Kept separate from Stack[direction] for LLM legibility per the roadmap. Barrel export, docs metadata + example + generated page, SSR smoke test. JS Δ +9 B · CSS Δ +102 B (gzip, dist/).
CSS-only centering via display:grid;place-items:center. Pass inline to shrink-wrap and center inline. Zero JavaScript. Barrel export, docs metadata + example + generated page, SSR smoke test. JS Δ +14 B · CSS Δ +22 B (gzip, dist/).
CSS-only flex spacer. Default flex:1 grows to push siblings to opposite ends of a Row/Cluster/Stack; pass size (xs-xl) for a fixed gap (via flex-basis, so it works along either axis). Completes the LAY family. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +5 B · CSS Δ +58 B (gzip, dist/).
Pure-CSS horizontal stepper: <ol k="steps"> + <li k="step"> with CSS-counter numbered markers and flex line connectors, zero JavaScript. Stepper.Step takes state (upcoming|current|complete) -> data-state, which drives the marker (number vs check), the primary outline for current, and the connector color for traversed steps. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +74 B · CSS Δ +233 B (gzip, dist/).
Pure-CSS chat message bubble. <div k="message" from="user|assistant| system"> drives alignment (user flips to the right) and bubble color (user = primary, assistant = muted, system = quiet italic). Compound parts Message.Avatar (round badge) and Message.Bubble (content; drop a <Prose> inside for markdown). Zero JavaScript. Barrel export, docs metadata (new Conversation category) + example + generated page, llms regen, SSR smoke test. JS Δ +62 B · CSS Δ +114 B (gzip, dist/).
A scroll container that pins to the latest message via native scroll anchoring: children opt out of anchoring and a 1px bottom anchor element keeps the viewport at the bottom as messages are appended — no scrollTo loop, no JavaScript. Pass scrollable to enable; degrades to a normal scroll container where anchoring is unsupported. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +60 B · CSS Δ +47 B (gzip, dist/).
A <form k="composer"> around an autosizing Textarea. Enter submits, Shift+Enter inserts a newline — one delegated keydown calls form.requestSubmit(), guarded against IME composition. Compound parts Composer.Send (submit button on the trailing edge) and Composer.Actions (row for attach/model controls). No chat engine: typing indicator is Spinner, suggestions are a Cluster of Chip; bring your own submit handler. Also types Textarea's documented `autosize` prop, which was missing from TextareaOwnProps (surfaced because docs examples aren't tsc-checked). Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +146 B · CSS Δ +151 B (gzip, dist/).
<form k="form"> with no form-state engine. On submit it runs native checkValidity(): if invalid it blocks submission and focuses the first invalid control; if valid it calls onValid with the event. Pairs with the :user-invalid CSS layer (FUN-1) and Field/Field.Error — no touched/errors state to manage. Repurposes the orphaned legacy examples/form.tsx. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +88 B · CSS Δ +7 B (gzip, dist/).
A command palette assembled entirely from shipped parts: a modal Dialog hosting a Listbox. The Listbox provides substring filtering (Command.Input) and keyboard navigation (installMenuShortcuts); the Dialog provides the modal + focus trap. Command.Input/List alias ListboxInput/ListboxList and Command.Item is Item, so items keep shortcut/destructive/href. No fuzzy engine, no new state. Place inside <Dialog> and open via trigger or hotkey. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +49 B · CSS Δ +147 B (gzip, dist/).
A tags/token input that submits natively: a Cluster-like box of Chips plus a text field, mirrored into a hidden form input. The one place a little array state is honest — a single per-mount ref manages the chips imperatively (no framework store), reading config from data-* attributes so it stays module-scoped and SSR-safe. Enter (or the separator) adds a tag, Backspace on empty removes the last, a chip's × removes it; every change rewrites the hidden field and dispatches input/change. API: name, value, separator, max, duplicates. Submitted value is the tags joined by separator. Imports Chip's CSS so it's styled standalone. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +740 B · CSS Δ +106 B (gzip, dist/).
Native <input type="password"> in an InputGroup with a Show/Hide toggle. The toggle rides the command bus (command="--toggle-password", like NumberField): a per-input ref flips the type and a [revealed] attribute, and the toggle's label follows via the adjacent-sibling combinator. No new global listeners, SSR-safe. Forwards native input attributes; pass defaultRevealed to start visible. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +206 B · CSS Δ +139 B (gzip, dist/).
Pure-CSS corner overlay: a <span k="indicator"> that positions a Badge child (or a [dot]) at a corner of whatever it wraps — Avatar, Button, icon. placement = top-end (default) | top-start | bottom-end | bottom-start; dot renders a small status dot with no count. Zero JavaScript. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +14 B · CSS Δ +148 B (gzip, dist/).
Add a composable [k="alert"][banner] modifier — full-bleed, square corners, page-level horizontal inset — for page-level banners. Implemented as a boolean attribute (not a tone variant) so it composes with any tone, e.g. <Alert variant="info" banner>. Replaces the proposed standalone Banner component. CSS-only. JS Δ +0 B · CSS Δ +35 B (gzip, dist/).
Pure-CSS chrome over <pre> (block) / <code inline> (inline): mono font, surface, radius, horizontal scroll, zero JS for the base. Code.Copy is a corner button (revealed on hover) that copies the enclosing block; its label renders via CSS ::after so it adds nothing to the copied text. No highlighter in core — the seam is the advisory [language] attribute (an opt-in plugin can target [k="code"][language]) and/or pre-highlighted children. Barrel export, docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +85 B · CSS Δ +200 B (gzip, dist/).
Add a @supports (appearance: base-select) block to select/style.css that upgrades Select to a fully stylable dropdown on Chromium — themed ::picker(select), hover/focus/checked option states, hidden default checkmark, and a rotating picker icon — using kinu tokens. Gated, so it degrades to the native styled <select> everywhere else with zero added JavaScript and zero risk (CON-3). No API change. JS Δ +0 B · CSS Δ +157 B (gzip, dist/).
A global keydown singleton (installHotkeys, CON-4 pattern) plus a <Hotkey> that renders a hidden command button tagged with the chord. On match the listener clicks it, firing its command on the bus — so keyboard shortcuts reuse command/commandfor with no per-app key handlers and no JS registry (the DOM is the registry). Chord syntax: mod (⌘/Ctrl), alt, shift + key; bare keys ignored while typing. SSR-safe; pairs with Command and Kbd. Barrel export (+ installHotkeys/normalizeChord), docs metadata + example + generated page, llms regen, SSR smoke test. JS Δ +481 B · CSS Δ +10 B (gzip, dist/).
Ship a tiny transition(cb) helper that runs a DOM update inside a View Transition where supported and falls back to a synchronous update otherwise (SSR-safe). Formalize the existing --k-ease-* motion tokens and document view-transition-name conventions and the cross-document @view-transition recipe in a new Motion & View Transitions page. Exported from kinu. Adds a real unit test for normalizeChord + transition. JS Δ +53 B · CSS Δ +0 B (gzip, dist/).
Productize theming per the FUN-5 plan (keeping the HSL-triplet format and hand-maintained dark palette — no light-dark(), per the decision log): - Document the --k-* tokens as a stable public contract in the Theming page (grouped token table + the h s l triplet/alpha convention). - Ship two importable preset brand themes, kinu/themes/violet.css and kinu/themes/emerald.css, each overriding only the brand tokens across the light, system-dark, and explicit-dark scopes; added package exports + files. - Fix a Theming doc bug: dark mode uses data-color-scheme, not data-theme. JS Δ 0 B · CSS Δ 0 B (standalone theme CSS + docs; not in the bundle).
- Add a --k-density scalar (default 1) that multiplies control heights/ padding; convert Button's md size as the reference (calc(... * var(--k-density))), unchanged at the default. - Restore a system-color focus outline under forced-colors (Windows High Contrast) in base.css, where box-shadow focus rings are dropped, for the interactive components. - Document density, RTL (components already use logical properties, so they mirror under dir=rtl), and forced-colors guidance in the Theming page. JS Δ +0 B · CSS Δ +93 B (gzip, dist/).
Add a [virtual] base rule that applies content-visibility:auto + contain-intrinsic-size to a container's rows, so List/Listbox/Tree (or any [k] wrapper) render thousands of rows smoothly without a JS virtualizer. Row-height estimate overridable via --k-virtual-row. Documented as a recipe in the Base Styles page. Pure CSS, degrades gracefully where unsupported. JS Δ +0 B · CSS Δ +42 B (gzip, dist/).
✅ Deploy Preview for kinu-sh ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
Size Change: +171 kB (+454.66%) 🆘 Total Size: 209 kB 📦 View Changed
ℹ️ View Unchanged
|
Replace the N-radio + :has() star widget with a single styled <input type="range"> and ZERO JavaScript. The fill follows the live value entirely in CSS: a 1px thumb's huge box-shadow paints the filled stars on WebKit, ::-moz-range-progress does it on Firefox, both masked into a star row; the star count reads from `max` via typed attr() like ProgressRing. One form-associated, keyboard-accessible element instead of 11. Verified in-browser (Chrome): renders correctly across value/count/size/ readOnly, and the fill tracks the live value on native arrow-key input with no script. API unchanged (name/value/count/readOnly/size). Net vs the radio group: JS Δ -225 B · CSS Δ +124 B (gzip) — the render loop + useId are gone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR significantly expands kinu's component library with 20+ new components focused on layout primitives, form enhancements, and chat/messaging UI. It also introduces comprehensive benchmarking infrastructure to track per-component bundle size and generates LLM-friendly documentation.
Key Changes
New Layout & Spacing Components
Stack,Row,Cluster,Grid,Center,Spacer— CSS-driven flex/grid layout primitives with token-based gap controlAppShell— full-page scaffold with header, sidebar, main, footer grid layoutStepper— horizontal ordered steps with numbered markersForm & Input Enhancements
NumberField— native number input with stepper buttons driven by the command busPasswordInput— password input with show/hide toggleTagsInput— token/tags input that submits via hidden fieldForm— wrapper that runs native validation before submit handler:user-invalidCSS styling toSelect,Textarea,Checkboxfor post-interaction validation feedbackChat & Messaging Components
Message— chat bubble with author-driven color and alignmentThread— message list that sticks to bottom as new messages arriveComposer— chat input form with autosizing textarea and Enter-to-sendCommand— command palette (Dialog + Listbox composition)Utility & Display Components
Code— styled code block and inline code with optional copy buttonCopyButton— one-tap copy-to-clipboard with CSS-only copied stateRating— star rating built from native radio groupIndicator— corner overlay for notification counts or status dotsHotkey— declarative keyboard shortcut binding to commandsInfrastructure & Documentation
benchmarks/size/run.mjs) tracking JS, CSS, and total sizesllms.txtandcomponents.jsonfrom component metadata for LLM authoring guidanceForms & ValidationandMotion & View TransitionsCONTRIBUTING.md— single source of truth for component contribution processROADMAP.md— long-horizon execution plan with stable IDs and status trackingARCHITECTURE.md— expanded with platform-gating policyLibrary Utilities
src/lib/hotkeys.ts— global hotkey singleton using delegated keydown listenersrc/lib/motion.ts— View Transitions API wrapper with fallbacksrc/lib/commands.ts— enhanced command bus for custom commands (--prev,--next,--step-up,--step-down)Styling & Theming
--k-space-0through--k-space-xl) for layout primitivesthemes/emerald.css,themes/violet.cssbase.csswith forced-colors (Windows High Contrast) supportAlertto supportbannerattribute for full-bleed page-level alertsButtonsizing defaults and addedTextareaautogrowattributeTests
layout.test.tsx,controls.test.tsx,display.test.tsx,chat.test.tsx,lib.test.tsxNotable Implementation Details
createSimpleComponentpattern — thin JSX-to-HTML pass-throughs withkattribute styling hooks:user-invalid,required,type,pattern, etc.) — no validation libraryMessage,Thread,Composer) compose existing primitives (e.g.,ComposerwrapsTextarea)https://claude.ai/code/session_01ScFTtdr4ETgSEGEYG5Hopr