Skip to content

feat(tokens)!: adopt espresso v2 tokens + codemod migration#727

Merged
netchampfaris merged 50 commits into
mainfrom
v1/espresso-tokens
Jun 10, 2026
Merged

feat(tokens)!: adopt espresso v2 tokens + codemod migration#727
netchampfaris merged 50 commits into
mainfrom
v1/espresso-tokens

Conversation

@netchampfaris

@netchampfaris netchampfaris commented May 21, 2026

Copy link
Copy Markdown
Contributor

What this does

Adopts the final espresso v2 design tokens from Figma and migrates the whole codebase onto the new token names.

The v2 palette renumbers and renames the semantic tokens — e.g. every colour family now has a full 1..10 ramp, and names like surface-white / surface-modal / ink-white are replaced by surface-base / surface-elevation-2 / ink-base.

Changes

  • New tokens — the final Figma export is piped into the Tailwind preset via yarn sync-tokens. Full 1–10 colour ramps, plus new semantic names: base, sidebar, elevation-1/2/3.
  • Codemodtailwind/migrate-tokens-v2.js renames every old token to its v2 name (utility classes and CSS vars), shifts typography utilities to the new scale, and merges static text-{size} font-* pairs into named text-style utilities. Run once on the whole repo. Downstream apps can run the same script to upgrade.
  • Old names removed — retired tokens no longer emit CSS, so any leftover usage breaks visibly instead of silently keeping old styles.
  • Global focus ring — the keyboard focus indicator is now one :focus-visible outline rule applied globally, instead of per-component classes. Retheme with focus-visible:focus-ring-red, suppress with focus-visible:outline-none.
  • Foundations docs — new /docs/foundations/* pages (Colours — Base/Semantic, Typography, Corner Radius, Drop Shadow) read directly from the generated tokens, plus interactive playgrounds added to 21 component pages.

Follow-up fixes

  • Alpha semantic tokens — generated and documented the surface-alpha-* and outline-alpha-* semantic tokens that were missing from the Tailwind output.
  • Dialog / overlay surfaces — Dialog now allows mouse focus inside inputs, and Dialog/editor popups use the final surface-elevation-* tokens.
  • Editor table interactions — upgraded Tiptap/ProseMirror, preserved ProseMirror classes in EditorContent, kept table resizing/scroll wrappers in read-only and toggled editors, hid the table toolbar on scroll, centered it over the visible table area, and added drag-scroll, selection overlay, table-size picker, and context-menu polish.
  • Editor uploads — TextEditor file uploads now default to private.
  • TabButtons / Pill — TabButtons render route/href/native tabs as components instead of tag strings; Pill active browser-tab and vertical underline indicators were corrected.
  • Docs / tests / exports — restored the Progress package export, fixed stale Progress/Select Cypress assertions, restored Shiki colors in docs dev, and synced playground code snippets with dark mode.

Upgrading an app

node node_modules/frappe-ui/tailwind/migrate-tokens-v2.js src

Run it once — re-running double-shifts chained renames.

Test plan

  • yarn build passes
  • yarn test passes (167/167) and yarn test:cypress passes (42 specs)
  • /docs/foundations/colours/semantic — surface/ink/outline render, light/dark toggle works
  • Badge / Button — accent colours and focus rings look right in light and dark mode
  • Tab through inputs/buttons — focus ring shows only on keyboard focus
Full token mapping (old → v2)

Class prefixes (bg-, text-, border-, ring-, …) and CSS vars (var(--…)) all follow the same token rename.

surface

old v2
surface-white surface-base
surface-menu-bar surface-sidebar
surface-card surface-elevation-1
surface-cards surface-elevation-1
surface-modal surface-elevation-2
surface-selected surface-elevation-3
surface-gray-2-contrast surface-elevation-3
surface-gray-5 surface-gray-8
surface-gray-6 surface-gray-9
surface-gray-7 surface-gray-10
surface-red-5 surface-red-7
surface-red-6 surface-red-8
surface-red-7 surface-red-9
surface-blue-5 surface-blue-7
surface-blue-6 surface-blue-8
surface-blue-7 surface-blue-9
surface-green-5 surface-green-7
surface-green-6 surface-green-8
surface-green-7 surface-green-9
surface-amber-5 surface-amber-7
surface-amber-6 surface-amber-8
surface-amber-7 surface-amber-9
surface-violet-5 surface-violet-7
surface-violet-6 surface-violet-8
surface-violet-7 surface-violet-9

ink

old v2
ink-white ink-base
ink-red-2 ink-red-5
ink-red-3 ink-red-6
ink-red-4 ink-red-8
ink-blue-2 ink-blue-5
ink-blue-3 ink-blue-6
ink-blue-4 ink-blue-8
ink-green-2 ink-green-5
ink-green-3 ink-green-6
ink-green-4 ink-green-8
ink-amber-2 ink-amber-5
ink-amber-3 ink-amber-6
ink-amber-4 ink-amber-8
ink-violet-2 ink-violet-5
ink-violet-3 ink-violet-6
ink-violet-4 ink-violet-8

outline

old v2
outline-white outline-base
outline-gray-modal outline-elevation-2
outline-gray-modals outline-elevation-2
outline-gray-5 outline-gray-7
outline-red-2 outline-red-3
outline-red-3 outline-red-4
outline-red-4 outline-red-5
outline-blue-2 outline-blue-3
outline-blue-3 outline-blue-4
outline-blue-4 outline-blue-5
outline-green-2 outline-green-3
outline-green-3 outline-green-4
outline-green-4 outline-green-5
outline-amber-2 outline-amber-3
outline-amber-3 outline-amber-4
outline-amber-4 outline-amber-5
outline-violet-2 outline-violet-3
outline-violet-3 outline-violet-4
outline-violet-4 outline-violet-5

text

The espresso text scale adds 15px (lg) and 17px (2xl) stops, so old lg and larger utility names shift upward to preserve the rendered size. The same mapping applies to text-*, text-p-*, and text-*-medium.

old v2
text-lg / text-p-lg / text-lg-medium text-xl / text-p-xl / text-xl-medium
text-xl / text-p-xl / text-xl-medium text-3xl / text-p-3xl / text-3xl-medium
text-2xl / text-p-2xl / text-2xl-medium text-4xl / text-p-4xl / text-4xl-medium
text-3xl / text-p-3xl / text-3xl-medium text-5xl / text-p-5xl / text-5xl-medium
text-4xl / text-p-4xl / text-4xl-medium text-6xl / text-p-6xl / text-6xl-medium
text-5xl / text-p-5xl / text-5xl-medium text-7xl / text-p-7xl / text-7xl-medium
text-6xl / text-p-6xl / text-6xl-medium text-8xl / text-p-8xl / text-8xl-medium
text-7xl / text-p-7xl / text-7xl-medium text-9xl / text-p-9xl / text-9xl-medium
text-8xl / text-p-8xl / text-8xl-medium text-10xl / text-p-10xl / text-10xl-medium
text-9xl / text-p-9xl / text-9xl-medium text-11xl / text-p-11xl / text-11xl-medium
text-10xl / text-p-10xl / text-10xl-medium text-12xl / text-p-12xl / text-12xl-medium
text-11xl / text-p-11xl / text-11xl-medium text-13xl / text-p-13xl / text-13xl-medium
text-12xl / text-p-12xl / text-12xl-medium text-14xl / text-p-14xl / text-14xl-medium
text-13xl / text-p-13xl / text-13xl-medium text-15xl / text-p-15xl / text-15xl-medium
text-14xl / text-p-14xl / text-14xl-medium text-16xl / text-p-16xl / text-16xl-medium
text-15xl / text-p-15xl / text-15xl-medium text-17xl / text-p-17xl / text-17xl-medium

Static class lists also merge one text size with one font weight after the size shift:

old pattern v2
text-{size} font-normal text-{size}
text-{size} font-medium text-{size}-medium
text-{size} font-semibold text-{size}-semibold
text-{size} font-bold text-{size}-bold
text-{size} font-extrabold text-{size}-black

Example: text-lg font-medium becomes text-xl-medium.

surface-alpha

old v2
surface-alpha-white surface-alpha-base
surface-alpha-menu-bar surface-alpha-sidebar
surface-alpha-card surface-alpha-elevation-1
surface-alpha-cards surface-alpha-elevation-1
surface-alpha-modal surface-alpha-elevation-2
surface-alpha-selected surface-alpha-elevation-3
surface-alpha-gray-5 surface-alpha-gray-8
surface-alpha-gray-6 surface-alpha-gray-9
surface-alpha-gray-7 surface-alpha-gray-10
surface-alpha-red-1 — (removed)
surface-alpha-red-2 — (removed)
surface-alpha-red-3 — (removed)
surface-alpha-red-4 — (removed)
surface-alpha-red-5 — (removed)
surface-alpha-red-6 — (removed)
surface-alpha-red-7 — (removed)

outline-alpha

old v2
outline-alpha-white outline-alpha-base
outline-alpha-gray-modal outline-alpha-elevation-2
outline-alpha-gray-modals outline-alpha-elevation-2
outline-alpha-gray-5 outline-alpha-gray-7
outline-alpha-red-2 — (removed)
outline-alpha-red-3 — (removed)
outline-alpha-red-4 — (removed)

Docs preview: https://ui.frappe.io/pr-preview/pr-727/

Coverage: 56.48% (-0.41% vs main)

github-actions Bot added a commit that referenced this pull request May 21, 2026
@netchampfaris netchampfaris changed the title feat(tokens): sync espresso v2 from Figma + rebuild foundations docs feat(tokens)!: adopt espresso v2 tokens + codemod migration Jun 8, 2026
netchampfaris and others added 24 commits June 8, 2026 23:40
…ndations docs

Generator
- tailwind/figma-tokens-to-theme.js reads the W3C DTCG token JSON
  exported from Figma (espresso-v2-design-tokens/) and emits
  tailwind/generated/{colors,radius,typography}.json
- Run with `yarn sync-tokens`
- Preserves legacy semantic names (surface-cards, surface-white,
  ink-gray-9, outline-gray-modals, …) via alias + legacy-entry maps
  so existing src/ and docs/ usage keeps working through the rename

Tailwind
- tailwind/colors.json refreshed from Figma; adds *.950 shades,
  gray-alpha and red-alpha ramps, surface/ink/outline updates
- tailwind/plugin.js now consumes radius.json + typography.json
  instead of hardcoded scales; exposes new sizes (tiny, 4xl..15xl)
  and the full numeric radius scale (rounded-0..9) alongside the
  named aliases

Docs
- New /docs/foundations/* pages replacing /docs/design-system/*:
  Colours — Base, Colours — Semantic, Typography, Corner Radius,
  Drop Shadow
- Components in docs/components/foundations/ read directly from
  the generated JSON; light/dark TabButtons toggle; click-to-copy
- Layout.vue respects `outline: false` frontmatter (custom
  OnThisPage was previously unconditional)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
transformerStyleToClass only flushes its CSS at buildEnd, so in dev the
generated shiki.css stays empty and code blocks lose their syntax
highlighting. Skip the transformer in dev and let shiki emit inline
--shiki-light/dark styles directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add text-{size}-medium utilities (ADR-0004) for Figma's named
  typography styles; Button md/lg adopt them for exact tracking parity
- Switch Button focus ring to ring-2 to match Figma's 2px shadow (ADR-0005)
- Adopt numbered radius tokens as canonical (ADR-0006); Button migrates
  from named aliases; named aliases flagged for migration
- Add spec/foundations.md covering typography, focus, radius, themes,
  code-only extensions, and the Figma verification process

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Delete Variants/Themes/Sizes/Icons stories; the API table already
  covers prop variations
- Add 6 contextual stories matching the Button Figma file: section
  controls, section action, selection toolbar, inline actions, stacked
  actions, and a live-class card
- Add a Playground builder (TabButtons + Switch knobs) with live
  preview and dynamically generated Vue snippet; registered globally
  as <ButtonBuilder> in the VitePress theme

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire the Figma token pipeline to emit elevation/focus effects, expose
them as CSS variables, and register `shadow-*` / `ring-*` utilities
that resolve to the new variables. Replace the standalone drop-shadow
docs page with a broader elevation page that previews both stacks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Consolidates the lucide / feather / emoji / Vue-component icon-rendering
pattern that's currently duplicated across Button, Dropdown, Dialog, and
OptionIcon into one small component, so new call sites have one obvious
choice. Falsy values render nothing; fallthrough attrs go to the
rendered root.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A compact tab item used by TabButtons (and reusable on its own). The
`icon` prop renders an icon-only pill — any `label` is exposed only to
assistive tech. `iconLeft` / `iconRight` are accent icons rendered next
to a visible label, matching Button's semantics. Variants cover the
segmented, outline, underline, and browser-tab looks. Icon rendering
goes through the shared `<Icon>` component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…wrap

TabButtons no longer wraps `<Button>` internally — each tab is now a
native `<button>`, `<a href>`, or `<RouterLink>` rendering a `<Pill>`
for its visual treatment.

BREAKING CHANGE: per-option `theme`, `variant`, `size`, `loading`,
`hideLabel` and `tooltip`-as-popover are no longer honored. Use the
shared `<Button>` directly if any of these matter, or supply richer
content via the `prefix` / `suffix` slots. Migration notes added to
`TabButtons.md`.

New: `route` and `href` per option render the tab as a RouterLink or
anchor respectively. The internal v-model fallback chain
(`value ?? label ?? index`) is preserved for back-compat with consumers
that bound to a label string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up the new Pill and Icon components in the auto-resolved
declaration file, drops stale Rating story entries, and absorbs
incidental type-union reordering in Divider/Popover/Tooltip api docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the four duplicated *Builder.vue files (~180 lines each, 90%
identical chrome) with a shared ComponentPlayground that takes a knob
schema + #preview slot + code(values) fn. Each per-component builder
shrinks to ~30-80 lines of pure config.

Code preview matches story preview visuals exactly: runtime shiki
highlighting with the same tokyo-night/github-light themes, .shiki
sizing and bg, and the global .copy button style.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously the active indicator was a 3px rounded bar floating 3px
outboard of the rail, with full pill height. Read as a text caret next
to the label rather than a tab marker.

Now: 1px-wide square segment positioned at right:-2px (overlapping the
column rail), label-height via inset-y-1.5. Active label gets
font-medium on underline variant so the label itself signals selection.
Adds pr-2 to vertical underline pills so short labels don't sit flush
against the indicator.

Also cleans up a stray </content> tag at the end of Pill.vue and
TabButtons.vue (left over from prior commits, broke SFC parsing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Swap the per-site focus-visible:ring-* + ring-outline-gray-* patterns
for the new focus-ring / focus-ring-{theme} utilities backed by the
--focus-* design tokens. Button maps theme → themed focus-ring
(blue/green/red); other components use the default gray focus-ring.

Triggers in Select / shared selection / MultiSelect TagsTrigger also
adopt focus-ring on data-[state=open] so the open ring matches the
focus ring exactly. Intentional suppression sites (Sidebar item, ghost
input variants) left unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace generic Tab/Tab placeholder options with Home/Task/Contact/Others
nav and an icon-only ghost sidebar, inspired by the Figma showcase. Use
distinct iconLeft icons per option and switch the Sizes / PrefixSuffix
demos to view-switcher and Inbox/Comments patterns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The docs render the stories/ snippets directly; this orphan Histoire-style
file isn't referenced anywhere.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Sidebar/page renames: "Colours — Base" → "Base Colors", "Colours —
  Semantic" → "Semantic Colors", "Corner Radius" → "Radius" (file
  renamed to foundations/radius.md, URL paths for colours unchanged).
- SemanticPage rewritten as a flat row list per token: swatch, name,
  → reference, and resolved value — no surrounding box, no hover.
- PalettePage: drop the per-swatch inset 1px ring and the p-2 wrappers
  around the overlay sections; each overlay swatch now layers its
  translucent color over its own bg-ink-gray-9 / bg-surface-white
  backdrop via a linear-gradient.
- New docs/composables/useTheme.ts watches the <html data-theme>
  attribute (set by Navbar.vue) so the local Light/Dark switchers on
  both foundations pages follow global theme toggles in real time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Espresso 2.0's Figma dark-mode page applies elevation/light/* shadows
universally — never the elevation/dark/* set. Match that: emit
--elevation-{sm..2xl} from :root only, so shadow-* resolves to the same
value in both themes. The Figma dark set is still exposed as
--dark-elevation-* / shadow-dark-* for opt-in heavier shadows.

Pill: raised active state now uses bg-surface-gray-2-contrast shadow-base
+ text-ink-gray-8, matching the Figma dark-mode active pill (lighter
surface over container, subtle light shadow with inner highlight, less
glaring label).

TabButtons: clip the container so the active pill's shadow doesn't
overflow the rounded corners.

Docs: elevation page copy reflects the single-shadow-set model;
ElevationPreview switcher uses TabButtons and syncs with the global
theme toggle (same pattern as Palette/Semantic pages).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pass defaultColor: false to Shiki's codeToHtml so both themes are emitted
as CSS variables (--shiki-light / --shiki-dark) instead of inline color
on the light theme. The [data-theme="dark"] .shiki span rule in
docs/css/style.css can then flip tokens at runtime — previously the
light theme's inline color won by specificity and snippets stayed light
even in dark mode.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Radius: collapse the numeric and named-alias sections into one divided
list (one row per scale step). Each row shows the rounded-N class +
--radius-N CSS variable, with aliases like `rounded`, `rounded-sm`
rendered as inline chips on the numeric row they map to. Swatches
enlarged so adjacent steps (e.g. 2 → 3 = 5px → 6px) are distinguishable.

Plumb --radius-* CSS variables through tailwind/plugin.js so the docs
aren't lying: borderRadius now resolves via var(--radius-N), with
`DEFAULT` reusing the numeric var that shares its value. Values
unchanged.

Typography: display line-height as a unitless ratio (lh / size) instead
of the raw px value from Figma — closer to how designers reason about
leading.
…values

PR #720 on main extracted borderRadius/boxShadow/fontSize into tokens.js
as a public sub-path export. Our branch already replaced those values
with Figma-generated tokens in plugin.js. After the rebase, tokens.js
still exported the legacy hardcoded values, drifting from what the
plugin actually emits. Source from ./generated/* so external consumers
match the plugin output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend the ComponentPlayground pattern (Button/Badge/Pill/TabButtons) to the
remaining atoms: Alert, Avatar, Breadcrumbs, Checkbox, Combobox, Dialog,
Divider, Dropdown, ErrorMessage, FormControl, MultiSelect, Password, Progress,
Rating, Select, Slider, Switch, Tabs, TextInput, Textarea, Tooltip. Each gets
a Builder component registered in the vitepress theme and a Playground section
in its docs page. Widen knob labels so longer prop names don't wrap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
netchampfaris and others added 12 commits June 8, 2026 23:40
Extend the masking pseudo 3px past the rail line (instead of 1px) and darken
the tab border one step so the active tab reads as a raised shape continuing
into the panel, not a closed bottom corner. No drop shadow — it would repaint
below the rail and re-close the edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also refresh auto-generated components.d.ts (picks up KeyboardShortcutsModal,
drops removed TabButtons.story).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the token JSONs with the final design export:
- Full 1-10 ramps for every color family in surface/ink/outline (the
  renumbered scheme from the v2 migration mapping)
- New semantic tokens: base, sidebar, elevation-1/2/3, alert-button-*
- Final primitive value pass (light blue/green/amber/violet refinements,
  dark ramps overhauled, radius-9 999px)
- red-alpha ramps dropped (surface-alpha/outline-alpha red removed)
- Primitives file renamed without the 🔵 prefix to match the export

Generator updates that rode along in the working tree: emit
neutral.transparent, pin menu-bar/gray-2-contrast/gray-modals as legacy
entries, auto-copy colors.json out of generated/, and check in the
token drift audit tool (audit-token-drift.cjs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical rename of all old semantic token names to their espresso v2
equivalents via the new codemod (tailwind/migrate-tokens-v2.js), applied
to src/, docs/, frappe/, skills/, icons/, spec/, v1-release/ and
tailwind/plugin.js — 372 replacements.

Highlights of the mapping (see the codemod for the full table):
- surface: white→base, menu-bar→sidebar, card(s)→elevation-1,
  modal→elevation-2, selected→elevation-3, gray-2-contrast→elevation-3,
  gray-5/6/7→8/9/10, accent 5/6/7→7/8/9
- ink: white→base, accent 2/3/4→5/6/8
- outline: white→base, gray-5→gray-7, gray-modal(s)→elevation-2,
  accent 2/3/4→3/4/5

The codemod is single-pass (chained renames like outline red-2→3→4→5
must not cascade) and ships in the package so consuming apps can run:
  node node_modules/frappe-ui/tailwind/migrate-tokens-v2.js src

BREAKING CHANGE: old espresso token names are retired; run the codemod
on app code when upgrading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the LEGACY_ENTRIES/ALIASES machinery from the generator. Every
pinned name is either retired by the v2 migration (surface-white,
surface-modal, surface-menu-bar, surface-cards, surface-selected,
surface-gray-2-contrast, ink-white, outline-gray-modals) or now defined
natively by the Figma export's full 1-10 ramps (surface-cyan-1,
ink-gray-9, outline-orange-1, ...).

Retired names no longer produce CSS variables or utility classes, so any
straggler usage fails visibly instead of silently keeping old styles.

BREAKING CHANGE: var(--surface-white) and friends are gone; run
tailwind/migrate-tokens-v2.js on app code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Progress built the expected token class via string interpolation
(bg-surface-gray-${7|2}) so the v2 token codemod couldn't rewrite it;
update to the migrated gray-10. Select still asserted the old
focus-visible:ring-2 class from before the focus-ring refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…utline

The default keyboard focus indicator now comes from one base-layer rule:

  :focus-visible { outline: var(--focus-outline-default); outline-offset: 0 }

backed by new --focus-outline-* vars (2px light / 3px dark, theme-flipped)
derived from the same Figma focus tokens as the --focus-* shadows.

focus-ring utilities switch from box-shadow to outline, which removes a
whole conflict class: box-shadow rings fought shadow-*/focus:shadow-sm on
the same element (TextInput had this), outlines cannot. Outlines also
follow border-radius and survive forced-colors mode.

Components drop their per-element 'outline-none focus-visible:focus-ring'
boilerplate (Button, Breadcrumbs, Checkbox, Rating, Select, Switch,
Textarea, TextInput, Toast, selection triggers) — the global rule covers
them. Kept: themed overrides (focus-ring-red/green/blue on Button),
state rings (data-[state=open]:focus-ring), wrapper focus-within rings,
and deliberate suppressions (Dialog panel, ghost inputs, menu panels).

BREAKING CHANGE: outline-none on a focusable element now suppresses the
design-system focus ring, not just the UA one. ADR-0005 amended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-rebase pass: code that landed on main since the branch point still
used old token names. Ran the codemod on the 275 main-only files (21
changed — mostly src/molecules/editor/) and a provenance-guarded
line-level pass on the 36 files both sides touched (1 line). Also drop
the xl/2xl size knobs from ButtonBuilder — main's Button rewrite
removed those sizes (xs/sm/md/lg now).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make text.styles.tokens.json the source of truth for the type scale and
add weight-aware text styles.

- Add 15px (lg) and 17px (2xl) text stops; the scale shifts up two names
  from the old lg (text-lg=16px → text-xl, etc.), in both text and
  paragraph groups.
- Derive the scale from text.styles.tokens.json — exact per-size
  size/line-height/letter-spacing, plus tiny's uppercase — instead of the
  variable export (Typography.Desktop), which rounds line-heights and
  drops per-size tracking. Weights stay name-mapped via FONT_WEIGHT_MAP
  (regular 420, medium 500, semibold 600, bold 700, black 800) because the
  Figma exporter corrupts the weight column (Regular's wght-420 override on
  the Thin instance exports as 100/400; Black exports as 700 not 800).
- Correct text.styles in place to match Figma: every regular→420, every
  black→800, and the paragraph/5xl 0.5px→0.5% unit typo. Build output is
  unchanged by this (weights are name-mapped), but the file is now truthful.
- Add text-{size}-{weight} and text-p-{size}-{weight} component classes
  carrying each weight's letter-spacing (values honored as-is from
  text.styles). Drop the hand-maintained FONT_SIZE_AUGMENT /
  PARAGRAPH_LINE_HEIGHT / FONT_SIZE_MEDIUM_TRACKING maps in plugin.js and
  the stale parallel copy in tokens.js.
- migrate-tokens-v2.js: add the text-size renames, merge co-located
  text-{size} + font-{weight} into text-{size}-{weight} (static class
  attrs only, non-adjacent and color-safe), and refuse to run on an
  already-migrated repo (--force / --dry-run to override).
- Apply to frappe-ui's own components: the size shift and 72 weight-class
  merges across the library.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename colours/ route to colors/
- Add a standalone Focus Ring page; fix colored focus-ring variants
  (literal class strings so the JIT emits them) and demo with real
  tabbable components
- Typography: page-wide weight switcher driving real text-<size>-<weight>
  token classes (safelisted), paragraph variants, even vertical rhythm
- Square color swatches; fix invisible white-overlay; same-size neutrals
- Ground elevation surface pairings in actual component usage and use
  surface-elevation-* tokens; flush, theme-driven preview
- In-page Light/Dark switchers now drive the global theme
- Consistent prose-v3 descriptions; trim verbose copy and em-dashes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-rebase pass for main's beta.4 changes (editor starter-kit rewrite
+ FrappeUI plugin). Ran the codemod on the 52 main-only files (4 changed)
and the 3 conflict-resolved editor files taken from main's version
(MediaToolbar, ImageGroupGridCell, ImageGroupNodeView): ink-white→base,
surface-gray-7→10, and text-{xs,sm} font-medium merged to the combined
style utilities — 18 renames + 2 weight merges across 7 files.

Skipped IframeNodeView's `text-xl` (L206): the auto-merge kept this
branch's already-migrated value (main's text-lg → v2 text-xl), so the
codemod's text-xl→text-3xl would double-shift it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
netchampfaris and others added 14 commits June 9, 2026 01:18
Each tab used `<component :is="tabElement(button)">` where `tabElement`
returned the bare string 'button'/'a'. At runtime Vue's
`resolveDynamicComponent` runs the name through the asset resolver, which
capitalizes it and matches a globally registered component — so in apps
that do `app.component('Button', ...)` (e.g. Gameplan) every tab rendered
as the frappe-ui Button component (nested truncate wrapper + Button
classes) instead of a native <button>, breaking the tab styling.

Render native tags through tiny functional-component wrappers so `:is` is
always bound to a component value, bypassing the resolver. `inheritAttrs`
is false so the merged Reka/data/class/@click attrs apply exactly once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DialogContent is nested inside the scrollable DialogOverlay, and the
overlay carries a pointerdown handler bound with Vue's `.prevent`
modifier. A pointerdown on a dialog input bubbles up to the overlay and
is preventDefault'd, which suppresses the compatibility mousedown — and
mousedown's default action is what focuses the input. Result: text
fields could not be focused/clicked with the mouse (keyboard and
click-based buttons still worked).

Stop content pointerdowns from reaching the overlay's prevent handler.
Outside-click dismissal is unaffected: Reka detects it in the capture
phase, which a bubble-phase `.stop` cannot intercept.

This is a workaround; the stray overlay pointerdown.prevent should be
removed at its source. Refs #766.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EditorContent mounts the editor directly onto its own root div, so that
element's `class` attribute has two writers: ProseMirror (adds `ProseMirror`/
`tiptap`/`ProseMirror-focused`) and Vue's `:class` binding. Vue's binding is a
wholesale replace, so any re-render dropped ProseMirror's classes until the next
state update re-added them. With `ProseMirror` gone, every `.ProseMirror`-scoped
content style silently no-ops — most visibly, table cells lose their borders in
editable mode (read-only never re-renders, so it was unaffected).

Manage only our own tokens via `classList.add/remove` instead of `:class`,
mirroring how prosemirror-view's `patchAttributes` surgically maintains its own
tokens. Both owners now coexist on the shared attribute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The contextual table toolbar is teleported to body with fixed positioning and
anchored to the table's top. Following the table on scroll floated it out of the
editor's scroll container and over the page header. Hide it on scroll instead;
it reappears the next time the caret lands in a table. Resize still repositions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggled editors

Stock @tiptap gates columnResizing behind `resizable && editor.isEditable`,
evaluated once at editor creation. That plugin also installs the TableView
`.tableWrapper` scroll container, so read-only editors (and editors toggled
into edit mode via setEditable, which never reconfigures plugins) got a bare
table that overflows its bounds, no table-only scroll, and no column resize
handles after reopening saved content.

Install columnResizing whenever `resizable` is set, dropping the isEditable
gate. prosemirror-tables already no-ops its own pointer handlers when
`!view.editable`, so handles only show/drag when editable and setEditable
flips view.editable live. Add `.tableWrapper { max-width: 100%; overflow-x:
auto }` so a too-wide table scrolls within its own bounds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… table

A wide table scrolls inside its `.tableWrapper` (overflow-x: auto), so the
table's own horizontal center can be scrolled out of view and the toolbar —
anchored with floating-ui `placement: 'top'` on the <table> — floated over the
clipped region. Anchor instead to the horizontal intersection of the table and
the wrapper viewport: it collapses to the table for a narrow, fully-visible
table and to the visible window for a wide, scrolled one, keeping the toolbar
centered over what the user can see. Vertical anchor stays at the table top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The default upload function created public File records (is_private=0),
served from the unauthenticated /files/ path, leaking editor images and
attachments to anyone with the URL. Default uploads to private; callers can
still override via uploadArgs (e.g. private: false) for intentionally public
files.

Ref: frappe/security#206

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump @tiptap/* to ^3.26.0 and the prosemirror-* resolutions. Add a
jsdom elementFromPoint polyfill since prosemirror-view ≥1.41 calls it
during cursor handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Keyboard: arrows exit the table at top/bottom edge, Tab on the last
  cell appends a row, Mod+A escalates cell → table → doc.
- Drag-select auto-scrolls past the wrapper edge; pointer capture ends
  drags released off-window.
- Touch: long-press opens the table context menu.
- Insert via a size-picker grid (toolbar button and /table slash command).
- Context menu gets keyboard focus, checked toggle state, and merge/split
  labels; floating toolbar follows the table on scroll and pins in view.
- Selection overlay re-measures on resize; IME and paste land in the
  anchor cell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netchampfaris netchampfaris added the beta-release Auto-publish a beta to npm when the PR is merged label Jun 10, 2026
@netchampfaris netchampfaris merged commit f29c515 into main Jun 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

beta-release Auto-publish a beta to npm when the PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant