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
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user