chore(design-systems): stop teaching one install's kit in product copy
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / integration (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 41s

The operator's check: this must be a system for managing design systems, not one
with the FabledSword family built into it.

No LOGIC was coupled — the audit found zero behavioural dependencies. But every
docstring example, every UI placeholder and several comments named this install's
palette, so a stranger creating their first design system was shown
"FabledSword" as the expected shape and `--fs-obsidian` as the expected token.
Examples teach, and these taught the wrong thing.

Placeholders now describe the SHAPE ("Your house style", "--surface-page")
rather than naming one instance's contents, and the token-name placeholder now
says the thing worth saying: name it for its purpose, because `--obsidian` and
`--button-bg` both stop being true the moment the value or the element changes.

Not fixed here, and it is the one real coupling left: DesignView.vue hardcodes
rule 65's button variants and rule 60's type scale as literal arrays, so a
stranger's Design page would display this family's specs. Those arrays exist
because there was no design system to read from — which there now is. They go
when the panel is repointed (#2295), not before.

Scribe's own stylesheet comments ("Moss action-primary per Hybrid") are left
alone: that is the app CONSUMING the family style, which is what dogfooding
looks like, not the tool assuming it.
This commit is contained in:
2026-07-31 10:18:53 -04:00
parent 1fde646c60
commit 473280e690
5 changed files with 32 additions and 23 deletions
+5 -5
View File
@@ -530,7 +530,7 @@ function isColourish(value: string): boolean {
<label class="field-label" for="first-title">Title</label> <label class="field-label" for="first-title">Title</label>
<input <input
id="first-title" v-model="newTitle" class="input" type="text" id="first-title" v-model="newTitle" class="input" type="text"
placeholder="FabledSword" @keyup.enter="submitCreate" placeholder="Your house style" @keyup.enter="submitCreate"
/> />
</div> </div>
<div class="field"> <div class="field">
@@ -581,7 +581,7 @@ function isColourish(value: string): boolean {
<label class="field-label" for="new-title">Title</label> <label class="field-label" for="new-title">Title</label>
<input <input
id="new-title" v-model="newTitle" class="input" type="text" id="new-title" v-model="newTitle" class="input" type="text"
placeholder="FabledSword, or Scribe" @keyup.enter="submitCreate" placeholder="A house style, or one app in it" @keyup.enter="submitCreate"
/> />
</div> </div>
<div class="field"> <div class="field">
@@ -852,7 +852,7 @@ function isColourish(value: string): boolean {
<label class="field-label" for="token-name">Name</label> <label class="field-label" for="token-name">Name</label>
<input <input
id="token-name" v-model="tokenName" class="input mono" type="text" id="token-name" v-model="tokenName" class="input mono" type="text"
placeholder="--fs-obsidian" placeholder="--surface-page"
/> />
</div> </div>
<div class="field-row"> <div class="field-row">
@@ -864,7 +864,7 @@ function isColourish(value: string): boolean {
<label class="field-label" for="token-purpose">Purpose</label> <label class="field-label" for="token-purpose">Purpose</label>
<input <input
id="token-purpose" v-model="tokenPurpose" class="input" type="text" id="token-purpose" v-model="tokenPurpose" class="input" type="text"
placeholder="page bg, deepest surface" placeholder="page background, deepest surface"
/> />
</div> </div>
</div> </div>
@@ -873,7 +873,7 @@ function isColourish(value: string): boolean {
<label class="field-label" for="token-rationale">Why this value</label> <label class="field-label" for="token-rationale">Why this value</label>
<input <input
id="token-rationale" v-model="tokenRationale" class="input" type="text" id="token-rationale" v-model="tokenRationale" class="input" type="text"
placeholder="Success equals Moss, aligned by design" placeholder="Matches the primary action colour, deliberately"
/> />
<p class="field-hint"> <p class="field-hint">
Distinct from purpose: purpose is what the token is FOR, this is Distinct from purpose: purpose is what the token is FOR, this is
+11 -6
View File
@@ -33,7 +33,8 @@ async def create_design_system(
"""Create a design system, optionally inheriting from another. """Create a design system, optionally inheriting from another.
Args: 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. description: What it covers and when it applies.
guidance: The narrative a token table cannot hold — aesthetic, voice and guidance: The narrative a token table cannot hold — aesthetic, voice and
tone, what is deliberately out of scope. Markdown, free-form. tone, what is deliberately out of scope. Markdown, free-form.
@@ -226,18 +227,22 @@ async def create_design_token(
Args: Args:
design_system_id: The system that owns this token. 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. 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 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 mode-dependent needs only "base". In a system WITH a parent, an
omitted mode is inherited rather than blanked. omitted mode is inherited rather than blanked.
group_name: Free-text grouping — "surface", "text", "radius", whatever group_name: Free-text grouping — "surface", "text", "radius", whatever
this system's own vocabulary is. 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. rationale: WHY it is this value — a different question from purpose.
"Success equals Moss, aligned by design" is a rationale; "page bg, "Deliberately the same value as the primary action colour" is a
deepest surface" is a purpose. rationale; "page background, deepest surface" is a purpose.
supersedes: Literal values this token should be used INSTEAD OF, e.g. supersedes: Literal values this token should be used INSTEAD OF, e.g.
["#fff", "#ffffff"]. This is how a design system records what a ["#fff", "#ffffff"]. This is how a design system records what a
prohibition was trying to say — not "white is banned" but "write prohibition was trying to say — not "white is banned" but "write
+3 -2
View File
@@ -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 Two ways a rulebook reaches projects, set by its always_on flag (toggle via
update_rulebook): update_rulebook):
- always_on = true -> binds EVERY one of your projects automatically. - 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 - always_on = false -> binds only projects that subscribe
(subscribe_project_to_rulebook). Use for a THEMED body of rules a (subscribe_project_to_rulebook). Use for a THEMED body of rules a
category of projects shares (e.g. a design system that visual apps 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. to any single project. Project-specific rules go in create_project_rule.
Args: Args:
title: Rulebook name (e.g. "FabledSword family"). title: Rulebook name.
description: Optional short description of what this rulebook covers. description: Optional short description of what this rulebook covers.
""" """
uid = current_user_id() uid = current_user_id()
+9 -7
View File
@@ -116,22 +116,24 @@ class DesignToken(Base, TimestampMixin, SoftDeleteMixin):
group_name: Mapped[str | None] = mapped_column(Text, nullable=True) group_name: Mapped[str | None] = mapped_column(Text, nullable=True)
purpose: 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 # 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; # is what it is FOR. "Deliberately the same value as the primary action
# "page bg, deepest surface" is a purpose. Rules carry the first routinely # colour" is a rationale; "page background, deepest surface" is a purpose.
# and a token row had nowhere to put it. # Design guidance carries the first routinely and a token row had nowhere to
# put it.
rationale: Mapped[str | None] = mapped_column(Text, nullable=True) rationale: Mapped[str | None] = mapped_column(Text, nullable=True)
# Literal values this token should be used INSTEAD OF, e.g. ["#fff", # Literal values this token should be used INSTEAD OF, e.g. ["#fff",
# "#ffffff"] on a text-on-action token. # "#ffffff"] on a text-on-action token.
# #
# This is how a design system records the thing a prohibition was trying to # 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 # say. "Pure white is never text" is the shadow of a positive fact — some
# Parchment — and a system that stores what things ARE has no row for a ban. # 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 # Recording the replacement keeps the check and makes it actionable: a
# finding can name what to write instead of merely objecting. # finding can name what to write instead of merely objecting.
# #
# It has to be DECLARED rather than inferred, because the superseded literal # 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 # and the token's own value are usually different colours entirely. No
# #E8E4D8). No value-matching rule could ever connect them. # value-matching rule could ever connect them.
# #
# Consumed by the source lint (#2277), not by the drift panel: these # 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. # literals live in component CSS, which the panel cannot see and says so.
+4 -3
View File
@@ -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 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 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 (`--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. with every new element, where a purpose name (`--action-primary`) is reused.
SAFETY 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 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. recorded twice — the token-level form of the duplicated-definition shape.
Reported rather than refused: a design system legitimately aligns colours on 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. would be wrong about the operator's intent.
Compares the BASE value only. Two tokens agreeing in one mode and diverging Compares the BASE value only. Two tokens agreeing in one mode and diverging