# Notification — Figma alignment fix spec (X.4.2 batch)

## Inputs (✓ all read)

- figma cache: `figma-data/normalized/figma-mcp-cache/notification.tsx` (542 lines, 7 status × 2 theme = 14 rendered branches)
- canonical: `src/components/Notification/Notification.vue` (332 lines, ~170 CSS lines)
- figma styles: `figma-styles.json` (Roboto/16 pop title + Roboto/14 body + L3 shadow)
- aliases: `token-aliases.ts`, `prop-aliases.md`, `icon-aliases.ts` (incl. COMPONENT_ICON_OVERRIDES)
- variables: `variables.css`

Variant axes (from figma `NotificationProps`):

- `status` axis: `"side pop" | "error" | "info" | "quick confirm" | "warning" | "pop confirm" | "secondary warning"` (7 values)
- `theme` axis: `"dark" | "light"` (2 values)

Width buckets per status (figma 真源):

- `side pop` → 320px, gap 12px
- `quick confirm` → 280px, gap 24px
- `error / info / warning / secondary warning / pop confirm` → 480px, gap 40px

## Mismatches by dimension

### Spacing mismatches

- Figma root `px-[24px] py-[16px]` (horizontal 24, vertical 16) = `var(--sp-l)` / `var(--sp-m)`; canonical `.notif { padding: 16px 24px }` raw values → **Fix:** `padding: var(--sp-m) var(--sp-l)`.
- Figma root gap (per status):
  - 480px wide statuses: `gap-[40px]` = `var(--sp-xxl)`; canonical `.notif { gap: 40px }` raw → **Fix:** `gap: var(--sp-xxl)`.
  - `quick confirm`: `gap-[24px]` = `var(--sp-l)`; canonical `.notif--compact { gap: 24px }` raw → **Fix:** `gap: var(--sp-l)`.
  - `side pop`: `gap-[12px]` = `var(--sp-s)`; canonical `.notif--side { gap: 12px }` raw → **Fix:** `gap: var(--sp-s)`.
- Figma `Notification_content` outer (icon + body) `gap-[16px]` = `var(--sp-m)`; canonical `.notif__main { gap: 16px }` raw → **Fix:** `gap: var(--sp-m)`.
- Figma `content` inner (title row + desc) `gap-[12px]` = `var(--sp-s)`; canonical `.notif__body { gap: 12px }` raw → **Fix:** `gap: var(--sp-s)`.
- Figma `title` row (title text + close icon) `gap-[16px]` = `var(--sp-m)`; canonical `.notif__header { gap: 16px }` raw → **Fix:** `gap: var(--sp-m)`.
- Figma `comfirm` row (cancel + confirm) `gap-[16px]` = `var(--sp-m)`; canonical `.notif__actions { gap: 16px }` raw → **Fix:** `gap: var(--sp-m)`.
- Figma `comfirm` row uses `pl-[264px]` (264px left padding) — this is figma 真源 hard right-align via large padding. Canonical uses `justify-content: flex-end` instead — semantically equivalent and more responsive. **No fix needed** (canonical equivalent ✓).
- Figma button `Button/dark M` / `Button/light M` `h-[32px] px-[16px] py-[10px] rounded-[4px]` = `var(--sp-m)` / 10px (no scale token for 10) / `var(--r-s)`. Canonical `.notif__button { height: 32px; padding: 0 16px; border-radius: 4px }` → **Fix:** `padding: 10px var(--sp-m)` (vertical 10px is off-scale — log as needs design review or tolerate as button-internal value), `border-radius: var(--r-s)`. Height 32px → keep raw (off-scale; Button is BRIDGE-Tier3 dep anyway).
- Figma close icon size `size-[16px]`; canonical `.notif__close { width: 16px; height: 16px }` → off-scale; OK as is (icon size is fixed per design).

### Color mismatches

- Figma 480px status root bg:
  - dark theme → `var(--color-type/background/layer_4,#353535)` = `--bg-layer4`. Canonical `.notif--dark { background: var(--notification-bg-dark) }` where `--notification-bg-dark: var(--color-grey-10)` (= `#353535` ✓ value match) → **Issue:** alias chain points to grey-10 not bg-layer4 alias name. **Decision:** keep `--notification-bg-dark` (already documented as theme-stable component-variant token in variables.css comment), but verify semantic — figma uses bg-layer4 for dark, **not** grey-10 directly. Since `--bg-layer4` in dark = `#353535` = `--color-grey-10` 同值, current canonical is value-correct but alias-mismatched. **Fix (low priority):** redefine `--notification-bg-dark: var(--bg-layer4)` (not grey-10) to track the figma alias chain rather than the underlying hex. Since canonical comment says "theme-stable, dark/light 同值", and `--bg-layer4` is theme-aware, this would break theme-stability. **Resolution:** keep current definition (`--color-grey-10`) — figma 真源 uses bg-layer4 alias **but** the variant is theme-pinned (Notification component has its own theme axis), so a theme-stable underlying value is correct. Document the rationale (already in variables.css). **No code fix.**
  - light theme → `var(--color-type/background/layer_2,#f8f8f8)` = `--bg-layer2`. Canonical `.notif--light { background: var(--notification-bg-light) }` where `--notification-bg-light: var(--color-white)` (= `#ffffff`). **MISMATCH:** figma 真源 light theme uses `--bg-layer2` (`#f8f8f8`), canonical uses `#ffffff`. → **Fix:** `--notification-bg-light: var(--color-grey-2)` (= `#f8f8f8`, theme-stable) OR redefine `--notification-bg-light: var(--bg-layer2)` if accepting theme-aware. Recommend **theme-stable** to match component-variant pattern. Update variables.css comment accordingly.
- Figma 480px status border:
  - dark theme → `var(--color-type/line/light-divider,#434343)` = `--line-light` ✓ canonical uses `var(--line-light)`.
  - light theme → `var(--color-type/line/light-divider,#ccc)` = `--line-light` (light theme value `#cccccc`) ✓.
  - Canonical `.notif { border: 1px solid var(--line-light) }` figma is `border-[1.2px]` (1.2px not 1px). → **Fix (minor):** `border-width: 1.2px` to match figma 真源, OR accept 1px as design tolerance (1.2px is off-scale and Vue/CSS rendering will round to 1 anyway on 1× DPR). **Log as design review** — likely 1px is fine.
- Figma title text:
  - dark → `var(--color-type/text/text_1,#f8f8f8)` = `--text-body`.
  - light → `var(--color-type/text/text_1,#141414)` = `--text-body` (theme-aware, light value `#141414`).
  - Canonical `.notif__title` has no `color` rule → inherits from `.notif--dark` / `.notif--light` `color: var(--text-body)` ✓.
- Figma desc text:
  - dark → `var(--color-type/text/tips,#9e9e9e)` = `--text-tips`.
  - light → `var(--color-type/text/tips,#595959)` = `--text-tips` (light value).
  - Canonical `.notif--dark .notif__desc { color: var(--text-tips) }` and `.notif--light .notif__desc { color: var(--text-tips) }` ✓ — but redundant (both set same token); could collapse to `.notif__desc { color: var(--text-tips); }`.
- Figma close icon (`Union`) — figma renders via `<img>` with stroke baked into SVG; canonical uses `<Icon name="close">` colored via `.notif__close { color: var(--icon-default) }`. Figma 真源 close icon stroke color appears as `--icon-default` (grey) for both themes ✓ acceptable.
- Figma confirm button (`warning / secondary warning / info` variants):
  - dark → `bg-[#33ab4f]` (raw hex) — this is **not** a registered token. Closest alias = `--brand-hover` (#41c760 dark) or `--brand` (#2fb54e dark). `#33ab4f` doesn't match either. **Likely figma intent = `--brand`** but variant designed before token unification. **Fix:** use `var(--brand)` in canonical (already doing this via `.notif__button--confirm { background: var(--brand) }` ✓). Document as figma-data-drift; flag in design review.
  - light → `bg-[var(--ux/brand/brand,#299f45)]` = `--brand` ✓ canonical match.
- Figma confirm button text:
  - dark → `var(--color-type/text/text_1,#f8f8f8)` = `--text-body`. Canonical uses `var(--text-primary-btn)` which equals `#ffffff` for both themes. **MISMATCH:** figma 真源 dark uses `--text-body` (= `#f8f8f8`), canonical uses primary-btn (`#ffffff`). Tiny diff; semantically `--text-body` is more correct since button-on-brand is body-light context. → **Fix:** `.notif--dark .notif__button--confirm { color: var(--text-body) }` (or keep primary-btn — design call).
  - light → `var(--ux/grey/grey-1-#ffffff,white)` = `--color-white` ≈ `--text-primary-btn` ✓.
- Figma danger button (`pop confirm / quick confirm`):
  - dark → `bg-[#ec5050]` raw hex. Closest = `--red` (`#ea4233` dark) or `--red-hover` (`#ed5b4e` dark). `#ec5050` is between — **likely intended as `--red-hover`**. Canonical `.notif__button--danger { background: var(--red) }`. → **Fix candidate:** swap to `var(--red-hover)`. **Decision:** canonical use of `--red` is closer to default red intent; figma `#ec5050` is design drift. Keep `--red`, log discrepancy.
  - light → `var(--ux/red/default,#dc2717)` = `--red` ✓ canonical match.
- Figma cancel button:
  - dark border → `var(--ux/grey/grey-9-#434343,#434343)` = `--color-grey-9` → maps to `--line-light` (dark) ✓ canonical uses `--line-light`.
  - light border → `var(--ux/grey/grey-4-#dbdbdb,#dbdbdb)` = `--color-grey-4` → maps to `--line-deep` in light. Canonical `.notif--light .notif__button--secondary { border-color: var(--line-deep) }` ✓.
  - dark text → `var(--ux/grey/grey-5-#cccccc,#ccc)` = `--text-2` (dark `#cccccc`) ✓.
  - light text → `var(--ux/grey/grey-9-#434343,#434343)` = `--text-2` (light `#434343`) ✓.

### Typography mismatches (use `--text-style-*` composite tokens or atomic)

- Figma title `Roboto/16 pop title` (size 16, line-height 24, weight 600) — maps to `--text-style-pop-title`. Canonical `.notif__title { font-size: 16px; line-height: 24px; font-weight: 600 }` hardcoded → **Fix:** `font: var(--text-style-pop-title)`.
- Figma desc + quick-copy + button + side-pop body all use `Roboto/14 body` — maps to `--text-style-body`. Canonical:
  - `.notif__desc, .notif__quick-copy { font-size: 14px; line-height: 21px }` → **Fix:** `font: var(--text-style-body)`.
  - `.notif__button { font-size: 14px; line-height: 21px }` → **Fix:** `font: var(--text-style-body)`.
- Figma title `font-['Roboto:SemiBold']` + canonical `font-weight: 600` ✓ matches `--text-style-pop-title`.
- Figma description `font-['Roboto:Regular']` + canonical no explicit weight → inherits 400 ✓ (or `--font-weight-regular`).

### Effect mismatches (use `--shadow-l*` / `--mask-overlay`)

- Figma root drop-shadow: `drop-shadow-[0px_6px_8px_rgba(0,0,0,0.08),0px_9px_14px_rgba(0,0,0,0.05),0px_12px_24px_rgba(0,0,0,0.03)]` — values: 6/8/0.08, 9/14/0.05, 12/24/0.03.
- figma-styles.json declares `L3 shadow` = `0 12px 48px 16px #00000008, 0 9px 28px 0 #0000000d, 0 6px 16px -8px #00000014`. **Note:** figma cache numbers (6/8, 9/14, 12/24) DO NOT match L3 (12/48/16, 9/28/0, 6/16/-8). The figma cache uses `drop-shadow()` filter (CSS) which omits spread, while L3 is `box-shadow` with spread. The 真源 STYLE is L3; cache is a CSS-filter approximation losing spread.
- Canonical uses inline `box-shadow: 0 12px 48px 16px rgb(0 0 0 / 3%), 0 9px 28px 0 rgb(0 0 0 / 5%), 0 6px 16px -8px rgb(0 0 0 / 8%)` — **value-identical to L3** but written long-form, not via token. → **Fix:** `box-shadow: var(--shadow-l3)`.

### Composition mismatches (canonical import vs figma data-name)

- Figma `data-name="icon/Edit/Close"` → `<Icon name="close">` per icon-aliases.ts. Canonical uses `<Icon name="close">` ✓.
- Figma `data-name="icon/Message/Error 4"` (warning + light/dark) → `<Icon name="message/error-4">` per ICON_ALIAS_TO_FIGMA_NAME + `COMPONENT_ICON_OVERRIDES.Notification.warning = 'icon/Message/Error 4'`. Canonical `iconMap.warning = 'message/error-4'` ✓.
- Figma `data-name="icon/Message/warning 2"` (secondary warning) → canonical `iconMap['secondary warning'] = 'message/warning-2'` ✓.
- Figma `data-name="icon/Message/Error 2"` (error) → canonical `iconMap.error = 'status-error'` (alias to `icon/Message/Error 2` via icon-aliases.ts) ✓.
- Figma `data-name="icon/Message/Info 2"` (info) → canonical `iconMap.info = 'status-info'` ✓.
- Figma `data-name="Button/dark M"` / `Button/light M` → BRIDGE-Tier3 deferred (`<Button>`). Canonical uses native `<button class="notif__button">` ✓ acceptable as documented blocker (per prop-aliases.md 表 C).

### Variant coverage gaps (per axis value)

- `status=warning` (dark + light): ✓ canonical via `iconMap.warning` + default layout (icon + body + actions). 480px wide.
- `status=secondary warning`: ✓ canonical via `iconMap['secondary warning']` + same layout. **Issue:** canonical sets `notif--warning` class for `secondary warning` (line 104) — name collision with `status=warning`. Both render same icon position but icon glyph differs. Actually canonical class name for secondary-warning becomes `notif--secondary-warning` from the modifier loop (line 100), AND adds `notif--warning` (line 104). The duplicate `notif--warning` class for secondary warning seems intentional for shared styling but is **misleading naming**. → **Fix:** drop the `notif--warning` class addition for `secondary warning`; rely on `notif--secondary-warning` and shared rules without the alias.
- `status=error` (dark + light): ✓ canonical via `iconMap.error`. Has `.notif--error .notif__icon { color: var(--red) }` ✓ — but figma error icon glyph (Error 2) is already red in SVG asset; if `<Icon>` is monochromatic this rule supplements correctly.
- `status=info` (dark + light): ✓ canonical via `iconMap.info`. Has `.notif--info .notif__icon { color: var(--blue) }` ✓.
- `status=pop confirm` (dark + light): ✓ canonical drops icon (`showsIcon=false` when isPopConfirm), keeps title + desc + actions. Figma layout: `flex flex-col gap-[12px] items-end` (no icon, items-end alignment) → canonical `.notif__main` would still render but `showsIcon=false` skips icon. **Issue:** canonical `flex-direction: row` for `.notif__main` means body fills horizontal space — but figma puts title row at top + desc below at full width. Since `showsIcon=false` removes the icon, body becomes full-width ✓. **Coverage OK.**
- `status=quick confirm` (dark + light): ✓ canonical via `isQuickConfirm` separate template branch (line 108). Width 280px, gap 24px ✓. 
- `status=side pop` (dark + light): ✓ canonical via `isSidePop` flag. Width 320px, gap 12px ✓. Layout: figma renders title row (title + close icon) at top, no body/actions. Canonical also skips actions (`showsActions=false` when isSidePop). **Issue:** canonical `.notif--side` shows actions=hidden but description renders inside `.notif__main` flow with icon (skipped via `showsIcon=false` only when also pop/quick confirm). Actually `showsIcon = !isQuickConfirm && !isSidePop && !isPopConfirm` — so `side pop` skips icon ✓. But canonical structure still has `.notif__main > .notif__icon (skipped) + .notif__body > .notif__header (title+close) + .notif__desc`. Figma `side pop` does NOT have a desc paragraph in title-only branches (figma cache lines 432-450 show only a single body p without title row). **Mismatch:** canonical renders title row + desc; figma side-pop has no separate title — the body is just multi-line desc text. Canonical title is generated from fallbackCopy `'This is the title'` for side pop, but figma 真源 side pop variant **has** a title (lines 38-46). Re-reading: lines 37-48 (status=side pop, both themes) show a `title` frame with title text + close icon — yes side pop DOES have title. Then lines 431-450 are `Notification_content` rendered **separately** (the body text). So side pop = title row (top) + body text (below), no icon, no actions, gap 12px. Canonical structure matches ✓.
- `theme=dark`: ✓ canonical `.notif--dark` with bg/border/text rules.
- `theme=light`: ✓ canonical `.notif--light` with bg/border/text rules.

### Variant cross-coverage matrix

14 figma branches × canonical branch coverage:

| Status × Theme | Figma branch | Canonical handler |
|---|---|---|
| `warning × dark` | `isDarkAndWarning` | `notif--warning` + `notif--dark` ✓ |
| `warning × light` | `isLightAndWarning` | same with `notif--light` ✓ |
| `secondary warning × dark` | `isDarkAndSecondaryWarning` | `notif--secondary-warning` + `notif--warning` (alias, see issue above) |
| `secondary warning × light` | `isLightAndSecondaryWarning` | same with light |
| `error × {dark,light}` | `isDarkAndError`, `isLightAndError` | `notif--error` + theme ✓ |
| `info × {dark,light}` | `isDarkAndInfo`, `isLightAndInfo` | `notif--info` + theme ✓ |
| `pop confirm × {dark,light}` | `isDarkAndPopConfirm`, `isLightAndPopConfirm` | `notif--pop-confirm` + theme + `isDangerAction` ✓ |
| `quick confirm × {dark,light}` | `isDarkAndQuickConfirm`, `isLightAndQuickConfirm` | `notif--compact` + theme + `isQuickConfirm` ✓ |
| `side pop × {dark,light}` | `isDarkAndSidePop`, `isLightAndSidePop` | `notif--side` + theme + `isSidePop` ✓ |

All 14 covered.

## Token usage summary

### Existing canonical tokens used

- Spacing: `--sp-s`, `--sp-m`, `--sp-l`, `--sp-xxl`
- Color: `--brand`, `--red`, `--text-body`, `--text-tips`, `--text-2`, `--line-light`, `--line-deep`, `--icon-default`, `--text-primary-btn`, `--notification-bg-dark`, `--notification-bg-light`
- Radius: `--r-s`
- Typography: `--text-style-pop-title`, `--text-style-body`
- Shadow: `--shadow-l3`

### New domain tokens proposed

None — all values map to existing tokens. The two existing component-variant tokens `--notification-bg-dark` / `--notification-bg-light` already exist; only `--notification-bg-light` value needs correction (`#ffffff` → `var(--color-grey-2)` = `#f8f8f8`) per figma 真源.

### BRIDGE-Tier3 deferred items

- Cancel + Confirm + Delete buttons (`Button/dark M` / `Button/light M`) → should become `<Button>` once Tier3 mega ready. Currently canonical uses native `<button>`. **No fix this batch.**

## Refactor scope

### Template changes

- Drop the redundant `notif--warning` class addition for `secondary warning` (line 104) — keep only `notif--secondary-warning`.
- No structural template changes otherwise.

### CSS changes

- Replace raw px in spacing rules (gap, padding) with `--sp-*` tokens.
- Replace hardcoded font properties with `--text-style-*` composite tokens.
- Replace inline 3-line `box-shadow` with `var(--shadow-l3)`.
- Collapse duplicate `.notif--dark .notif__desc` + `.notif--light .notif__desc` into a single `.notif__desc { color: var(--text-tips); }`.
- Update `--notification-bg-light` value in `variables.css`: `#ffffff` → `var(--color-grey-2)` (= `#f8f8f8`).
- Optionally update border width `1px` → `1.2px` (off-scale; defer to design).
- Optionally swap `.notif--dark .notif__button--confirm { color }` from `--text-primary-btn` to `--text-body` to match figma alias.

### Estimated diff

- Template: ~1 line removed (drop redundant class).
- CSS: ~15 lines changed (token swaps), ~3 lines removed (collapsed desc rules), ~0 new lines structurally.
- Tokens: 1 value correction in `variables.css`.
- Total: **~18 lines changed, ~4 lines net removed**.

## Blockers / open decisions

- Off-scale values: button vertical padding `10px`, root border `1.2px`, icon `16px` close, button height `32px`. Off-scale but design-intent — flag for design review only; not blockers for this batch.
- Design drift in figma cache: confirm button dark uses raw `#33ab4f` (close to but not exactly `--brand` dark `#2fb54e`); danger button dark uses `#ec5050` (between `--red` and `--red-hover`). Canonical resolves to nearest token (`--brand`, `--red`). Confirm with design owner whether figma should be re-aligned to the token.
- `--notification-bg-light` value correction (white → grey-2) — needs plan-owner sign-off because it changes pixel output.
