diff --git a/.changeset/fair-rules-heal.md b/.changeset/fair-rules-heal.md new file mode 100644 index 000000000..99b7ed805 --- /dev/null +++ b/.changeset/fair-rules-heal.md @@ -0,0 +1,8 @@ +--- +'@tiny-design/tokens': minor +'@tiny-design/mcp': patch +--- + +Add seed-driven token foundations to `@tiny-design/tokens` with an internal `primitive -> semantic -> component` model, a shared `compile-brand-theme` runtime export, stricter build validation, and richer registry metadata including resolved token values for downstream tooling. + +Fix `@tiny-design/mcp` token extraction so MCP clients receive concrete resolved token values instead of unresolved token references. diff --git a/apps/docs/src/containers/theme-studio/defaults.ts b/apps/docs/src/containers/theme-studio/defaults.ts index 3d24bb0fa..29a89217e 100644 --- a/apps/docs/src/containers/theme-studio/defaults.ts +++ b/apps/docs/src/containers/theme-studio/defaults.ts @@ -1,75 +1,7 @@ +import { DEFAULT_BRAND_SEED_FIELDS } from '@tiny-design/tokens/compile-brand-theme'; import type { ThemeEditorDraft, ThemeEditorFields, ThemeMode } from './types'; -const DEFAULT_FONT_SANS = '"Instrument Sans", "Inter", system-ui, sans-serif'; -const DEFAULT_FONT_MONO = '"JetBrains Mono", "SFMono-Regular", monospace'; - -export const DEFAULT_FIELDS: ThemeEditorFields = { - primary: '#6e41bf', - primaryForeground: '#ffffff', - secondary: '#f5f5f7', - secondaryForeground: '#16181d', - accent: '#f3eefa', - accentForeground: '#6e41bf', - success: '#52c41a', - successForeground: '#ffffff', - info: '#1890ff', - infoForeground: '#ffffff', - warning: '#fa8c16', - warningForeground: '#ffffff', - danger: '#dc2626', - dangerForeground: '#ffffff', - base: '#ffffff', - baseForeground: 'rgba(0, 0, 0, 0.85)', - card: '#ffffff', - cardForeground: '#111827', - popover: '#ffffff', - popoverForeground: '#111827', - muted: '#f5f5f5', - mutedForeground: 'rgba(0, 0, 0, 0.55)', - border: '#d9d9d9', - input: '#d9d9d9', - ring: '#6e41bf', - chart1: '#6e41bf', - chart2: '#1890ff', - chart3: '#52c41a', - chart4: '#fa8c16', - chart5: '#eb2f96', - sidebar: '#12131a', - sidebarForeground: '#f8fafc', - sidebarPrimary: '#6e41bf', - sidebarPrimaryForeground: '#ffffff', - sidebarAccent: '#23173f', - sidebarAccentForeground: '#efe8ff', - sidebarBorder: '#2a2d36', - sidebarRing: '#8b62d0', - fontSans: DEFAULT_FONT_SANS, - fontMono: DEFAULT_FONT_MONO, - fontSizeBase: '14px', - lineHeightBase: '1.5', - h1Size: '40px', - h2Size: '32px', - letterSpacing: '-0.02em', - radius: '0.3rem', - shadowControl: 'none', - shadowCard: '0 20px 55px rgba(17, 24, 39, 0.08)', - shadowFocus: '0 0 0 3px rgba(110, 65, 191, 0.22)', - buttonRadius: '0.3rem', - inputRadius: '0.3rem', - cardRadius: '0.3rem', - fieldPaddingSm: '8px', - fieldPaddingMd: '8px', - fieldPaddingLg: '8px', - buttonPaddingSm: '8px', - buttonPaddingMd: '15px', - buttonPaddingLg: '20px', - fieldHeightSm: '24px', - fieldHeightMd: '35px', - fieldHeightLg: '44px', - buttonHeightSm: '24px', - buttonHeightMd: '35px', - buttonHeightLg: '44px', - cardPadding: '18px', -}; +export const DEFAULT_FIELDS: ThemeEditorFields = { ...DEFAULT_BRAND_SEED_FIELDS }; export function createDraft( id: string, diff --git a/apps/docs/src/containers/theme-studio/editor-config.ts b/apps/docs/src/containers/theme-studio/editor-config.ts index e35e579da..2114a7334 100644 --- a/apps/docs/src/containers/theme-studio/editor-config.ts +++ b/apps/docs/src/containers/theme-studio/editor-config.ts @@ -1,70 +1,53 @@ -import type { FieldKey, ThemeEditorColorGroup } from './types'; +import type { ThemeEditorSeedGroup } from './types'; -export const COLOR_GROUPS: ThemeEditorColorGroup[] = [ +export const SEED_COLOR_GROUPS: ThemeEditorSeedGroup[] = [ { - title: 'Primary', + title: 'Brand Core', + description: 'Primary, accent, and supporting brand surfaces.', + tier: 'core', fields: [ - { key: 'primary', label: 'Background' }, - { key: 'primaryForeground', label: 'Foreground' }, + { key: 'primary', label: 'Primary' }, + { key: 'primaryForeground', label: 'On Primary' }, + { key: 'secondary', label: 'Secondary' }, + { key: 'secondaryForeground', label: 'On Secondary' }, + { key: 'accent', label: 'Accent' }, + { key: 'accentForeground', label: 'On Accent' }, ], }, { - title: 'Secondary', - fields: [ - { key: 'secondary', label: 'Background' }, - { key: 'secondaryForeground', label: 'Foreground' }, - ], - }, - { - title: 'Accent', - fields: [ - { key: 'accent', label: 'Background' }, - { key: 'accentForeground', label: 'Foreground' }, - ], - }, - { - title: 'Status', + title: 'Feedback States', + description: 'Semantic status hues that propagate through alerts, tags, and feedback UI.', + tier: 'core', fields: [ { key: 'success', label: 'Success' }, - { key: 'successForeground', label: 'Success FG' }, + { key: 'successForeground', label: 'On Success' }, { key: 'info', label: 'Info' }, - { key: 'infoForeground', label: 'Info FG' }, + { key: 'infoForeground', label: 'On Info' }, { key: 'warning', label: 'Warning' }, - { key: 'warningForeground', label: 'Warning FG' }, + { key: 'warningForeground', label: 'On Warning' }, { key: 'danger', label: 'Danger' }, - { key: 'dangerForeground', label: 'Danger FG' }, + { key: 'dangerForeground', label: 'On Danger' }, ], }, { - title: 'Base', + title: 'Surfaces & Text', + description: 'Page, card, popover, and muted surfaces plus their text companions.', + tier: 'core', fields: [ - { key: 'base', label: 'Background' }, - { key: 'baseForeground', label: 'Foreground' }, + { key: 'base', label: 'Page' }, + { key: 'baseForeground', label: 'On Page' }, + { key: 'card', label: 'Card' }, + { key: 'cardForeground', label: 'On Card' }, + { key: 'popover', label: 'Popover' }, + { key: 'popoverForeground', label: 'On Popover' }, + { key: 'muted', label: 'Muted' }, + { key: 'mutedForeground', label: 'On Muted' }, ], }, { - title: 'Card', - fields: [ - { key: 'card', label: 'Background' }, - { key: 'cardForeground', label: 'Foreground' }, - ], - }, - { - title: 'Popover', - fields: [ - { key: 'popover', label: 'Background' }, - { key: 'popoverForeground', label: 'Foreground' }, - ], - }, - { - title: 'Muted', - fields: [ - { key: 'muted', label: 'Background' }, - { key: 'mutedForeground', label: 'Foreground' }, - ], - }, - { - title: 'Border & Input', + title: 'Lines & Focus', + description: 'Borders, field chrome, and the interaction ring seed.', + tier: 'core', fields: [ { key: 'border', label: 'Border' }, { key: 'input', label: 'Input' }, @@ -72,40 +55,34 @@ export const COLOR_GROUPS: ThemeEditorColorGroup[] = [ ], }, { - title: 'Chart', + title: 'Data Visualization', + description: 'Chart palette seeds for data-heavy views.', + tier: 'advanced', fields: [ - { key: 'chart1', label: 'Chart 1' }, - { key: 'chart2', label: 'Chart 2' }, - { key: 'chart3', label: 'Chart 3' }, - { key: 'chart4', label: 'Chart 4' }, - { key: 'chart5', label: 'Chart 5' }, + { key: 'chart1', label: 'Series 1' }, + { key: 'chart2', label: 'Series 2' }, + { key: 'chart3', label: 'Series 3' }, + { key: 'chart4', label: 'Series 4' }, + { key: 'chart5', label: 'Series 5' }, ], }, { - title: 'Sidebar', + title: 'Sidebar Shell', + description: 'Dedicated app-shell seeds for navigation-heavy layouts.', + tier: 'advanced', fields: [ - { key: 'sidebar', label: 'Background' }, - { key: 'sidebarForeground', label: 'Foreground' }, - { key: 'sidebarPrimary', label: 'Primary' }, - { key: 'sidebarPrimaryForeground', label: 'Primary FG' }, - { key: 'sidebarAccent', label: 'Accent' }, - { key: 'sidebarAccentForeground', label: 'Accent FG' }, - { key: 'sidebarBorder', label: 'Border' }, - { key: 'sidebarRing', label: 'Ring' }, + { key: 'sidebar', label: 'Sidebar' }, + { key: 'sidebarForeground', label: 'On Sidebar' }, + { key: 'sidebarPrimary', label: 'Sidebar Primary' }, + { key: 'sidebarPrimaryForeground', label: 'On Sidebar Primary' }, + { key: 'sidebarAccent', label: 'Sidebar Accent' }, + { key: 'sidebarAccentForeground', label: 'On Sidebar Accent' }, + { key: 'sidebarBorder', label: 'Sidebar Border' }, + { key: 'sidebarRing', label: 'Sidebar Ring' }, ], }, ]; -export const CORE_COLOR_GROUP_TITLES = new Set([ - 'Primary', - 'Secondary', - 'Accent', - 'Status', - 'Base', - 'Muted', - 'Border & Input', -]); - export const FONT_OPTIONS = [ 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif', '"Instrument Sans", "Inter", system-ui, sans-serif', diff --git a/apps/docs/src/containers/theme-studio/index.tsx b/apps/docs/src/containers/theme-studio/index.tsx index 93f72541a..f35b1b349 100644 --- a/apps/docs/src/containers/theme-studio/index.tsx +++ b/apps/docs/src/containers/theme-studio/index.tsx @@ -46,7 +46,7 @@ const ThemeStudioPage = (): React.ReactElement => { const [codeVisible, setCodeVisible] = useState(false); const [importText, setImportText] = useState(''); const [importError, setImportError] = useState(null); - const [status, setStatus] = useState('Editing local draft'); + const [status, setStatus] = useState('Editing local seed draft'); const globalMode: ThemeMode = resolvedTheme === 'dark' ? 'dark' : 'light'; useEffect(() => { @@ -54,6 +54,20 @@ const ThemeStudioPage = (): React.ReactElement => { }, [draft]); const themeDocument = useMemo(() => buildThemeDocumentFromDraft(draft), [draft]); + const seedJson = useMemo( + () => + JSON.stringify( + { + meta: draft.meta, + mode: draft.mode, + presetId: draft.presetId, + fields: draft.fields, + }, + null, + 2 + ), + [draft] + ); const themeJson = useMemo(() => generateThemeDocumentJSON(themeDocument), [themeDocument]); const cssVars = useMemo(() => generateThemeCssVariables(themeDocument), [themeDocument]); const changedTokens = useMemo(() => compareThemeAgainstBase(themeDocument), [themeDocument]); @@ -105,13 +119,13 @@ const ThemeStudioPage = (): React.ReactElement => { const resetToPreset = () => { setDraft((current) => applyPresetToDraft(current.presetId, current)); - setStatus('Reset to preset defaults'); + setStatus('Reset seed draft to preset defaults'); }; const handlePresetChange = (presetId: string) => { setDraft((current) => applyPresetToDraft(presetId, current)); setStatus( - `Applied ${THEME_EDITOR_PRESETS.find((preset) => preset.id === presetId)?.name ?? 'preset'}` + `Applied ${THEME_EDITOR_PRESETS.find((preset) => preset.id === presetId)?.name ?? 'preset'} seed preset` ); }; @@ -129,8 +143,8 @@ const ThemeStudioPage = (): React.ReactElement => { setImportError(null); setStatus( validation.warnings.length > 0 - ? 'Imported theme document with validation warnings' - : 'Imported theme document' + ? 'Imported theme document and remapped it into seed groups with validation warnings' + : 'Imported theme document and remapped it into seed groups' ); } catch { setImportError('Invalid theme document JSON'); @@ -253,7 +267,8 @@ const ThemeStudioPage = (): React.ReactElement => { Paste a Tiny theme document JSON export to replace the current global theme. - Preset selection and all editor controls will sync to the imported values. + The studio will map semantic and component token overrides back into the seed groups + shown in the editor.