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
+9 -7
View File
@@ -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.