fix(build): Containerfile builds again — relative module imports, all four pages and shared assets in the image

This commit is contained in:
2026-08-24 22:54:30 -04:00
parent 0adc9b5801
commit d7a4064616
16 changed files with 616 additions and 222 deletions
+36 -17
View File
@@ -127,22 +127,32 @@ def test_nav_sources_is_absent_from_the_viewer() -> None:
def test_sources_and_viewer_carry_the_shared_controls() -> None:
"""Sources AND the document viewer gain the New Chat button + the
Sign in / Sign out pair (both starting hidden — initSharedHeader
reveals exactly one after whoami), and they load header.js."""
reveals exactly one after whoami), and their page scripts load
header.js (phase 23: via the page script's relative import, not a
direct script tag)."""
for html in (SOURCES_HTML, DOCUMENT_HTML):
text = _text(html)
assert 'id="new-chat-btn"' in text
assert re.search(r'id="sign-in-link"[^>]*\bhidden\b', text)
assert re.search(r'id="sign-out-btn"[^>]*\bhidden\b', text)
assert "header.js" in text, "page must load the shared header module"
for js_file in (SOURCES_JS, DOCUMENT_JS):
assert 'from "./header.js"' in _text(js_file), (
f"{js_file.name}: page script must load the shared header module"
)
# Each page's Sign in link returns to ITS OWN page after login.
assert 'href="/login.html?next=/sources.html"' in _text(SOURCES_HTML)
assert 'href="/login.html?next=/document.html"' in _text(DOCUMENT_HTML)
def test_header_module_loads_before_the_page_script() -> None:
"""Every page loads header.js (type=module) BEFORE its page script,
so the sign-out binding and the whoami cache exist when the page
script boots."""
"""Phase 23 (owner-confirmed single-evaluation design): NO page loads
header.js with a direct <script> tag anymore. Each page script
imports it relatively (`from "./header.js"`) — a hoisted import that
the browser evaluates BEFORE the page script body runs, and that the
image bundler inlines into the page bundle. The sign-out binding and
the whoami cache therefore exist when the page script boots, and
header.js can never be evaluated twice on a page (a tag + import pair
would double-bind the sign-out listener)."""
cases = [
(INDEX_HTML, "app.js"),
(SOURCES_HTML, "sources.js"),
@@ -151,12 +161,18 @@ def test_header_module_loads_before_the_page_script() -> None:
]
for html, page_script in cases:
srcs = _script_srcs(html)
header_idx = [i for i, s in enumerate(srcs) if "header.js" in s]
page_idx = [i for i, s in enumerate(srcs) if page_script in s]
assert header_idx, f"{html.name}: must load header.js"
assert page_idx, f"{html.name}: must load {page_script}"
assert header_idx[0] < page_idx[0], (
f"{html.name}: header.js must load before {page_script}"
assert [s for s in srcs if "header.js" in s] == [], (
f"{html.name}: no direct header.js <script> tag (single-evaluation design)"
)
assert [s for s in srcs if page_script in s], (
f"{html.name}: must load {page_script}"
)
js = _text(ASSETS / page_script)
assert 'from "./header.js"' in js, (
f"{page_script}: must import the shared header module relatively"
)
assert 'from "/assets/header.js"' not in js, (
f"{page_script}: absolute header import would break the esbuild bundle"
)
@@ -177,9 +193,10 @@ def test_app_js_delegates_the_shared_controls_to_header_module() -> None:
"""app.js imports the shared module, runs initSharedHeader() at boot
(BEFORE the phase-14 restore), takes its isAdmin from the cached
fetchIsAdmin(), and owns NO whoami fetch and NO sign-out binding
anymore (both moved to header.js)."""
anymore (both moved to header.js). Phase 23: the import is relative
(`./header.js`) so esbuild can bundle it into the image."""
js = _text(APP_JS)
assert 'from "/assets/header.js"' in js
assert 'from "./header.js"' in js
assert "fetchIsAdmin" in js and "initSharedHeader" in js
assert "signOutBtn.addEventListener" not in js, (
"the sign-out binding moved to header.js"
@@ -200,9 +217,10 @@ def test_app_js_delegates_the_shared_controls_to_header_module() -> None:
def test_login_js_uses_the_shared_fetch_is_admin() -> None:
"""login.js switches its whoami check to the shared cached promise
(one request per page) and calls initSharedHeader for the Sources
link; its already-admin → redirect behavior is unchanged."""
link; its already-admin → redirect behavior is unchanged. Phase 23:
the import is relative (`./header.js`)."""
js = _text(LOGIN_JS)
assert 'from "/assets/header.js"' in js
assert 'from "./header.js"' in js
assert "fetchIsAdmin" in js
assert "fetchIsAdmin()" in js
assert "initSharedHeader()" in js
@@ -214,10 +232,11 @@ def test_non_chat_pages_bind_new_chat_to_the_chat_page() -> None:
"""On sources and the viewer, New Chat means "go to the chat,
fresh": the binding clears the phase-14 key (clearChatStorage) and
navigates to "/" — and both pages run initSharedHeader() at boot
on the shared cached whoami."""
on the shared cached whoami. Phase 23: the import is relative
(`./header.js`)."""
for js_file in (SOURCES_JS, DOCUMENT_JS):
js = _text(js_file)
assert 'from "/assets/header.js"' in js
assert 'from "./header.js"' in js
assert "initSharedHeader()" in js
btn_idx = js.find("new-chat-btn")
clear_idx = js.find("clearChatStorage();")