Skip to content

stardust: four-scope CSS organization (global / section / block / default-content) for mechanical block extraction #147

Description

@catalan-adobe

Summary

Add a CSS-organization discipline to stardust:prototype that classifies
every CSS rule in a rendered prototype into one of four scopes —
global, section, block, or default-content — with
mandatory marker comments and a brief/craft-time validator. The discipline
makes downstream block extraction (the planned future EDS converter
referenced in data-attributes.md § Why and artifact-map.md)
mechanical instead of LLM-driven, at no cost to the existing review /
iteration / audit loop.

Authored against stardust v0.7.1 (per plugins/stardust/tile.json
on 2026-05-22). Future agent picking this up: verify the current
version and that the file paths / Discipline-numbering referenced
below still match HEAD before drafting the PR.

Background

Stardust v0.7.1 produces a self-contained styled HTML file per page
at stardust/prototypes/<slug>-proposed.html and a deployable static
bundle at stardust/migrated/. The README + master SKILL.md +
migrate/SKILL.md are explicit that EDS / CMS / framework output is
out of scope for stardust itself; a separate downstream plugin is
expected to convert the static HTML into EDS blocks (and similar for
other targets).

The handoff contract is already partially designed:

  • data-attribute vocabularydata-template, data-module,
    data-slot, data-canon, data-deviation, data-bespoke,
    data-section, data-fragment, data-broken-link,
    data-nav-collapse (v2 / v2.1 / v2.2 sets per
    skills/stardust/reference/data-attributes.md § Versioning).
  • :root token contract — stable cross-tool vocabulary per
    skills/stardust/reference/token-contract.md.
  • canon.css — site-wide compound visual language (.btn-primary,
    .card, .link, form inputs) lifted from approved prototypes per
    skills/prototype/reference/canon-extraction.md § 2.
  • _meta.json sidecars + state.json.migrate block
    machine-readable per-page handoff per
    skills/stardust/reference/migrate-output-format.md.

The DOM and data attributes are already EDS-shaped. The page CSS
inside the prototype's <style> block
is not.

Problem

Today the proposed file's <style> block has:

  1. :root { ... } at the top (required, well-defined).
  2. canon.css injected as the second block at migrate time (well-defined).
  3. Page-specific styles after that — unstructured. Any class name
    (.headline, .featured-coffee, .hero-grid), any descendant
    selector, any media query is acceptable as long as the source-order
    rule for @media (per
    skills/prototype/reference/mobile-nav-collapse.md § Source order)
    and the cascade requirements are honored.

token-contract.md § Per-section overrides documents
section[data-section="X"] { ... } as one optional pattern, not
as the required organization model.

A downstream converter that wants to extract block CSS faces a
classification problem per rule: "does .headline belong to the
hero block, the features block, the catalogue block, or none of them?"
The answer requires LLM judgment over the DOM. The extraction is not
mechanical, not deterministic, and not cheap to build.

Proposal

Every CSS rule emitted by stardust (in the proposed file's <style>
block and in canon.css) is classifiable into exactly one of four
scopes, grouped contiguously, preceded by a marker comment:

The four scopes

Scope Selector pattern Purpose EDS handoff target
Global :root, html, body, main, and class selectors with no [data-section] / [data-module] ancestor (e.g. .btn-primary from canon.css) Tokens, base resets, site-wide compound utility styles/styles.css
Section Selector starts with section[data-section="<name>"] (or [data-section="<name>"]) Per-section context — typically token redefinition; cascades to default-content within styles/styles.css (scoped via section class)
Block Selector starts with [data-module="<name>"] (or [data-template="<name>"] for page-template scope) Self-contained component styling blocks/<name>/<name>.css
Default-content Element selectors descending directly from a section (section > h1, section > p, section[data-section="hero"] > p) — does not match deep-descended elements inside modules Default styling for un-blocked prose inside sections styles/styles.css (default-content rules)

Inheritance via cascade

Default-content inherits both global and section context for free
via the normal CSS cascade:

  • Global default-content rule: section > h1 { font: var(--heading-xxl)/var(--lh-heading) var(--heading-font); }
  • Section override redefines a custom property:
    section[data-section="hero"] { --heading-xxl: clamp(80px, 12vw, 160px); }
  • The hero's h1 picks up the lifted size automatically because
    var(--heading-xxl) resolves against the section's redefinition.

The direct-child combinator (>) in default-content selectors is
load-bearing. It isolates default-content rules from block internals:
section > h1 does NOT match an h1 deep-descended inside a
[data-module]. Block authors are free to style their own headings
inside the module subtree without colliding with defaults.

No engineered inheritance machinery is required. The cascade does the
work.

Example <style> block (canonical organization)

<style>
  /* === GLOBAL: tokens === */
  :root {
    --color-bg: ...; --color-fg: ...; --color-accent: ...;
    --heading-xxl: ...; --body: ...;
    --section-padding: ...; --radius: ...;
  }

  /* === GLOBAL: resets === */
  html, body, main { ... }

  /* === GLOBAL: default-content === */
  section > h1, section > h2, section > h3 { ... }
  section > p, section > ul, section > ol, section > li { ... }
  section > img, section > picture { ... }
  section > a, section > blockquote { ... }

  /* canon.css injected here at migrate time — GLOBAL: compound utility */
  .btn-primary { ... }
  .btn-secondary { ... }
  .card { ... }
  .link { ... }

  /* === SECTION: hero === */
  section[data-section="hero"] {
    --color-bg: var(--color-accent);
    --heading-xxl: clamp(80px, 12vw, 160px);
  }
  section[data-section="hero"] > h1 { ... }  /* per-section default override */

  /* === BLOCK: hotline-211 === */
  [data-module="hotline-211"] { ... }
  [data-module="hotline-211"] [data-slot="phone"] { ... }
  [data-module="hotline-211"] [data-slot="hours"] { ... }

  /* === SECTION: features === */
  section[data-section="features"] { ... }

  /* === BLOCK: donate-band === */
  [data-module="donate-band"] { ... }
  [data-module="donate-band"] [data-slot="amount-pills"] { ... }

  /* === MEDIA: ≤ 640px === */
  @media (max-width: 640px) {
    /* media queries that affect multiple scopes live in a site-global media block */
    section { --section-padding: 32px; }
  }
</style>

Section- or block-specific media queries live inside their owning
group (per mobile-nav-collapse.md § Source order — same-specificity
later-wins rule still applies).

Discipline 11 — Four-scope CSS organization

Lands in skills/prototype/SKILL.md § Craft-time disciplines
(currently disciplines 6, 7, 8 exist per
PR #131; this would be the
next addition, numbered per the current spec at PR time).

Validator behavior (brief/craft-time, pre-write)

After craft returns the rendered file, the validator parses every
rule selector in the <style> block. Each selector classifies as
one of:

  1. global — selector matches:

    • :root declaration, OR
    • element selectors against html / body / main, OR
    • a class selector not preceded by any attribute selector that
      scopes to [data-section] or [data-module]. canon.css
      classes (.btn-primary, .card, etc.) land here.
  2. section — selector starts with
    section[data-section="<name>"] (or [data-section="<name>"]).
    Descendants of the section selector still classify as section.

  3. block — selector starts with [data-module="<name>"] OR
    [data-template="<name>"] (page-template scope).
    Descendants of the module selector classify as block.

  4. default-content — selector pattern
    section[> direct child] or section[data-section="X"][> direct child]
    where the direct child is one of the default-content HTML elements
    (h1h6, p, ul, ol, li, img, picture, figure,
    figcaption, a, blockquote, hr).

  5. Unclassifiable — refuse the write.

Marker comments

Mandatory and machine-parseable. Format:

/* === <SCOPE>: <name|kind> === */

Where:

  • <SCOPE> is GLOBAL / SECTION / BLOCK / MEDIA.
  • For GLOBAL: <name> is one of tokens / resets /
    default-content / compound utility (the canon.css block).
  • For SECTION: <name> matches the data-section attribute value.
  • For BLOCK: <name> matches the data-module or data-template
    attribute value.
  • For MEDIA: <kind> describes the breakpoint, e.g. ≤ 640px.

The validator regex over the <style> text is straightforward:

/\* === (GLOBAL|SECTION|BLOCK|MEDIA): ([^=]+) === \*/

Refusal messages

When a rule cannot be classified, refuse the write with a structured
message naming the offending selector and the suggested remediation:

unscoped CSS rule: `.headline { ... }` at line 142

A page-specific rule must be one of:
  - canon.css class (lifted because used across templates), OR
  - section-scoped (`section[data-section="X"] .headline`), OR
  - block-scoped (`[data-module="Y"] .headline`), OR
  - default-content (`section > h1` if it's a default heading style).

Pick one and re-render. See
  skills/prototype/reference/proposed-file-shell.md § Four-scope CSS
  skills/stardust/reference/data-attributes.md § Why

Block rules without a matching data-module/data-template value in
the DOM also refuse (typo / dead-block detection).

Site-global escape hatch

Some rules legitimately span scopes (e.g. section { padding: var(--section-padding); }
applies to every section). Those live in GLOBAL: resets or a
dedicated GLOBAL: section-defaults group. The escape hatch keeps
the discipline from forcing absurd duplication.

Implementation plan (file-by-file)

File Change
plugins/stardust/skills/prototype/reference/proposed-file-shell.md § Required structure Add item 8 — "Four-scope CSS organization." Cite the marker comment format, the four scopes, the example <style> block above.
plugins/stardust/skills/prototype/SKILL.md § Craft-time disciplines Add Discipline 11 — "Four-scope CSS organization." Define the validator (regex + classification logic), refusal messages, site-global escape hatch. Mirror the style of Disciplines 6 / 7 / 8 already present.
plugins/stardust/skills/stardust/reference/token-contract.md § Per-section overrides Promote the existing optional pattern to "the canonical section-scope pattern of the four-scope organization." Link to Discipline 11.
plugins/stardust/skills/stardust/reference/data-attributes.md § Why → Downstream consumers Add a paragraph naming data-module as the block primitive for downstream EDS extraction, and section > <element> selectors as the default-content selector pattern. Add a cross-link to Discipline 11.
plugins/stardust/skills/prototype/reference/canon-extraction.md § 2 Compound CSS — Selection rule Re-label as the "GLOBAL: compound utility" group of the four-scope organization. No behavior change; just naming alignment.
plugins/stardust/skills/stardust/reference/migrate-output-format.md § Asset reference shape Add one bullet: "The migrated page's first <style> block follows the four-scope organization per Discipline 11. Downstream converters can rely on the marker comments to extract block CSS mechanically."
plugins/stardust/evals/css-four-scopes/ (NEW, optional) New eval directory with task.md + criteria.json per the format in evals/README.md. Positive fixture: prototype that classifies cleanly. Negative fixture: prototype with unscoped .headline rule that the validator must refuse.
plugins/stardust/skills/prototype/fixtures/four-scope-css-example.html (NEW, optional) Reference fixture mirroring mobile-nav-collapse-example.html's role — shows the canonical organization on a realistic page.

Spec-only changes; no JS code is added. The validator is implemented
by the agent at craft time reading the discipline (same pattern as
Disciplines 6 / 7 / 8 today — those are also agent-implemented, not
code).

Downstream extraction algorithm (for the future EDS converter)

With the discipline in place, the converter is mechanical:

# pseudocode
sections = []
blocks = []
global_rules = []
default_content_rules = []
section_rules_by_name = defaultdict(list)
block_rules_by_name = defaultdict(list)

for marker, name, rules in parse_marker_comments(style_block):
    if marker == "GLOBAL" and name == "tokens":
        global_rules.extend(rules)  # → :root in styles.css
    elif marker == "GLOBAL" and name == "default-content":
        default_content_rules.extend(rules)  # → default styles in styles.css
    elif marker == "GLOBAL" and name == "compound utility":
        global_rules.extend(rules)  # → block-utility classes in styles.css
    elif marker == "SECTION":
        section_rules_by_name[name].extend(rules)  # → .section.<name> { ... }
    elif marker == "BLOCK":
        block_rules_by_name[name].extend(rules)  # → blocks/<name>/<name>.css

# Emit
write("styles/styles.css",
      global_rules + default_content_rules + sectioned(section_rules_by_name))
for name, rules in block_rules_by_name.items():
    write(f"blocks/{name}/{name}.css", rescope(rules, f".{name}"))

No LLM judgment required per rule. ~200 lines of script with regex +
file split + path mapping.

Alternatives considered

Two-scope (global + section only)

Rejected. A converter still has to LLM-judge which rules within a
section belong to a block vs to default content. Block extraction
stays AI-driven. The four-scope version separates blocks from
sections explicitly via [data-module] and is strictly more useful
downstream at the same authoring cost.

CSS-in-JS / Shadow DOM / @scope

Rejected. Stardust's iteration loop depends on a single
self-contained styled HTML file viewable via file://. Shadow DOM
breaks file:// rendering. CSS-in-JS adds runtime. @scope has
limited browser support and adds parser complexity without solving
the "which block does .headline belong to" question.

Apply discipline retroactively to existing prototypes

Rejected. Existing approved prototypes (e.g. lovesac, wasatch
references in the spec) that pass current critique+audit+adapt should
not be invalidated. New craft output applies the discipline; old
output remains valid. Phrasing in the spec: "Discipline 11 fires at
craft time; existing prototypes are grandfathered until re-craft."

Acceptance criteria

  1. proposed-file-shell.md § Required structure includes item 8
    describing the four-scope CSS organization with the marker comment
    format.
  2. prototype/SKILL.md § Craft-time disciplines includes Discipline
    11 with the validator behavior, classification rules, and refusal
    messages.
  3. data-attributes.md cross-references Discipline 11 and names
    data-module as the block primitive.
  4. token-contract.md § Per-section overrides is re-framed as the
    canonical section-scope pattern (not just one optional option).
  5. canon-extraction.md § 2 names the canon.css output as the
    "GLOBAL: compound utility" group.
  6. migrate-output-format.md documents the four-scope organization
    as a property downstream converters can rely on.
  7. (Optional) An eval under evals/css-four-scopes/ that exercises
    both positive and negative fixtures.
  8. npm run validate passes.
  9. The Tessl Skill Review scores ≥ 50% (CONTRIBUTING.md gate).

Out of scope

  • Implementing the future EDS converter. This issue specifies only
    the upstream contract.
  • Refactoring existing approved prototypes to the new discipline.
  • Adding new data-* attributes. The proposal uses the existing
    v2.1 vocabulary verbatim.
  • Changing the :root token contract. The :root block is unchanged
    in shape and lives in the GLOBAL: tokens group by definition.

Open questions for the maintainer

  1. Discipline numbering. Is "Discipline 11" still the next free
    slot at HEAD, or did another PR land disciplines 11+ in the
    interim? Confirm before drafting the PR.
  2. data-template block scope. Should page-template-level CSS
    ([data-template="article"] { ... }) classify as a BLOCK (per
    this proposal) or as a separate TEMPLATE scope? Current
    proposal folds them together because both extract 1:1 to a
    downstream block file; a maintainer may prefer the split.
  3. canon.css authoring vs the discipline. canon.css is currently
    "compound CSS" per canon-extraction.md § 2. The discipline
    labels its contents as GLOBAL: compound utility. Is the rename
    merely cosmetic, or does the maintainer want canon.css to also
    carry section / block markers for its own contents (e.g. if a
    canon module's CSS lives in canon.css)?
  4. Eval scope. Is a single positive+negative eval sufficient, or
    does the maintainer want per-scope coverage (one eval per scope
    type) in line with the granular eval pattern in evals/?
  5. Retroactivity exception for --publish-sample. The
    --publish-sample flow (per
    skills/prototype/reference/publish-sample.md) submits a
    prototype to the showcase. Should published-sample prototypes
    already in the showcase be back-filled to the new organization,
    or only new submissions?

For future agents picking this up

If you are an agent (Claude or otherwise) opening this issue in a
later session, here's the minimum you need to do before drafting
the PR:

  1. Re-read this issue end-to-end. It's self-contained — every
    architectural reason and design decision is here.

  2. Verify state at HEAD. stardust evolves fast (PR stardust: ia-fidelity axis + 10 non-slop disciplines + approval fold-back #131 landed
    10 disciplines in one sweep; PRs feat(stardust): migrate produces self-contained zip-and-deploy bundle #132 / fix(stardust): make migrate output genuinely zip-and-deploy portable #133 reshaped the
    migrate bundle). Before writing the PR:

    • git fetch origin && git log --oneline origin/main -- plugins/stardust/ | head -20
    • Read plugins/stardust/skills/prototype/SKILL.md § Craft-time
      disciplines to confirm the next free discipline number.
    • Read plugins/stardust/skills/prototype/reference/proposed-file-shell.md
      to confirm the "Required structure" section still has the
      numbered list shape this proposal extends.
    • Verify plugins/stardust/skills/stardust/reference/data-attributes.md
      still uses data-module as the block primitive (not renamed).
    • Check plugins/stardust/tile.json for current version (this
      issue was authored against v0.7.1; if >= v0.8.0 at PR
      time, expect refactors that may shift the file shape).
  3. Confirm no overlapping PR is open. Search:
    gh pr list --repo adobe/skills --search "css scope OR section-scoped OR block-scoped OR Discipline 11".

  4. Branch off main with conventional name.
    git checkout -b feat/stardust-four-scope-css.

  5. Make edits per the Implementation plan table above. File-by-file.

  6. Commit using conventional commits format:

    feat(stardust): four-scope CSS organization for mechanical block extraction
    
    <body explaining what changed and why; reference this issue>
    
    Refs #<this-issue-number>
    
  7. Open PR with --label ai-generated per CONTRIBUTING.md if
    the PR is AI-drafted. Reference this issue in the body. PR title
    should mirror the commit subject. Drop a note in
    #agentskills Slack per CONTRIBUTING.md.

  8. Verify gates. npm run validate locally; Tessl Skill Review
    runs on CI. Tessl Evals require an empty eval:-prefixed commit
    and aren't available to fork PRs (CONTRIBUTING.md § Requesting Evals).

  9. Honest signal. If the maintainer's response is "we already
    have a different plan for this," abandon this PR gracefully and
    close the issue with a note pointing at their plan.


Drafted with assistance from Claude (Anthropic) on 2026-05-22.
Per CONTRIBUTING.md, the corresponding PR should carry the
ai-generated label.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions