Let the panel reach the card by its hashed name, not by the one address without a version

The sidebar page could serve a previous card for hours. It imported the
card through the stable facade, `./houseplan-card.js` — a relative
specifier, and relative resolution does not inherit a query. A dashboard
reaches the same file as `houseplan-card.js?v=1.74.0`, so an upgrade
changes its URL and the browser must refetch. The panel always asked for
the same address, and entries are served with no Cache-Control at all —
only ETag and Last-Modified — so the browser applies heuristic freshness
and may answer from cache without asking. A stale 1164-byte loader names
a stale chunk, chunks are immutable for a year, and the panel then ran a
previous card against the current backend without a single error. The
version banner was telling the truth; reloading could not help, because
the address never changed.

Rollup already emits the right edge: the panel's side-effect import
points straight at the shared implementation. The rewrite in
entryFallbackPlugin replaced it with the facade for a fallback that the
hashed name gives anyway — and better: a chunk the manifest no longer
serves now raises the panel's own "House Plan was updated" notice
instead of silently working on old code.

Two #486 assertions change meaning and are rewritten, not adjusted: the
panel no longer routes through the facade, and its initial graph no
longer contains it. The invariant they defended — the panel reuses the
exact card graph, never a second copy — is now stated over the
implementation, and a new test pins that no built entry reaches the card
by an address without a version.

Issue: #535
User-Visible: yes
This commit is contained in:
Claude
2026-09-11 22:12:11 +00:00
committed by claude[bot]
parent a52dcab77a
commit 063de0eef4
57 changed files with 536 additions and 428 deletions
+9 -4
View File
@@ -63,11 +63,16 @@ export function assertBundleManifest(manifest, label = BUNDLE_MANIFEST) {
if (!initialView.includes(CARD_ENTRY) || initialView.includes(PANEL_ENTRY)) {
throw new Error(`${label}: initial View graph must contain only the card stable entry`);
}
if (!initialPanel.includes(CARD_ENTRY) || !initialPanel.includes(PANEL_ENTRY)) {
throw new Error(`${label}: initial panel graph must contain both stable entries`);
// #535: the panel reaches the implementation by its content-hashed name, so
// the card's stable facade is no longer part of what the panel loads. That is
// the point: the facade is the one address with no version in it, and the
// panel must not depend on a URL a browser may keep for hours. What still has
// to hold is that the panel reuses the exact card IMPLEMENTATION graph.
if (initialPanel.includes(CARD_ENTRY) || !initialPanel.includes(PANEL_ENTRY)) {
throw new Error(`${label}: initial panel graph must contain its own stable entry only`);
}
if (initialView.some((path) => !initialPanel.includes(path))) {
throw new Error(`${label}: initial View graph is not a subset of initial panel graph`);
if (initialView.some((path) => path !== CARD_ENTRY && !initialPanel.includes(path))) {
throw new Error(`${label}: initial View implementation is not a subset of initial panel graph`);
}
const expectedPanelOnly = initialPanel
.filter((path) => !initialView.includes(path))