From 350ad72782fd0af4774e2b2bbc7954d53c1ed6c8 Mon Sep 17 00:00:00 2001 From: type-two Date: Sun, 12 Jul 2026 13:43:48 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=B1=20LIFESTRUMENT=20=E2=80=94=20the?= =?UTF-8?q?=20gentle=20client=20(viz/life.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A second, self-contained client for the same hub, mobile-first: only the sources about living (bodies, births, weather, sun & moon, play), eight one-tap Vibes instead of a matrix, two macro knobs (ENERGY / DREAM), a breathing orb, and dopamine Moments (somewhere a baby, the sun where you stand, a kickoff). Its own compact Web Audio synth, all routing client-side so it plays against the read-only public hub. Reuses the invite-only /api auth; local sensors (heart BT, device tilt, shake, mic, tap) never touch the hub. No money, no machinery, no cables. Links back to the full instrument; added a "🌱 lifestrument" item to godstrument's sky menu. Built understandβ†’buildβ†’review with an adversarial pass; 21 findings fixed (a11y sliders, zoom, contrast, warm-dawn background, reachable heart connect, pluck de-click, scheduler catch-up cap, moment tuning). test_lifestrument.py guards the whitelist and the money/machinery ban. Co-Authored-By: Claude Fable 5 --- test_lifestrument.py | 117 +++++ viz/index.html | 1 + viz/life.html | 1151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1269 insertions(+) create mode 100644 test_lifestrument.py create mode 100644 viz/life.html diff --git a/test_lifestrument.py b/test_lifestrument.py new file mode 100644 index 0000000..add4c0b --- /dev/null +++ b/test_lifestrument.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Self-check for viz/life.html (Lifestrument). Stdlib only, no deps. + +Asserts the three things that keep the fun page honest: + 1. life.html parses as balanced HTML (well-formed tags). + 2. every hub source it wires is a real config.json signal (or a local me.* one). + 3. the banned families (money & machinery) appear NOWHERE in the file. + +Run: python3 test_lifestrument.py +""" +import json +import os +import re +import sys +from html.parser import HTMLParser + +HERE = os.path.dirname(os.path.abspath(__file__)) +LIFE = os.path.join(HERE, "viz", "life.html") +CONFIG = os.path.join(HERE, "config.json") + +BANNED_PREFIXES = ("crypto.", "fx.", "market.", "econ.", "debt.", "quake.", "planes.", "iss.") +# void elements never need a close tag +VOID = {"area", "base", "br", "col", "embed", "hr", "img", "input", "link", + "meta", "param", "source", "track", "wbr"} + + +class Balance(HTMLParser): + def __init__(self): + super().__init__() + self.stack = [] + self.errors = [] + + def handle_starttag(self, tag, attrs): + if tag not in VOID: + self.stack.append(tag) + + def handle_startendtag(self, tag, attrs): + pass # self-closing, balanced by definition + + def handle_endtag(self, tag): + if tag in VOID: + return + if not self.stack: + self.errors.append(f"stray ") + return + if self.stack[-1] == tag: + self.stack.pop() + elif tag in self.stack: + # close intervening (lenient, like a browser) but flag it + while self.stack and self.stack[-1] != tag: + self.stack.pop() + if self.stack: + self.stack.pop() + else: + self.errors.append(f"unexpected ") + + +def test_balanced_html(html): + p = Balance() + p.feed(html) + assert not p.errors, f"HTML tag errors: {p.errors}" + assert not p.stack, f"unclosed tags: {p.stack}" + print(" ok: HTML is balanced") + + +def test_no_banned(html): + hits = [] + for pref in BANNED_PREFIXES: + # word-ish boundary so 'econ.' doesn't match e.g. 'second.' β€” require a + # quote/backtick/space before the prefix, as our source keys always are. + for m in re.finditer(r'["\'`\s(]' + re.escape(pref), html): + frag = html[m.start():m.start() + 40].replace("\n", " ") + hits.append(f"{pref!r} near: …{frag}…") + assert not hits, "banned money/machinery keys present:\n " + "\n ".join(hits) + print(f" ok: none of {BANNED_PREFIXES} appear") + + +def test_whitelist_valid(html): + signals = set(json.load(open(CONFIG))["signals"].keys()) + # pull the WHITELIST Set literal from the source + m = re.search(r"const WHITELIST = new Set\(\[(.*?)\]\)", html, re.S) + assert m, "could not find WHITELIST Set in life.html" + keys = re.findall(r'"([^"]+)"', m.group(1)) + assert len(keys) >= 20, f"whitelist suspiciously small: {len(keys)} keys" + missing = [k for k in keys if k not in signals] + assert not missing, f"whitelist keys not in config.json signals: {missing}" + # none of the whitelisted keys may themselves be banned + bad = [k for k in keys if k.startswith(BANNED_PREFIXES)] + assert not bad, f"whitelist contains banned keys: {bad}" + print(f" ok: all {len(keys)} whitelist keys exist in config.json, none banned") + + +def test_reuses_real_endpoints(html): + for ep in ("/api/login", "/api/signup", "/api/me", "/api/logout"): + assert ep in html, f"missing expected auth endpoint {ep}" + # exact field names the hub expects + assert "identifier:" in html, "login must send {identifier, password}" + assert "invite:" in html, "signup must send an invite code" + print(" ok: reuses the real /api/* auth endpoints with correct fields") + + +def main(): + html = open(LIFE, encoding="utf-8").read() + print("test_lifestrument.py β€” checking viz/life.html") + test_balanced_html(html) + test_no_banned(html) + test_whitelist_valid(html) + test_reuses_real_endpoints(html) + print("ALL CHECKS PASSED") + + +if __name__ == "__main__": + try: + main() + except AssertionError as e: + print("FAIL:", e) + sys.exit(1) diff --git a/viz/index.html b/viz/index.html index 3291371..f2de019 100644 --- a/viz/index.html +++ b/viz/index.html @@ -6642,6 +6642,7 @@ : "🌐 tab feed cancelled"; eventTicker.unshift({ text: msg, tAdded: now(), src: "sys" }); }); ctxItem("⌨ keys β€” remap the keyboard", () => openKeys()); + ctxItem("🌱 lifestrument β€” the gentle one (new tab)", () => window.open("/life.html", "_blank")); if (commune.room) { const gOwner = commune.alloc["@godtime"]; ctxItem(gOwner === communeMe() ? "πŸ•Έ release the godtime" : diff --git a/viz/life.html b/viz/life.html new file mode 100644 index 0000000..1715456 --- /dev/null +++ b/viz/life.html @@ -0,0 +1,1151 @@ + + + + + + + +lifestrument + + + + + +
+
+
lifestrument
+
+ godstrument β†— + +
+
+
+
Daylight
+
the sun plays the room
+
+ +
+
+
+
+ + +
+ +
+
lifestrument
+
played by being alive
+
your pulse, the weather, the sun going down, a baby born somewhere β€” the world plays, and so do you. no cables, no settings, just a feeling.
+ +
+
+ + +
+
+ + + + +
+
+ + + + + + + +
+
✦ invite-only ✦ ask a friend for a code
+
+ +
+ +
+
+
+ + + +