28 KiB
Design System Foundation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Создать внутренний пакет @moex-vibe/design-system с трёхуровневыми токенами, MUI light theme, согласованным UI-каталогом, Storybook и опубликованными правилами.
Architecture: Platform-neutral токены хранятся как JSON-compatible TypeScript records и разрешаются собственным проверяемым resolver. MUI-адаптер переводит semantic/component tokens в CSS-variable theme; React-компоненты доступны только через public exports пакета. Storybook служит локальным/CI workbench, Docusaurus — единственной опубликованной документацией.
Tech Stack: TypeScript 5, React 18, MUI 6.5, TanStack Table 8, Vitest 4 browser mode, Storybook 10.2 (@storybook/react-vite), Playwright Chromium, Docusaurus 3.7.
Зафиксированные интерфейсы
Пакет предоставляет subpath exports:
{
".": "./dist/index.js",
"./tokens": "./dist/tokens/index.js",
"./theme": "./dist/theme/index.js"
}
Token source использует плоские DTCG-подобные записи:
type TokenType = 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'duration' | 'cubicBezier' | 'shadow';
type TokenValue = string | number | readonly number[] | readonly string[];
type TokenDefinition = { readonly $type: TokenType; readonly $value: TokenValue };
type TokenCollection = Readonly<Record<string, TokenDefinition>>;
// Primitive содержит абсолютное значение.
'color.green.700': { $type: 'color', $value: '#176747' }
// Semantic/component содержит alias и никогда не дублирует primitive.
'color.action.primary': { $type: 'color', $value: '{color.green.700}' }
Публичный React API:
type Tone = 'default' | 'secondary' | 'positive' | 'negative' | 'muted';
type Density = 'balanced' | 'compact';
type ActionVariant = 'primary' | 'secondary' | 'tertiary' | 'danger';
type TextProps = { variant?: 'body' | 'caption' | 'label' | 'numeric'; tone?: Tone };
type HeadingProps = { level: 1 | 2 | 3 | 4 | 5 | 6; size?: 'display' | 'title' | 'section' | 'subsection' };
type ButtonProps = { variant?: ActionVariant; size?: 'small' | 'medium'; loading?: boolean };
type IconButtonProps = { label: string; size?: 'small' | 'medium' };
type SurfaceProps = { padding?: 'none' | 'sm' | 'md' | 'lg'; elevation?: 'none' | 'sm' | 'md' };
type DataTableProps<T> = { table: Table<T>; density?: Density; loading?: boolean; empty?: ReactNode; caption: string };
type MoneyProps = { value: number; currency?: string; locale?: string; signDisplay?: 'auto' | 'always' | 'never' };
type PriceChangeProps = { value: number; format?: 'percent' | 'money'; currency?: string; locale?: string };
type MetricProps = { label: string; value: ReactNode; supportingText?: ReactNode; trend?: ReactNode };
Остальные контракты фиксированы так:
Link: MUI Link безcolor,variant,sx; добавляетtoneи сохраняет polymorphiccomponent.TextField: MUI TextField безcolor,variant,size,sx; всегдаvariant="outlined"иsize="medium".Select:label,value,onChange(value: string),options: { value; label; disabled? }[],error?,helperText?,disabled?.Checkbox:label,checked,onChange(checked: boolean),disabled?,error?.Card: Surface props плюсheader?,actions?,children; не знает маршруты и домен.Chip:label,tone: neutral|info|success|warning|error,onDelete?.Badge:value,max?,label; decorative badge запрещён без accessible label.Alert:severity: info|success|warning|error,title?,children,action?.Dialog:open,onClose, обязательныеtitle,children,actions?.Skeleton:width?,height?,shape: text|rectangular|rounded|circular.Progress:label,value?; отсутствие value означает indeterminate.FormField:label,htmlFor,helperText?,error?,required?,children.FilterBar:children,actions?; отвечает только за responsive layout.- Page states: обязательный
title, optionaldescription/action;LoadingStateдополнительно имеетlabelиsize: section|page.
Все wrappers запрещают произвольные color, variant, size и sx, где они обходят контракт.
Box, Stack, Grid остаются полным и единственным прямым MUI allowlist во frontend.
Карта файлов
packages/design-system/src/tokens/— schema, три token collections, resolver и guards.packages/design-system/src/theme/— MUI augmentation, theme factory, provider, component overrides.packages/design-system/src/components/— Core UI и product patterns; один каталог на компонент.packages/design-system/.storybook/— единый provider, a11y и browser-test annotations.apps/frontend/src/app/providers/AppProviders.tsx— подключение provider пакета без миграции страниц.apps/docs/docs/design-system/— канонические правила;apps/docs/docs/adr/ADR-016-design-system.md— архитектурное решение.
Task 1: Pre-flight и workspace shell
Files:
-
Modify:
package.json -
Create:
packages/design-system/package.json -
Create:
packages/design-system/tsconfig.json -
Create:
packages/design-system/vitest.config.ts -
Create:
packages/design-system/.eslintrc.cjs -
Create:
packages/design-system/src/test/setup.ts -
Create:
packages/design-system/src/index.ts -
Step 1: Проверить pre-flight до любых implementation edits
Run:
git branch --show-current
git status --short
npm run test:backend
npm run test:frontend
npm run lint
npm run build:backend
npm run build:frontend
Expected: ветка codex/design-system-foundation, чистый worktree и все команды PASS. При сбое
остановиться и классифицировать его до изменения кода.
- Step 2: Добавить workspace и root scripts
В package.json добавить packages/design-system в workspaces и scripts:
{
"build:design-system": "npm run build -w packages/design-system",
"test:design-system": "npm run test -w packages/design-system",
"lint:design-system": "npm run lint -w packages/design-system",
"storybook": "npm run storybook -w packages/design-system",
"build:storybook": "npm run build-storybook -w packages/design-system",
"test:storybook": "npm run test:storybook -w packages/design-system"
}
- Step 3: Создать package manifest и TypeScript build
packages/design-system/package.json должен быть private ESM package версии 0.1.0, иметь files: ["dist"], exports из раздела выше, peer dependencies на React 18, MUI 6 и TanStack Table 8, dev
dependencies Storybook ^10.2.9, @vitest/browser-playwright/Vitest ^4.1.8 и Playwright, scripts
build: tsc -p tsconfig.json, test: vitest run --project unit, storybook: storybook dev -p 6006 --no-open, build-storybook: storybook build, test:storybook: vitest run --project storybook,
lint: eslint "src/**/*.{ts,tsx}".
tsconfig.json компилирует src в dist, включает declarations, jsx: react-jsx, strict и
moduleResolution: bundler. vitest.config.ts сначала содержит unit project с jsdom и
passWithNoTests: true; флаг удаляется после Task 2. Test setup подключает jest-dom и matchMedia mock.
Package ESLint config проверяет TypeScript/React hooks, но не включает frontend FSD zones. Root lint
дополняется lint:design-system.
- Step 4: Установить зависимости и проверить пустой пакет
Run:
npm install
npm run build:design-system
npm run test:design-system
Expected: package-lock обновлён, build PASS, Vitest PASS с passWithNoTests: true.
- Step 5: Commit
git add package.json package-lock.json packages/design-system
git commit -m "build(design-system): add workspace package"
Task 2: Token schema и resolver (TDD)
Files:
-
Create:
packages/design-system/src/tokens/types.ts -
Create:
packages/design-system/src/tokens/resolveToken.ts -
Test:
packages/design-system/src/tokens/resolveToken.test.ts -
Step 1: Написать failing tests
it('resolves an alias chain and preserves the declared type', () => {
const tokens = {
base: { $type: 'color', $value: '#176747' },
semantic: { $type: 'color', $value: '{base}' },
component: { $type: 'color', $value: '{semantic}' },
} satisfies TokenCollection;
expect(resolveToken(tokens, 'component')).toEqual({ type: 'color', value: '#176747' });
});
it.each([
['missing alias', { a: { $type: 'color', $value: '{missing}' } }, /Unknown token/],
['cycle', { a: { $type: 'color', $value: '{b}' }, b: { $type: 'color', $value: '{a}' } }, /cycle/i],
['type mismatch', { a: { $type: 'color', $value: '#fff' }, b: { $type: 'dimension', $value: '{a}' } }, /type/i],
])('rejects %s', (_name, tokens, error) => expect(() => resolveToken(tokens, 'a')).toThrow(error));
- Step 2: Запустить тест и подтвердить RED
Run: npm run test:design-system -- resolveToken.test.ts
Expected: FAIL, module/functions отсутствуют.
- Step 3: Реализовать schema и resolver
Resolver распознаёт только полную alias-строку /^\{([^}]+)\}$/, хранит visited path, проверяет
существование target и равенство $type, возвращает { type, value }. Смешанные строки вроде
calc({space.2} * 2) запрещены.
- Step 4: Запустить тест и commit
Run: npm run test:design-system -- resolveToken.test.ts
Expected: PASS.
git add packages/design-system/src/tokens
git commit -m "feat(design-system): add token contracts"
Task 3: Три уровня токенов
Files:
-
Create:
packages/design-system/src/tokens/primitives.ts -
Create:
packages/design-system/src/tokens/semantic.light.ts -
Create:
packages/design-system/src/tokens/components.ts -
Create:
packages/design-system/src/tokens/index.ts -
Test:
packages/design-system/src/tokens/tokens.test.ts -
Step 1: Написать token integrity tests
Тест объединяет три collections, резолвит каждый token, проверяет уникальные имена и инварианты:
expect(Object.keys(primitiveTokens).every((name) => !isAlias(primitiveTokens[name].$value))).toBe(true);
expect(Object.keys(semanticLightTokens).every((name) => isAlias(semanticLightTokens[name].$value))).toBe(true);
expect(Object.keys(componentTokens).every((name) => isAlias(componentTokens[name].$value))).toBe(true);
for (const name of Object.keys(allTokens)) expect(() => resolveToken(allTokens, name)).not.toThrow();
- Step 2: Запустить тест и подтвердить RED
Run: npm run test:design-system -- tokens.test.ts
Expected: FAIL, collections отсутствуют.
- Step 3: Добавить минимальный полный набор foundations
Primitive groups: color.neutral.{0,50,100,200,400,600,800,900}, color.green.{50,100,600,700,800},
color.red.{50,600,700}, color.amber.{50,600}, color.blue.{50,600}, space.{0,1,2,3,4,5,6,8,10,12}
на 4px grid, radius.{none,sm,md,lg,pill}, font.family.{sans,mono}, font.size.{100..700},
font.weight.{regular,medium,semibold,bold}, lineHeight.{tight,normal,relaxed}, shadow.{none,sm,md},
duration.{instant,fast,normal}, easing.standard, size.control.{sm,md}.
font.family.sans использует Inter с system-ui fallback. Webfont weights 400/500/600/700 подключаются
один раз во frontend shell в Task 10; Roboto не становится частью нового foundation.
Semantic groups: color.canvas, color.surface.{default,subtle,raised}, color.text.{primary,secondary, disabled,inverse}, color.border.{subtle,default,strong,focus}, color.action.{primary,primaryHover, secondary,danger}, color.feedback.{info,success,warning,error}, color.finance.{positive,negative, neutral}, plus semantic typography, focus ring, spacing and control sizes.
Component groups cover только API v1: button, iconButton, field, checkbox, surface, card, chip, badge, alert, dialog, skeleton, progress, table, metric и pageState.
- Step 4: Проверить GREEN и запрет hardcoded values вне primitives
Run: npm run test:design-system -- tokens.test.ts
Expected: PASS. Дополнительный test scan подтверждает, что semantic.light.ts и components.ts не
содержат hex/rgb/px literals.
- Step 5: Commit
git add packages/design-system/src/tokens
git commit -m "feat(design-system): define three-level tokens"
Task 4: MUI adapter и provider (TDD)
Files:
-
Create:
packages/design-system/src/theme/createMoexVibeTheme.ts -
Create:
packages/design-system/src/theme/MoexVibeThemeProvider.tsx -
Create:
packages/design-system/src/theme/mui.d.ts -
Create:
packages/design-system/src/theme/index.ts -
Test:
packages/design-system/src/theme/createMoexVibeTheme.test.ts -
Step 1: Написать failing theme contract tests
const theme = createMoexVibeTheme();
expect(theme.cssVarPrefix).toBe('mv');
expect(theme.colorSchemes.light.palette.primary.main).toBe(resolveValue('color.action.primary'));
expect(theme.typography.body1.fontFamily).toContain('Inter');
expect(theme.shape.borderRadius).toBe(resolveValue('radius.md'));
expect(theme.components?.MuiButton?.defaultProps).toMatchObject({ disableElevation: true });
- Step 2: Подтвердить RED
Run: npm run test:design-system -- createMoexVibeTheme.test.ts
Expected: FAIL, factory отсутствует.
- Step 3: Реализовать adapter
Использовать createTheme({ cssVariables: { cssVarPrefix: 'mv' }, colorSchemes: { light: ... } }).
Theme получает palette, typography, spacing, shape, shadows, transitions и component overrides только
через resolveValue. mui.d.ts включает themeCssVarsAugmentation и добавляет finance palette roles.
- Step 4: Реализовать provider
MoexVibeThemeProvider оборачивает MUI ThemeProvider и CssBaseline, принимает только children;
mode API в v1 не экспортируется.
- Step 5: Проверить и commit
Run: npm run test:design-system -- createMoexVibeTheme.test.ts && npm run build:design-system
Expected: PASS.
git add packages/design-system/src/theme packages/design-system/src/tokens
git commit -m "feat(design-system): add MUI theme adapter"
Task 5: Storybook и automated story checks
Files:
-
Create:
packages/design-system/.storybook/main.ts -
Create:
packages/design-system/.storybook/preview.tsx -
Create:
packages/design-system/.storybook/vitest.setup.ts -
Modify:
packages/design-system/vitest.config.ts -
Modify:
packages/design-system/package.json -
Step 1: Настроить Storybook 10.2
const config: StorybookConfig = {
framework: '@storybook/react-vite',
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: ['@storybook/addon-a11y', '@storybook/addon-vitest'],
};
preview.tsx добавляет decorator MoexVibeThemeProvider, background color.canvas и параметры
a11y.test = 'error', layout centered по умолчанию.
- Step 2: Добавить Storybook Vitest browser project
Использовать storybookTest, @vitest/browser-playwright и headless Chromium; setup регистрирует
@storybook/addon-a11y/preview и project annotations через setProjectAnnotations.
- Step 3: Проверить стенд
Run:
npx playwright install chromium
npm run build:storybook
Expected: static Storybook build PASS. Первый test:storybook запускается после появления stories в
Task 6.
- Step 4: Commit
git add package.json package-lock.json packages/design-system
git commit -m "build(design-system): configure Storybook workbench"
Task 6: Typography и actions (TDD)
Files:
-
Create:
packages/design-system/src/components/{Text,Heading,Link,Button,IconButton}/ -
Test: colocated
*.test.tsx -
Stories: colocated
*.stories.tsx -
Step 1: Написать failing interaction/type tests
Проверить semantic heading level, tone mapping, external link rel, loading button disabled state и
progress label, обязательный accessible label IconButton. Type tests отклоняют color, произвольный
variant и sx.
- Step 2: Подтвердить RED
Run: npm run test:design-system -- Text Heading Link Button IconButton
Expected: FAIL, exports отсутствуют.
- Step 3: Реализовать компоненты по public contracts
Каждый каталог содержит component, props и index. Button loading сохраняет ширину, блокирует повторный
click и имеет aria-busy; IconButton всегда устанавливает aria-label={label}.
- Step 4: Добавить stories и проверить GREEN
Stories: все variants/sizes/tones, disabled, loading, long Russian label, keyboard focus reference.
Run: npm run test:design-system && npm run test:storybook && npm run build:storybook
Expected: PASS без a11y violations.
- Step 5: Commit
git add packages/design-system/src
git commit -m "feat(design-system): add typography and actions"
Task 7: Inputs, surfaces и feedback (TDD)
Files:
-
Create:
packages/design-system/src/components/{TextField,Select,Checkbox,Surface,Card,Chip,Badge,Alert,Dialog,Skeleton,Progress}/ -
Test/Stories: colocated
*.test.tsx,*.stories.tsx -
Step 1: Написать failing contract tests
Проверить label/helper/error association, Select options и keyboard opening, Checkbox label click,
Surface padding/elevation mapping, Dialog focus/escape/label, Alert role, determinate/indeterminate
Progress accessible name и Skeleton aria-hidden.
- Step 2: Подтвердить RED
Run: npm run test:design-system -- TextField Select Checkbox Surface Card Chip Badge Alert Dialog Skeleton Progress
Expected: FAIL, components отсутствуют.
- Step 3: Реализовать ограниченные wrappers
Select принимает { value: string; label: string; disabled?: boolean }[]; Checkbox требует label;
Dialog требует title и управляется через open/onClose; Surface/Card не принимают raw elevation.
- Step 4: Stories, GREEN и commit
Stories покрывают default/focus/disabled/error/loading, длинный русский текст и narrow viewport.
Run: npm run test:design-system && npm run test:storybook && npm run build:storybook
Expected: PASS.
git add packages/design-system/src
git commit -m "feat(design-system): add controls and feedback"
Task 8: Финансовые data patterns (TDD)
Files:
-
Create:
packages/design-system/src/components/{DataTable,Metric,Money,PriceChange}/ -
Test/Stories: colocated
*.test.tsx,*.stories.tsx -
Test:
packages/design-system/src/visual/financial-patterns.visual.test.ts -
Step 1: Написать failing tests
Проверить table caption, semantic headers, sortable button aria-sort, balanced/compact density, loading
и empty states; Money через Intl.NumberFormat; PriceChange знак, текстовое направление и не только
цвет; Metric composition.
- Step 2: Подтвердить RED
Run: npm run test:design-system -- DataTable Metric Money PriceChange
Expected: FAIL.
- Step 3: Реализовать patterns
DataTable принимает готовый TanStack Table<T> и не владеет server pagination/sorting. Defaults:
density="balanced", locale ru-RU, currency RUB. Negative/positive output включает видимый знак и
screen-reader label.
- Step 4: Stories, visual baselines и commit
Canonical screenshot cases: balanced table, compact table, positive/negative metrics, empty/loading.
Browser test запускает portable stories через composeStories, затем делает
expect(page.getByTestId('visual-root')).toMatchScreenshot('<stable-name>.png'); baselines коммитятся
рядом с visual test. Обновление baseline допустимо только вместе с объяснением визуального изменения.
Run: npm run test:design-system && npm run test:storybook
Expected: interaction, a11y и screenshot checks PASS.
git add packages/design-system/src
git commit -m "feat(design-system): add financial data patterns"
Task 9: Form и page-state patterns (TDD)
Files:
-
Create:
packages/design-system/src/components/{FormField,FilterBar,EmptyState,ErrorState,LoadingState}/ -
Test/Stories: colocated
*.test.tsx,*.stories.tsx -
Step 1: Написать failing tests
Проверить htmlFor/description/error association FormField, wrapping and action placement FilterBar,
semantic heading/action для Empty/Error, aria-live="polite" LoadingState и reduced-motion rendering.
- Step 2: RED, implementation, GREEN
Run before: npm run test:design-system -- FormField FilterBar EmptyState ErrorState LoadingState
Expected before: FAIL. Реализовать только layout/semantics без API/domain knowledge.
Run after: npm run test:design-system && npm run test:storybook
Expected after: PASS.
- Step 3: Commit
git add packages/design-system/src
git commit -m "feat(design-system): add form and page-state patterns"
Task 10: Frontend integration и MUI boundary
Files:
-
Modify:
apps/frontend/package.json -
Modify:
apps/frontend/tsconfig.json -
Modify:
apps/frontend/vite.config.ts -
Modify:
apps/frontend/src/app/providers/AppProviders.tsx -
Modify:
apps/frontend/.eslintrc.cjs -
Delete:
apps/frontend/src/app/styles/theme.ts -
Test:
apps/frontend/src/app/providers/AppProviders.test.tsx -
Step 1: Написать failing provider test
Render AppProviders, assert generated --mv-palette-primary-main exists and Query/Session providers
по-прежнему доступны. Test не проверяет редизайн страниц.
- Step 2: Подключить workspace package
Добавить dependency, @fontsource/inter и aliases к source для dev/test; заменить MUI
ThemeProvider/CssBaseline на MoexVibeThemeProvider. Удалить старый локальный theme и импортировать
Inter 400/500/600/700 в provider entry ровно один раз.
- Step 3: Зафиксировать ESLint allowlist
Во frontend запретить root import @mui/material и subpaths через no-restricted-imports; исключить
только @mui/material/Box, Stack, Grid. Сам пакет design-system использует отдельный lint config.
- Step 4: Проверить отсутствие визуальной миграции
Run:
npm run test:frontend
npm run lint -w apps/frontend
npm run build:design-system
npm run build:frontend
Expected: PASS; legacy styles.css и product component markup не изменены.
- Step 5: Commit
git add apps/frontend packages/design-system package.json package-lock.json
git commit -m "feat(frontend): connect design system foundation"
Task 11: Docusaurus и ADR
Files:
-
Create:
apps/docs/docs/design-system/{overview,foundations,tokens,components,patterns,accessibility,governance}.md -
Create:
apps/docs/docs/adr/ADR-016-design-system.md -
Create:
packages/design-system/CHANGELOG.md -
Modify:
apps/docs/sidebars.ts -
Modify:
apps/docs/docs/adr/index.md -
Modify:
apps/docs/docs/frontend/styling.md -
Step 1: Написать канонические правила
Документы фиксируют light-only v1, balanced density, три token levels, MUI allowlist, каталог,
component-card template, WCAG 2.2 AA и promotion/change process. Components page содержит матрицу
«задача → компонент → не использовать» и полную карточку обязательного шаблона для каждого public
export. CHANGELOG.md начинается с 0.1.0 и документирует public API foundation; governance описывает
обязательную migration note для будущих breaking changes.
- Step 2: Добавить ADR-016
ADR содержит Context, варианты theme-only/hybrid/full-wrapper, решение hybrid, последствия, Storybook роль и будущие DTCG/Android adapters.
- Step 3: Обновить sidebar и проверить docs
Run: npm run build:docs
Expected: PASS без broken links; новый раздел виден как отдельная категория «Дизайн-система».
- Step 4: Commit
git add apps/docs packages/design-system/CHANGELOG.md
git commit -m "docs(design-system): publish usage guidelines"
Task 12: CI, visual checks и финальная верификация
Files:
-
Modify:
.gitea/workflows/ci.yml -
Modify:
README.md -
Modify:
docs/features/design-system-foundation/tasks.md -
Step 1: Добавить CI gates
После npm ci установить Chromium npx playwright install --with-deps chromium; добавить design-system
unit/story tests, Storybook build, design-system build и docs build. Сохранять Storybook static build и
visual diff через actions/upload-artifact@v4 с if: failure().
- Step 2: Обновить команды README
Добавить npm run storybook, build:storybook, test:design-system, test:storybook,
build:design-system и пояснить, что Docusaurus — published docs, Storybook — engineering workbench.
- Step 3: Запустить полный DoD
npm run format:check
npm run lint
npm run test:backend
npm run test:frontend
npm run test:design-system
npm run test:storybook
npm run build:backend
npm run build:design-system
npm run build:storybook
npm run build:frontend
npm run build:docs
git diff --check
git status --short
Expected: все команды PASS; status содержит только намеренные изменения до финального commit.
- Step 4: Обновить SDD tasks и запросить code review
Отметить выполненные checkbox в tasks.md, применить superpowers:requesting-code-review, исправить
только подтверждённые scope issues и повторить полный DoD.
- Step 5: Final commit
git add .gitea/workflows/ci.yml README.md docs/features/design-system-foundation
git commit -m "ci(design-system): enforce foundation quality gates"