A phone, and how much of this could not be used on one
The sidebar was a 280px panel laid over the page below the phone breakpoint, opened from first paint, with the only control that closed it underneath it -- and that control existed on /chat and on none of the seven other pages carrying a sidebar, Settings included. It starts closed at that width now, slides, dims the page behind it, and closes by tapping beside it, by Escape, or by its own button, which is inside the drawer where it can be reached. Everything a finger has to hit was 36px, or 28 for renaming a chat, every action on a message and every panel's close button. Raising --control-h under a coarse pointer is the only fix that reaches all forty of them, which is what that token is for. The row and message actions were also hover-only, so on a phone they did not exist at all. Installing: the splash and the browser chrome follow the instance's theme rather than always being Moria's near-black; there are screenshots, so the install offer is a dialog rather than a one-line bar; a new release no longer takes over a page somebody is reading; the notification badge is a silhouette rather than a grey square; and a browser rotating its own subscription no longer ends notifications for good. Every request now says it is happening -- nothing did before, so anything slower than a few milliseconds looked like a click that had not registered. A chat can be archived. The column has been filtered on in four places since folders arrived and written by nothing, which is what made it look built. chat.css may contain media queries. The ban protected the composer toolbar from being "fixed" with a breakpoint; that guarantee is asserted directly now, and the old test would have passed a version of the file that wrapped the toolbar without one. scripts/shoot.py is the instrument all of this was found with: it renders a page through TestClient into a real headless browser at a real size and refuses to run if an asset URL was left pointing at testserver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,3 +165,102 @@ def test_the_mic_appears_only_when_dictation_is_configured(
|
||||
key=settings_store.AUDIO,
|
||||
)
|
||||
assert "data-mic" in client.get("/chat").text
|
||||
|
||||
|
||||
# --- The manifest, beyond the installability minimum -------------------------
|
||||
def test_the_manifest_offers_launcher_shortcuts(client: TestClient):
|
||||
"""A long-press on the launcher icon should reach the three places worth
|
||||
going to directly. Absent, it offers nothing."""
|
||||
payload = client.get("/manifest.webmanifest").json()
|
||||
urls = {s["url"] for s in payload["shortcuts"]}
|
||||
assert urls == {"/chat", "/messages", "/scheduled"}
|
||||
|
||||
|
||||
def test_the_manifest_identity_matches_where_it_starts(client: TestClient):
|
||||
"""`id` was "/", which serves nothing but a redirect, while the app started
|
||||
at /chat. Legal, and it reads as a mistake to anyone comparing the two."""
|
||||
payload = client.get("/manifest.webmanifest").json()
|
||||
assert payload["id"] == payload["start_url"]
|
||||
|
||||
|
||||
def test_the_manifest_declares_the_rest_of_the_quality_set(client: TestClient):
|
||||
payload = client.get("/manifest.webmanifest").json()
|
||||
for key in ("orientation", "categories", "lang", "dir",
|
||||
"display_override", "launch_handler"):
|
||||
assert key in payload, key
|
||||
|
||||
|
||||
def test_the_splash_follows_the_instance_theme(client: TestClient, monkeypatch):
|
||||
"""It was Moria's near-black whatever the instance was set up in, so a
|
||||
parchment instance installed to a phone flashed dark and opened light --
|
||||
and `THEME_COLOUR["shire"]` sat beside it, defined and read by nothing."""
|
||||
from lembas.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "default_theme", "shire")
|
||||
payload = client.get("/manifest.webmanifest").json()
|
||||
assert payload["theme_color"] == "#F6F1E4"
|
||||
assert payload["background_color"] == payload["theme_color"]
|
||||
|
||||
|
||||
def test_the_page_paints_the_right_chrome_before_any_script_runs(client, registered):
|
||||
"""One unscoped `theme-color` meant a light-theme reader got dark browser
|
||||
chrome on every load until the deferred script corrected it."""
|
||||
page = client.get("/chat").text
|
||||
assert 'media="(prefers-color-scheme: dark)"' in page
|
||||
assert 'media="(prefers-color-scheme: light)"' in page
|
||||
|
||||
|
||||
# --- The worker --------------------------------------------------------------
|
||||
def test_the_worker_does_not_take_over_a_page_being_read():
|
||||
"""It called skipWaiting() unconditionally, so a release replaced the
|
||||
assets under an open tab mid-session. It waits to be asked now."""
|
||||
import re
|
||||
|
||||
source = (STATIC_DIR / "js" / "sw.js").read_text()
|
||||
# Comments stripped first: the install handler explains at length that it
|
||||
# deliberately does not call this, and a test that reads prose would fail
|
||||
# on the explanation for the fix.
|
||||
code = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
|
||||
code = re.sub(r"//[^\n]*", "", code)
|
||||
install = code.split('addEventListener("install"', 1)[1].split("addEventListener(", 1)[0]
|
||||
assert "skipWaiting" not in install
|
||||
assert 'event.data.type === "SKIP_WAITING"' in code
|
||||
|
||||
|
||||
def test_the_worker_survives_a_rotated_subscription():
|
||||
"""A browser replacing a subscription on its own is the normal way push
|
||||
stops working, and nothing anywhere said so."""
|
||||
source = (STATIC_DIR / "js" / "sw.js").read_text()
|
||||
assert "pushsubscriptionchange" in source
|
||||
|
||||
|
||||
def test_the_badge_is_not_the_full_colour_icon():
|
||||
"""A badge is drawn as a mask -- the device keeps the alpha and throws the
|
||||
colour away -- so an icon opaque to its edges renders as a grey square."""
|
||||
source = (STATIC_DIR / "js" / "sw.js").read_text()
|
||||
assert 'badge: "/static/img/badge-72.png"' in source
|
||||
badge = Path(STATIC_DIR) / "img" / "badge-72.png"
|
||||
assert badge.exists() and badge.read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
|
||||
def test_the_two_icons_a_device_crops_are_cached():
|
||||
source = (STATIC_DIR / "js" / "sw.js").read_text()
|
||||
shell = source.split("var SHELL = [", 1)[1].split("];", 1)[0]
|
||||
assert "icon-maskable-512.png" in shell
|
||||
assert "apple-touch-icon-180.png" in shell
|
||||
|
||||
|
||||
# --- The window's own edges --------------------------------------------------
|
||||
def test_the_page_asks_for_the_whole_screen_and_then_pays_for_it(client, registered):
|
||||
"""`viewport-fit=cover` is what makes `env(safe-area-inset-*)` resolve to
|
||||
anything but zero, and `black-translucent` below it is what puts the page
|
||||
under the status bar in the first place. One without the other is a topbar
|
||||
beneath the clock."""
|
||||
page = client.get("/chat").text
|
||||
assert "viewport-fit=cover" in page
|
||||
|
||||
css = (STATIC_DIR / "css" / "tokens.css").read_text()
|
||||
assert "safe-area-inset-top" in css
|
||||
app = (STATIC_DIR / "css" / "app.css").read_text()
|
||||
assert "var(--safe-top)" in app
|
||||
assert "var(--safe-bottom)" in app
|
||||
|
||||
Reference in New Issue
Block a user