feat(design-systems): central prose — guidance on the system, rationale on tokens
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Failing after 20s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Failing after 20s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Skipped
Last piece of the architecture in #2296. The operator: "the prose doesn't have to live as one offs, there's a central system for managing it." Two fields, both free-form: design_systems.guidance the narrative a token table cannot hold — aesthetic, voice and tone, what is deliberately out of scope. design_tokens.rationale WHY a token is this value, which is a different question from `purpose` (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. Free-form rather than a column per category, deliberately. A schema with `voice`, `aesthetic` and `scope` columns would bake one rulebook's table of contents into every install (rule #115), leaving the next install three empty columns and nowhere for what it actually cares about. Both nullable: a design system with no prose at all is complete, not a draft. `rationale` cascades like `purpose` — deepest non-empty wins — so an app overriding a colour keeps the family's reasoning rather than blanking it. Same argument as `supersedes`: the override was about the value, not the meaning. In the generated sheet the inline comment prefers `purpose` and falls back to `rationale`, so a token carrying only the why still says something instead of rendering bare.
This commit is contained in:
@@ -387,3 +387,34 @@ def test_rows_without_a_supersedes_attribute_at_all_still_resolve():
|
||||
)
|
||||
resolved = _by_name(resolve_tokens(FAMILY, PARENTS, {FAMILY: [legacy]}))
|
||||
assert resolved["--fs-x"].supersedes == ()
|
||||
|
||||
|
||||
# --- rationale --------------------------------------------------------------
|
||||
|
||||
def test_rationale_cascades_like_purpose_and_is_a_different_question():
|
||||
"""`purpose` is what the token is FOR; `rationale` is why it is this value.
|
||||
Rules carry the second routinely ("Success = Moss, aligned by design") and a
|
||||
token row had nowhere to put it until now."""
|
||||
resolved = _by_name(resolve_tokens(
|
||||
APP, PARENTS,
|
||||
{
|
||||
FAMILY: [SimpleNamespace(
|
||||
name="--fs-success", value_by_mode={"base": "#4a5d3f"},
|
||||
group_name="semantic", purpose="success states",
|
||||
rationale="equals Moss, aligned by design",
|
||||
order_index=0, supersedes=[],
|
||||
)],
|
||||
APP: [_token("--fs-success", {"base": "#3f5236"})],
|
||||
},
|
||||
))
|
||||
token = resolved["--fs-success"]
|
||||
assert token.rationale == "equals Moss, aligned by design"
|
||||
assert token.purpose == "success states"
|
||||
assert token.value_by_mode == {"base": "#3f5236"} # the value still overrode
|
||||
|
||||
|
||||
def test_a_token_without_a_rationale_resolves_to_none():
|
||||
resolved = _by_name(resolve_tokens(
|
||||
FAMILY, PARENTS, {FAMILY: [_token("--fs-x", {"base": "1px"})]},
|
||||
))
|
||||
assert resolved["--fs-x"].rationale is None
|
||||
|
||||
@@ -319,3 +319,24 @@ def test_clean_code_reports_nothing_to_act_on():
|
||||
assert report["superseded_literals"] == []
|
||||
assert report["local_definitions"] == []
|
||||
assert report["used"] == ["--fs-obsidian", "--fs-parchment"]
|
||||
|
||||
|
||||
def test_the_inline_comment_falls_back_to_rationale_when_there_is_no_purpose():
|
||||
"""A token carrying only the why still says something in the sheet, rather
|
||||
than rendering bare because the other field happened to be empty."""
|
||||
token = SimpleNamespace(
|
||||
name="--fs-success", value_by_mode={"base": "#4a5d3f"},
|
||||
group_name=None, purpose=None, rationale="equals Moss, by design",
|
||||
)
|
||||
assert "equals Moss, by design" in render_stylesheet([token])
|
||||
|
||||
|
||||
def test_purpose_wins_over_rationale_in_the_comment():
|
||||
"""What a token is FOR is what a reader of the stylesheet needs first."""
|
||||
token = SimpleNamespace(
|
||||
name="--fs-x", value_by_mode={"base": "1px"},
|
||||
group_name=None, purpose="hairline borders", rationale="because thin",
|
||||
)
|
||||
css = render_stylesheet([token])
|
||||
assert "hairline borders" in css
|
||||
assert "because thin" not in css
|
||||
|
||||
@@ -398,7 +398,8 @@ async def test_stylesheet_reports_what_the_sheet_cannot_say_for_itself():
|
||||
contributions=(
|
||||
{"base": (Contribution(system_id=1, value=base),)} if base else {}
|
||||
),
|
||||
group_name=None, purpose=None, supersedes=(), order_index=0,
|
||||
group_name=None, purpose=None, rationale=None,
|
||||
supersedes=(), order_index=0,
|
||||
)
|
||||
|
||||
system = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user