Wave 9 #122.d.ii — Phase 6 Tailscale wizard QR code (mobile pairing) - #119
Open
bernardc6 wants to merge 1 commit into
Open
Wave 9 #122.d.ii — Phase 6 Tailscale wizard QR code (mobile pairing)#119bernardc6 wants to merge 1 commit into
bernardc6 wants to merge 1 commit into
Conversation
User-facing: When RoamCore is ready for me to pair my phone with the van, the dashboard shows a small black-and-white square I can scan with the Tailscale app on my phone — so I don't have to type a long secret code by hand. Context Phase 6 of the 2026-08-03 Product Build Directive calls for 'Guided Tailscale onboarding — wizard, QR, connectivity test, remote PWA access, troubleshooting.' The Phase 6 wizard package (roamcore_remote_access_setup.yaml, sub-slice #122.a) already wires Path A (Tailscale) end-to-end + Path B/C/D honest stubs; this slice adds the QR step that the directive §'Phase 6 delivery' calls out ('QR codes, one-click links, clear explanations, no RoamCore-operated VPN control plane'). The QR tile surfaces a 'tailscale login' URL of the form 'https://login.tailscale.com/a/<device_key>' that the operator scans with the Tailscale app on their phone instead of typing the long auth key by hand. If the QR ever fails (no qrcode dep, display too small, etc.), a plain-English copy-paste block with the same URL surfaces automatically — fail-softly recovery per Bernard's 'must not fail + super intuitive + critical infrastructure' doctrine. Scope (additive + isolated — no overlap with in-flight slices #122.a/#122.b/#122.c/#122.d.i which all live in roamcore_remote_access_setup.yaml): NEW homeassistant/packages/roamcore_tailscale_qr.yaml The new wizard-extension package. Adds the QR tile with: - 4 input_text helpers (device_key [mode: password], login_url, svg_path, nonce). - 1 input_button (qr_regenerate). - 1 binary_sensor (rc_tailscale_qr_visible — true when stage in [tailscale_paste_key, tailscale_done] AND key set). - 1 template sensor (rc_tailscale_qr_status — plain-English status string for the dashboard tile). - 1 shell_command (qr_render — invokes the stdlib generator). - 2 §8 MANDATORY AUTOMATIONS: §8.Q.1 qr_compute_login_url — stage transition → URL build + persist. §8.Q.2 qr_regenerate_on_request — button press → nonce increment + shell_command invocation. Tier-b recipe over a self-contained Python QR generator (pure stdlib, no qrcode / segno / qrencode dep, no RoamCore-operated VPN control plane). NEW homeassistant/packages/scripts/qr_generator.py Self-contained pure-Python QR encoder (stdlib-only). Implements the byte-mode + Reed-Solomon EC + mask pattern 0 + SVG emission. Writes a valid SVG with the right viewBox + deterministic per-pixel modules. Tested via '--self-test' (deterministic + valid for 3 sample payloads). Verifies: viewBox='0 0 256 256' + ≥1 dark module + idempotent (same input → identical SVG) + gracefully rejects payloads beyond version 10 capacity. No third-party deps (paranoid import check in the smoke). NEW homeassistant/packages/tests/test_tailscale_qr.py pytest rig (30 contract tests, all PASS): - (a) YAML parse + 4 input_text + input_button + binary_sensor + sensor + shell_command presence. - rc-entity-naming compliance (every entity_id starts with 'rc_tailscale_qr_'). - Device key is 'mode: password' (never logged). - YAML is idempotent (re-parsing produces the same dict). - (b) §8 Q.1 + §8 Q.2 automation contracts (stage transition + button press triggers, URL build + shell_command call actions). - (c) Secrets-leak grep (no tskey- / ts-auth- / hardcoded device-key patterns in any file). - (d) QR generator is stdlib-only (AST import scan). - QR generator produces valid SVG with correct viewBox + ≥1 dark module. - QR generator is idempotent. - QR generator handles over-long payloads gracefully (ValueError, NOT crash). - parse_svg returns structured info (viewBox, dark_modules, size_modules). - (e) IKEA doc has 5 sections (§1..§5) + no operator jargon in §1..§4 (no 'integration', 'entity', 'shell_command', 'automation', 'input_text', 'binary_sensor', 'template sensor'). - No internal-speak references (no 'cron', 'sub-agent', 'Wave 9', etc.). - (f) Tile visibility contract (only on the right stages). - (g) Kitchen-sink (every contract marker present). NEW scripts/checks/tailscale-qr-smoke.sh bash smoke wrapper (16 assertions, all PASS): - 4 file-presence checks (package + pytest + generator + doc). - YAML parse. - rc-entity-naming pre-check (every helper + automation + template binary_sensor + template sensor + automation id prefixed with rc_tailscale_qr_). - Helper/automation contract (4 input_text + button + 2 automations + shell_command, device_key mode:password). - Secrets-leak check (no tskey- / ts-auth- patterns). - YAML idempotency probe. - QR generator: stdlib-only check (AST import scan) + self-test (exits 0) + canonical-Tailscale-URL render (valid SVG with right viewBox + ≥1 dark module). - IKEA doc shape check (5 sections + no jargon in §1..§4 + no internal-speak references). - Pytest rig (test_tailscale_qr.py). NEW docs/setup/tailscale-qr.md IKEA 5-step user-facing guide: §1 What this is (one sentence plain English). §2 What you see. §3 What you do (5 numbered steps: open Tailscale app → point phone at square → approve → done). §4 What to do if it goes wrong (copy-paste URL fallback, regenerate button, etc.). §5 Useful links (developer plumbing references — internal to the doc). Operator→vanlifer translation: integration → connection; entity → device; shell_command → action; automation → rule; input_text → settings box; binary_sensor → on/off indicator; template sensor → what your dashboard shows. MOD homeassistant/packages/roamcore_setup_wizard.yaml (+24 lines) Adds 1 §8 helper automation: - rc_setup_register_tailscale_qr_device_key: copies input_text.rc_tailscale_auth_key (in the remote-access-setup wizard) into input_text.rc_tailscale_qr_device_key (in the new QR package) on stage transition to tailscale_paste_key. One automation, idempotent across re-fires. The remote-access wizard owns the secret; the QR package owns the public URL — this glues them without rewiring either package. MOD scripts/check.sh (+11 lines) Wires the new tailscale-qr-smoke.sh + test_tailscale_qr.py into the core-only chain via the run_if_present pattern. No other chain entries touched. Verification - bash scripts/check.sh --core-only → exit 0, GREEN. - bash scripts/checks/tailscale-qr-smoke.sh → 16/16 PASS. - cd homeassistant/packages && python3 -m pytest tests/test_tailscale_qr.py -v → 30/30 PASS. - python3 homeassistant/packages/scripts/qr_generator.py 'https://login.tailscale.com/a/test-abc123' 256 /tmp/test-qr.svg → produces valid SVG with viewBox='0 0 256 256' + 210 dark modules + 211 total rects (xml.etree.ElementTree parses cleanly). Visual sanity check (the render-on-host path): $ python3 homeassistant/packages/scripts/qr_generator.py \ 'https://login.tailscale.com/a/test-abc123' 256 /tmp/test-qr.svg OK: /tmp/test-qr.svg $ head -c 200 /tmp/test-qr.svg <?xml version='1.0' encoding='UTF-8'?>\n<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' width='256' height='256' ... $ grep -c '<rect' /tmp/test-qr.svg 211 # 210 dark modules + 1 white background rect Doctrine applied Bernard's 2026-08-04 doctrine ('must not fail + super intuitive + critical infrastructure'), expanded 2026-08-04 21:18 UTC chat #7580 — applied: 1. Verification is mandatory (real pytest rig + smoke). The QR payload parses as a valid 'tailscale login' URL; the rendered SVG has the correct size (viewBox='0 0 256 256') + dark modules (210 cells on the 21-57 module grid); the wizard surfaces the QR tile only when stage == tailscale_paste_key OR stage == tailscale_done (verified by test_qr_visible_only_at_correct_stages). 2. Auto-recover (graceful fallback). If QR generation fails (no qrcode dep, qr_generator.py returns exit 2 on overflow, etc.), sensor.rc_tailscale_qr_status surfaces 'QR unavailable — use the URL below' AND the dashboard falls back to the plain-English copy-paste block with the same URL. No locked state. 3. Plain-English errors ('Your phone can't see this QR code — copy this URL instead') — no raw Python stack trace surfaces on the dashboard tile. Status template strings are all one-sentence plain English. 4. Idempotent installers (pytest asserts uniqueness). Every helper has a unique_id; the YAML is idempotent on re-parse (smoke asserts this); the QR generator is bit-deterministic (same input → byte-for-byte same output). 5. Backup-before-mutate. This slice touches YAML + pytest only — no Proxmox / HA / OpenWrt / networking. Pure repo-local code. No state to back up; rollback is a single 'git revert'. 6. Tier discipline. Tier-b recipe. The QR generator is pure stdlib (no extra dep), no RoamCore-operated VPN control plane (the directive §'Phase 6 delivery' is explicit on that). 7. User-facing repo hygiene. IKEA 5-step user doc lives in docs/setup/ (not memory/ or Cron-handoff/). Developer plumbing in homeassistant/packages/ + tests/ + scripts/checks/. No SUPERSEDED banners in user tree. No Cron-handoff references in user copy. No tier-letter exposition in user copy. GOLDEN.md alignment (re-read 2026-08-06 21:22 UTC) Product principles served: P1 Novice-first UX — the wizard surfaces a one-tap QR tile + a fallback copy-paste block. No YAML editing, no entity IDs in any user-facing string, no jargon in the docs/runbooks. IKEA 5-step user guide with one plain-English opener. P2 Mission-critical connectivity — the QR tile is the operator- friendly shortcut to remote access. The copy-paste fallback is the operational twin of fail-softly UI when the camera won't scan (e.g. cracked screen, dirty fingerprint sensor). P6 OpenClaw as a first-class citizen — binary_sensor rc_tailscale_qr_visible + sensor rc_tailscale_qr_status are readable from OpenClaw ('Is the QR ready?' / 'Show me the QR status'). Engineering principles respected: E1 Customer-facing repo — no internal logs, no sensitive infra. The user doc lives in docs/setup/; the runbook-style §5 in the doc references the internal locations only. E2 Documentation-driven — the IKEA doc is the user-facing source of truth; the pytest rig is the source of truth for the YAML contract; the smoke is the source of truth for the chain. E3 Backup + rollback discipline — no Proxmox / HA / OpenWrt / networking change. Pure repo-local code + data. Rollback is a single 'git revert' with no state to recover. E4 Git fast mode — direct-to-branch push on subagent/phase6-tailscale-qr-code. One PR opened via 'gh pr create'. E5 Safety rule: do not touch vmbr0 — no networking config touched. E7 Naming follows rc-entity-naming.md — every new entity_id starts with 'rc_tailscale_qr_'. The new subsystem is OWNED by 'homeassistant/packages/roamcore_tailscale_qr.yaml'. Anti-patterns explicitly avoided: - ❌ Hand-configuring Victron instead of using capability discovery → N/A: this slice is about a wizard step, not device discovery. - ❌ Touching vmbr0 without explicit Bernard override → no networking config touched. - ❌ Committing secrets or HA tokens to the repo → input_text.rc_tailscale_qr_device_key is 'mode: password' (operator-entered in HA, never committed to YAML). Smoke + pytest assert no tskey- / ts-auth- patterns in any committed file. - ❌ Adding 'advanced' features before the novice path is solid → the QR step IS the novice path; advanced operators can skip it (the URL is also exposed). - ❌ Wide PRs that mix networking + UI + infra changes → pure Phase 6 / wizard extension scope; 7 files +1/-0 in a single additive commit. - ❌ Pulling in unrelated project context → strictly Wave 9 #122.d.ii scope; no overlap with #122.a/#122.b/#122.c/#122.d.i (they all live in roamcore_remote_access_setup.yaml). - ❌ Putting internal engineering logs on the public GitHub → user-facing doc has no SUPERSEDED banners, no Cron-handoff references, no Wave 9 mention. User-facing: When RoamCore is ready for me to pair my phone with the van, the dashboard shows a small black-and-white square I can scan with the Tailscale app on my phone — so I don't have to type a long secret code by hand. Rollback git revert <this sha> undoes the QR package + tests + smoke + doc + setup wizard helper + check.sh wiring in a single commit. No state to recover; nothing touches Proxmox / HA / OpenWrt / vmbr0 / openclaw / sensitive infra. The pre-revert state is just the Phase 6 wizard package without the QR tile; everything else (the auth-key, the wizard stages, the recovery automation) is untouched.
| if col_left < 0: | ||
| break | ||
| for col in (col_right, col_left): | ||
| if 0 <= row < size and 0 <= col < size: |
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import math |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this slice does
User-facing: When RoamCore is ready for me to pair my phone with the van, the dashboard shows a small black-and-white square I can scan with the Tailscale app on my phone — so I don't have to type a long secret code by hand.
Context
Phase 6 of the 2026-08-03 Product Build Directive calls for 'Guided Tailscale onboarding — wizard, QR, connectivity test, remote PWA access, troubleshooting.' The Phase 6 wizard package (
roamcore_remote_access_setup.yaml, sub-slice #122.a) already wires Path A (Tailscale) end-to-end + Path B/C/D honest stubs; this slice adds the QR step that the directive §'Phase 6 delivery' calls out ('QR codes, one-click links, clear explanations, no RoamCore-operated VPN control plane').The QR tile surfaces a
tailscale loginURL of the formhttps://login.tailscale.com/a/<device_key>that the operator scans with the Tailscale app on their phone instead of typing the long auth key by hand. If the QR ever fails (noqrcodedep, display too small, etc.), a plain-English copy-paste block with the same URL surfaces automatically — fail-softly recovery per Bernard's 'must not fail + super intuitive + critical infrastructure' doctrine.Files
homeassistant/packages/roamcore_tailscale_qr.yaml— the new wizard-extension package (167 LOC): 4 input_text + 1 input_button + 1 binary_sensor + 1 template sensor + 1 shell_command + 2 §8 MANDATORY AUTOMATIONS.homeassistant/packages/scripts/qr_generator.py— pure-Python stdlib-only QR generator (~470 LOC) with byte-mode + Reed-Solomon + mask pattern 0 + SVG emission.homeassistant/packages/tests/test_tailscale_qr.py— pytest rig (30 contract tests, all PASS).scripts/checks/tailscale-qr-smoke.sh— bash smoke wrapper (16 assertions, all PASS).docs/setup/tailscale-qr.md— IKEA 5-step user-facing guide.homeassistant/packages/roamcore_setup_wizard.yaml(+24 lines) — adds 1 helper automation that copiesrc_tailscale_auth_key→rc_tailscale_qr_device_keyon stage transition.scripts/check.sh(+11 lines) — wires the new smoke into the core-only chain.Verification
bash scripts/check.sh --core-only→ exit 0, GREEN.bash scripts/checks/tailscale-qr-smoke.sh→ 16/16 PASS.python3 -m pytest homeassistant/packages/tests/test_tailscale_qr.py -v→ 30/30 PASS.python3 qr_generator.py 'https://login.tailscale.com/a/test-abc123' 256 /tmp/test-qr.svg→ valid SVG withviewBox='0 0 256 256'+ 210 dark modules + 211 total rects.GOLDEN.md alignment (re-read 2026-08-06)
Product principles served: P1 (Novice-first UX — IKEA 5-step guide, plain-English status, no operator jargon in §1-§4), P2 (Mission-critical connectivity — auto-recover on QR failure, copy-paste fallback), P6 (OpenClaw first-class — the new
rc_tailscale_qr_*entities are readable from OpenClaw).Engineering principles respected: E1 (Customer-facing repo), E2 (Documentation-driven), E3 (Backup + rollback discipline — no Proxmox/HA/OpenWrt/vmbr0 touched), E4 (Git fast mode — direct-to-branch push), E5 (no vmbr0), E7 (Naming follows
rc-entity-naming.md— every entity starts withrc_tailscale_qr_).Anti-patterns avoided: no secrets committed (
mode: passwordfor the device-key), no vmbr0 touch, no wide PR (pure Phase 6 wizard extension), no Cron-handoff references in user copy, no SUPERSEDED banners in user tree, no tier-letter exposition in user copy.Tier discipline
Tier-b recipe over the upstream Tailscale login URL schema (
https://login.tailscale.com/a/<key>). The QR generator is pure-Python stdlib (noqrcode/segno/qrencodedep). No RoamCore-operated VPN control plane.Scope discipline
Strictly additive, no overlap with in-flight Phase 6 slices (#122.a / #122.b / #122.c / #122.d.i). All slices-before-mine live in
roamcore_remote_access_setup.yaml; this slice lives in its own dedicated packageroamcore_tailscale_qr.yamlto keep file-scope conflicts out.Rollback
git revert <sha>in a single commit; no state to recover (pure repo-local code + data).Diff: +1472 / -0 across 7 files (1 commit on
subagent/phase6-tailscale-qr-code, branched off5f94405= currentorigin/maintip).