# @servicetitan/hammer-token

Design token system for the Hammer design system, built with [Style Dictionary v5](https://styledictionary.com/) and the [Design Tokens Community Group (DTCG) format](https://www.designtokens.org/tr/2025.10/format/).

## Overview

This package transforms design tokens from JSON source files into multiple output formats for use across web applications. It generates JavaScript modules, SCSS variables, CSS utility classes, and TypeScript definitions to support both runtime and build-time token consumption.

## Token Structure

Tokens follow the [Design Tokens Community Group (DTCG) format](https://www.designtokens.org/tr/2025.10/format/).

### Primitive Color Token

Primitive tokens have a static value and declare their Figma variable scope via `$extensions["com.figma.scopes"]`. An empty array means no scope restriction (the variable appears in all pickers).

```json
{
  "$type": "color",
  "$value": "#ffffff",
  "$extensions": {
    "com.figma.scopes": []
  }
}
```

### Semantic Color Token (with dark mode)

Semantic tokens reference primitives and include light/dark appearance variants under `$extensions.appearance`. Both `light` and `dark` repeat the `$type` and use `$value`.

```json
{
  "$type": "color",
  "$value": "{status.color.info}",
  "$extensions": {
    "appearance": {
      "light": {
        "$type": "color",
        "$value": "{status.color.info}"
      },
      "dark": {
        "$type": "color",
        "$value": "{status.color.info}"
      }
    },
    "com.figma.scopes": ["STROKE_COLOR"]
  }
}
```

### Composite Color Token (color + alpha)

Used for colors that need an alpha channel. The `color` field is a reference to a primitive token; the build system emits `color-mix()` in CSS output to preserve the live primitive CSS variable.

```json
{
  "$type": "color",
  "$value": {
    "color": "{color.neutral.900}",
    "alpha": 0.08
  },
  "$extensions": {
    "appearance": {
      "light": {
        "$type": "color",
        "$value": { "color": "{color.neutral.900}", "alpha": 0.08 }
      },
      "dark": {
        "$type": "color",
        "$value": { "color": "{color.neutral.0}", "alpha": 0.08 }
      }
    },
    "com.figma.scopes": ["EFFECT_COLOR"]
  }
}
```

### Dimension Token

Dimension tokens (sizes, radii, breakpoints) use a nested object for `$value` with `value` and `unit` fields.

```json
{
  "$type": "dimension",
  "$value": {
    "value": 0.375,
    "unit": "rem"
  },
  "$extensions": {
    "com.figma.scopes": ["CORNER_RADIUS"]
  }
}
```

### The $root Pattern

The `$root` pattern allows a token group to have a default value while still containing sub-tokens (e.g. interactive states). A node with `$value` cannot normally also have children; `$root` bridges this by placing the default value one level deeper.

```json
"primary": {
  "$root": {
    "$type": "color",
    "$value": "{color.blue.500}"
  },
  "hover": {
    "$type": "color",
    "$value": "{color.blue.600}"
  },
  "active": {
    "$type": "color",
    "$value": "{color.blue.700}"
  }
}
```

You can reference the group directly as `{background.color.primary}` — the build system resolves it to the `.$root` token automatically. The `$root` segment is stripped from all generated names (e.g. `--a2-background-color-primary`, not `--a2-background-color-primary-root`).

## Token Naming Conventions

1. **Path to name**: JSON object paths become hyphen-separated names (CSS/SCSS) or PascalCase (JS exports).
2. **`$root` segments**: Stripped from the path in all outputs.
3. **State suffixes**: State names (`hover`, `active`) are included as-is in the hyphenated name and capitalized in PascalCase exports.

## Build Output (`/build/web`)

All generated CSS variable names use the `a2-` prefix by default (e.g. `--a2-background-color-primary`).

The build process executes three sequential Style Dictionary builds — **Primitive → Theme (semantic) → Component** — and writes output to `/build/web`:

```
build/web/
├── index.js              # Main entry point
├── index.d.ts            # TypeScript definitions
├── types.d.ts            # Core types (TokenObj, Token)
└── core/
    ├── primitive.js              # Primitive tokens (JS)
    ├── primitive.scss            # Primitive tokens (SCSS)
    ├── primitive-variables.scss  # Primitive tokens map (SCSS)
    ├── primitive.d.ts            # Primitive tokens (TypeScript)
    ├── semantic.js               # Semantic tokens (JS)
    ├── semantic.scss             # Semantic tokens (SCSS)
    ├── semantic-variables.scss   # Semantic tokens map (SCSS)
    ├── semantic.d.ts             # Semantic tokens (TypeScript)
    ├── component.js              # Component tokens (JS)
    ├── component.scss            # Component tokens (SCSS)
    ├── component-variables.scss  # Component tokens map (SCSS)
    ├── component.d.ts            # Component tokens (TypeScript)
    ├── index.js                  # Core index
    ├── index.d.ts                # Core TypeScript definitions
    └── css-utils/                # CSS utility classes
```

### File Types

#### `*.js` Files

ES6 named exports with static resolved values. Tokens with dark variants include an `extensions.appearance.dark.value` property.

```javascript
import { BackgroundColorPrimary } from "@servicetitan/hammer-token/build/web/core/semantic";
// { value: "#0265dc", extensions: { appearance: { dark: { value: "#78bbfa" } } } }
```

> **Caution**: Values are resolved at build time and do not respond to CSS variable changes at runtime. Use `*.scss` files when dynamic theming is needed.

#### `*.scss` Files

SCSS variables with `var(--a2-name, fallback)` syntax, supporting recursive reference chains and `light-dark()` for dark mode.

#### `*-variables.scss` Files

SCSS maps (`$light`, `$dark`, `$nonColor`) used by `ThemeProvider.module.scss`.

#### `.d.ts` Files

Auto-generated TypeScript definitions using the unified `TokenObj` type.

```typescript
import { TokenObj } from "../types";
export declare const ButtonPrimaryBackgroundColor: TokenObj;
```

## Utility Files (`src/utils/`)

### `token-helpers.js`

Core helpers for token value extraction, reference resolution, and CSS fallback building. Key points:

- **Smart `$root` resolution**: `resolveReference` automatically retries with `.$root` appended when a reference points to a group rather than a leaf token.
- **Memoized token map**: `buildTokenMap` caches an O(1) name→token `Map` per dictionary instance via `WeakMap`.
- **Composite colors**: `{ color, alpha }` tokens emit `color-mix(in srgb, var(--a2-name, #hex) N%, transparent)` in CSS contexts, preserving the live primitive CSS variable reference. Static contexts (JS exports, SCSS maps) fall back to hex8.
- **`buildFallbackWithRefs`**: Main entry point used by all formats. Builds recursive `var(--name, ...)` chains and inserts `light-dark()` at the level where light and dark values first diverge.

### `sd-transforms.js`

Registers DTCG transforms and the `dtcg` transform group:

- **`dtcg/set-token-names`** _(preprocessor)_: Pre-sets `token.name` to the full hyphenated path before transforms run. Prevents false name-collision warnings for tokens with unresolvable references that skip the transform pipeline.
- **`dtcg/name`**, **`dtcg/value`**, **`dtcg/cubic-bezier`**, **`dtcg/color-opacity`**: Path normalization, dimension formatting, cubicBezier→CSS, and composite color pass-through.

### `css-utils-format-utils.js`

Pure functions that generate CSS utility class strings — `generateBorderClasses`, `generateColorClasses`, `generateFontClasses`, `generateSpacingClasses`. Each accepts a pre-resolved `value` (which may already contain a `light-dark()` expression built by `buildFallbackWithRefs`) and an optional `prefix`.

### `sd-formats.js`

Registers all custom Style Dictionary output formats:

- `custom/scss-variables` — SCSS variables with `var()` + `light-dark()` fallbacks.
- `custom/scss-variables-map` — SCSS maps (`$light`, `$dark`, `$nonColor` / `$token`) for `ThemeProvider.module.scss`.
- `custom/es6-variable` — ES6 named exports with static resolved values.
- `custom/CSSVariables` — `:root {}` block with CSS custom properties.
- `custom/CSSUtils/{prefix}All|Borders|Colors|Fonts|Spacing` — CSS utility class files (registered for both `""` and `"a2-"` prefixes).

The `All` format uses a single-pass loop over `allTokens` (one `buildFallbackWithRefs` call per token for the light value, a second only when a dark variant exists) feeding three output buckets. The dedicated `Borders`, `Colors`, `Fonts`, and `Spacing` formats use separate filter+map pipelines.

## Figma Sync (`src/utils/figma/`)

Script to sync design tokens to Figma variables using the Figma REST API.

### Features

- Syncs all tokens (primitives, semantic, component) to a single Figma variable collection
- Creates Light and Dark modes for appearance variants
- Resolves primitive token references to actual values
- Creates variable aliases for semantic/component tokens that reference other tokens
- Handles `$root` pattern by creating separate variables for root and state variants
- Uses path-based naming (e.g., `color/blue/500`, `background/color/primary`)
- Applies **Figma variable scopes** so variables only appear in the relevant UI pickers
- Only updates variables when values change; still sends scope-only updates when scopes need to be applied
- Handles rate limiting with automatic retries

### Directory

- **`auth.js`** - Authentication and configuration
- **`constants.js`** - Figma file and collection constants
- **`errors.js`** - Custom error classes
- **`get-token.js`** - OAuth2 token helper script
- **`token-parsing.js`** - Token file loading, parsing, and flattening
- **`token-resolution.js`** - Reference resolution and dependency sorting
- **`token-conversion.js`** - Converting tokens to Figma format
- **`figma-api.js`** - Figma API requests, collections, and modes
- **`sync-primitives.js`** - Primitive token sync logic
- **`sync-semantic.js`** - Semantic token sync logic
- **`sync-components.js`** - Component token sync logic
- **`sync-orchestration.js`** - Theme sync orchestration
- **`sync-main.js`** - Main entry point and CLI
- **`utils.js`** - Shared utility functions

### Variable Scopes

Figma variables can be scoped so they only show in certain property pickers (e.g. a color variable only in stroke color, or a dimension only in corner radius).

Scopes are defined directly on each token in `$extensions["com.figma.scopes"]` — an array of Figma scope names (e.g. `SHAPE_FILL`, `STROKE_COLOR`, `CORNER_RADIUS`, `FONT_SIZE`). The sync reads this field when creating or updating variables; scopes are deduplicated before sending.

To change which pickers a token appears in, edit `$extensions["com.figma.scopes"]` in the token file.

### Commands

- `pnpm figma:sync` - Sync all tokens to Figma (default file)
- `pnpm figma:sync:file <fileKey>` - Sync tokens to a specific Figma file
- `pnpm figma:test` - Test Figma API access (default file)
- `pnpm figma:test:file <fileKey>` - Test access to a specific Figma file
- `pnpm figma:validate` - Validate token files without syncing
- `pnpm figma:get-token` - Interactive OAuth2 token helper script

**CLI Options:** `--file-key`/`-f`, `--dry-run`, `--verbose`/`-v`, `--full`, `--help`/`-h`

### Authentication

Supports Personal Access Tokens (PAT) and OAuth2 refresh tokens. Configure via environment variables or `.figma-config.json` (repo root, gitignored).

**Environment variables:**

| Variable              | Description                |
| --------------------- | -------------------------- |
| `FIGMA_ACCESS_TOKEN`  | PAT                        |
| `FIGMA_CLIENT_ID`     | OAuth2 client ID           |
| `FIGMA_CLIENT_SECRET` | OAuth2 client secret       |
| `FIGMA_REFRESH_TOKEN` | OAuth2 refresh token       |
| `FIGMA_FILE_KEY`      | Optional file key override |

**`.figma-config.json`:**

```json
{
  "accessToken": "figd_...",
  "clientId": "your-oauth-client-id",
  "clientSecret": "your-oauth-client-secret",
  "refreshToken": "your-oauth-refresh-token"
}
```

Environment variables take priority over the config file. OAuth2 access tokens are cached in memory and refreshed automatically on expiry (~1 hour).

**GitHub Actions:**

```yaml
- name: Sync tokens to Figma
  env:
    FIGMA_CLIENT_ID: ${{ secrets.FIGMA_CLIENT_ID }}
    FIGMA_CLIENT_SECRET: ${{ secrets.FIGMA_CLIENT_SECRET }}
    FIGMA_REFRESH_TOKEN: ${{ secrets.FIGMA_REFRESH_TOKEN }}
  run: pnpm figma:sync
```

**Dry-run mode** (`--dry-run`): validates and reads existing variables without writing. Reports counts of what would be created/updated/skipped.

**Troubleshooting:**

- **Auth errors**: Verify credentials. For OAuth2, re-authenticate if refresh token has expired.
- **"Authentication Failed - Please Re-Login"**: OAuth2 refresh token expired or revoked.
- **403 Forbidden**: Token may lack organization-level access.
- **File not found**: Check the file key and access permissions.
- **Rate limiting**: Retried automatically with exponential backoff.
- **Refresh token rotation**: Update credentials with the new token logged to stdout.
- Run `pnpm figma:test` to diagnose issues before syncing; `pnpm figma:validate` to check token structure without API calls.

## Development

### Adding New Tokens

1. Add token JSON files to `src/global/primitive/` (primitives) or `src/theme/core/` (semantic/component)
2. Run `pnpm build` to regenerate output files
3. Import tokens in your code using the generated files

### Modifying Build Output

- **Transforms**: Edit `src/utils/sd-transforms.js`
- **Formats**: Edit `src/utils/sd-formats.js`
- **Build Configs**: Edit `src/utils/sd-build-configs.js`
- **CSS Utils**: Edit `src/utils/css-utils-format-utils.js`
