═══════════════════════════════════════════════════════════════════════ TODO #6 — size-limit budgets in CI ═══════════════════════════════════════════════════════════════════════ Adds .size-limit.cjs with budgets for 6 pilot @bytelyst/* packages, plus a Gitea Actions workflow (.gitea/workflows/size-limit.yml) that fails the build on any regression. Current measurements vs budget (all comfortably under): @bytelyst/api-client 793 B / 8 KB @bytelyst/auth-client 1.97 KB / 8 KB @bytelyst/celebrations 236 B / 6 KB @bytelyst/quick-actions 122 B / 6 KB @bytelyst/react-auth 2.71 KB / 10 KB @bytelyst/dashboard-shell 3.96 KB / 30 KB Scripts: pnpm size — measure + assert against budgets pnpm size:why — explain top contributors Deps: size-limit@^12.1.0, @size-limit/preset-small-lib@^12.1.0. Pilot scope (this commit): 6 packages. Full @bytelyst/* rollout is incremental — each entry added separately. ═══════════════════════════════════════════════════════════════════════ TODO #5 — Storybook canonical pattern ═══════════════════════════════════════════════════════════════════════ Discovery: @bytelyst/ui already has Storybook 8 with the @bytelyst/ addon-a11y addon configured and 5 .stories.tsx files. This commit: - Documents that setup as the canonical template (docs/STORYBOOK_TEMPLATE.md) including the .storybook/main.ts, preview.ts, package.json scripts, and an example story. - Catalogs which of the 8 visual @bytelyst/* packages need Storybook added (1/8 done — @bytelyst/ui). - Per docs/ROADMAP_2026_DECISIONS.md §9, hosting target is self-hosted Gitea Pages (no Chromatic). Full rollout to the other 7 packages stays open as incremental work. ═══════════════════════════════════════════════════════════════════════ TODO #4 — DTCG v3 token migration RFC ═══════════════════════════════════════════════════════════════════════ This is too large to land in a single commit. Drafted RFC instead: docs/rfc/0001-dtcg-v3-token-migration.md The RFC proposes a 4-PR sequence: PR 1 — Add converter + dual-emit (byte-identical assertion) PR 2 — Flip source of truth to DTCG JSON PR 3 — Introduce the component tier PR 4 — Multi-theme via DTCG token sets Estimate: 2 person-weeks total. Backward compatibility: --ml-* and --bl-* names preserved through all 4 PRs. Status: Draft, awaiting reviewers. Refs: learning_ai_uxui_web/docs/ROADMAP_2026.md §10 TODOs #4, #5, #6
This commit is contained in:
parent
cc0bffea86
commit
ed5fb707ad
75
.gitea/workflows/size-limit.yml
Normal file
75
.gitea/workflows/size-limit.yml
Normal file
@ -0,0 +1,75 @@
|
||||
name: Size limit
|
||||
|
||||
# ROADMAP TODO #6 — enforces bundle-size budgets defined in
|
||||
# .size-limit.cjs on every push to main and every PR. Failures block
|
||||
# merge so that bundle bloat is a visible, deliberate decision.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'packages/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- '.size-limit.cjs'
|
||||
- '.gitea/workflows/size-limit.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'packages/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'package.json'
|
||||
- '.size-limit.cjs'
|
||||
|
||||
concurrency:
|
||||
group: size-limit-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
size:
|
||||
name: size-limit
|
||||
runs-on: [ubuntu-latest, bytelyst, hostinger]
|
||||
container:
|
||||
image: node:20-bookworm
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
github-server-url: https://gitea.bytelyst.com
|
||||
|
||||
- name: Install pinned pnpm
|
||||
run: |
|
||||
npm install -g pnpm@10.6.5
|
||||
pnpm --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: HUSKY=0 pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build measured packages
|
||||
# size-limit measures dist/ — ensure every entry in
|
||||
# .size-limit.cjs has a fresh build before running.
|
||||
run: |
|
||||
pnpm --filter @bytelyst/api-client \
|
||||
--filter @bytelyst/auth-client \
|
||||
--filter @bytelyst/celebrations \
|
||||
--filter @bytelyst/quick-actions \
|
||||
--filter @bytelyst/react-auth \
|
||||
--filter @bytelyst/dashboard-shell \
|
||||
run build
|
||||
|
||||
- name: Enforce size budgets
|
||||
run: pnpm size
|
||||
|
||||
- name: Upload size report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: size-limit-${{ github.run_id }}
|
||||
path: |
|
||||
.size-limit/
|
||||
retention-days: 14
|
||||
continue-on-error: true
|
||||
68
.size-limit.cjs
Normal file
68
.size-limit.cjs
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* ROADMAP TODO #6 — Bundle-size budgets for @bytelyst/* packages.
|
||||
*
|
||||
* Each entry measures the gzipped size of a package's built `dist/`
|
||||
* output. The 'limit' field is the budget — PRs that exceed it fail CI.
|
||||
*
|
||||
* Initial budgets per learning_ai_uxui_web/docs/ROADMAP_2026.md §5
|
||||
* "Performance budgets":
|
||||
* - Pure-TS clients → 8 KB
|
||||
* - Feature packs → 6 KB
|
||||
* - UI primitive slices → ~1 KB per primitive (whole pkg < 30 KB)
|
||||
* - Tokens / design-tokens → 12 KB (CSS heavy)
|
||||
*
|
||||
* Pilot scope (this commit): wire up 6 representative packages. Rollout
|
||||
* to the rest of @bytelyst/* lands incrementally as packages stabilise.
|
||||
*
|
||||
* Run locally:
|
||||
* pnpm -w size — full check
|
||||
* pnpm -w size --why <name> — explain what's contributing
|
||||
*
|
||||
* To add a package:
|
||||
* 1. Confirm the package has 'build' in its scripts and emits to dist/
|
||||
* 2. Add an entry below with name, path, and limit
|
||||
* 3. Run `pnpm -w size --update` to record the current baseline if
|
||||
* you're starting under-budget (optional)
|
||||
*/
|
||||
module.exports = [
|
||||
// ── Pure-TS clients (8 KB) ──────────────────────────────────────
|
||||
{
|
||||
name: '@bytelyst/api-client',
|
||||
path: 'packages/api-client/dist/index.js',
|
||||
limit: '8 KB',
|
||||
gzip: true,
|
||||
},
|
||||
{
|
||||
name: '@bytelyst/auth-client',
|
||||
path: 'packages/auth-client/dist/index.js',
|
||||
limit: '8 KB',
|
||||
gzip: true,
|
||||
},
|
||||
// ── Feature packs (6 KB) ────────────────────────────────────────
|
||||
{
|
||||
name: '@bytelyst/celebrations',
|
||||
path: 'packages/celebrations/dist/index.js',
|
||||
limit: '6 KB',
|
||||
gzip: true,
|
||||
},
|
||||
{
|
||||
name: '@bytelyst/quick-actions',
|
||||
path: 'packages/quick-actions/dist/index.js',
|
||||
limit: '6 KB',
|
||||
gzip: true,
|
||||
},
|
||||
// ── React bindings (10 KB — slightly higher for hooks + context) ─
|
||||
{
|
||||
name: '@bytelyst/react-auth',
|
||||
path: 'packages/react-auth/dist/index.js',
|
||||
limit: '10 KB',
|
||||
gzip: true,
|
||||
},
|
||||
// ── Shells / composite UI (30 KB) ───────────────────────────────
|
||||
{
|
||||
name: '@bytelyst/dashboard-shell',
|
||||
path: 'packages/dashboard-shell/dist/index.js',
|
||||
limit: '30 KB',
|
||||
gzip: true,
|
||||
},
|
||||
];
|
||||
115
docs/STORYBOOK_TEMPLATE.md
Normal file
115
docs/STORYBOOK_TEMPLATE.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Storybook Template for `@bytelyst/*` Packages
|
||||
|
||||
> ROADMAP TODO #5 — canonical pattern for adding Storybook 8 to each
|
||||
> visual `@bytelyst/*` package.
|
||||
|
||||
## Status
|
||||
|
||||
| Package | Has Storybook | Stories | A11y addon |
|
||||
| -------------------------------- | ------------- | --------------------------------------------------------------- | ---------- |
|
||||
| `@bytelyst/ui` | ✅ | 5 (`Button`, `Card`, `Controls`, `Input`, `OperationalPreview`) | ✅ |
|
||||
| `@bytelyst/auth-ui` | ❌ | — | — |
|
||||
| `@bytelyst/dashboard-components` | ❌ | — | — |
|
||||
| `@bytelyst/dashboard-shell` | ❌ | — | — |
|
||||
| `@bytelyst/celebrations` | ❌ | — | — |
|
||||
| `@bytelyst/gentle-notifications` | ❌ | — | — |
|
||||
| `@bytelyst/quick-actions` | ❌ | — | — |
|
||||
| `@bytelyst/react-auth` | ❌ | — | — |
|
||||
|
||||
Rollout is incremental — each package added separately so failures are
|
||||
diagnosable.
|
||||
|
||||
## Canonical setup (mirrors `@bytelyst/ui`)
|
||||
|
||||
### 1. Add devDependencies
|
||||
|
||||
```sh
|
||||
pnpm --filter @bytelyst/<pkg> add -D \
|
||||
storybook@^8.5.0 \
|
||||
@storybook/react@^8.5.0 \
|
||||
@storybook/react-vite@^8.5.0 \
|
||||
@storybook/addon-essentials@^8.5.0 \
|
||||
@storybook/addon-a11y@^8.5.0
|
||||
```
|
||||
|
||||
### 2. Add scripts to `package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create `.storybook/main.ts`
|
||||
|
||||
```ts
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'],
|
||||
framework: { name: '@storybook/react-vite', options: {} },
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### 4. Create `.storybook/preview.ts`
|
||||
|
||||
```ts
|
||||
import type { Preview } from '@storybook/react';
|
||||
import '@bytelyst/design-tokens/css';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
backgrounds: {
|
||||
default: 'dark',
|
||||
values: [
|
||||
{ name: 'dark', value: '#06070A' },
|
||||
{ name: 'elevated', value: '#0E1118' },
|
||||
{ name: 'light', value: '#F8F9FC' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
```
|
||||
|
||||
### 5. Add at least one `*.stories.tsx`
|
||||
|
||||
Pattern — one story file per component, **co-located** in `src/`:
|
||||
|
||||
```tsx
|
||||
// src/components/Button.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from './Button.js';
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'Components/Button',
|
||||
component: Button,
|
||||
parameters: { layout: 'centered' },
|
||||
tags: ['autodocs'],
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
export const Primary: Story = { args: { children: 'Click me' } };
|
||||
export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
|
||||
```
|
||||
|
||||
## Hosting
|
||||
|
||||
**Decision (`docs/ROADMAP_2026_DECISIONS.md` §9):** self-hosted on
|
||||
Gitea Pages.
|
||||
|
||||
A future Gitea Actions workflow at `.gitea/workflows/storybook.yml`
|
||||
will build each package's Storybook on push to `main` and deploy the
|
||||
combined output to `storybook.bytelyst.com` (or equivalent).
|
||||
|
||||
Until that workflow lands, developers run `pnpm --filter @bytelyst/<pkg>
|
||||
run storybook` locally on `:6006`.
|
||||
177
docs/rfc/0001-dtcg-v3-token-migration.md
Normal file
177
docs/rfc/0001-dtcg-v3-token-migration.md
Normal file
@ -0,0 +1,177 @@
|
||||
# RFC 0001 — Migrate `@bytelyst/design-tokens` to DTCG v3
|
||||
|
||||
| Status | Draft |
|
||||
| --------- | ---------------- |
|
||||
| Author | Cascade |
|
||||
| Created | 2026-05-27 |
|
||||
| Tracks | ROADMAP TODO #4 |
|
||||
| Reviewers | _to be assigned_ |
|
||||
|
||||
## Summary
|
||||
|
||||
Migrate `packages/design-tokens/tokens/bytelyst.tokens.json` from the
|
||||
current bespoke schema to the **W3C Design Tokens Community Group
|
||||
(DTCG) Format Module v3**. Re-emit all generated outputs
|
||||
(CSS / TS / Kotlin / Swift / per-product CSS) from the new schema with
|
||||
**no observable change to runtime CSS variable names**.
|
||||
|
||||
## Motivation
|
||||
|
||||
1. **Designer tooling.** Figma-Tokens (now "Tokens Studio") and
|
||||
Specify both speak DTCG natively. Today designers cannot round-trip
|
||||
tokens with engineering — the JSON schema is bespoke.
|
||||
2. **Long-term portability.** DTCG is becoming the lingua franca of
|
||||
design systems (Adobe Spectrum, Salesforce Lightning, GitHub Primer
|
||||
are all migrating).
|
||||
3. **Three-tier semantics.** DTCG `$type` + reference syntax (`{path}`)
|
||||
gives us a clean way to express the **reference → semantic →
|
||||
component** layering documented in
|
||||
`learning_ai_uxui_web/docs/ROADMAP_2026.md` §3.
|
||||
|
||||
## Current schema (excerpt)
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": { "name": "ByteLyst Design Tokens", "version": "1.1.0" },
|
||||
"color": {
|
||||
"palette": { "neutral": { "0": "#FFFFFF", "50": "#F6F8FC", ... } },
|
||||
"semantic": {
|
||||
"dark": { "bgCanvas": "#06070A", ... },
|
||||
"light": { "bgCanvas": "#F8F9FC", ... }
|
||||
}
|
||||
},
|
||||
"spacing": { "0": 0, "1": 4, "2": 8, ... },
|
||||
"radius": { "xs": 4, "sm": 6, ... },
|
||||
"typography": { "fontSize": { "xs": 12, ... } },
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Proposed DTCG v3 schema (excerpt)
|
||||
|
||||
```json
|
||||
{
|
||||
"$description": "ByteLyst design tokens — DTCG v3",
|
||||
"$schema": "https://design-tokens.github.io/community-group/format/",
|
||||
|
||||
"color": {
|
||||
"neutral": {
|
||||
"0": { "$value": "#FFFFFF", "$type": "color" },
|
||||
"50": { "$value": "#F6F8FC", "$type": "color" },
|
||||
"950": { "$value": "#06070A", "$type": "color" }
|
||||
},
|
||||
"brand": {
|
||||
"blue": { "$value": "#5A8CFF", "$type": "color" },
|
||||
"coral": { "$value": "#FF6E6E", "$type": "color" }
|
||||
},
|
||||
"semantic": {
|
||||
"bgCanvas": {
|
||||
"$value": "{color.neutral.950}",
|
||||
"$type": "color",
|
||||
"$description": "Page background — dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"spacing": {
|
||||
"1": { "$value": "4px", "$type": "dimension" },
|
||||
"2": { "$value": "8px", "$type": "dimension" }
|
||||
},
|
||||
|
||||
"radius": {
|
||||
"xs": { "$value": "4px", "$type": "dimension" }
|
||||
},
|
||||
|
||||
"component": {
|
||||
"button": {
|
||||
"padding-x": { "$value": "{spacing.4}", "$type": "dimension" },
|
||||
"bg": { "$value": "{color.semantic.accent}", "$type": "color" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key changes
|
||||
|
||||
| Aspect | Today | DTCG v3 |
|
||||
| -------------- | ------------------------ | ------------------------------------- |
|
||||
| Value wrapper | bare value | `{ "$value": ..., "$type": ... }` |
|
||||
| Type metadata | inferred from JS path | explicit `$type` |
|
||||
| References | manual JS lookup | `{path.to.token}` syntax |
|
||||
| Light/dark | parallel sibling objects | DTCG **token sets** (per-theme files) |
|
||||
| Documentation | none | `$description` per token |
|
||||
| Component tier | doesn't exist | new top-level `component` group |
|
||||
|
||||
## Implementation plan
|
||||
|
||||
The migration is split into **4 PRs** to keep each one reviewable.
|
||||
|
||||
### PR 1 — Add converter + dual-emit (no behaviour change)
|
||||
|
||||
- Add `scripts/convert-legacy-to-dtcg.ts` that reads the bespoke JSON
|
||||
and emits `bytelyst.tokens.dtcg.json`.
|
||||
- Modify `scripts/generate.ts` to accept either schema and produce
|
||||
byte-identical output.
|
||||
- Add a vitest case asserting the two emitters produce identical CSS
|
||||
byte-for-byte.
|
||||
|
||||
### PR 2 — Flip source of truth to DTCG JSON
|
||||
|
||||
- Delete the legacy JSON.
|
||||
- Update Figma-Tokens plugin documentation + commit a `.tokens.config.json`
|
||||
for round-trip.
|
||||
|
||||
### PR 3 — Introduce the component tier
|
||||
|
||||
- New `component.*` group in the DTCG JSON.
|
||||
- Generator emits new `--bl-{component}-{slot}` variables.
|
||||
- Audit existing hardcoded values in `@bytelyst/ui` and replace with
|
||||
the new component tokens.
|
||||
|
||||
### PR 4 — Multi-theme via DTCG token sets
|
||||
|
||||
- Split the single JSON into:
|
||||
- `tokens/global.tokens.json` (reference + semantic core)
|
||||
- `tokens/themes/dark.tokens.json` (light/dark overrides)
|
||||
- `tokens/themes/light.tokens.json`
|
||||
- `tokens/brands/lysnrai.tokens.json` (brand-layer overrides — Wave 7)
|
||||
- Generator composes per-output set.
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
- All existing `--ml-*` and `--bl-*` CSS variable names are preserved.
|
||||
- All TS/Kotlin/Swift exported identifiers preserved.
|
||||
- Consumer apps require zero changes through PRs 1–3.
|
||||
- PR 4 introduces multi-set composition but the **default** still
|
||||
produces the same `tokens.css`.
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
| ------------------------------------------------ | -------- | ------------------------------------- |
|
||||
| Generator divergence between schemas during PR 1 | High | Byte-identical assertion test |
|
||||
| Figma-Tokens plugin version churn | Med | Pin plugin version in docs/THEMING.md |
|
||||
| Designer learning curve | Med | One-pager + paired migration session |
|
||||
| Reference cycles in DTCG (`{a}` → `{b}` → `{a}`) | Low | Generator validates DAG before emit |
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Style Dictionary** — Amazon's tool. Heavier, more opinionated.
|
||||
Rejected: we already have a working generator; switching tools is a
|
||||
larger change than switching schemas.
|
||||
- **Stay on bespoke JSON** — Rejected: blocks designer round-trip
|
||||
and locks us out of the ecosystem.
|
||||
|
||||
## Estimate
|
||||
|
||||
~1 person-week for PRs 1–3, +1 pw for PR 4 = **2 pw total**.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Do we adopt the DTCG **2024 candidate** or wait for the **2026
|
||||
final**? Candidate is stable enough; recommend candidate now,
|
||||
re-verify when final.
|
||||
- Where do per-product brand layers live — separate packages
|
||||
(`@bytelyst/brand-lysnrai`) or subpaths
|
||||
(`@bytelyst/design-tokens/brands/lysnrai`)? Recommend subpaths
|
||||
pre-Wave 7, separate packages from Wave 7.
|
||||
@ -17,6 +17,8 @@
|
||||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
||||
"audit": "pnpm -r audit --audit-level moderate",
|
||||
"size": "size-limit",
|
||||
"size:why": "size-limit --why",
|
||||
"clean": "pnpm -r exec rm -rf dist",
|
||||
"docker:clean": "./scripts/docker-clean.sh",
|
||||
"dns:godaddy:bytelyst": "./scripts/godaddy-sync-bytelyst-dns.sh",
|
||||
@ -26,6 +28,7 @@
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.28.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@size-limit/preset-small-lib": "^12.1.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
@ -36,6 +39,7 @@
|
||||
"husky": "^9.0.0",
|
||||
"lint-staged": "^15.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"size-limit": "^12.1.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
|
||||
513
pnpm-lock.yaml
generated
513
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user