Skip to content

chore(deps): upgrade frappe-ui to 1.0.0-beta.45 - #747

Closed
NagariaHussain wants to merge 15 commits into
developfrom
feat/frappe-ui-beta45
Closed

chore(deps): upgrade frappe-ui to 1.0.0-beta.45#747
NagariaHussain wants to merge 15 commits into
developfrom
feat/frappe-ui-beta45

Conversation

@NagariaHussain

Copy link
Copy Markdown
Member

Why?

We are on frappe-ui@1.0.0-beta.25 (20 July). Upstream is at beta.45 — 696 commits, ~76 changelog entries, and the delta only grows the longer we wait.

What?

Upgrades to 1.0.0-beta.45, and moves tiptap to 3.29.2 as a set.

Breaking changes that reached us:

Kind Change
Loud frappe-ui/editor-style.css and frappe-ui/list-style.css exports removed — the barrels self-import their CSS now
Loud Autocomplete removed → MultiSelect in AssignDialog
Loud pageMetaPlugin removed
Silent Radius aliases removed. rounded-md emits no CSS at all — 113 renames over 29 files
Silent Chromatic ink scales shift one level. ink-gray does not shift — 33 renames over 11 files
Silent Tabs is a composed family and models the trigger value, not an index
Silent Dropdown ignores placement; menus quietly left-align
Silent PageHeaderMobile #left/#right#prefix/#suffix — Vue drops unknown slots without a word
Silent Combobox template ref exposes clear(), not reset()

Alert was registered globally with no call sites, so the registration goes rather than the redesign migration.

Audited and left alone: all three useFileUpload() calls already pass private: false, so the default flip to private cannot 403 an editor image or a space logo. Select's trigger slot is unused, Sidebar already owns its padding and scroll container, and the editor's caption change cannot reach WikiImage — it is ours.

How?

Two codemod passes, both non-idempotent, both landed with the version bump because either half alone renders wrong:

  • tokens-v2 --radius-onlynot the plain codemod, whose color and typography passes already ran during the beta.25 upgrade
  • tokens-v2 --ink-shift, with its .tokens-v2-ink-shift markers committed so a fresh clone keeps the re-run guard

The six var(--ink-*) and three var(--radius-*) reads in hand-written CSS are migrated by hand — the codemods only rewrite classes.

All 43 tiptap packages are pinned by name in resolutions. Bumping only our ten direct deps left frappe-ui's transitive @tiptap/extension-* at 3.27.1, each declaring an exact @tiptap/core peer, which produced 19 nested copies of core; yarn v1 ignores a "@tiptap/**" glob resolution.

tailwind.config.js now spreads the preset's own content export. The hand-written globs it replaces covered src/components and src/molecules only, silently dropping every class the editor and list molecules emit.

Testing

  • yarn build clean. Verified the editor and list stylesheets still ship after dropping the explicit CSS imports: 119 ProseMirror and 36 list-slot rules in the built output.
  • Unit tests 47/47 (node --test from frontend/).
  • One e2e break came from the token migration and is fixed here: change-request-flow.spec.ts located a change card by rounded-lg, which the codemod renamed to rounded-6. It was the only token-class locator in the suite.
  • Local Playwright is not a clean signal on this machine and CI should be treated as the verdict. The dev site holds 1847 spaces and 11k documents of accumulated fixture data, and the web server died partway through the long runs. Where a comparison was possible it came out even: callout-rich-text + local-first-store in isolation give 7 failed / 9 passed on this branch and 7 failed / 9 passed on develop, with the two differing tests going in both directions.

Spec and reconciliation: specs/frappe_ui_beta45_upgrade.md.

🤖 Generated with Claude Code

NagariaHussain and others added 15 commits August 11, 2026 14:46
Inventory of the 696-commit delta, restricted to what touches wiki:
four loud breaks, thirteen silent ones, two non-idempotent codemods,
and a phased order that puts the useFileUpload privacy audit first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clears every break in the delta that fails the build.

- tiptap moves to 3.29.2 as a set. @tiptap/markdown declares exact peers
  on @tiptap/core and @tiptap/pm at its own version, so a partial bump
  leaves two cores in the tree. yarn v1 ignores a "@tiptap/**" glob
  resolution, so all 43 packages are pinned explicitly.
- frappe-ui/editor-style.css and frappe-ui/list-style.css are gone. The
  editor and list barrels self-import their CSS and are marked
  side-effectful, so the stylesheets ship with the subpath imports. The
  built output still carries 119 ProseMirror and 36 list-slot rules.
- pageMetaPlugin is removed. Nothing declares the pageMeta() option;
  Spaces.vue already uses usePageMeta.
- Alert is redesigned and stateless. It was registered globally with no
  call sites, so the registration goes rather than the migration.
- Autocomplete is removed. AssignDialog used it with `multiple`, so it
  becomes MultiSelect, whose model is a value array rather than option
  objects.
- tailwind.config.js spreads the preset's own `content` export. The
  hand-written globs it replaces covered src/components and src/molecules
  only, dropping every class the editor and list molecules emit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The named radius aliases are removed in frappe-ui 1.0.0 (ADR-0006). The
preset replaces Tailwind's borderRadius scale, so an unmigrated
rounded-md emits no CSS at all — square corners, no build error.

Applied with `tokens-v2 --radius-only` across the editor SPA and the
public reader's Jinja templates: 113 renames over 29 files. --radius-only
rather than the full codemod because the color and typography passes
already ran during the beta.25 upgrade and neither is idempotent.

The three var(--radius-sm|lg) reads are hand-migrated — the codemod only
rewrites rounded-* classes, and the alias CSS variables go away with the
aliases. Pixel values are identical throughout; this changes vocabulary,
not rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The updated espresso v2 export shifts every chromatic ink scale down one
level — the new ink-red-1 is the old ink-red-2 — and the scales now end
at -9. ink-gray keeps its own 9-step scale and does not shift. Nothing
warns: every ink-<family>-N site renders one shade off after the token
update, so this lands with the upgrade rather than after it.

Applied with `tokens-v2 --ink-shift` over frontend/src and wiki/templates
(33 renames, 11 files). The mode has no sentinel it can read back from
file content, so it writes a .tokens-v2-ink-shift marker in each target
and refuses to run again; the markers are committed so a fresh clone
keeps the guard.

The six var(--ink-<family>-N) reads in the reader's hand-written CSS are
migrated by hand — they sit beside generated files the codemod would
rewrite for nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
None of these fail the build. Each one renders wrong instead.

- Tabs is a composed family now and models the trigger value, not an
  index, so the index round-trip through the tab query goes away.
  Composed rather than the `:tabs` shorthand because v1 ships no layout
  defaults: the panel has to be a constrained flex column for
  ContributionsPanel to scroll, and generated panels take no classes.
- Dropdown ignores `placement` and falls back to align="start", so both
  right-aligned menus quietly moved left.
- PageHeaderMobile's #left/#right are #prefix/#suffix. Vue drops content
  passed to an unknown slot, so the tree toggle and the two menus just
  stopped rendering.
- Combobox's template ref exposes clear(), not reset().

Audited and unchanged: all three useFileUpload calls already pass
`private: false`, so the default flip to private cannot 403 an editor
image or a space logo; Select's trigger slot is unused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The change-card locator matched on `rounded-lg`, which the radius codemod
renamed to `rounded-6` in the source. The class is gone from the markup,
so the locator resolved to nothing and the Reordered label assertion
timed out.

The only e2e locator in the suite that matched on a token class.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the two corrections the work forced on the plan — the radius
codemod needs --radius-only because the color and typography passes
already ran and are not idempotent, and the tiptap resolutions have to
name all 43 packages because yarn v1 ignores a glob — plus the four
breaks that turned out to need no change, and the e2e baseline that
separates the upgrade's one real test break from local flake.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
frappe-ui 1.0.0-beta.41 switched sync-tokens to oklch, so colors.json
holds oklch() where it used to hold hex. The OG card template declares
those six values inline, and the drift test compares the two directly:

    AssertionError: '#0f0f0f' != 'oklch(0.168 0 0)'

The card renders through headless Chromium, which reads oklch, so the
template carries the new notation rather than a hex approximation of it.
The test's scraper accepts either notation, so it keeps comparing what
colors.json actually holds instead of assuming one form.

TEMPLATE_VERSION is part of the cache fingerprint, so bumping it drops
every cached card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A callout's markdown never reached a fixed point under tiptap 3.29.2:
every parse/serialize pass appended two more newlines, so repeated saves
inflated the stored content without bound.

    :::note[Test]\nhello\n:::\n\n\n\n
    :::note[Test]\nhello\n:::\n\n\n\n\n\n
    :::note[Test]\nhello\n:::\n\n\n\n\n\n\n\n

renderMarkdown appended its own `\n\n` after the closing fence. The
serializer already separates blocks and the doc's trailing empty
paragraph serialises to another blank line, which put four newlines after
the fence. Four is one past where PreserveBlankLines turns marked's
`space` token into a real empty paragraph, and that paragraph then
serialised to two more newlines on the next pass. Paragraphs, headings
and lists were stable throughout because they leave the separator to the
serializer; the callout is now the same.

The markdown hooks move to callout-markdown.js so they can be tested:
callout-block.js imports a .vue node view at the top level, which
`node --test` cannot load. Verified by reverting the one-line change with
the tests in place — six round-trip cases and both direct assertions fail
without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FeatherIcon and its bare-name back-compat are removed in frappe-ui 1.0.0
(ADR-0008). A string that does not start with `lucide-` now resolves to
nothing — renderIcon returns null and dev logs one warning per component
and prop — so every `icon="settings"` and `icon: 'trash-2'` in the app
silently stopped drawing: dropdown rows, tree actions, review actions,
the space chrome bar.

All 41 sites take the `lucide-` prefix. Every name survives the move
except `github`, which lucide dropped along with the rest of its brand
icons; the two GitHub affordances use `lucide-git-branch`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SidebarHeader is a fixed 48px region that owns its own gutter, so it is
meant to go straight into Sidebar. Our wrapping `p-2` div added a second
one, pushing the logo and title in past every row below them.

Padding moves to the scroll region and the footer, which is what v1's
composition contract asks for now that Sidebar no longer applies any of
its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
useColorScheme owns everything the local composable was doing by hand:
the `data-theme` attribute, persistence, following the OS, and muting
transitions across a swap so the page doesn't flash. Its version also
cancels a pending unmute, so back-to-back swaps can't uncover a repaint —
which the two-rAF dance here could. The `.no-transition` rule goes too;
frappe-ui/style.css ships it.

What stays local, and why:

- The toggle flips the *painted* scheme rather than delegating to
  toggleColorScheme, which branches on the preference: starting from
  `system` on a dark OS it picks `dark`, and the first click looks dead.
- `resolvedTheme` tracks the `data-theme` attribute through a
  MutationObserver. DiffViewer and MermaidBlockView need the painted
  scheme, and it isn't derivable from the preference — `system` resolves
  against the OS, and an OS flip repaints without changing the
  preference, so there is nothing reactive to watch.

The storage key moves from `wiki-theme` to frappe-ui's `theme`. A saved
value under the old key is copied over once, and the public reader —
which keeps its own Alpine implementation, having no frappe-ui to import
— reads the new key with the old one as a fallback and writes the new
one, so a toggle on either surface still reaches the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three tab panels rendered at once, splitting the height three ways
and clipping the list after about three rows.

reka keeps an inactive TabsContent's wrapper in the DOM and drops only
its slot content, marking the wrapper with `hidden`. Tailwind's preflight
turns that into `display: none` via `[hidden]`, which is a single
attribute selector — exactly as specific as a `.flex` utility, and
utilities are emitted after preflight. So the `flex` this call site put
on TabPanel won the cascade, and every panel stayed a visible flex child
claiming `flex-1`.

Moving `display` behind `data-[state=active]:` leaves `hidden` to win on
the inactive ones. `min-h-0` and `flex-1` are inert while hidden, so they
can stay unconditional.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The underline variant's indicator is a 1px rail line. It measured and
positioned correctly, but against the dark theme it reads as part of the
track border rather than as a selection, so the active tab looked
unmarked.

TabList has full variant parity with TabButtons, so the button-style
track is a prop rather than a different component — and the composed
family keeps owning the panels, which TabButtons does not do.

Page padding moves to a wrapper: the pill track hugs its content, so
padding on TabList itself would sit inside the pill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`icon: node.is_published ? 'eye-off' : 'eye'` kept bare Feather names, so
the publish toggle in the tree's three-dot menu drew no icon. The upgrade
sweep that prefixed the other 41 call sites matched `icon: '...'` and
`icon="..."` only — a ternary's branches were invisible to it, and it ran
over .vue files alone.

Rather than grep harder next time, this adds a test that walks every
source file, isolates each icon key or prop's own expression, and fails
on any string literal in it that is not a `lucide-` class. It reads
ternaries, template props and bindings, and .js as well as .vue.

The break is silent by design — v1 renders null for an unsupported icon
string and only warns in dev — so a build and a passing e2e run both stay
green while the icon is missing. Verified by restoring the bare names
with the test in place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NagariaHussain

Copy link
Copy Markdown
Member Author

Superseded by #752, which carries this branch plus the upgrade to beta.55.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant