show sync button for admin
Build and Push Containers / build-and-push-app (push) Successful in 1m38s
Build and Push Containers / build-and-push-db (push) Successful in 11s

This commit is contained in:
2026-08-28 23:24:23 -04:00
parent 3a404eb161
commit 6bf7f456d4
17 changed files with 368 additions and 284 deletions
+2 -2
View File
@@ -47,7 +47,7 @@ Test → story mapping (Playwright Mapping Rule):
::before``/``::after`` spots): ``position: fixed``, ``z-index: -1``,
``pointer-events: none``, ``inset: 0`` (UI Structure Check: behind
content, click-through, full-viewport); the page canvas stays on
``<html>`` (``rgb(10, 14, 23)`` = ``var(--bg)``) and ``<body>``
``<html>`` (``rgb(15, 10, 10)`` = ``var(--bg)``) and ``<body>``
stays transparent (``rgba(0, 0, 0, 0)``) — the no-occlusion
contract.
5. ``test_no_horizontal_overflow_with_layers`` — at a 360px viewport
@@ -71,7 +71,7 @@ from playwright.sync_api import Browser, Page
GRID_OLD = "bg-grid-drift" # deleted in phase 25 — must not exist anywhere
GLOWS = ("bg-glow-a", "bg-glow-b", "bg-glow-c") # the three phase-25 spot fades
PAGE_BG = "rgb(10, 14, 23)" # var(--bg) — the <html> canvas (phase-08 palette)
PAGE_BG = "rgb(15, 10, 10)" # var(--bg) — the <html> canvas (dark-red rebrand #0f0a0a)
# Computed styles of all four pseudo-layers + the html/body background
# contract (single evaluate — one round-trip per test).
+2 -2
View File
@@ -67,7 +67,7 @@ Test → story mapping (Playwright Mapping Rule):
6. ``test_background_layers_contracts`` — all four pseudo-layers:
``position: fixed``, ``z-index: -1``, ``pointer-events: none``,
top/right/bottom/left all ``0px``; the ``documentElement`` computed
background is ``rgb(10, 14, 23)`` (``var(--bg)`` — the canvas stays
background is ``rgb(15, 10, 10)`` (``var(--bg)`` — the canvas stays
on ``html``); ``document.body`` computed background is
``rgba(0, 0, 0, 0)`` (no occlusion).
7. ``test_reduced_motion_stills_all_layers`` —
@@ -99,7 +99,7 @@ GLOW_B = "bg-glow-b" # html::before — phase-08 cyan spot, 34s, -12s delay
GLOW_C = "bg-glow-c" # html::after — third indigo spot, 42s, -23s delay
GLOWS = (GLOW_A, GLOW_B, GLOW_C) # the three spot keyframe names
EASE_IN_OUT = "ease-in-out" # computed animationTimingFunction for ease-in-out
PAGE_BG = "rgb(10, 14, 23)" # var(--bg) — the <html> canvas (phase-08 palette)
PAGE_BG = "rgb(15, 10, 10)" # var(--bg) — the <html> canvas (dark-red rebrand #0f0a0a)
# Computed styles of all four pseudo-layers + the html/body background
# contract (single evaluate — one round-trip per test).
+5 -5
View File
@@ -272,13 +272,13 @@ def test_persists_across_page_navigation(
_ask_deflected(page, OFF_TOPIC) # mixed conversation: grounded + deflected
# A trip to Sources (phase 16: the catalog is admin-only — the trip
# starts with a real form login). Phase 19 (owner permission
# 2026-08-23): the New chat control is part of the SHARED bar, so it
# is present on the sources page too — only clicking it would clear
# the conversation (pinned in tests/e2e/test_shared_header.py).
# starts with a real form login). The New chat button is chat-page
# only (owner rework 2026-08-28 — it left the shared bar), so the
# sources page carries none of it (pinned in
# tests/e2e/test_shared_header.py).
login(page, app_url, next="/sources.html")
expect(page.locator("#docs-tbody tr").first).to_be_visible(timeout=15_000)
expect(page.locator("#new-chat-btn")).to_be_visible()
expect(page.locator("#new-chat-btn")).to_have_count(0)
# Back to the chat: the conversation is exactly as left — both turns,
# the source chip, and the amber deflected bubble with its chips.
+6 -4
View File
@@ -173,13 +173,15 @@ def test_dark_palette_and_contrast(
>= 4.5:1 from live computed styles (not eyeballed)."""
for path in ("/", "/sources.html"):
page.goto(f"{app_url}{path}")
# The visible page background is the <html> canvas (rgb(10, 14, 23)
# = #0a0e17). <body> itself stays transparent so the z-index:-1
# grid/glow layers (test_animated_background) are not painted over.
# The visible page background is the <html> canvas (rgb(15, 10, 10)
# = #0f0a0a — the 2026-08-28 dark-red rebrand; it was the phase-08
# #0a0e17 canvas). <body> itself stays transparent so the
# z-index:-1 grid/glow layers (test_animated_background) are not
# painted over.
bg = page.evaluate(
"() => getComputedStyle(document.documentElement).backgroundColor"
)
assert bg == "rgb(10, 14, 23)", f"expected the dark page bg on {path}, got {bg}"
assert bg == "rgb(15, 10, 10)", f"expected the dark page bg on {path}, got {bg}"
body_bg = page.evaluate("() => getComputedStyle(document.body).backgroundColor")
assert body_bg == "rgba(0, 0, 0, 0)", (
f"{path}: body must stay transparent (the background layers need to show)"
+6 -5
View File
@@ -370,7 +370,7 @@ def test_standalone_page_still_works(
page.goto(f"{app_url}/document.html?source=docs&path=homelab%2Fkubernetes.md")
expect(page.locator("#doc-content .doc-md")).not_to_be_empty()
bg = page.evaluate("() => getComputedStyle(document.documentElement).backgroundColor")
assert bg == "rgb(10, 14, 23)"
assert bg == "rgb(15, 10, 10)", "the dark-red rebrand canvas (#0f0a0a)"
refs = page.evaluate(
"""() => [...document.querySelectorAll("script[src], link[href]")]
@@ -409,14 +409,15 @@ def test_modal_theme_and_no_cdn(
expect(page.locator("#doc-modal-title")).to_have_text("Kubernetes Homelab Cluster")
expect(page.locator("#doc-modal-content .doc-md")).not_to_be_empty()
# Dark theme (phase 08): the page background is untouched, and the
# modal panel sits on the Phase-08 surface colour (#121a2e).
# Dark theme (phase 08, dark-red rebrand 2026-08-28): the page
# background is untouched, and the modal panel sits on the --surface
# colour (#1a0f0f).
bg = page.evaluate("() => getComputedStyle(document.documentElement).backgroundColor")
assert bg == "rgb(10, 14, 23)"
assert bg == "rgb(15, 10, 10)"
surface = page.evaluate(
"() => getComputedStyle(document.querySelector('.doc-modal-panel')).backgroundColor"
)
assert surface == "rgb(18, 26, 46)", f"panel not on the Phase-08 surface: {surface}"
assert surface == "rgb(26, 15, 15)", f"panel not on the --surface colour: {surface}"
# No-CDN: every script/link reference on the chat page (the touched
# page) is same-origin or a data: URI — the modal adds no assets.
+4 -3
View File
@@ -416,12 +416,13 @@ def test_tuning_page_a11y_and_no_cdn(
assert announcer.get_attribute("role") == "status"
assert announcer.get_attribute("aria-live") == "polite"
# ≥44px touch targets on every interactive control.
# ≥44px touch targets on every interactive control (the New chat
# button is chat-page only — owner rework 2026-08-28 — so it is not
# a tuning-page target).
for selector in (
"#tune-save",
"#tune-note",
"#nav-tuning",
".new-chat-btn",
"#sign-out-btn",
".tuning-edit",
".tuning-delete",
@@ -447,7 +448,7 @@ def test_tuning_page_a11y_and_no_cdn(
# Dark theme: the page canvas is the phase-08 dark bg (body stays
# transparent for the background layers).
bg = page.evaluate("() => getComputedStyle(document.documentElement).backgroundColor")
assert bg == "rgb(10, 14, 23)", f"expected the dark page bg, got {bg}"
assert bg == "rgb(15, 10, 10)", f"expected the dark page bg (rebrand #0f0a0a), got {bg}"
# Contrast: the live text/background pairs compute ≥ 4.5:1 (AA).
pairs = page.evaluate(
+3 -2
View File
@@ -114,8 +114,9 @@ def test_off_topic_question_deflects_honestly(
expect(group.first).to_have_attribute("role", "list")
# Chip component contract: brand pill, ≥44px touch target.
chip_style = chips.first.evaluate("el => getComputedStyle(el)")
assert chip_style["backgroundColor"] == "rgb(35, 43, 82)" # --brand-soft #232b52 (dark theme)
assert chip_style["color"] == "rgb(165, 180, 252)" # --brand-ink #a5b4fc
# --brand-soft #2d0a0a / --brand-ink #fca5a5 (dark-red rebrand)
assert chip_style["backgroundColor"] == "rgb(45, 10, 10)"
assert chip_style["color"] == "rgb(252, 165, 165)"
box = chips.first.bounding_box()
assert box is not None and box["height"] >= 44
+8 -10
View File
@@ -328,8 +328,9 @@ def test_completed_turn_unaffected(
# ---------------------------------------------------------------------------
# 4. The DELIBERATE clear is untouched: New Chat from the sources page
# still clears the conversation (phase 14/19 contract)
# 4. The DELIBERATE clear is untouched: New chat (chat-page only since
# the owner rework 2026-08-28) still clears the conversation
# (phase 14 contract)
# ---------------------------------------------------------------------------
@@ -341,18 +342,15 @@ def test_new_chat_still_clears_conversation(
_ask(page, QUESTION)
assert _stored(page) is not None
# To the sources page (anonymous is fine — the shared bar carries the
# New Chat control regardless of auth, phase 19).
page.goto(app_url + "/sources.html")
# The New chat button is chat-page only (it left the shared bar at
# owner request, 2026-08-28) — the deliberate clear runs right here.
new_chat = page.locator("#new-chat-btn")
expect(new_chat).to_be_visible()
new_chat.click()
# "New chat" on a non-chat page means "go to the chat, fresh": the
# land page shows the empty state and the conversation key is GONE —
# the deliberate clearChatStorage() is unaffected by the phase-20
# pagehide save point.
expect(page).to_have_url(app_url + "/")
# The chat lands on its empty state and the conversation key is
# GONE — the deliberate clear is unaffected by the phase-20 pagehide
# save point.
expect(page.locator("#empty-state")).to_be_visible()
expect(page.locator(".msg")).to_have_count(0)
assert _stored(page) is None, "New Chat must clear the localStorage key"
+2 -2
View File
@@ -108,8 +108,8 @@ def test_onboarding_chips_render(
expect(page.locator("#empty-state")).to_be_visible()
# Chip component contract: brand pill, >=44px touch target.
style = chips.first.evaluate("el => getComputedStyle(el)")
assert style["backgroundColor"] == "rgb(35, 43, 82)" # --brand-soft #232b52 (dark theme)
assert style["color"] == "rgb(165, 180, 252)" # --brand-ink #a5b4fc
assert style["backgroundColor"] == "rgb(45, 10, 10)" # --brand-soft #2d0a0a (dark-red rebrand)
assert style["color"] == "rgb(252, 165, 165)" # --brand-ink #fca5a5 (dark-red rebrand)
assert style["borderRadius"] == "999px"
box = chips.first.bounding_box()
assert box is not None and box["height"] >= 44
+5 -4
View File
@@ -181,15 +181,16 @@ def test_full_page_shows_summary_and_original_together(
assert order[0] == "doc-summary", f"panel not first: {order}"
assert "doc-raw" in order and order.index("doc-raw") > order.index("doc-summary")
# Theme (phase 08): panel on the surface colour, title in brand-ink
# (#a5b4fc on #121a2e ≈8.7:1 ≥4.5:1), and the panel matches the
# Theme (phase 08, dark-red rebrand 2026-08-28): panel on the
# surface colour, title in brand-ink (--brand-ink #fca5a5 on
# --surface #1a0f0f = 9.0:1 ≥4.5:1), and the panel matches the
# raw-content width — it does not break the content layout.
bg = panel.evaluate("el => getComputedStyle(el).backgroundColor")
assert bg == "rgb(18, 26, 46)", f"panel not on the Phase-08 surface: {bg}"
assert bg == "rgb(26, 15, 15)", f"panel not on the --surface colour (rebrand #1a0f0f): {bg}"
title_color = panel.locator(".doc-summary-title").evaluate(
"el => getComputedStyle(el).color"
)
assert title_color == "rgb(165, 180, 252)", f"title not brand-ink: {title_color}"
assert title_color == "rgb(252, 165, 165)", f"title not brand-ink: {title_color}"
panel_box = panel.bounding_box()
pre_box = pre.bounding_box()
assert panel_box is not None and pre_box is not None
+5 -4
View File
@@ -376,10 +376,11 @@ def test_sync_error_surfaces_unaffected(
app) the ``#sync-error-banner`` still renders the error off
``bor:sync-status`` — the phase-32/34 contract the modal is
additive to."""
# Trigger the failure on the chat page (the modal is module-owned —
# it follows the button onto every page). The re-attach modal from
# the prior test's last failure closes first (clean trigger state).
login(page, dead_app_url, next="/")
# Trigger the failure on the Sources page (the button's home since
# the owner rework 2026-08-28; the modal is page-local there). The
# re-attach modal from the prior test's last failure closes first
# (clean trigger state).
login(page, dead_app_url)
_dismiss_reattached_modal(page)
_trigger_dead_failure(page, dead_app_url)