Skip to content

fix(core): make lingui macro transform match Vite's normalized paths on Windows - #2462

Open
marcusbellamyshaw-cell wants to merge 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/1862-admin-lingui-macro-windows-hang
Open

fix(core): make lingui macro transform match Vite's normalized paths on Windows#2462
marcusbellamyshaw-cell wants to merge 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/1862-admin-lingui-macro-windows-hang

Conversation

@marcusbellamyshaw-cell

Copy link
Copy Markdown
Contributor

What does this PR do?

On Windows, the admin UI can hang forever on "Loading EmDash…" in local dev with no visible error — no console error, no failed network request, nothing. This reproduces reliably in this environment via the documented pnpm dev contributor workflow (aliasing @emdash-cms/admin to source for HMR).

Root cause, found via live reproduction with browser devtools:

The dev-mode linguiMacroPlugin in packages/core/src/astro/integration/vite-config.ts gates its Babel transform on id.startsWith(adminSourcePath). adminSourcePath is built with path.resolve, which on Windows returns a backslash-separated path in whatever casing the process happened to launch with. Vite, however, normalizes module ids it passes to plugin hooks to forward slashes and to the file's true on-disk casing. Whenever those two don't literally match — which is easy to hit (e.g. any case difference between how a shell was invoked and the real directory casing) — the startsWith check silently fails for every admin source file, so the Lingui macro compilation step never runs. Raw, uncompiled @lingui/react/macro / @lingui/core/macro imports then ship straight to the browser. Lingui's macro runtime throws by design when it isn't run through the Babel transform, and Astro's astro-island hydration error handler swallows that throw to the console with no user-visible fallback — so the page is left stuck on its static pre-hydration "Loading EmDash…" placeholder forever.

This normalizes adminSourcePath to match Vite's own convention (realpathSync for casing, forward slashes for separators), so the comparison actually works.

Fixing that exposed a second, previously-unreached bug in the same function: once the transform actually runs, it does await import(babelCorePath) with a raw filesystem path resolved via require.resolve. Node's ESM loader rejects that on Windows with ERR_UNSUPPORTED_ESM_URL_SCHEME (absolute paths need a file:// URL). Fixed by wrapping the resolved path with pathToFileURL(...).href.

I believe this is the underlying cause of #1862 ("React admin UI silently hangs on 'Loading EmDash...' — createRoot().render() runs but produces zero DOM output, zero errors, zero network activity") — that symptom (including "zero errors" for a reporter who likely didn't check the console) matches exactly what I observed before this fix.

Verified by reproducing live: before the fix, the admin UI hangs indefinitely with a [astro-island] Error hydrating ... Could not resolve "babel-plugin-macros" console error; after the fix, the dashboard loads and renders normally with real API calls succeeding, no console errors.

Closes #1862

Type of change

  • Bug fix

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable) — n/a, no user-visible strings touched
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion — n/a, bug fix

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Sonnet 5

Screenshots / test output

Before fix (hung, console error):

[astro-island] Error hydrating /@fs/.../packages/core/dist/astro/routes/PluginRegistry.mjs Error: Could not resolve "babel-plugin-macros" imported by "@lingui/babel-plugin-lingui-macro". Is it installed?

After fix: admin dashboard renders normally, /_emdash/api/manifest, /_emdash/api/auth/me, /_emdash/api/dashboard all return 200, zero console errors.

…on Windows

The dev-mode Lingui macro transform compared its id.startsWith(adminSourcePath)
using a backslash-separated, launch-cwd-cased path against Vite's
forward-slash, on-disk-cased module ids. On Windows this comparison silently
failed for every admin source file, so macro compilation never ran and raw
@lingui/*/macro imports shipped to the browser — throwing during hydration
and leaving the admin UI hung on "Loading EmDash..." with no visible error.

Also fixes a second, previously-masked bug in the same code path: a raw
Windows drive-letter path passed to a dynamic import(), which Node's ESM
loader rejects with ERR_UNSUPPORTED_ESM_URL_SCHEME.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2cdd60d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added area/core size/S review/needs-review No maintainer or bot review yet labels Aug 13, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right fix for a real, narrow problem. Normalizing adminSourcePath to Vite’s forward-slash/true-casing convention and wrapping the Babel core resolution in pathToFileURL directly address the two Windows-only failure modes described in #1862. The change is small, scoped to the dev-mode source-aliasing path, and doesn’t affect production builds or external-app dist usage.

I checked the diff, the full vite-config.ts implementation, the existing vite-config tests, and the changeset against AGENTS.md. The code itself looks correct and there are no obvious logic or security regressions. The main issues are in the changeset and test commentary:

  • The changeset is written as PR prose and includes internal mechanics (Windows-style backslash path, dynamic import(), ERR_UNSUPPORTED_ESM_URL_SCHEME, etc.). AGENTS.md says changesets are user-facing release notes and should leave out implementation details.
  • The new regression test is a valid Windows guard, but its comment is long and duplicative of the it() title and the source comment. It also won’t fail on non-Windows CI if the normalization is reverted, so its protective value depends on running tests on Windows.

I’m leaving this as comment rather than request_changes because the underlying fix is sound and the findings are polish/test-hygiene items, not bugs that would ship broken behavior.

"emdash": patch
---

Fixes the admin UI hanging indefinitely on "Loading EmDash…" in local dev on Windows, with no visible error. The Lingui macro compiler used to build admin source in dev mode compared file paths using a Windows-style backslash path against Vite's forward-slash-normalized module ids, so the comparison always failed and macro compilation silently never ran — shipping raw, uncompiled `@lingui/*/macro` imports to the browser, which then threw during hydration. A second, previously-masked bug in the same code path passed a raw Windows drive-letter path to a dynamic `import()`, which Node's ESM loader rejects.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] This changeset reads like a PR description: it walks through the root cause (Windows-style backslash path, Vite's forward-slash-normalized module ids, ERR_UNSUPPORTED_ESM_URL_SCHEME) rather than telling a user what changed. AGENTS.md says changesets are release notes that should describe the observable effect and leave out internal mechanics.

Suggested change
Fixes the admin UI hanging indefinitely on "Loading EmDash…" in local dev on Windows, with no visible error. The Lingui macro compiler used to build admin source in dev mode compared file paths using a Windows-style backslash path against Vite's forward-slash-normalized module ids, so the comparison always failed and macro compilation silently never ran — shipping raw, uncompiled `@lingui/*/macro` imports to the browser, which then threw during hydration. A second, previously-masked bug in the same code path passed a raw Windows drive-letter path to a dynamic `import()`, which Node's ESM loader rejects.
Fixes the admin UI hanging indefinitely on "Loading EmDash…" during local dev on Windows.

Comment on lines +57 to +62
// Regression: Vite normalizes module ids to forward slashes (and, on
// case-insensitive filesystems, to their on-disk casing) before calling
// plugin hooks. The Lingui macro transform compares its own id against
// this same alias path with a plain id.startsWith(adminSourcePath) check,
// so a backslash-separated path here makes that check always fail —
// silently skipping macro compilation for every admin source file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] This block is a detailed regression narrative that largely restates the it() title and the production-code comment. Under AGENTS.md, comments should explain a non-obvious "why" to a future reader, not summarize the PR. The test name is already descriptive, so the comment can be removed.

Suggested change
// Regression: Vite normalizes module ids to forward slashes (and, on
// case-insensitive filesystems, to their on-disk casing) before calling
// plugin hooks. The Lingui macro transform compares its own id against
// this same alias path with a plain id.startsWith(adminSourcePath) check,
// so a backslash-separated path here makes that check always fail —
// silently skipping macro compilation for every admin source file.
it("returns the admin source path with forward slashes only", () => {

// so a backslash-separated path here makes that check always fail —
// silently skipping macro compilation for every admin source file.
it("returns the admin source path with forward slashes only", () => {
const config = buildConfig(monorepoDemoRoot);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] This assertion only guards the regression on Windows. On Unix CI the path is already forward-slash, so reverting the normalization would still pass here and the bug could silently regress again. Consider either gating the test to Windows (it.skipIf(process.platform !== "win32")) or adding a stronger assertion that exercises the normalization directly, so the test fails on any platform if the transform comparison breaks.

@pkg-pr-new

pkg-pr-new Bot commented Aug 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2462

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2462

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2462

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2462

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2462

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2462

emdash

npm i https://pkg.pr.new/emdash@2462

create-emdash

npm i https://pkg.pr.new/create-emdash@2462

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2462

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2462

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2462

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2462

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2462

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2462

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2462

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2462

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2462

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2462

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2462

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2462

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2462

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2462

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2462

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2462

commit: 2cdd60d

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/awaiting-author Reviewed; waiting on the author to respond size/S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

React admin UI silently hangs on "Loading EmDash..." — createRoot().render() runs but produces zero DOM output, zero errors, zero network activity

1 participant