# RFC 0001 — Migrate `@bytelyst/design-tokens` to DTCG v3

| Status    | Draft            |
| --------- | ---------------- |
| Author    | Cascade          |
| Created   | 2026-05-27       |
| Tracks    | ROADMAP TODO #4  |
| Reviewers | _to be assigned_ |

## Summary

Migrate `packages/design-tokens/tokens/bytelyst.tokens.json` from the
current bespoke schema to the **W3C Design Tokens Community Group
(DTCG) Format Module v3**. Re-emit all generated outputs
(CSS / TS / Kotlin / Swift / per-product CSS) from the new schema with
**no observable change to runtime CSS variable names**.

## Motivation

1. **Designer tooling.** Figma-Tokens (now "Tokens Studio") and
   Specify both speak DTCG natively. Today designers cannot round-trip
   tokens with engineering — the JSON schema is bespoke.
2. **Long-term portability.** DTCG is becoming the lingua franca of
   design systems (Adobe Spectrum, Salesforce Lightning, GitHub Primer
   are all migrating).
3. **Three-tier semantics.** DTCG `$type` + reference syntax (`{path}`)
   gives us a clean way to express the **reference → semantic →
   component** layering documented in
   `learning_ai_uxui_web/docs/ROADMAP_2026.md` §3.

## Current schema (excerpt)

```json
{
  "meta": { "name": "ByteLyst Design Tokens", "version": "1.1.0" },
  "color": {
    "palette": { "neutral": { "0": "#FFFFFF", "50": "#F6F8FC", ... } },
    "semantic": {
      "dark":  { "bgCanvas": "#06070A", ... },
      "light": { "bgCanvas": "#F8F9FC", ... }
    }
  },
  "spacing": { "0": 0, "1": 4, "2": 8, ... },
  "radius":  { "xs": 4, "sm": 6, ... },
  "typography": { "fontSize": { "xs": 12, ... } },
  ...
}
```

## Proposed DTCG v3 schema (excerpt)

```json
{
  "$description": "ByteLyst design tokens — DTCG v3",
  "$schema": "https://design-tokens.github.io/community-group/format/",

  "color": {
    "neutral": {
      "0": { "$value": "#FFFFFF", "$type": "color" },
      "50": { "$value": "#F6F8FC", "$type": "color" },
      "950": { "$value": "#06070A", "$type": "color" }
    },
    "brand": {
      "blue": { "$value": "#5A8CFF", "$type": "color" },
      "coral": { "$value": "#FF6E6E", "$type": "color" }
    },
    "semantic": {
      "bgCanvas": {
        "$value": "{color.neutral.950}",
        "$type": "color",
        "$description": "Page background — dark"
      }
    }
  },

  "spacing": {
    "1": { "$value": "4px", "$type": "dimension" },
    "2": { "$value": "8px", "$type": "dimension" }
  },

  "radius": {
    "xs": { "$value": "4px", "$type": "dimension" }
  },

  "component": {
    "button": {
      "padding-x": { "$value": "{spacing.4}", "$type": "dimension" },
      "bg": { "$value": "{color.semantic.accent}", "$type": "color" }
    }
  }
}
```

### Key changes

| Aspect         | Today                    | DTCG v3                               |
| -------------- | ------------------------ | ------------------------------------- |
| Value wrapper  | bare value               | `{ "$value": ..., "$type": ... }`     |
| Type metadata  | inferred from JS path    | explicit `$type`                      |
| References     | manual JS lookup         | `{path.to.token}` syntax              |
| Light/dark     | parallel sibling objects | DTCG **token sets** (per-theme files) |
| Documentation  | none                     | `$description` per token              |
| Component tier | doesn't exist            | new top-level `component` group       |

## Implementation plan

The migration is split into **4 PRs** to keep each one reviewable.

### PR 1 — Add converter + dual-emit (no behaviour change)

- Add `scripts/convert-legacy-to-dtcg.ts` that reads the bespoke JSON
  and emits `bytelyst.tokens.dtcg.json`.
- Modify `scripts/generate.ts` to accept either schema and produce
  byte-identical output.
- Add a vitest case asserting the two emitters produce identical CSS
  byte-for-byte.

### PR 2 — Flip source of truth to DTCG JSON

- Delete the legacy JSON.
- Update Figma-Tokens plugin documentation + commit a `.tokens.config.json`
  for round-trip.

### PR 3 — Introduce the component tier

- New `component.*` group in the DTCG JSON.
- Generator emits new `--bl-{component}-{slot}` variables.
- Audit existing hardcoded values in `@bytelyst/ui` and replace with
  the new component tokens.

### PR 4 — Multi-theme via DTCG token sets

- Split the single JSON into:
  - `tokens/global.tokens.json` (reference + semantic core)
  - `tokens/themes/dark.tokens.json` (light/dark overrides)
  - `tokens/themes/light.tokens.json`
  - `tokens/brands/lysnrai.tokens.json` (brand-layer overrides — Wave 7)
- Generator composes per-output set.

## Backward compatibility

- All existing `--ml-*` and `--bl-*` CSS variable names are preserved.
- All TS/Kotlin/Swift exported identifiers preserved.
- Consumer apps require zero changes through PRs 1–3.
- PR 4 introduces multi-set composition but the **default** still
  produces the same `tokens.css`.

## Risks

| Risk                                             | Severity | Mitigation                            |
| ------------------------------------------------ | -------- | ------------------------------------- |
| Generator divergence between schemas during PR 1 | High     | Byte-identical assertion test         |
| Figma-Tokens plugin version churn                | Med      | Pin plugin version in docs/THEMING.md |
| Designer learning curve                          | Med      | One-pager + paired migration session  |
| Reference cycles in DTCG (`{a}` → `{b}` → `{a}`) | Low      | Generator validates DAG before emit   |

## Alternatives considered

- **Style Dictionary** — Amazon's tool. Heavier, more opinionated.
  Rejected: we already have a working generator; switching tools is a
  larger change than switching schemas.
- **Stay on bespoke JSON** — Rejected: blocks designer round-trip
  and locks us out of the ecosystem.

## Estimate

~1 person-week for PRs 1–3, +1 pw for PR 4 = **2 pw total**.

## Open questions

- Do we adopt the DTCG **2024 candidate** or wait for the **2026
  final**? Candidate is stable enough; recommend candidate now,
  re-verify when final.
- Where do per-product brand layers live — separate packages
  (`@bytelyst/brand-lysnrai`) or subpaths
  (`@bytelyst/design-tokens/brands/lysnrai`)? Recommend subpaths
  pre-Wave 7, separate packages from Wave 7.
