Skip to content

Fix the server not loading translations, which broke hydration - #207

Merged
sneridagh merged 1 commit into
mainfrom
fix-image-block-remount
Oct 2, 2026
Merged

sneridagh merged 1 commit into
mainfrom
fix-image-block-remount

Conversation

@sneridagh

Copy link
Copy Markdown
Member

Problem

While working on #199 (step A3), image-block-style-fields.test.ts › "combinations set in the editor render correctly after saving" was flaky: the .image-block element it measured was sometimes detached (isConnected === false, 0x0 rect) shortly after page load.

The cause is not the image block. The server-side i18next backend in apps/aurora/app/i18next.server.ts resolved its locales with resolve('../locales/...'), relative to the working directory (apps/aurora), i.e. apps/locales, which doesn't exist. So the server rendered raw message keys (e.g. layout.slots.headertools.anonymousTools.login) while the client rendered the translations ("Log In"). React reports a hydration text mismatch and discards the whole server-rendered tree, re-rendering it on the client — on every load. The apparent "1 in 6" flakiness was only whether the test grabbed the element before or after hydration.

Fix

Resolve the locales relative to the module instead of the working directory: public/locales in development (app/i18next.server.ts) and build/client/locales in production (build/server/index.js).

Tests

New apps/aurora/acceptance/tests/hydration.test.ts:

  • the server HTML contains the translated "Log In", not the raw key;
  • the server-rendered .image-block (captured at DOMContentLoaded) is still connected, is the current .image-block, and has a non-zero width once React owns it.

Both fail without the fix. With it, the full pnpm acceptance-test suite passes (166 tests), and the previously flaky style-fields test passed 6/6 with --repeat-each=6. Dev build shows no remaining hydration warnings.

The server-side i18next backend resolved its locales from the working
directory (`apps/locales`, which doesn't exist), so the server rendered raw
message keys while the client rendered translations. The resulting hydration
mismatch made React discard and re-render the whole server-rendered page on
every load, detaching any element grabbed before hydration (e.g. the image
block measured by `image-block-style-fields.test.ts`).

Resolve the locales relative to the module instead: `public/locales` in
development and `build/client/locales` in production.

Add acceptance tests asserting the server renders translated messages and
that the server-rendered image block stays connected after hydration.
@sneridagh
sneridagh requested a review from pnicolli October 2, 2026 21:39
@sneridagh

Copy link
Copy Markdown
Member Author

@pnicolli merging, since I think it's important, at least to have this fixed. We can improve later, in case we find a better way.

@sneridagh
sneridagh merged commit 70e336c into main Oct 2, 2026
36 checks passed
@sneridagh
sneridagh deleted the fix-image-block-remount branch October 2, 2026 21:45
sneridagh added a commit that referenced this pull request Oct 2, 2026
* a3-content-css-pilot:
  Measure the image block width once again in the style fields test
  Fix the server not loading translations, which broke hydration (#207)
sneridagh added a commit that referenced this pull request Oct 2, 2026
… into b1b-anatomy-gaps

* origin/b1a-content-contract-foundation:
  Measure the image block width once again in the style fields test
  Fix the server not loading translations, which broke hydration (#207)
sneridagh added a commit that referenced this pull request Oct 6, 2026
…#199 A3) (#205)

* Move the image block and inner container styles to styles/content.css

Step A3 of #199, the pilot for the content CSS architecture. The image
block drops its CSS Module and the block inner container its duplicated
Public UI and CMSUI rules: both now live in styles/content.css, loaded in
both user interfaces inside the plone-content cascade layer. The editor
gets the .content-area content root, and Agave ships its first content
token. Acceptance tests prove that a theme token reaches the image block
in both user interfaces and that its layout doesn't depend on the
public theme's reset.

* Make the image block reset test independent of fonts

A centered image right after a float is pushed below it by an amount
that depends on how tall the text next to the float is, which varies with
the fonts installed. The test page now puts the floated image last.

* Measure the image block width once again in the style fields test

The retry worked around React replacing the server-rendered DOM on
hydration. #207 fixed the cause (the server didn't load the
translations), so the nodes now stay connected.
sneridagh added a commit that referenced this pull request Oct 6, 2026
…tent.css (#216)

* Move the Tailwind reset out of the top cascade layer in the CMS UI

Step A1 of #199. The CMS UI loads Tailwind with a plain import, so its
theme variables, preflight and utilities land in the declared theme,
base and utilities layers instead of a top-level cmsui layer. The reset
now sits below every other layer. Removes the cmsui layer and adds the
plone-content layer for block content CSS.

The quanta table row drag handle no longer relies on the global reset
to drop the basic button styles.

* Add the styles/content.css entry point to the add-on styles loader

Step A2 of #199. Every add-on's styles/content.css is aggregated into a
generated .plone/content.css, which both the Public UI and the CMSUI
loaders import first, inside the plone-content cascade layer. Add-ons
write block styles once and get them in both user interfaces. Documents
the convention, its authoring rules and how to override block styles.

* Move the image block and inner container styles to styles/content.css

Step A3 of #199, the pilot for the content CSS architecture. The image
block drops its CSS Module and the block inner container its duplicated
Public UI and CMSUI rules: both now live in styles/content.css, loaded in
both user interfaces inside the plone-content cascade layer. The editor
gets the .content-area content root, and Agave ships its first content
token. Acceptance tests prove that a theme token reaches the image block
in both user interfaces and that its layout doesn't depend on the
public theme's reset.

* Add the block content classname contract foundations

Phase 1a of #200. An acceptance test checks that the public block content
only uses contract classnames, against a list of pending Tailwind classes
that may only shrink as nodes are converted. Stylelint rules guard
styles/content.css. Lists get the block-p__list and block-p__item parts
and a data-list-style-type attribute, and the classname contract is
documented for themers.

* Make the image block reset test independent of fonts

A centered image right after a float is pushed below it by an amount
that depends on how tall the text next to the float is, which varies with
the fonts installed. The test page now puts the floated image last.

* Add h1 and hr to the Plate block anatomy

Phase 1b of #200. h1 (category text) and hr (category separator) were the
Plate-native blocks without anatomy classnames. In the public view, the
separator now gets the existing separator category spacing.

* Measure the image block width once again in the style fields test

The retry worked around React replacing the server-rendered DOM on
hydration. #207 fixed the cause (the server didn't load the
translations), so the nodes now stay connected.

* Move the text block styles to styles/content.css

Phase 2 of #200. Paragraph, title, headings, blockquote and separator are
styled by plain CSS in @plone/plate's styles/content.css instead of
Tailwind utilities, in both the public renderer and the editor. Values
read the theme's Tailwind variables with Tailwind's defaults as fallback.
Editor affordances stay Tailwind. A new acceptance test checks the text
blocks render the same under any public theme reset; the reset helpers
move to the shared Playwright tooling.

* Move the inline mark styles to styles/content.css

Phase 3 of #200. Inline code, keyboard input, highlight, links and
mentions are styled by plain CSS in @plone/plate's styles/content.css
instead of Tailwind utilities. Mention marks become data attributes.
Comment and suggestion marks render as plain text in the public view;
the editor keeps showing them. New tests: a visual test for the inline
marks, a contract check for inline and editorial marks, and a reset
independence test for the inline styles.

* Move the list styles to styles/content.css

Phase 4 of #200. Lists, to-do items and the read-only to-do checkbox of
the rendered content are styled by plain CSS in @plone/plate's
styles/content.css instead of Tailwind utilities. Checked to-do items get
a data-checked attribute. The editor's interactive checkbox stays a
Tailwind editor control. A new acceptance test checks lists render the
same under any public theme reset, and the reset tests share their
measuring helpers.

* Move the code block styles to styles/content.css

Part of #200.

* Move the table styles to styles/content.css

Part of #200.

* Remove the Plate media nodes from Aurora's presets

Nothing in Aurora's editor could insert them, pasted iframes became embeds the public view didn't render, and file drops needed an upload backend Aurora doesn't have. Aurora uses Plone blocks for media. The stock full preset keeps them.

Part of #200.

* Move the structural block and content root styles to styles/content.css

Callout, toggle, columns, table of contents and the rendered content root. The rendered content no longer has any Tailwind classes, so the contract test's PENDING list is gone.

Part of #200.

* Move the table drag handle fragment to @plone/quanta

The quanta Table moved from @plone/components to @plone/quanta in #212.

* Keep EditorView as the Tailwind-free content root

PlateRenderer renders EditorView again instead of PlateView directly, and EditorView no longer applies editorVariants: the content root is styled by styles/content.css. EditorView was only used by PlateRenderer, so it isn't left as dead code that would bring the Tailwind root classes back.
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