diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index c96aed0..1b26bfb 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -1,149 +1,307 @@ @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Inter:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:ital,wght@0,400;1,400&display=swap'); +/* ========================================================================== + GENERATED FROM THE DESIGN SYSTEM — Scribe (design system 2), which inherits + the FabledSword house style (design system 1). + + Do not hand-edit the --fs-* block below. Edit the design system in the app + and regenerate: /design-systems -> Master stylesheet -> Copy. + + DARK IS THE BASE LAYER. The kit is dark-mode-first, so :root carries the dark + palette and [data-theme="light"] overrides it. That is the inverse of how this + file used to read, and it is deliberate: the light palette was never specified + by any rule, so it is recorded as a departure rather than as the default. + + Only 12 tokens differ between modes. Everything else — spacing, type, motion, + radius, and every derived colour — is stated once, because a value built with + var() resolves where it is USED, not where it is written. + ========================================================================== */ + :root { - /* Light mode — warm parchment palette */ - --color-bg: #F5F1E8; - --color-bg-secondary: #FBF8F0; - --color-bg-card: #FBF8F0; - --color-surface: #EFEAE0; - --color-text: #14171A; - --color-text-secondary: #5A5852; - --color-text-muted: #9A9890; - --color-border: #D9D6CE; - --color-input-border: #D9D6CE; - --color-primary: #5B4A8A; - --color-danger: #C04A1F; - --color-tag-bg: rgba(91, 74, 138, 0.12); - --color-tag-text: #5B4A8A; - --color-shadow: rgba(0, 0, 0, 0.08); - --color-toast-success: #4A5D3F; - --color-toast-error: #C04A1F; - --color-status-todo: #3F4651; - --color-status-todo-bg: rgba(63, 70, 81, 0.10); - --color-status-in-progress: #5B4A8A; - --color-status-in-progress-bg: rgba(91, 74, 138, 0.12); - --color-status-done: #4A5D3F; - --color-status-done-bg: rgba(74, 93, 63, 0.12); - --color-priority-low: #3D5A6E; - --color-priority-low-bg: rgba(61, 90, 110, 0.12); - --color-priority-medium: #8B6F1E; - --color-priority-medium-bg: rgba(139, 111, 30, 0.12); - --color-priority-high: #C04A1F; - --color-priority-high-bg: rgba(192, 74, 31, 0.12); - --color-wikilink: #5B4A8A; - --color-wikilink-bg: rgba(91, 74, 138, 0.12); - --color-overdue: #C04A1F; - --color-code-bg: #EBEDF0; - --color-code-inline-bg: #EBEDF0; - --color-table-stripe: rgba(20, 23, 26, 0.025); - --color-success: #4A5D3F; - --color-warning: #8B6F1E; - --color-input-bar-bg: #EFEAE0; - --color-input-bar-text: #14171A; - --color-input-bar-placeholder: rgba(20, 23, 26, 0.4); - --color-overlay: rgba(0, 0, 0, 0.45); - --color-bubble-user-bg: transparent; - --color-bubble-user-border: #D9D6CE; - --color-bubble-user-text: #5A5852; - --color-bubble-asst-shadow: 0 2px 14px rgba(91, 74, 138, 0.06), 0 1px 4px rgba(0, 0, 0, 0.05); - --color-primary-solid: #5B4A8A; - --color-primary-deep: #3F3560; - --gradient-cta: linear-gradient(135deg, var(--color-primary-solid), var(--color-primary-deep)); - --glow-cta: 0 2px 10px rgba(91, 74, 138, 0.35); - --glow-cta-hover: 0 4px 20px rgba(91, 74, 138, 0.55); - --glow-soft: 0 0 16px rgba(91, 74, 138, 0.35); - --color-primary-faint: rgba(91, 74, 138, 0.08); - --color-primary-tint: rgba(91, 74, 138, 0.12); - --color-primary-wash: rgba(91, 74, 138, 0.20); + /* accent */ + --fs-accent: #5B4A8A; /* Scribe's signature colour */ + --fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent); /* Pill, tag and badge backgrounds */ + --fs-accent-faint: color-mix(in srgb, var(--fs-accent) 8%, transparent); /* The faintest accent wash */ + --fs-accent-deep: color-mix(in srgb, var(--fs-accent) 70%, black); /* The accent, darkened */ + --fs-accent-wash: color-mix(in srgb, var(--fs-accent) 22%, transparent); /* Heaviest accent tint */ + --fs-gradient-cta: linear-gradient(135deg, var(--fs-accent), var(--fs-accent-deep)); + --fs-glow-cta: 0 2px 10px color-mix(in srgb, var(--fs-accent) 35%, transparent); + --fs-glow-cta-hover: 0 4px 24px color-mix(in srgb, var(--fs-accent) 65%, transparent); - /* Action color set — Hybrid rule: action buttons use these, accent reserved for brand moments */ - --color-action-primary: #4A5D3F; - --color-action-primary-hover: #5A6F4D; - --color-action-secondary: #8B7355; - --color-action-secondary-hover: #A0876A; - --color-action-destructive: #6B2118; - --color-action-destructive-hover: #7E2A1F; - --color-action-ghost-border: #3F4651; + /* action */ + --fs-action-primary: #4A5D3F; /* Save, Submit, Confirm */ + --fs-action-secondary: #8B7355; /* Non-destructive alternates */ + --fs-action-tertiary: var(--fs-border-color); /* Ghost / outline actions */ + --fs-action-destructive: var(--fs-destructive); + --fs-action-primary-hover: color-mix(in srgb, var(--fs-action-primary) 88%, white); + --fs-action-secondary-hover: color-mix(in srgb, var(--fs-action-secondary) 88%, white); + --fs-action-destructive-hover: color-mix(in srgb, var(--fs-action-destructive) 88%, white); - --radius-sm: 6px; - --radius-md: 12px; - --radius-lg: 18px; - --radius-pill: 9999px; - --focus-ring: 0 0 0 2px rgba(91, 74, 138, 0.5); - /* Layout */ - --page-max-width: 1200px; - --page-padding-x: 1rem; - --sidebar-width: 260px; - --chat-reading-width: min(1200px, 100%); - --chat-context-sidebar-width: 220px; + /* border */ + --fs-border-color: #3F4651; /* Borders, dividers and ghost outlines */ + --fs-border: 0.5px solid var(--fs-border-color); + --fs-border-hover: 0.5px solid color-mix(in srgb, var(--fs-text-secondary) 30%, transparent); + --fs-border-active: 2px solid var(--fs-accent); /* Selected card or active tab only */ + + /* editor */ + --fs-wikilink: var(--fs-accent); + + /* elevation */ + --fs-shadow-1: 0 1px 0 rgba(0,0,0,0.4); /* Hairline lift */ + --fs-shadow-2: 0 4px 12px rgba(0,0,0,0.35); /* Dropdowns, popovers */ + --fs-shadow-3: 0 16px 40px rgba(0,0,0,0.5); /* Modals */ + + /* focus */ + --fs-focus-ring: 0 0 0 2px var(--fs-accent); + + /* font */ + --fs-font-display: 'Fraunces', Georgia, serif; + --fs-font-body: 'Inter', system-ui, sans-serif; + --fs-font-mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace; + + /* icon */ + --fs-icon-stroke: 1.5px; /* at 24px */ + --fs-icon-stroke-sm: 1px; /* at 16px */ + + /* layout */ + --fs-layout-page-max: 1200px; + --fs-layout-page-pad: 1rem; + --fs-layout-sidebar: 260px; + --fs-layout-header: 52px; + + /* motion */ + --fs-ease: cubic-bezier(0.2, 0.6, 0.2, 1); /* the one curve */ + --fs-dur-fast: 120ms; + --fs-dur-base: 180ms; + --fs-dur-slow: 280ms; + + /* priority */ + --fs-priority-low: var(--fs-info); + --fs-priority-low-bg: color-mix(in srgb, var(--fs-priority-low) 12%, transparent); + --fs-priority-medium: var(--fs-warning); + --fs-priority-medium-bg: color-mix(in srgb, var(--fs-priority-medium) 12%, transparent); + --fs-priority-high: var(--fs-error); + --fs-priority-high-bg: color-mix(in srgb, var(--fs-priority-high) 12%, transparent); + + /* radius */ + --fs-radius-sm: 4px; /* pills, tags, code spans */ + --fs-radius-md: 8px; /* buttons, inputs, small cards */ + --fs-radius-lg: 12px; /* cards, panels, modals */ + --fs-radius-xl: 16px; /* hero containers */ + --fs-radius-pill: 9999px; + + /* semantic */ + --fs-success: var(--fs-action-primary); + --fs-warning: #8B6F1E; + --fs-error: #C04A1F; + --fs-info: #3D5A6E; + --fs-destructive: #6B2118; /* irreversible — deliberately not the error colour */ + + /* space */ + --fs-space-1: 4px; + --fs-space-2: 8px; + --fs-space-3: 12px; + --fs-space-4: 16px; + --fs-space-5: 20px; + --fs-space-6: 24px; + --fs-space-7: 32px; + --fs-space-8: 48px; + --fs-space-9: 64px; + --fs-space-10: 96px; + + /* state */ + --fs-disabled-opacity: 0.5; + --fs-overlay: rgba(0, 0, 0, 0.65); + + /* status */ + --fs-status-todo: var(--fs-border-color); + --fs-status-todo-bg: color-mix(in srgb, var(--fs-status-todo) 12%, transparent); + --fs-status-in-progress: var(--fs-accent); + --fs-status-in-progress-bg: color-mix(in srgb, var(--fs-status-in-progress) 12%, transparent); + --fs-status-done: var(--fs-success); + --fs-status-done-bg: color-mix(in srgb, var(--fs-status-done) 12%, transparent); + --fs-overdue: var(--fs-error); + --fs-status-cancelled: var(--fs-text-tertiary); /* set aside, not failed */ + + /* surface */ + --fs-surface-page: #14171A; /* page bg, deepest surface */ + --fs-surface-raised: #1E2228; /* cards, raised elements */ + --fs-surface-hover: #2C313A; /* hovered surfaces */ + --fs-surface-code: var(--fs-surface-page); + --fs-surface-code-inline: var(--fs-surface-raised); + --fs-table-stripe: color-mix(in srgb, var(--fs-text-primary) 3%, transparent); + + /* text */ + --fs-text-primary: #E8E4D8; /* also the text colour on filled action buttons */ + --fs-text-secondary: #C2BFB4; + --fs-text-tertiary: #9C9A92; + + /* type */ + --fs-size-display: 40px; + --fs-size-h1: 32px; + --fs-size-h2: 24px; + --fs-size-h3: 18px; + --fs-size-body: 15px; + --fs-size-body-sm: 13px; + --fs-size-label: 12px; + --fs-size-code: 13px; + --fs-size-tiny: 11px; /* the only ALL CAPS, the only non-default tracking */ + --fs-weight-regular: 400; + --fs-weight-medium: 500; /* the heaviest the system goes */ + --fs-leading-heading: 1.3; + --fs-leading-body: 1.5; + --fs-leading-longform: 1.7; + --fs-tracking-tiny: 0.08em; } -[data-theme="dark"] { - /* Dark mode — Obsidian / Iron / Pewter */ - --color-bg: #14171A; - --color-bg-secondary: #1E2228; - --color-bg-card: #1E2228; - --color-surface: #2C313A; - --color-text: #E8E4D8; - --color-text-secondary: #C2BFB4; - --color-text-muted: #9C9A92; - --color-border: #3F4651; - --color-input-border: #3F4651; - --color-primary: #5B4A8A; - --color-danger: #C04A1F; - --color-tag-bg: rgba(91, 74, 138, 0.15); - --color-tag-text: #5B4A8A; +[data-theme="light"] { + /* accent */ + --fs-accent-wash: color-mix(in srgb, var(--fs-accent) 20%, transparent); + --fs-glow-cta-hover: 0 4px 20px color-mix(in srgb, var(--fs-accent) 55%, transparent); + + /* border */ + --fs-border-color: #D9D6CE; + + /* state */ + --fs-overlay: rgba(0, 0, 0, 0.45); + + /* surface */ + --fs-surface-page: #F5F1E8; + --fs-surface-raised: #FBF8F0; + --fs-surface-hover: #EFEAE0; + --fs-surface-code: #EBEDF0; + --fs-surface-code-inline: #EBEDF0; + + /* text */ + --fs-text-primary: #14171A; + --fs-text-secondary: #5A5852; + --fs-text-tertiary: #9A9890; +} + +/* ========================================================================== + COMPATIBILITY ALIASES — the app's historical names, pointing at the system. + + These exist so ~55 components keep working while they migrate to --fs-* + one at a time. Every one is a plain var() reference, which is what lets this + block be declared ONCE: when [data-theme="light"] moves --fs-surface-page, + --color-bg follows, because the alias resolves at use time. + + That is why this file lost 48 of its 60 dark-mode overrides — they were all + restating relationships the aliases now express directly. + + Removing this block is a rename sweep across the components, tracked + separately. Nothing new should reference a --color-* name. + ========================================================================== */ + +:root { + /* surfaces */ + --color-bg: var(--fs-surface-page); + --color-bg-secondary: var(--fs-surface-raised); + --color-bg-card: var(--fs-surface-raised); + --color-surface: var(--fs-surface-hover); + --color-code-bg: var(--fs-surface-code); + --color-code-inline-bg: var(--fs-surface-code-inline); + --color-table-stripe: var(--fs-table-stripe); + --color-overlay: var(--fs-overlay); + + /* text */ + --color-text: var(--fs-text-primary); + --color-text-secondary: var(--fs-text-secondary); + --color-text-muted: var(--fs-text-tertiary); + + /* lines */ + --color-border: var(--fs-border-color); + --color-input-border: var(--fs-border-color); + --focus-ring: var(--fs-focus-ring); + + /* brand */ + --color-primary: var(--fs-accent); + --color-primary-solid: var(--fs-accent); + --color-primary-deep: var(--fs-accent-deep); + --color-primary-faint: var(--fs-accent-faint); + --color-primary-tint: var(--fs-accent-soft); + --color-primary-wash: var(--fs-accent-wash); + --color-tag-bg: var(--fs-accent-soft); + --color-tag-text: var(--fs-accent); + --color-wikilink: var(--fs-wikilink); + --color-wikilink-bg: var(--fs-accent-soft); + --gradient-cta: var(--fs-gradient-cta); + --glow-cta: var(--fs-glow-cta); + --glow-cta-hover: var(--fs-glow-cta-hover); + + /* actions */ + --color-action-primary: var(--fs-action-primary); + --color-action-primary-hover: var(--fs-action-primary-hover); + --color-action-secondary: var(--fs-action-secondary); + --color-action-secondary-hover: var(--fs-action-secondary-hover); + --color-action-destructive: var(--fs-action-destructive); + --color-action-destructive-hover: var(--fs-action-destructive-hover); + + /* semantic */ + --color-success: var(--fs-success); + --color-warning: var(--fs-warning); + --color-danger: var(--fs-error); + --color-overdue: var(--fs-overdue); + --color-toast-success: var(--fs-success); + --color-toast-error: var(--fs-error); --color-shadow: rgba(0, 0, 0, 0.4); - --color-toast-success: #4A5D3F; - --color-toast-error: #C04A1F; - --color-status-todo: #3F4651; - --color-status-todo-bg: rgba(63, 70, 81, 0.18); - --color-status-in-progress: #5B4A8A; - --color-status-in-progress-bg: rgba(91, 74, 138, 0.18); - --color-status-done: #4A5D3F; - --color-status-done-bg: rgba(74, 93, 63, 0.18); - --color-priority-low: #3D5A6E; - --color-priority-low-bg: rgba(61, 90, 110, 0.18); - --color-priority-medium: #8B6F1E; - --color-priority-medium-bg: rgba(139, 111, 30, 0.18); - --color-priority-high: #C04A1F; - --color-priority-high-bg: rgba(192, 74, 31, 0.18); - --color-wikilink: #5B4A8A; - --color-wikilink-bg: rgba(91, 74, 138, 0.18); - --color-overdue: #C04A1F; - --color-code-bg: #14171A; - --color-code-inline-bg: #1E2228; - --color-table-stripe: rgba(255, 255, 255, 0.025); - --color-success: #4A5D3F; - --color-warning: #8B6F1E; - --color-input-bar-bg: #1E2228; - --color-input-bar-text: #E8E4D8; - --color-input-bar-placeholder: rgba(232, 228, 216, 0.35); - --color-overlay: rgba(0, 0, 0, 0.65); - --color-bubble-user-bg: transparent; - --color-bubble-user-border: #3F4651; - --color-bubble-user-text: #C2BFB4; - --color-bubble-asst-shadow: 0 4px 28px rgba(91, 74, 138, 0.14), 0 2px 8px rgba(0, 0, 0, 0.4); - --color-primary-solid: #5B4A8A; - --color-primary-deep: #3F3560; - --gradient-cta: linear-gradient(135deg, var(--color-primary-solid), var(--color-primary-deep)); - --glow-cta: 0 2px 12px rgba(91, 74, 138, 0.45); - --glow-cta-hover: 0 4px 24px rgba(91, 74, 138, 0.65); - --glow-soft: 0 0 18px rgba(91, 74, 138, 0.4); - --color-primary-faint: rgba(91, 74, 138, 0.10); - --color-primary-tint: rgba(91, 74, 138, 0.14); - --color-primary-wash: rgba(91, 74, 138, 0.22); - /* Action color set — identical across themes */ - --color-action-primary: #4A5D3F; - --color-action-primary-hover: #5A6F4D; - --color-action-secondary: #8B7355; - --color-action-secondary-hover: #A0876A; - --color-action-destructive: #6B2118; - --color-action-destructive-hover: #7E2A1F; - --color-action-ghost-border: #3F4651; + /* task status + priority */ + --color-status-todo: var(--fs-status-todo); + --color-status-todo-bg: var(--fs-status-todo-bg); + --color-status-in-progress: var(--fs-status-in-progress); + --color-status-in-progress-bg: var(--fs-status-in-progress-bg); + --color-status-done: var(--fs-status-done); + --color-status-done-bg: var(--fs-status-done-bg); + --color-priority-low: var(--fs-priority-low); + --color-priority-low-bg: var(--fs-priority-low-bg); + --color-priority-medium: var(--fs-priority-medium); + --color-priority-medium-bg: var(--fs-priority-medium-bg); + --color-priority-high: var(--fs-priority-high); + --color-priority-high-bg: var(--fs-priority-high-bg); + + /* geometry */ + --radius-sm: var(--fs-radius-sm); + --radius-md: var(--fs-radius-lg); /* NB: the app's "md" is the system's LARGE */ + --radius-lg: var(--fs-radius-xl); /* and the app's "lg" is the system's XL */ + --page-max-width: var(--fs-layout-page-max); + --page-padding-x: var(--fs-layout-page-pad); + --sidebar-width: var(--fs-layout-sidebar); + --header-height: var(--fs-layout-header); + + /* ------------------------------------------------------------------ + Names components reference that were NEVER declared anywhere. + + Each of these was reached for with a hardcoded fallback, so the page + rendered — but the fallback was what rendered, always, and several were + off-palette: --color-primary-bg fell back to an indigo, --color-destructive + to a brick that is not the oxblood, --color-status-cancelled to a grey from + no palette in this system. + + Wiring them to real tokens is the whole point of the exercise. Expect small + visual shifts exactly where a fallback had drifted; that shift IS the fix. + ------------------------------------------------------------------ */ + --color-accent: var(--fs-accent); + --color-accent-fg: var(--fs-text-primary); + --color-hover: var(--fs-surface-hover); + --color-bg-hover: var(--fs-surface-hover); + --color-bg-tertiary: var(--fs-surface-hover); + --color-surface-2: var(--fs-surface-hover); + --color-surface-alt: var(--fs-surface-hover); + --color-surface-raised: var(--fs-surface-raised); + --color-input-bg: var(--fs-surface-page); + --color-muted: var(--fs-text-tertiary); + --color-destructive: var(--fs-destructive); + --color-primary-bg: var(--fs-accent-soft); + --color-status-cancelled: var(--fs-status-cancelled); + --font-display: var(--fs-font-display); + --font-mono: var(--fs-font-mono); } +/* ========================================================================== + Base element styles + ========================================================================== */ + *, *::before, *::after { @@ -152,36 +310,36 @@ body { margin: 0; - background: var(--color-bg); - color: var(--color-text); - font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, - "Segoe UI", Roboto, sans-serif; + background: var(--fs-surface-page); + color: var(--fs-text-primary); + font-family: var(--fs-font-body); font-feature-settings: "cv11"; - line-height: 1.5; - transition: background-color 0.2s, color 0.2s; + line-height: var(--fs-leading-body); + transition: background-color var(--fs-dur-base) var(--fs-ease), + color var(--fs-dur-base) var(--fs-ease); } h1, h2 { - font-family: 'Fraunces', Georgia, serif; + font-family: var(--fs-font-display); font-optical-sizing: auto; - font-weight: 500; - line-height: 1.3; + font-weight: var(--fs-weight-medium); + line-height: var(--fs-leading-heading); } h3 { - font-family: 'Inter', system-ui, sans-serif; - font-weight: 500; - line-height: 1.3; + font-family: var(--fs-font-body); + font-weight: var(--fs-weight-medium); + line-height: var(--fs-leading-heading); } code, pre, kbd, samp { - font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace; + font-family: var(--fs-font-mono); font-feature-settings: "liga", "calt"; } ::selection { - background: rgba(91, 74, 138, 0.3); - color: var(--color-text); + background: var(--fs-accent-wash); + color: var(--fs-text-primary); } input:focus-visible, @@ -190,15 +348,15 @@ select:focus-visible, button:focus-visible, a:focus-visible { outline: none; - box-shadow: var(--focus-ring); - border-radius: var(--radius-sm); + box-shadow: var(--fs-focus-ring); + border-radius: var(--fs-radius-sm); } button:not(:disabled):active, .btn:not(:disabled):active, [role="button"]:not(:disabled):active { transform: scale(0.97); - transition: transform 0.08s ease; + transition: transform 0.08s var(--fs-ease); } /* Responsive breakpoints: 480px (phone), 768px (tablet), 1024px (desktop) */ @@ -228,11 +386,11 @@ button:not(:disabled):active, background: transparent; } ::-webkit-scrollbar-thumb { - background: var(--color-border); - border-radius: 9999px; + background: var(--fs-border-color); + border-radius: var(--fs-radius-pill); } ::-webkit-scrollbar-thumb:hover { - background: var(--color-text-muted); + background: var(--fs-text-tertiary); } /* Floating inline assist button (teleported to body, cannot be scoped) */ @@ -240,14 +398,14 @@ button:not(:disabled):active, position: fixed; z-index: 150; transform: translateX(-50%); - background: var(--color-primary); - color: #fff; + background: var(--fs-accent); + color: var(--fs-text-primary); border: none; - border-radius: 999px; + border-radius: var(--fs-radius-pill); padding: 0.3rem 0.8rem; - font-size: 0.8rem; + font-size: var(--fs-size-body-sm); cursor: pointer; - box-shadow: 0 2px 8px var(--color-shadow); + box-shadow: var(--fs-shadow-2); white-space: nowrap; } .inline-assist-btn:hover { diff --git a/frontend/src/views/DesignSystemsView.vue b/frontend/src/views/DesignSystemsView.vue index 828fb16..a11b09f 100644 --- a/frontend/src/views/DesignSystemsView.vue +++ b/frontend/src/views/DesignSystemsView.vue @@ -530,7 +530,7 @@ function isColourish(value: string): boolean {
Distinct from purpose: purpose is what the token is FOR, this is diff --git a/src/scribe/mcp/tools/design_systems.py b/src/scribe/mcp/tools/design_systems.py index 7043a34..c8af5db 100644 --- a/src/scribe/mcp/tools/design_systems.py +++ b/src/scribe/mcp/tools/design_systems.py @@ -33,7 +33,8 @@ async def create_design_system( """Create a design system, optionally inheriting from another. Args: - title: What this system is, e.g. "FabledSword" or "Scribe" (required). + title: What this system is — a house style, or one app within it + (required). description: What it covers and when it applies. guidance: The narrative a token table cannot hold — aesthetic, voice and tone, what is deliberately out of scope. Markdown, free-form. @@ -226,18 +227,22 @@ async def create_design_token( Args: design_system_id: The system that owns this token. - name: The custom-property name, e.g. "--fs-obsidian" (required). + name: The custom-property name, e.g. "--surface-page" (required). + Name it for its PURPOSE, not its value: a name like "--obsidian" + or "--button-bg" stops being true the moment the value or the + element changes. value_by_mode: Values keyed by mode, e.g. - {"base": "#f7f5ef", "dark": "#14171a"}. Use "base" for the value + {"base": "#14171a", "light": "#f7f5ef"}. Use "base" for the value that applies when no mode is more specific; a token that is not mode-dependent needs only "base". In a system WITH a parent, an omitted mode is inherited rather than blanked. group_name: Free-text grouping — "surface", "text", "radius", whatever this system's own vocabulary is. - purpose: What the token is for, e.g. "page bg, deepest surface". + purpose: What the token is for, e.g. "page background, deepest + surface". rationale: WHY it is this value — a different question from purpose. - "Success equals Moss, aligned by design" is a rationale; "page bg, - deepest surface" is a purpose. + "Deliberately the same value as the primary action colour" is a + rationale; "page background, deepest surface" is a purpose. supersedes: Literal values this token should be used INSTEAD OF, e.g. ["#fff", "#ffffff"]. This is how a design system records what a prohibition was trying to say — not "white is banned" but "write diff --git a/src/scribe/mcp/tools/rulebooks.py b/src/scribe/mcp/tools/rulebooks.py index ccb9979..55ff7dd 100644 --- a/src/scribe/mcp/tools/rulebooks.py +++ b/src/scribe/mcp/tools/rulebooks.py @@ -45,7 +45,8 @@ async def create_rulebook(title: str, description: str = "") -> dict: Two ways a rulebook reaches projects, set by its always_on flag (toggle via update_rulebook): - always_on = true -> binds EVERY one of your projects automatically. - Use for universal cross-project norms (e.g. "FabledSword family"). + Use for universal cross-project norms that apply across every + project, not just one. - always_on = false -> binds only projects that subscribe (subscribe_project_to_rulebook). Use for a THEMED body of rules a category of projects shares (e.g. a design system that visual apps @@ -54,7 +55,7 @@ async def create_rulebook(title: str, description: str = "") -> dict: to any single project. Project-specific rules go in create_project_rule. Args: - title: Rulebook name (e.g. "FabledSword family"). + title: Rulebook name. description: Optional short description of what this rulebook covers. """ uid = current_user_id() diff --git a/src/scribe/models/design_system.py b/src/scribe/models/design_system.py index 8aa2c6e..d2d8570 100644 --- a/src/scribe/models/design_system.py +++ b/src/scribe/models/design_system.py @@ -116,22 +116,24 @@ class DesignToken(Base, TimestampMixin, SoftDeleteMixin): group_name: Mapped[str | None] = mapped_column(Text, nullable=True) purpose: Mapped[str | None] = mapped_column(Text, nullable=True) # WHY this token is this value — a different question from `purpose`, which - # is what it is FOR. "Success equals Moss, aligned by design" is a rationale; - # "page bg, deepest surface" is a purpose. Rules carry the first routinely - # and a token row had nowhere to put it. + # is what it is FOR. "Deliberately the same value as the primary action + # colour" is a rationale; "page background, deepest surface" is a purpose. + # Design guidance carries the first routinely and a token row had nowhere to + # put it. rationale: Mapped[str | None] = mapped_column(Text, nullable=True) # Literal values this token should be used INSTEAD OF, e.g. ["#fff", # "#ffffff"] on a text-on-action token. # # This is how a design system records the thing a prohibition was trying to - # say. "Pure white is never text" is the shadow of a positive fact — text is - # Parchment — and a system that stores what things ARE has no row for a ban. + # say. "Pure white is never text" is the shadow of a positive fact — some + # other colour IS the text colour — and a system that stores what things ARE + # has no row for a ban. # Recording the replacement keeps the check and makes it actionable: a # finding can name what to write instead of merely objecting. # # It has to be DECLARED rather than inferred, because the superseded literal - # and the token's own value are usually different colours (#fff is not - # #E8E4D8). No value-matching rule could ever connect them. + # and the token's own value are usually different colours entirely. No + # value-matching rule could ever connect them. # # Consumed by the source lint (#2277), not by the drift panel: these # literals live in component CSS, which the panel cannot see and says so. diff --git a/src/scribe/services/design_stylesheet.py b/src/scribe/services/design_stylesheet.py index 1a2ef27..0c61c9f 100644 --- a/src/scribe/services/design_stylesheet.py +++ b/src/scribe/services/design_stylesheet.py @@ -18,8 +18,8 @@ has recall, locations, drift checks and merge. So the division is: this sheet says what the values MEAN; snippets say what things LOOK LIKE, in terms of those values. A token named after an element -(`--fs-button-bg`) is the smell that the two have been mixed — it multiplies -with every new element, where a purpose name (`--fs-action-primary`) is reused. +(`--button-bg`) is the smell that the two have been mixed — it multiplies +with every new element, where a purpose name (`--action-primary`) is reused. SAFETY ------ @@ -203,7 +203,8 @@ def duplicate_values(tokens: Sequence) -> dict[str, list[str]]: Two names for one value are either a deliberate alias or the same idea recorded twice — the token-level form of the duplicated-definition shape. Reported rather than refused: a design system legitimately aligns colours on - purpose ("Success = Moss, by design"), and a generator that rejected that + purpose (one palette entry defined as equal to another), and a generator + that rejected that would be wrong about the operator's intent. Compares the BASE value only. Two tokens agreeing in one mode and diverging