show sync button for admin
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user