Repository navigation
feat(ui): status tones, chips, lists and a preview stylesheet, so a solution page is built from the kit (Part of #1001) - #1005
Merged
antoinetoussaint-byte merged 1 commit intoOct 4, 2026
Conversation
antoinetoussaint-byte
requested a review
from AntoineToussaint
as a code owner
October 4, 2026 00:25
…olution page is built from the kit (Part of #1001) A consuming solution measured where it reached past @codefly-dev/ui: about a third of one 332-line page was hand-laid markup standing in for missing kit pieces, and the page could not be rendered at all without a running host. This delivers items 1-7 of #1001; items 8 and 9 (Markdown source offsets and link resolution, Board) are #1010. Status tones. `success`, `warning` and `info` join `destructive` as appearance tokens (contract 2.4.0, defaults for both modes, TOKENS.md), each one colour read as text and as a tint of itself, the shape `destructive` already had. `StatusTone` (neutral | success | warning | danger | info) is shared by `Badge tone`, `Chip tone` and `Banner tone`. A tone replaces a variant's colours rather than stacking on them. `Badge` also takes `size` (sm | default | lg) and keeps #998's decorative `dot`, now sized with the badge. New components: `Chip` / `ChipGroup` (link, button or router link; leading icon, trailing meta, a remove control beside the action, never inside it; no overflow clipping, so the focus ring is never cropped), `List` / `ListItem` and `DescriptionList`. `EmptyState reference` is a pointer elsewhere on the page, distinct from the explanation and from the action. Each gets its own type slots, so a skin can restyle them independently. The token layer moves into the kit. The `@theme` mapping, the light/dark binding and the shadcn custom variants lived in the host's globals.css, so a class a kit component wrote meant nothing outside the host. They are now `@codefly-dev/ui/theme.css`, which globals.css imports; the host's compiled stylesheet is byte-identical apart from the new tokens. The projection of a skin onto custom properties moves with it to `@codefly-dev/ui/skin`, and src/lib/appearance.ts re-exports it. `@codefly-dev/ui/preview.css` is the kit's source compiled with that layer and the default skin. It is committed and checked for staleness rather than built at publish: its bytes depend on the contract's defaults and on the Tailwind toolchain in the lockfile, neither inside the kit's folder, so the kit-version gate could not see them move and the publish would refuse the rebuilt bytes under an unchanged version. It is for a solution's local preview, stories and screenshots only: never loaded by a host or bundled into a remote, not evidence of a deployment's look, and it styles kit components only. A host test renders every kit story and fails if any class one of them carries is not defined there, matched as a whole class. The kit eats its own components: DatasourcesPanel and DeclaredSourceCard read their statuses from tones (paused is a warning in both, not destructive), the panel's hand-laid error card becomes a danger Banner, and the card's declaration and ambiguous-match list become DescriptionList and List. Kit 0.11.5 -> 0.12.0 (ui and saas-ui co-versioned; saas-ui now needs ^0.12.0); @codefly/saas-plugin-contract 2.3.0 -> 2.4.0 with every exact pin. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
antoinetoussaint-byte
force-pushed
the
issue-1001-codefly-dev-ui-the-kit-elements-a-solution
branch
from
October 4, 2026 16:07
9e9a713 to
8e525d1
Compare
This was referenced Oct 4, 2026
antoinetoussaint-byte
deleted the
issue-1001-codefly-dev-ui-the-kit-elements-a-solution
branch
October 25, 2026 17:07
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.
Part of #1001: items 1–7. Items 8 (
Markdownsource offsets andresolveLink) and 9 (Board) are #1010. Issue #1001 stays open until both land.A consuming solution measured where it reached past
@codefly-dev/ui: about a third of one 332-line page was hand-laid markup standing in for missing kit pieces, and the page could not be rendered at all without a running host. This PR adds the seven pieces from its gap list. It also makessaas-ui's two data-source surfaces use them, so the kit eats its own components.Classification
Fix, every part. The kit lacked the primitives, and the token layer behind them lived in the wrong package. Each change is made where that behaviour is owned:
There is no stopgap and no special case for one consumer. Nothing here names a solution.
What changed
success,warningandinfoare new appearance tokens besidedestructive, defined in the contract, with light and dark defaults, and listed in TOKENS.md.@codefly/saas-plugin-contractgoes 2.3.0 → 2.4.0, and every exact pin moves with it, as Carry a design system in four layers: scale, roles, slots and rules #850 and feat: a DateField whose shape the skin chooses, and the explorer adapter that shows it #881 did.destructivealready had, so no foreground pair is needed.StatusToneisneutral | success | warning | danger | info. It is shared byBadge tone,Chip toneandBanner tone, anddangerreadsdestructive.Badgealso takessize(sm | default | lg). It keeps feat: a solution declares the source it is built on, and saas-ui asks only for the credential (Closes #997) #998's decorativedot, now sized with the badge.neutral/success/danger.destructive's text contrast. A skin overrides them like any other token.Chip/ChipGroup.hrefrenders a link,onClicka button, andrenderswaps in a router link.iconleads andmetatrails after a separator:meta="unread"reads "· unread".onRemovewith a requiredremoveLabeladds a remove control. It sits beside the action, never inside a link.ChipGroupis a named, wrappingulof chips.Card description. feat: a solution declares the source it is built on, and saas-ui asks only for the credential (Closes #997) #998 shipped this. This PR leaves it unchanged; it's in the stories and the docs here.List/ListItemandDescriptionList.ListItemtakesicon,description,metaandactions.variant="divided"rules a line between rows.DescriptionListis term/value pairs,inlinein two baseline-aligned columns orstacked. Rows are keyed by index unlessitem.keyis given, since two rows may share a term.Banner tone.neutralis the default and keeps today's look.dangerisrole="alert"; the rest stayrole="status".EmptyState reference. It holds a pointer elsewhere on the page, separate from the explanation (description) and from the action (children). Links inside it are underlined.Every new surface has its own type slot (
badge-lg,chip,chip-meta,list-item,list-item-description,description-term,description-details,empty-state-reference), so a skin can restyle each one independently. The raw-type baseline is honoured: new files carry zero raw type utilities, anddatasources-panel.tsxdrops from 17 to 15.The kit eats its components:
warning, no longersecondary; degraded isdanger; unknown isneutral.warning, and collection read access issuccess/neutral.Banner tone="danger".success,neutralanddanger.warning, labelled "Paused", the same as in the panel; it still offers Reconnect. Before, it read "Error · paused" indanger.warning, not destructive: both keep ingesting and nothing is broken, the card just can't say which one is the solution's.<dl>is nowDescriptionList, and the ambiguous-match<ul>is nowList.Item 7: the preview stylesheet, and what it may be used for
The move. The
@themecolour mapping, the light/dark binding to--appearance-*, and the shadcn custom variants lived in the host'sglobals.css. So a class a kit component wrote meant nothing outside the host.@codefly-dev/ui/theme.css, andglobals.cssimports it.src/app/shadcn.cssis gone.appearanceStyleProperties) moved with it into@codefly-dev/ui/skin.src/lib/appearance.tsre-exports it.What
@codefly-dev/ui/preview.cssis. The kit's own source compiled with that layer and with the default skin's values at:root, as plain CSS that needs no Tailwind.It is committed (
src/skin/preview.generated.css) and checked for staleness, not built at publish. This changed after review, which found that a dist build was invisible to the kit-version gate:tailwindcss,@tailwindcss/postcss, Lightning CSS andtw-animate-cssversions inpackage-lock.json. None of those is inside the kit's folder.publish-frontend-kit.mjswould refuse the release as "already published with different contents" — the v0.0.67 failure.--check, and a companion test proves--checkrefuses a copy one byte off.A preview build may use it for: rendering, reviewing and screenshotting a solution's pages and stories locally, and visual tests against them. Dark mode is
class="dark"on an ancestor.A preview build may not use it for:
@codefly-dev/saas-uiis not covered either. Its components render inside the host's stylesheet. Whatever classes they share with the kit will paint; the rest won't.The guard.
src/lib/__tests__/preview-stylesheet.test.tsxrenders every kit story and fails if any class one of them carries is missing frompreview.css. That keeps "built from kit components" and "painted in preview" the same claim. The class has to match as a whole selector:.borderis not satisfied by.border-border, nor.p-4by.p-4\.5.test-published-ui.mjspacks the kit, installs it into a scratch consumer, and checks that@codefly-dev/ui/preview.cssandtheme.cssresolve through the package's exports and arrive compiled.Arbitrary values.
DescriptionList'sgrid-cols-[minmax(0,max-content)_minmax(0,1fr)]is the one structural arbitrary utility. It's fine here because the host compiles this kit. The README now says that a kit element meant to render from a remote carries its structure inline, because the host compiles nothing a remote or another module's kit ships.That test names one exclusion,
file:type-input. The bug predates this PR, and I am reporting it rather than fixing it: the type-slot utilities wrap their rule in:where(&), so behind thefile:variant they become:where(.x::file-selector-button). A pseudo-element inside:where()is invalid, so browsers drop that rule in the host stylesheet too, and the preview compile removes it. The input's file-button type has never been styled.Docs updated
packages/codefly-ui/README.md:theme.css.packages/codefly-ui/TOKENS.md: three colour rows, the new slots, and the name→colour chain, which now runs throughtheme.cssandpreview.css.packages/codefly-ui/CATALOG.md: the layout tier, and the sentence on host CSS authority.packages/codefly-ui/README.md, also: whypreview.cssis committed, and that structure travels inline for anything the host doesn't compile.module/services/frontend/AGENTS.md, "The published client kit": the kit owns the token layer, whatpreview.cssis, and when to regenerate it.Verification
Run locally on the rebased branch (on top of #998):
prepare:frontend,tsc --noEmitandeslint(0 errors).pureVitest project: 300 files, 3,742 tests.kit-sourcemaps,audit-page,onboarding-reminderandcontent, none of which this PR touches. All four files passed in isolation, and a full rerun was green.scripts/cikit-version and release-gates tests: 127 passing. The frontendscripts/tests: 32 passing.kit-version.mjs checkafter committing and fetching tags: 0.12.0 against v0.0.89.check-boundaries.sh,test:published-ui(now including the stylesheet check), and the inventory test.Each new component has tests beside the existing ones, covering every variant and state. Each guard I added was checked to bite:
--color-successremoved;overflow-hiddenrestored on the chip;.borderagainst.border-border;--checkrefuses a stale copy.I also server-rendered the new stories with only
preview.cssand screenshotted them in headless Chromium, in light and dark mode. Every component painted.Kit 0.11.5 → 0.12.0, with
uiandsaas-uico-versioned. saas-ui's peer range moves to^0.12.0because it now usestone.Not verified
preview.csson macOS. CI regenerates it on Linux and compares; if Tailwind or Lightning CSS output differs by platform, that test will show it.codefly ci runwas not run.preview.cssyet. My render harness was a stand-in for one.destructive's contrast, not checked against a WCAG figure.platform-*skins don't declare the new tokens, so they inherit the defaults.🤖 Generated with Claude Code