Skip to content

fix(app): full reload risky ui hmr edits#593

Merged
Astro-Han merged 2 commits into
devfrom
slock/solid-hmr-context-loss
May 13, 2026
Merged

fix(app): full reload risky ui hmr edits#593
Astro-Han merged 2 commits into
devfrom
slock/solid-hmr-context-loss

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

Add a narrow Vite dev-only guard in packages/app so high-fanout hot updates from packages/ui/src/* trigger a full reload instead of letting Solid HMR keep a broken provider tree alive:

  • detect workspace UI source edits that fan out across many importer modules
  • force a full reload for those specific updates inside desktop dev
  • cover the guard logic with focused unit tests for path matching, importer counting, and threshold behavior

Why

The root cause is not in PawWork's providers themselves. The failure mode is an upstream Solid/Vite HMR edge case: after a large hot update to a high-fanout workspace UI module, the app can keep rendering with a stale provider graph and throw errors such as Layout context must be used within a context provider.

This PR does not fix that upstream HMR bug. It adds a PawWork-side workaround: when a packages/ui/src/* edit fans out broadly enough to hit that risky path, desktop dev stops trying to apply HMR and falls back to a full reload instead.

The linked upstream issues are related evidence, not a claim that either issue is this exact repro:

  • solidjs/vite-plugin-solid#80 shows a real provider/context breakage across a library boundary in dev
  • solidjs/vite-plugin-solid#106 shows another case where Solid/Vite HMR corruption was avoided by falling back to a full reload

Related Issue

Closes #189.

Human Review Status

Pending. A human should make the final merge decision after reviewing the final diff and verification evidence.

Review Focus

  1. packages/app/vite.js: the new dev-only plugin forces a full reload instead of HMR for risky workspace UI updates
  2. packages/app/vite.hmr.js: the guard stays narrow by checking both source path and importer fanout before switching away from HMR
  3. packages/app/vite.hmr.test.ts: the workaround logic is covered without changing production code paths or app runtime behavior

Risk Notes

This is a workaround, not a root fix:

  • production behavior is unchanged; only desktop dev HMR behavior is touched
  • some high-fanout edits under packages/ui/src/* will now do a full reload instead of preserving in-place HMR state
  • that trade-off costs some dev speed, but it is better than leaving the app in a broken state with missing context providers
  • the threshold stays narrow so ordinary low-fanout UI edits keep normal HMR
  • the current threshold is 30 because the known high-fanout UI tier in packages/app starts around icon (39 imports), button (34), and toast (31); if false positives show up in day-to-day dev, this threshold should be retuned empirically
  • this guard should be removed once Solid/Vite fixes the upstream context/HMR issue or after we upgrade to a version that makes this workaround unnecessary

How To Verify

cd packages/app && bun test ./vite.hmr.test.ts
Result: pass (4 tests, 0 fail)

cd packages/app && bun run typecheck
Result: pass

git diff --check
Result: clean

Screenshots or Recordings

None. Dev-only HMR workaround.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, primary area, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Enhanced hot module replacement logic to optimize reload behavior during development.
  • Tests

    • Added comprehensive test coverage for HMR functionality.
  • Chores

    • Updated build tooling configuration to support improved development experience.

Review Change Stack

@github-actions github-actions Bot added the app Application behavior and product flows label May 13, 2026
@Astro-Han Astro-Han added bug Something isn't working ui Design system and user interface P3 Low priority labels May 13, 2026
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b9c379f1-dbf3-452c-83fa-fea3db361b93

📥 Commits

Reviewing files that changed from the base of the PR and between e1ca90b and b8ff132.

📒 Files selected for processing (2)
  • packages/app/vite.hmr.js
  • packages/app/vite.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/app/vite.js
  • packages/app/vite.hmr.js

📝 Walkthrough

Walkthrough

Adds a Vite plugin and helper module that detect UI-source changes with high importer fanout and force a full page reload (via dev server WS) instead of incremental HMR to avoid context-provider breakage during large UI edits.

Changes

UI HMR Guard for High-Fanout Modules

Layer / File(s) Summary
HMR guard detection logic
packages/app/vite.hmr.js
Exports UI_HMR_FULL_RELOAD_THRESHOLD (30), normalizePath(), isUiSourceFile() for /packages/ui/src/, countUniqueImporters() BFS traversal of module.importers, and shouldForceFullReloadForUiHmr() comparing importer count to a threshold.
HMR guard test suite
packages/app/vite.hmr.test.ts
Bun tests for POSIX/Windows path detection, unique importer counting across a transitive graph, and full-reload vs normal-HMR behavior for high- and low-fanout UI modules.
Vite plugin integration
packages/app/vite.js
Adds opencode-desktop:ui-hmr-guard plugin that calls shouldForceFullReloadForUiHmr() in handleHotUpdate, logs when forcing a reload, and sends { type: "full-reload", path: "*" } over the dev server WebSocket, returning no module updates to trigger a full reload.

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant Vite HMR
  participant ui-hmr-guard Plugin
  participant Detection Function
  participant Dev Server WS
  participant Browser

  Developer->>Vite HMR: Edit packages/ui/src/components/icon.tsx (bulk change)
  Vite HMR->>ui-hmr-guard Plugin: handleHotUpdate({file, modules})
  ui-hmr-guard Plugin->>Detection Function: shouldForceFullReloadForUiHmr({file, modules})
  Detection Function-->>ui-hmr-guard Plugin: true (high importer fanout)
  ui-hmr-guard Plugin->>Dev Server WS: send({type: "full-reload", path: "*"})
  ui-hmr-guard Plugin-->>Vite HMR: return []
  Dev Server WS->>Browser: full-reload message
  Browser->>Browser: Full page reload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled code where icons grow so wide,
A bulk edit shook branches far and wide.
Now when imports fan and HMR might stray,
I hop in and ask the page to reload the day.
Context providers safe — hop, skip, and glide.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a full reload guard for risky UI HMR edits in the app package.
Description check ✅ Passed The description comprehensively covers all required sections: Summary, Why, Related Issue, Review Focus, Risk Notes, How To Verify, and completed Checklist with all items marked complete.
Linked Issues check ✅ Passed All code changes directly address issue #189: the guard detects high-fanout UI source edits and forces full reload, preventing broken provider trees during HMR as required.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: new HMR guard logic in vite.hmr.js, plugin integration in vite.js, and focused test coverage without unrelated refactors.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slock/solid-hmr-context-loss

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/app/vite.hmr.test.ts (1)

27-50: ⚡ Quick win

Add an explicit threshold boundary test (== 30).

You test below and above behavior, but not the exact boundary. Adding one assertion at the threshold will lock in intended >= semantics and guard against off-by-one regressions.

Proposed test addition
+  test("forces full reload at the exact threshold boundary", () => {
+    const leaf = module()
+    leaf.importers = new Set(Array.from({ length: 30 }, () => module()))
+
+    expect(
+      shouldForceFullReloadForUiHmr({
+        file: "/repo/packages/ui/src/components/icon.tsx",
+        modules: [leaf],
+      }),
+    ).toBe(true)
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/vite.hmr.test.ts` around lines 27 - 50, Add a boundary test for
shouldForceFullReloadForUiHmr to cover the exact threshold (30): create a test
named like "forces a full reload at high-fanout boundary (== 30)" that builds a
leaf module, sets leaf.importers = new Set(Array.from({ length: 30 }, () =>
module())), and expects shouldForceFullReloadForUiHmr({ file:
"/repo/packages/ui/src/components/icon.tsx", modules: [leaf] }) toBe(true) to
lock in >=30 semantics and prevent off-by-one regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/app/vite.hmr.test.ts`:
- Around line 27-50: Add a boundary test for shouldForceFullReloadForUiHmr to
cover the exact threshold (30): create a test named like "forces a full reload
at high-fanout boundary (== 30)" that builds a leaf module, sets leaf.importers
= new Set(Array.from({ length: 30 }, () => module())), and expects
shouldForceFullReloadForUiHmr({ file:
"/repo/packages/ui/src/components/icon.tsx", modules: [leaf] }) toBe(true) to
lock in >=30 semantics and prevent off-by-one regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 090046c0-994c-4157-944e-687e87556ef1

📥 Commits

Reviewing files that changed from the base of the PR and between ccd79e9 and e1ca90b.

📒 Files selected for processing (3)
  • packages/app/vite.hmr.js
  • packages/app/vite.hmr.test.ts
  • packages/app/vite.js

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a Vite HMR guard to manage high-fanout UI module edits by triggering a full reload when the number of unique importers exceeds a specific threshold. This implementation includes a new utility for counting transitive importers and a Vite plugin to intercept hot updates, serving as a workaround for upstream Solid/Vite HMR issues. Comprehensive unit tests for the new logic have also been added. I have no feedback to provide.

@github-actions github-actions Bot removed the ui Design system and user interface label May 13, 2026
@Astro-Han Astro-Han merged commit 935396d into dev May 13, 2026
25 checks passed
@Astro-Han Astro-Han deleted the slock/solid-hmr-context-loss branch May 13, 2026 00:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P3 Low priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Solid HMR loses context providers after bulk edits to high-fanout modules

1 participant