docs: settings page plan, engineering details, audit record and inventory
Plan for the settings page on NA Ingest (every setting editable, what runs where, multi-campus), built with a plan-and-audit workflow and consolidated against a decisions list; the audit's 157 findings and their dispositions; the 544-item inventory of everything configurable today. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,556 @@
|
|||||||
|
# Status Panel: inventory of everything configurable (2026-10-13)
|
||||||
|
|
||||||
|
Every setting, hard-coded value, installed piece, Companion-side object, secret and workflow step the system has today: 544 items in four lenses, gathered by four readers (the "what runs where" reader checked NA Ingest live, read-only). Each line: [kind] area :: name @ where | how it is changed today | values/validation | what another campus would change | whether it needs to be editable. Used by the settings-page plan (settings-plan.md) and its coverage table (settings-plan-details.md).
|
||||||
|
|
||||||
|
### Lens: Operator workflow (every step a person does today to change something) and the Companion-side state the panel depends on (what another campus must recreate inside Companion)
|
||||||
|
- [workflow-step] workflow: edit :: Edit config.json in the repo on the MacBook (source of truth) @ /Users/christian/Documents/Status Indicators/config.json (README.md:139) | today: Open config.json in a text editor; every tile/check/fix/host/window change starts here. Nothing on NA Ingest is edited directly. | JSON only; no schema file; typos are caught only by install.sh's json.load or by load_config (unknown check type, unknown member/fix/host, duplicate tile id, restart key with a tile's id, auto_fix fix that pkill/taskkill/killall's without restart_ok). | other campus: Another campus needs its own config.json (and its own repo or branch); today there is one file for NA only.
|
||||||
|
- [workflow-step] workflow: edit :: Validate config.json before deploying (load_config) @ status_panel.py:2234-2276 load_config; .claude/settings.local.json allowlist shows the command | today: /usr/bin/python3 -c "import status_panel as sp; sp.load_config('config.json')" from the repo folder (also runs implicitly in make_page.py and in the tests' CONFIG = sp.load_config(...)). | Raises ValueError on: duplicate tile id; unknown check type (not in CHECKS); member not a tile id; check 'fix' not in fixes (except restart-connection / dante-auto); ssh fix whose host is not in hosts; remote check with unknown host; hold_always not in fixes; auto_fix fix unknown or containing pkill|taskkill|killall without restart_ok; restart key id equal to a tile id; restart key fix or watch entry unknown (watch may be 'conn:<label>'). It does NOT check tile positions overlap (make_page.place does), connection labels exist in Companion, hosts answer, or playlist URIs. | other campus: Same validator for every site; a UI must run it (or an equivalent) before saving.
|
||||||
|
- [workflow-step] workflow: test :: Run the offline tests @ tests/test_status_panel.py (README.md:169) | today: /usr/bin/python3 -m unittest discover -s tests (from the repo folder on the MacBook; ~1771 lines, 9 test classes) | Classes: TestChecks, TestTextSize, TestBehaviour, TestFixes, TestHyperDeckProbe (FakeDeck TCP server on 9993), TestNetgearParsing, TestAgainstFakeCompanion (ThreadingHTTPServer imitating /api/connections, /api/variable, /api/custom-variable, /api/connections/:id/restart), TestPage (make_page.build/build_restart + push_page.safe_target), TestCoreMIDI (real CoreMIDI loopback). Stubs: engine.executor and poe_executor replaced with lambdas (no real fix ever runs); end-to-end test replaces poll_mdns, poll_system, poll_midi, poll_remote, poll_dante_map, start_remote_midi_streams, pops cfg['atem'], sets gateway None; AutoLog dir = tempfile; service_windows pinned to Sunday 07:00-12:30; two remote-MIDI tests run coremidi.py in a local /usr/bin/python3 subprocess. | other campus: Tests read the repo's config.json and hard-code NA facts: page 19/20, name '20\nRestart', RESTART key at [2,6], tiles[0].pos == [0,1], hd1 at row1 col1, ingest at 3/5, net at 3/4, REC at 0/2, 'ack hd1', 'force rs_tascam', ATEM connection label, 'x32' fallback check. A different layout breaks TestPage/TestFixes unless tests become config-driven. | (need not be editable)
|
||||||
|
- [workflow-step] workflow: test :: Poll once on NA Ingest without writing (status_panel.py --once) @ status_panel.py:2445-2476 run_once; :2483 arg; README.md:168 | today: ssh checklist-ingest 'cd ~/Library/Application\ Support/status-panel && /usr/bin/python3 status_panel.py --once' (prints r<row>c<col> tile level text, plus hidden tiles). --dry-run runs the loop but never writes to Companion. | Needs Companion at config.companion (127.0.0.1:8000) so it only works on NA Ingest; no debounce; pings run twice; MIDI errors are printed not raised; it does SSH to the other machines (poll_remote) and reads nothing from the ATEM/Dante map. | other campus: Same on any Ingest Mac.
|
||||||
|
- [workflow-step] workflow: deploy :: Copy the changed files to NA Ingest's staging folder @ README.md:142; .claude/settings.local.json (older 4-file variant) | today: scp status_panel.py coremidi.py netgear.py config.json install.sh checklist-ingest:status-panel-install/ | Staging folder ~/status-panel-install/ on NA Ingest (home of user 'Studio'). README's list omits atem_watch.js and atem-reference.xml although install.sh copies atem_watch.js unconditionally (cp fails under set -e if it is missing from the staging folder) and atem-reference.xml when present. Tests, make_page.py, push_page.py, nav_keys.py are not deployed (they run on the MacBook). | other campus: Each site has its own Ingest host alias and staging folder; the file list must be generated, not remembered.
|
||||||
|
- [external-setup] workflow: deploy :: SSH alias checklist-ingest (how the MacBook reaches NA Ingest) @ ~/.ssh/config:5-10 on the MacBook | today: Host checklist-ingest -> HostName 10.1.1.1, User Studio, IdentityFile ~/.ssh/id_ed25519_checklist_ingest, IdentitiesOnly yes, ConnectTimeout 10. Edited by hand in ~/.ssh/config. | NA Ingest also answers as na-ingest.local and 10.6.3.222 (second network). | other campus: One alias per campus Ingest Mac; the UI (if it runs ON the Ingest Mac) removes the need for this hop. | (need not be editable)
|
||||||
|
- [workflow-step] workflow: deploy :: Run the installer on NA Ingest @ install.sh:1-60; README.md:148 | today: ssh checklist-ingest 'zsh ~/status-panel-install/install.sh' | Steps: set -e; json.load config.json (refuses invalid JSON); mkdir DEST; if DEST/config.json exists and differs -> copy to DEST/config.json.bak-YYYYmmdd-HHMMSS; cp status_panel.py coremidi.py netgear.py config.json atem_watch.js (+ atem-reference.xml if present) to DEST; rewrite the LaunchAgent plist; launchctl bootout; wait up to 10 s for the old job to vanish (bootstrap too early -> error 5); launchctl bootstrap with 3 tries; prints 'installed ... log: ~/Library/Logs/status-panel.log'. No rollback of the Python files (only config is backed up); no 'is a service live right now' guard. | other campus: Same script; DEST/LABEL/log paths are hard-coded org.waterschurch names.
|
||||||
|
- [workflow-step] workflow: deploy :: install.sh --remove (stop the watchdog) @ install.sh:15-20; README.md:170 | today: ssh checklist-ingest 'zsh ~/status-panel-install/install.sh --remove' -> launchctl bootout + rm plist; files in DEST stay. | After removal every page key turns purple WATCHDOG DOWN within 15 s (sp_hb stale). | other campus: Same.
|
||||||
|
- [hardcoded] NA Ingest install :: Install folder (data dir) on NA Ingest @ install.sh:11 DEST="$HOME/Library/Application Support/status-panel" | today: Edit install.sh. Comment: App Support, not ~/Documents, because launchd-run python3 cannot read ~/Documents (TCC). | Holds status_panel.py, coremidi.py, netgear.py, atem_watch.js, config.json, config.json.bak-*, atem-reference.xml, atem-reference.json (generated), dante-map.json, service-mode.json. | other campus: Same path works on any Mac; keep it. | (need not be editable)
|
||||||
|
- [hardcoded] NA Ingest install :: LaunchAgent org.waterschurch.status-panel @ install.sh:9 LABEL; :12 PLIST=~/Library/LaunchAgents/$LABEL.plist; :30-49 plist body | today: Rewritten by install.sh every run: /usr/bin/python3 DEST/status_panel.py --data-dir DEST; RunAtLoad; KeepAlive; ThrottleInterval 10; StandardErrorPath ~/Library/Logs/status-panel.err.log; domain gui/<uid> (must run in the logged-in GUI session: keychain, osascript to Spotify, CoreMIDI). | Restart by hand: launchctl kickstart -k gui/$(id -u)/org.waterschurch.status-panel. --config defaults to <script dir>/config.json; --log defaults to ~/Library/Logs/status-panel.log. | other campus: Label could carry the campus name but does not need to.
|
||||||
|
- [runtime-data] NA Ingest install :: Watchdog log files @ status_panel.py:2485 --log default; :2491 RotatingFileHandler 1 MB x3; install.sh:46 err log; README.md:173 | today: Read with ssh checklist-ingest 'tail ~/Library/Logs/status-panel.log' or grep HOLD|FIX|AUTO. Every fault/clear, ACK, HOLD, FIX, FORCE, AUTO line is timestamped. | ~/Library/Logs/status-panel.log (+ .1 .2 .3), ~/Library/Logs/status-panel.err.log (python tracebacks), ~/Desktop/Auto-Fix Logs/<date> <weekday> Auto-Fix.log (auto_fix.log_dir default, one per day, deleted a calendar month later). | other campus: Same.
|
||||||
|
- [runtime-data] NA Ingest install :: Runtime state files in the data dir @ status_panel.py:2384 service-mode.json; :1840 dante-map.json; :2107 atem-reference.json; install.sh:25 config.json.bak-* | today: Written by the watchdog; never edited. service-mode.json = ALARMS override + last scheduled state; dante-map.json = name->ip->mac->switch/port/shared/poe/seen (every 30 s until first success then 600 s); atem-reference.json = parsed restore XML, rewritten at every start; config.json.bak-<timestamp> = previous config before each differing install. | No pruning of config.json.bak-* files; no history of what changed. | other campus: Same.
|
||||||
|
- [workflow-step] workflow: pages :: Rebuild the two Companion page files (make_page.py) @ make_page.py:296-304 main; README.md:154 | today: python3 make_page.py (on the MacBook, in the repo) -> writes status-page.companionconfig and restart-page.companionconfig (gzipped JSON). Only needed when a tile/key is added, moved, relabelled (stale text), when restart_page/nav settings change, or the expressions change; watchdog-only changes need no rebuild. | python3 make_page.py <path> writes only the status page to <path>. Refuses two buttons on one position (ValueError 'two buttons at row r column c'). Imports load_config so a bad config stops it. Both .companionconfig files are committed to git. | other campus: Same tool; output depends only on config.json.
|
||||||
|
- [workflow-step] workflow: pages :: Import the status page into Companion (push_page.py) @ push_page.py:168-224; README.md:160-163 | today: python3 push_page.py (from the MacBook; default --host na-ingest.local --port 8000, page and expected name from config.page/page_name, file status-page.companionconfig) | Refuses when: the built file's page name != config page_name ('builds page X, not Y'); the target page is not empty-but-nav-keys and not already named the same (use --force to overwrite anyway); Companion rejects the upload ('Companion could not read the file'); another upload session is open (waits up to 40x3 s for it to expire). Needs Companion's admin port reachable on the LAN with no auth. Page name is read before/after via /api/variable/internal/page_number_N_name/value. | other campus: Host must be that campus's Ingest; page numbers come from that site's config.
|
||||||
|
- [workflow-step] workflow: pages :: Import the Restart page (push_page.py --which restart) @ push_page.py:178-181; README.md:94 | today: python3 make_page.py && python3 push_page.py --which restart (page 20, name '20\nRestart', file restart-page.companionconfig) | Same refusals as the status page. Other flags: --page N, --file F, --host, --port, --force. | other campus: Same.
|
||||||
|
- [workflow-step] workflow: pages :: Alternative: import by hand in Companion's UI @ README.md:134,157; memory companion-status-page.md:20 | today: Copy the .companionconfig to NA Ingest's Desktop (use a NEW filename: overwriting in place keeps Finder's old date), then Companion web UI -> Import/Export -> Import -> 'import into page 19' (single page). | Never a full import: a full import resets ALL custom/expression variables and pages (memory :16). Trigger import only with replaceExisting=false. | other campus: Same. | (need not be editable)
|
||||||
|
- [hardcoded] workflow: pages :: push_page's safe-target rule (page emptiness) @ push_page.py:122-159 NAV_TYPES, NAV_MARKER, real_buttons, safe_target | today: Edit push_page.py. A page is safe if its name equals the expected name, or it holds nothing but pageup/pagenum/pagedown controls or buttons whose notes contain 'STATUS nav key'. | Pages 22 and 97 are named 'PAGE' yet hold real buttons, which is why the name alone is not trusted. | other campus: Same rule everywhere. | (need not be editable)
|
||||||
|
- [companion-side] workflow: pages :: tRPC import sequence used by push_page.py @ push_page.py:195-220 (WebSocket /trpc; importExport.prepareImport.start -> uploadChunk (256 KiB, base64) -> complete (sha1, userData null) -> importExport.importSinglePage {targetPage, sourcePage 1, connectionIdRemapping {}}) | today: Internal Companion 5.0.7 API, hand-implemented over a minimal WebSocket client (stdlib only). | Written against Companion 5.0.7; a Companion upgrade can change method names or the export format. | other campus: Each campus's Companion must be a compatible version. | (need not be editable)
|
||||||
|
- [companion-side] workflow: pages :: Page export fetch used by push_page/nav_keys @ push_page.py:126-134 GET /int/export/page/<n>; nav_keys.py:32-43 same with ?includeSecrets=false (HTTPError = no such page) | today: Not changed; internal Companion endpoint returning gzipped page JSON. | nav_keys.py walks page 1, 2, 3... until the first HTTPError, so a gap in page numbers would stop the walk early. | other campus: Same. | (need not be editable)
|
||||||
|
- [workflow-step] workflow: nav keys :: Colour the page-number key on every page (nav_keys.py) @ nav_keys.py:78-122; README.md:96-104; guideline 9 | today: python3 nav_keys.py --dry-run (list), python3 nav_keys.py (apply), python3 nav_keys.py --revert (put Companion's built-in keys back). Re-run after adding or moving pages. | Scans every page except the status page; for each nav key of a kind in config.nav_keys it tRPC controls.copyControl's from the status page's row 1 col 0 (NAV_LOCATIONS) onto that location; kinds no longer listed get controls.resetControl back to the built-in type. Needs the status page already imported (it copies from it). Raises if a copy returns false. Default --host na-ingest.local. | other campus: Page count and nav key positions differ per site; run per campus.
|
||||||
|
- [runtime-data] workflow: nav keys :: nav_locations.json (record of converted nav keys) @ nav_locations.json (97 entries); nav_keys.py:28,89,111,117,121 | today: Written by nav_keys.py after every run (page/row/col -> kind), committed to git. Today: pagenum at 1/0 on pages 1-95 and 97 (96 absent), plus an extra pagenum at 15/0/1. | It is a record only; nav_keys.py re-scans Companion each run rather than trusting it. | other campus: One per campus (lives in the repo today, so a second campus would overwrite it).
|
||||||
|
- [workflow-step] workflow: git :: Commit and push to the private Gitea repo @ git remote origin http://chronicles-ab.waterschurch.org:3000/christian/status-indicators.git; .claude/settings.local.json credential helper line; memory companion-status-page.md:11; guidelines 13,16 | today: git add -A && git commit -m '...' (ends with the Co-Authored-By line) && git push, with credential helper reading the keychain item gitea-mcp (account christian). Commits normally touch README.md, config.json, status_panel.py, tests/, and the two .companionconfig binaries. | Repo must stay private. Branch main only. The two built pages are committed, so a rebuild without a commit leaves the repo stale. | other campus: One repo today; other campuses need their own config (branch, folder, or separate repo) or a sites/ layout. | (need not be editable)
|
||||||
|
- [secret] workflow: git :: Gitea credential in the MacBook keychain @ .claude/settings.local.json (security find-generic-password -s gitea-mcp -a christian -w) | today: Stored once in the login keychain; never printed or typed. | Used only by git push on the MacBook. | other campus: Not needed on the Ingest Macs. | (need not be editable)
|
||||||
|
- [workflow-step] workflow: docs :: Update README.md with every behaviour change @ README.md (216 lines); guideline 16 | today: Hand-edit the relevant table row (Buttons / What each button checks / Restart page / Files) in the same commit. | README is the only teaching material today; it is NA-specific (names, IPs, playlist, page numbers). | other campus: A per-site generated 'what is what' page would replace most of it.
|
||||||
|
- [workflow-step] workflow: docs :: Keep 'the artifact' current @ memory status-panel-guidelines.md:31 ('keep the artifact current'); settings-interface.md mentions docs/ in the repo 'once written' | today: Unknown: no artifact URL or docs/ folder exists in the repo or the memory files I read. | Could not determine what artifact is meant or where it lives. | other campus: n/a | (need not be editable)
|
||||||
|
- [workflow-step] workflow: verify :: Verify live after a change @ memory companion-status-page.md:20; .claude/settings.local.json verification commands | today: Open http://na-ingest.local:8000/tablet?pages=19&cols=8&rows=4 (web buttons, read-only look); ssh checklist-ingest 'grep -E "HOLD|FIX" ~/Library/Logs/status-panel.log | tail'; curl http://127.0.0.1:8000/api/custom-variable/sp_state/value; POST /api/location/19/3/0/press to toggle ALARMS; curl /api/variable/audinate-dantecontroller/devices/value. | Guideline 15: test it yourself before saying done; when the user tested, read the log. | other campus: Host and page number differ.
|
||||||
|
- [workflow-step] workflow: pages :: Replace the ATEM restore file @ README.md:114; install.sh:28; status_panel.py:2109-2115 (parsed into atem-reference.json at start) | today: Save a new restore .xml from ATEM Software Control (today a copy of '4k plus 2026-10-07 14-59-55.xml' from Google Drive Backup/NA/ATEM), copy it over atem-reference.xml in the repo, scp it to the staging folder, re-run install.sh (watchdog restart re-parses it). | config.atem.reference is a path relative to the script dir or absolute; 1.6 MB XML committed to git. | other campus: Every campus has its own switcher and restore file.
|
||||||
|
- [workflow-step] workflow: Companion modules :: Build and install the quiet Tascam module @ tascam-quiet/make_quiet_tascam.py; README.md:187-194 | today: python3 make_quiet_tascam.py '~/Library/Application Support/companion/modules/tascam-da-6400-1.3.0' <out> -> tascam-da-6400-1.3.1-quiet.tgz; then Companion -> Modules -> Import module package; Connections -> tascam -> edit -> version 1.3.1-quiet -> save. | Patches the stock 1.3.0 minified source by string replacement; a newer stock module may not match the PATCHES. | other campus: Only where a DA-6400 exists. | (need not be editable)
|
||||||
|
- [companion-side] workflow: one-time :: Create the three custom variables in Companion by hand @ README.md:130-133; memory companion-status-page.md:16; config.variables; status_panel.py:2414-2425 warnings | today: Companion web UI -> Variables -> Custom Variables -> add sp_state, sp_hb, sp_cmd (start value empty, persist off). Done once on NA. | Cannot be created through the HTTP API (POST to a missing one -> 404); the watchdog logs 'cannot set custom:<name> (HTTP 404) - create it in Companion' once. Names are config.variables.state/heartbeat/command and must match the names baked into the page expressions by make_page.py. Whether tRPC can create them was not determined. | other campus: Must be repeated on every campus's Companion (first thing on the checklist).
|
||||||
|
- [companion-side] companion-side :: Custom variable sp_state (tile JSON) @ config.variables.state; status_panel.py:2410 set_custom; make_page.py:51-66 jsonpath reads | today: Written by the watchdog twice a second when changed (and every 10 s anyway), as compact JSON {tile: {x text, b/f on-phase colours, b2/f2 off-phase colours, s font size}} including hidden tiles and 'all'; sent in the POST body (several KB). | Page expressions: jsonpath($(custom:sp_state), '$.<tile>.x') etc. Tile ids therefore must be valid jsonpath names (letters/digits/underscore). | other campus: Same mechanism. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Custom variable sp_hb (heartbeat) and the 15 s stale rule @ config.variables.heartbeat; make_page.py:34 STALE_SECONDS=15, :53 '$(internal:time_unix) - $(custom:sp_hb) > 15' | today: Watchdog writes unix seconds every 0.5 s loop; every key turns PURPLE 'WATCHDOG DOWN <n> ERR' (master) or '<LABEL>\n?' when older than 15 s. | STALE_SECONDS is a constant in make_page.py (needs page rebuild + re-import to change); PURPLE 0x6A1B9A constant. | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Custom variable sp_cmd (button command queue) and its grammar @ config.variables.command; make_page.py:83-97 command_action; status_panel.py:2341-2375 handle_commands, HOLD_MAX_AGE=15 | today: Buttons append '$(custom:sp_cmd);<cmd>' as plain text (not an expression: '+' is numeric in Companion 5). Watchdog reads, clears, then handles: 'ack <tile>', 'ackall', 'service toggle|on|off', 'restart <tile> <unix>', 'force <key> <unix>'. Holds older than 15 s or unstamped forces are dropped. | Verbs are hard-coded in both make_page.py and status_panel.py. | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Custom variable flash_tick + trigger flash_tick_500 (the blink clock) @ README.md:135; make_page.py:9,61-62,211-212 '$(custom:flash_tick) == 1'; tests FakeCompanion store has flash_tick | today: Pre-existing on NA (not created by this project): a trigger toggling custom variable flash_tick between 0 and 1 every 500 ms; it must stay enabled or nothing blinks. | Hard-coded name 'flash_tick' in make_page.py (not in config.variables). | other campus: Another campus must create both the variable and the trigger by hand (or import a trigger with replaceExisting=false); the name should become a config key.
|
||||||
|
- [config-key] companion-side :: Status page number and name (page 19, '19\nStatus') @ config.page=19, config.page_name='19\nStatus'; make_page.py:255-266 page_export; push_page.py:182,188 | today: Edit config.json, rebuild, push; the page was also renamed once in the admin UI (memory :20). push_page refuses unless the live page already has this exact name or holds only nav keys. | int page; string name with literal backslash-n; page must exist in Companion (Companion 5 pages are created in the UI). | other campus: Every campus picks its own free page numbers; nav hold jumps to this page.
|
||||||
|
- [config-key] companion-side :: Restart page number and name (page 20, '20\nRestart') + jump/back positions @ config.restart_page.page=20, page_name, jump_pos [2,6] (on the status page), back_pos [2,6] (on the restart page); make_page.py:274-288 | today: Edit config.json; rebuild both pages; push both. The RESTART PAGE key (0x37474F) on page 19 and the STATUS PAGE key on page 20 are plain set_page buttons. | Positions are [row, col]; place() refuses overlaps. | other campus: Per site.
|
||||||
|
- [hardcoded] companion-side :: Nav key positions (UP 0/0, page number 1/0, DOWN 2/0) and the ALARMS key at 3/0 @ make_page.py:195 NAV_LOCATIONS (hard-coded); config tiles 'service' pos [3,0] | today: Edit make_page.py. Column 0 is Companion's own nav column on an 8x4 XL; tiles may not be placed on 0/0, 1/0, 2/0. | nav_controls() puts {'type': 'pageup'/'pagenum'/'pagedown'} or the coloured copy at these spots on both pages. | other campus: Same on any Stream Deck XL layout. | (need not be editable)
|
||||||
|
- [config-key] companion-side :: Which nav keys follow ALL, tap page, hold time @ config.nav_keys=['pagenum'], nav_tap_page=1, nav_hold_ms=1000; make_page.py:199-245; README.md:98-104 | today: Edit config.json; rebuild; push page 19; re-run nav_keys.py. Tap (on release) -> set_page nav_tap_page; hold nav_hold_ms -> set_page config.page. | nav_keys subset of pageup/pagenum/pagedown; tap page must be named outright (the converted key's 'startup' means the current page on surfaces set to 'use last page'). | other campus: Per site (home page number differs).
|
||||||
|
- [hardcoded] companion-side :: nav_templates.json (Companion 5.0.7's converted nav keys) @ nav_templates.json (_comment; keys pageup/pagenum/pagedown, button-layered with inc_page/set_page startup/dec_page actions); make_page.py:207 | today: Captured once from page 19 after 'convert to regular button' in Companion 5.0.7; make_page.py recolours box/text/line layers with expressions and rewrites the pagenum steps. | Version-specific layer structure; a new Companion major may need a recapture. | other campus: Same file as long as Companion versions match. | (need not be editable)
|
||||||
|
- [hardcoded] companion-side :: Export format pin: EXPORT_VERSION 12, COMPANION_BUILD '5.0.7+9763-stable-cec2f88e6f', GRID 0-8 x 0-5 @ make_page.py:31-33 | today: Edit make_page.py when Companion changes its export format. | push_page/tRPC calls and nav_templates are also 5.0.7-specific. | other campus: Every campus must run a compatible Companion 5.0.x; record each site's version. | (need not be editable)
|
||||||
|
- [hardcoded] companion-side :: Hold time 2000 ms (HOLD_MS) and runWhileHeld @ make_page.py:36 HOLD_MS; :144-146, :181-182 | today: Edit make_page.py, rebuild, push both pages. Every status tile with a fix, every group, NET, hold_always tiles and every restart key use a 2000 ms action set. | Guideline 3: holding any key 2 s always fixes. | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Button expressions and internal variables the pages rely on @ make_page.py:51-66 (jsonpath(), $(internal:time_unix), $(custom:flash_tick)); :157 $(internal:instance_errors); template string for stale text | today: Baked by make_page.py. Gotchas (memory :18): '+' is numeric; join text with template strings; single-quoted strings process escapes, template strings do not. | Layers: canvas (border decoration), box (bg expression), image (empty), text (companion-sans, fontsize expression from sp_state.s, fontsizeAllowShrink). canModifyStyleInApis true on tiles. | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Button actions used (internal module) @ make_page.py:83-103: custom_variable_set_value (connectionId internal) and set_page (surfaceId self) | today: Baked by make_page.py; ids via stable_id(sha256) so re-imports keep the same ids. | Only the internal connection is referenced; no module connections are in the export ('instances': {}). | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Button notes as machine markers @ make_page.py:140,188,196,233 ('STATUS tile', 'RESTART key', NAV_MARKER 'STATUS nav key (<kind>)'); push_page.py:123,145; nav_keys.py:46-58 | today: Written into each button's notes field; push_page and nav_keys identify our keys by these strings. | Editing a note in Companion's UI would make a key invisible to the scripts. | other campus: Same. | (need not be editable)
|
||||||
|
- [config-key] companion-side :: Companion host/port the watchdog talks to @ config.companion {host 127.0.0.1, port 8000}; status_panel.py:2290 make_companion; optional config.companion.db | today: Edit config.json. HTTP API, no auth, persistent http.client connection, 3 s timeout, one connection object per poller (4). | push_page.py/nav_keys.py do NOT read this; they default to na-ingest.local:8000 (flags --host/--port). | other campus: Same on any Ingest Mac running Companion locally; the LAN name differs.
|
||||||
|
- [companion-side] companion-side :: Companion HTTP endpoints the watchdog uses @ status_panel.py:102-138: GET /api/connections; GET /api/variable/<label>/<name>/value; GET+POST /api/custom-variable/<name>/value (POST body text/plain; '?value=' to clear); POST /api/connections/<id>/restart|disable|enable | today: Not configurable; Companion 5.0.7 HTTP API. Connection ids are resolved from labels at call time. | A 400 is returned for an empty POST body; a 404 for a missing custom variable. | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Connection status fields read for every 'conn'-type check @ status_panel.py:1428-1436 poll_connections every 1 s: label -> {id, enabled, status.category, status.message} | today: Category good/warning/error; enabled false -> grey 'DISABLED' (amber) and a hold turns it back on; 'ignore_warning': true on a check ignores category warning (ProPresenter). | Labels are the only key: renaming a connection in Companion silently breaks its check (shows OFFLINE/unknown). | other campus: Each campus must use the same labels as its config.
|
||||||
|
- [companion-side] companion-side :: Connection addresses from Companion's settings database @ status_panel.py:1552-1593 companion_db_path (newest ~/Library/Application Support/companion/vN.N/db.sqlite, or config.companion.db), companion_conn_hosts (SELECT value FROM instances; JSON label + config.host), poll_conn_hosts every 60 s | today: Opened read-only (mode=ro, 2 s timeout) only when some ping check has a 'conn'; result overrides the check's 'host' fallback (Shure receivers, Tascam). Schema errors are logged and ignored. | Companion's private schema; a Companion upgrade that renames the table or folder breaks the lookup (falls back to config hosts). | other campus: Same path on any Mac; versions folder name differs. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Module variables polled every 1 s (fast list) @ status_panel.py:552-573 needed_variables; :1438 poll_variables; Periodic 'variables' 1.0 s | today: Derived from config: hyperdeck -> status, slotId, recordingTime; dante -> devices; tascam -> error, caution. Each is one GET /api/variable/<label>/<name>/value. | Today: 6 decks x3 + devices + error + caution = 21 GETs/s on one connection. | other campus: Same derivation. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Module variables polled every 3 s (slow list) @ status_panel.py:560-571; Periodic 'slow-variables' 3.0 s | today: hyperdeck -> ip; shure -> ch_N_name, ch_N_battery_bars, ch_N_interference_status for N in 1..channels; psm -> ch_N_name, ch_N_rf_mute. | channels per check (Axient 4, ULXD 1, PSM 2). Variable names are the Shure/PSM module's and are hard-coded in needed_variables and the checks (:325-365). | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side: connections :: HyperDeck_1 .. HyperDeck_6 (Blackmagic HyperDeck module) @ config tiles hd1..hd6 {type hyperdeck, conn HyperDeck_N, optional on 2 and 4, fix {conn: hyperdeck}}, hold_always 'hyperdeck'; status_panel.py:255-290, :987 ip fallback | today: Reads variables status ('record'), slotId, recordingTime (h:m:s), ip; NO DISK / LOW <30 min / FULL <5 min / NOT REC when another deck records. Hold: disable+enable the connection, verify after 15 s; probes port 9993 (hyperdeck_probe) but never reboots. | 'optional': true on a deck = grey with no alarm when it has no disk. Which decks exist is simply which hd tiles are in config and which connections exist in Companion. | other campus: A campus with decks 2 and 4 only: remove/skip the other hd tiles (and REC members) and ensure the labels match its Companion.
|
||||||
|
- [companion-side] companion-side: connections :: tascam (Tascam DA-6400 module, version 1.3.1-quiet) @ config tile tascam {type tascam conn tascam fix tascam_reconnect; ping conn tascam host 10.1.11.74}; status_panel.py:375-384 error/caution; fixes.tascam_reconnect kind reconnect verify 20 s | today: Variables error and caution (text); ping address taken from Companion's connection config (DB) else 10.1.11.74. Hold always reconnects; 'HUNG - POWER CYCLE IT' when it pings but the control port is dead. | Needs the quiet module to keep Companion's log sane. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: audinate-dantecontroller (Dante module) and its 'devices' variable @ config tile dante {type dante conn audinate-dantecontroller, devices map of 19 Dante names -> short labels, fix map by device name + '*': dante-auto}; status_panel.py:297-320, :1880 | today: Variable devices = JSON array of device names present. Config lists the 19 expected names (FOH-DiGiCo-DMI-1/2, BOH-WING, BOH-DQ-Rack, BOH-AD4Q-A/B, STUDIO-A-X32, STUDIO-A-Resi-AJA-SDI, STUDIO-A-Ingest, NA-Translation, STUDIO-B-RedNet-TNX, STUDIO-B-TB, STUDIO-B-TB-MON, STUDIO-B-MON, STUDIO-B-SOLO-MON, STUDIO-B-Tascam, NA-Tracks, FOH-SuperRack-LiveBox, NA-ProPresenter-Mac). Per-device fixes dvs_prop/dvs_tracks/dvs_translation/dvs_ingest; everything else -> PoE cycle via the live Dante map or restart the Dante module. | Device names are Dante names (renaming in Dante Controller needs a config edit; re-patching does not). | other campus: Entire list is per site.
|
||||||
|
- [companion-side] companion-side: connections :: Axient_1, Axient_2 (Shure AD4Q, 4 channels), ULXD-4 (1 channel) @ config hidden tiles axient1/axient2/ulxd {type shure conn ..., channels; ping conn ... host 10.1.1.101/.102/.106}; status_panel.py:325-360 | today: Variables ch_N_name, ch_N_battery_bars (<=1 red, <=2 amber; thresholds battery_err_bars/battery_warn_bars per check), ch_N_interference_status (only while the transmitter is on). Receivers pinged every 3 s at Companion's address for them. Members of the RF group only (no own key). | channels int; ping host fallback. | other campus: Per site RF inventory.
|
||||||
|
- [companion-side] companion-side: connections :: PSM_1 .. PSM_3 (Shure PSM1000, 2 channels) @ config hidden tiles psm1..3 {type psm conn PSM_N channels 2; ping host 10.1.1.103-.105}; status_panel.py:363 | today: Variables ch_N_name, ch_N_rf_mute ('MUTED' = alarm). | Same ping-from-Companion-address rule. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: x32 (Behringer X32) @ config tile x32 {type conn conn x32} | today: Connection status only; hold = restart-connection. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: lighting-midi (Companion's MIDI connection to the lighting port) @ config tile midi check {type conn conn lighting-midi tag 'COMPANION MIDI'} | today: Connection status only, as one line of the MIDI key. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: Propresenter-API (ProPresenter module) @ config tile apps {type conn conn Propresenter-API tag PROPRES fix propresenter ignore_warning true} | today: Status only; its periodic yellow warning is ignored; hold restarts ProPresenter on the ProP Mac over SSH then the connection. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: ableton (OSC), BibleTV (OSC) @ config tile apps checks {conn ableton tag ABLETON}, {conn BibleTV tag 'BIBLE OSC'}; restart key rs_ableton watch ['ableton','conn:ableton'] | today: Status only; ableton_restart fix has conn ableton + reconnect so Companion reconnects after the app restarts. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: ATEM (bmd-atem module) and its bundled atem-connection library @ config tile atem {conn ATEM}; config.atem {host 10.1.10.2, node glob '/Applications/Companion [0-9]*.app/.../node22/bin/node', library glob '~/Library/Application Support/companion/modules/bmd-atem-*/node_modules/atem-connection', reference atem-reference.xml}; status_panel.py:2089-2115 newest() | today: Connection status from Companion; audio/camera compared by atem_watch.js run with Companion's own node and the ATEM module's atem-connection library (highest version number chosen by glob). If either glob finds nothing: 'ATEM checks off' warning and the atem checks never report. | Depends on the Companion app name/location and the module being installed; a Companion update that moves node or the module breaks the watcher silently (warning only). | other campus: Per site switcher IP + restore file; globs may need per-site paths.
|
||||||
|
- [companion-side] companion-side: connections :: OBS-Youtube (OBS module) @ config tile obs {type conn conn OBS-Youtube service_only true fix obs}; hosts.livestream has mac + wake_broadcast | today: Status only; service_only = idle (grey) outside the service window instead of red. Hold starts OBS over SSH (Wake-on-LAN if the PC is off) then reconnects. | service_only is the one exception to 'every key flashes red any day' and service_windows are now 24/7, so it is moot today. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: RESI_Decoder (RossTalk) and resi-studio-cues @ config tile resi checks {conn RESI_Decoder tag DECODER}, {conn resi-studio-cues}; plus pings 10.1.10.203/.202, http://10.1.10.202/, https://central.resi.io/ | today: Status only. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: VX1000 and UHDJr (NovaStar modules) @ config tile led checks {conn VX1000}, {conn UHDJr}, pings 10.1.14.116/.115 | today: Status + ping, because the NovaStar module keeps saying Connected after power-off. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side: connections :: arena (Resolume Arena module) @ config tile arena {type conn conn arena fix arena}; fixes.arena reconnect true | today: Status only; hold starts Arena on the Lighting PC via its StatusPanel task then reconnects. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side :: Fix kinds that act on Companion itself @ status_panel.py:542 RESTARTABLE ('conn','hyperdeck','shure','psm','tascam'); :545 'restart-connection'; :1957 kind reconnect; :1983 kind hyperdeck (disable+enable); execute_fix 'reconnect': true -> restart_connection after a successful app start; disabled connection -> enable only | today: Built in; which applies is derived from the check type and the fix entry. | 60 s cooldown per fix (run_fixes cooldown=60). | other campus: Same. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Stream Deck surfaces and the layout grid @ README.md:10-17 (XL 8x4; MK.2 and Mini show the top-left corner); make_page.py:33 GRID; tests:1657 master at [0,1] 'where the smallest Stream Deck can see it' | today: Surfaces are configured in Companion's Surfaces tab (not by this project); tiles use rows 0-3, cols 0-7; col 0 = UP/page/DOWN + ALARMS. | Surface setting 'use last page' affects the converted page-number key (hence tap page named outright). | other campus: A campus with only an MK.2 (5x3) would need the important keys in the top-left 5x3; layout is per site.
|
||||||
|
- [companion-side] companion-side :: Web buttons / tablet URL for a screen-only view @ memory companion-status-page.md:20: http://na-ingest.local:8000/tablet?pages=19&cols=8&rows=4 | today: Open in a browser on the LAN; read-only look at the page (presses also work there). | Page number and host per site. | other campus: Per site.
|
||||||
|
- [companion-side] companion-side :: Pressing a key over the API (for tests) @ memory :20: POST http://na-ingest.local:8000/api/location/19/3/0/press (ALARMS) | today: curl -X POST ...; same for any row/col. | other campus: Per site. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Why only single-page import is used @ memory companion-status-page.md:16 | today: A full Companion import resets ALL custom/expression variables and pages; only importSinglePage into page 19/20 and trigger import with replaceExisting=false are safe. | other campus: Same caution everywhere. | (need not be editable)
|
||||||
|
- [companion-side] companion-side :: Companion pages 22 and 97 named 'PAGE' with real buttons @ push_page.py:10; tests:1683 | today: Observed NA fact that shaped the safety rule; nothing to change. | other campus: n/a | (need not be editable)
|
||||||
|
- [config-key] config: watchdog :: service_windows (when alarms are armed) and the ALARMS key @ config.service_windows [{days all, 00:00-23:59}]; status_panel.py:696 ServiceMode; tile 'service' kind service pos [3,0] | today: Edit config.json + install. Press ALARMS to override until the next window edge (persisted in service-mode.json). | days from mon..sun; start/end 'HH:MM' with start <= hm < end. | other campus: Per site, but guideline 1 says alarms should be 24/7 everywhere.
|
||||||
|
- [config-key] config: watchdog :: auto_fix window and list @ config.auto_fix {windows [{sun 06:30-13:30}], after_seconds 20, max_tries 2, retry_seconds 90, pause_after_network_seconds 60, restart_connections true, fixes [16 gentle fix ids], log_dir default ~/Desktop/Auto-Fix Logs} | today: Edit config.json + install; load_config refuses a fix that kills a program unless restart_ok. | Today's list: hyperdeck, tascam_reconnect, midi_reconnect, rtpmidi_start, vista, arena, obs, ableton, translate, dvs_prop, dvs_tracks, dvs_translation, solo_afl, atem_audio, atem_camera (+1 truncated in my dump). | other campus: Per site service times.
|
||||||
|
- [hardcoded] config: watchdog :: Poller periods (not configurable) @ status_panel.py:2299-2330 start_pollers: connections 1 s, variables 1 s, slow-variables 3 s, conn-hosts 60 s, spotify 3 s, pings 3 s, midi 0.5 s, system 10 s, bonjour 15 s, http 5 s, remote 10 s, dante-map 30 s (600 s after first success); remote MIDI streams continuous; ATEM watcher; Art-Net listener | today: Edit status_panel.py. | debounce_seconds=3 and flap {count 3, window 600} are config keys; network burst thresholds are config.network. | other campus: Same. | (need not be editable)
|
||||||
|
- [config-key] config: watchdog :: SSH identity and options for every other machine @ config.ssh_key '~/.ssh/id_status_panel'; status_panel.py:1748-1753 ssh_cmd (BatchMode, ConnectTimeout 5, IdentitiesOnly, LogLevel ERROR, StrictHostKeyChecking accept-new, ControlMaster auto, ControlPath /tmp/status-panel-ssh-%C, ControlPersist 120) | today: Key made once on NA Ingest; public key literal is pasted inside remote-access/mac-authorize.sh and windows-setup.ps1 ('ssh-ed25519 AAAAC3...bgY status-panel@NA-Ingest'). | Exit 255 = unreachable -> '<SHORT> NOT REACHABLE' or Wake-on-LAN if the host has 'mac'. | other campus: Each Ingest Mac needs its own key pair and its own copies of the setup scripts with that public key embedded.
|
||||||
|
- [config-key] config: hosts :: hosts table (who the watchdog can SSH to) @ config.hosts: prop napropresenter@10.1.14.225; tracks waterschurchmedia@10.1.3.227; translation studio@10.1.7.39; bibletv media@10.6.7.13; lighting media@10.1.10.206; studiob studiob@10.1.13.175; livestream media@10.1.9.126 + mac 6c:24:08:d7:fb:57 + wake_broadcast 10.1.255.255 | today: Edit config.json + install. Referenced by fixes (host) and remote/midi_peer checks (host). | keys: ssh 'user@ip' (required), mac (Wake-on-LAN), wake_broadcast. | other campus: Entirely per site; this is the 'which computers' list the user wants visible.
|
||||||
|
- [external-setup] external-setup: Macs :: Authorize NA Ingest's key on each Mac (mac-authorize.sh via ARD as root) @ remote-access/mac-authorize.sh; memory :25-26 | today: Apple Remote Desktop -> select Macs -> Send UNIX Command as root -> paste the script. Appends the public key to the console user's ~/.ssh/authorized_keys and prints 'OK user=... mac=... ssh-group=...'. Remote Login must be on (System Settings > Sharing). | Done 2026-10-08 on ProP Mac, Tracks Mac, NA Translation; Studio B Mac Studio later. The user runs it; never passwords. | other campus: Repeat per Mac per campus with that campus's key.
|
||||||
|
- [external-setup] external-setup: Macs :: DVS start helper on each Dante Mac (mac-dvs-helper.sh via ARD as root) @ remote-access/mac-dvs-helper.sh -> /usr/local/sbin/status-panel-dvs-start (defaults write wantToRun 1; launchctl kickstart system/com.audinate.dante.DanteVirtualSoundcard) + /etc/sudoers.d/status-panel (NOPASSWD for that one script); fixes dvs_* run 'sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a Dante Virtual Soundcard' | today: ARD Send UNIX Command as root on ProP, Tracks, Translation and NA Ingest. Undo: rm both files. | Installed 2026-10-08 on all four Macs. | other campus: Per Mac that runs DVS.
|
||||||
|
- [external-setup] external-setup: Macs :: Studio B Pro Tools solo-mode helper @ remote-access/install-solo-mode.sh + solo-mode-agent.swift -> ~/Library/Application Support/status-panel/solo-mode-agent, LaunchAgent org.waterschurch.status-panel.solo-mode, writes solo-mode.json every second, acts on solo-mode.fix; config apps checks 'PT SOLO NOT AFL' / 'PT SOLO CHECK OFF' (file older than 20 s or trusted false); fix solo_afl | today: From NA Ingest: scp the two files to studiob@10.1.13.175:/tmp/ ; ssh ... 'zsh /tmp/install-solo-mode.sh' (needs swiftc on that Mac); then allow solo-mode-agent in System Settings > Privacy & Security > Accessibility and restart the helper. | Only Mac with a compiled helper; only campus-specific thing on Studio B. | other campus: Only where Pro Tools runs.
|
||||||
|
- [external-setup] external-setup: Windows :: windows-setup.ps1 on each Windows PC (OBS/LiveStream, Lighting, Bible TV) @ remote-access/windows-setup.ps1 (sshd install/auto-start + sc failure restart; firewall rule OpenSSH-Server-In-TCP, Profile Any, RemoteAddress 10.1.1.1 and 10.6.3.222 only; key into C:\ProgramData\ssh\administrators_authorized_keys and %USERPROFILE%\.ssh\authorized_keys; scheduled tasks \StatusPanel\Start OBS|Vista|Arena|rtpMIDI|BibleApp (BibleApp also at logon +15 s; RUNASADMIN apps get RunLevel Highest); rtpMIDIService failure restart; Wake-on-LAN on the NIC) | today: On the PC: PowerShell as administrator, Set-ExecutionPolicy RemoteSigned -Scope CurrentUser, run the script, paste the RESULT block back; restart and rerun if it says so. Needs the user's OK (guideline 12). | $IngestIPs and the public key are hard-coded in the script; app paths are discovered (OBS on D:, Vista on G:/E:). | other campus: Per PC per campus; edit $IngestIPs and $Key for that campus.
|
||||||
|
- [external-setup] external-setup: Windows :: Lighting PC must stay on a Public network profile @ README.md:53; fixes.rtpmidi_start note | today: Manual Windows setting; its Bonjour firewall rules only allow Public; on Private it vanishes from the MIDI directory. | Nothing checks this automatically. | other campus: Same rule for any rtpMIDI PC. | (need not be editable)
|
||||||
|
- [external-setup] external-setup: Windows :: Vista autoload preference and show folder @ README.md:119: <autoloadpath> in E:\media\Documents\Vista 3\.preferences -> Prop Takeover in E:\media\Documents\Vista 3\Shows (set 2026-10-08) | today: Set by hand on the Lighting PC. | other campus: Per site. | (need not be editable)
|
||||||
|
- [secret] external-setup: NA Ingest :: Netgear switch login in the login keychain @ netgear.py:4-8,22-35 (service 'netgear-status-panel', account = username, read with security find-generic-password); config.dante_switches ['10.66.1.100','10.66.1.102']; memory :33 | today: Once, in a GUI session on NA Ingest: security add-generic-password -s netgear-status-panel -a admin -w (prompts). Readable only from the GUI session (hence the watchdog runs as a gui/ LaunchAgent, not over SSH). | Missing/locked -> RuntimeError logged; Dante map and PoE cycling stop working. Never printed. | other campus: Per site switches and credentials.
|
||||||
|
- [external-setup] external-setup: NA Ingest :: Spotify Automation (AppleEvents) permission for the watchdog @ status_panel.py:1636 osascript 'tell application Spotify'; fixes.spotify_play; README.md:123 | today: macOS TCC prompt once for python3/osascript controlling Spotify in the GUI session; how it was granted on NA Ingest is not recorded. | If denied, poll_spotify returns state None with an error string (SPOTIFY key shows the error). The playlist list is fetched from open.spotify.com embed pages + spclient (anonymous token) hourly. | other campus: Per Ingest Mac; playlist URI per site.
|
||||||
|
- [external-setup] external-setup: NA Ingest :: Network MIDI sessions on NA Ingest ('NA Ingest', optional 'NA Ingest Listen') @ config midi checks session 'NA Ingest' peers 'Lighting PC' and 'SuperRack-LiveBox'; midi_rx source 'Network NA Ingest Listen'; README.md:175-185; coremidi.py sessions()/connect_peer() | today: Audio MIDI Setup -> MIDI Studio -> Network: sessions and peers are created by hand once; the watchdog only reads them (ctypes CoreMIDI) and re-adds a dropped peer at its last address or the check's 'address'. | Session and peer names must match exactly; Bonjour instance names ('Lighting PC', 'NA Tracks') for mdns checks likewise. | other campus: Per site session/peer names and addresses.
|
||||||
|
- [external-setup] external-setup: other Macs :: Remote MIDI sessions read over SSH ('NA Tracks' on the Tracks Mac, 'NA ProP' on the ProP Mac) @ config midi_peer checks with host tracks/prop; status_panel.py:1668-1746 (coremidi.py sent over stdin to /usr/bin/python3 on that Mac; one long-lived ssh stream per host) | today: Nothing installed on those Macs; needs only SSH + /usr/bin/python3. Sessions themselves are set up by hand in their Audio MIDI Setup. | Stream shows a drop within ~1-2 s; no verdict while the stream is down. | other campus: Per site.
|
||||||
|
- [external-setup] external-setup: NA Ingest :: Services the INGEST key watches (must exist as LaunchAgents/apps on the Ingest Mac) @ config ingest checks: launchd org.waterschurch.resi-propresenter (BRIDGE), org.waterschurch.lobby-captions (CAPTIONS), org.waterschurch.lobby-captions-display (CAPTION SCREEN); http 127.0.0.1:24480/api/v1/ping (ProdCom); process /Applications/ProdDash.app/... + http 127.0.0.1:24500/ (ProdDash); disk / warn 25 GB err 8 GB; fixes bridge/captions/caption_screen/prodcom/proddash/dvs_ingest kind local | today: Those agents/apps are installed separately (not by this repo); Church Translate's startup item on NA Ingest was moved to ~/Library/LaunchAgents.disabled/ (memory :25). | other campus: Per site set of local services.
|
||||||
|
- [external-setup] external-setup: NA Ingest :: ATEM Software Control on NA Ingest (midiEnabled pref) @ config atem check {type pref domain 'com.blackmagic-design.ATEM Software Control' key midiEnabled want '0'}; fix atem_midi kind local restart_ok (quits/reopens the app) | today: App installed by hand; the watchdog reads 'defaults read' every 10 s. | other campus: Per site.
|
||||||
|
- [config-key] config: tiles :: Hidden tiles (RF members with no key) @ config tiles axient1, axient2, ulxd, psm1, psm2, psm3 have no 'pos'; make_page skips them; run_once prints them as 'hidden' | today: Edit config.json; they show only through the RF group key. | Any tile without pos is allowed; must still be a member somewhere to be visible. | other campus: Per site.
|
||||||
|
- [config-key] config: tiles :: Group keys and their members @ config tiles rec/audio/rf/show/video/stream with 'members' lists | today: Edit config.json, rebuild, push (members changes alone need only install; position/label changes need a page rebuild). | Members must be tile ids; a group shows the first problem + '+N'; hold fixes all members. | other campus: Per site.
|
||||||
|
- [config-key] config: tiles :: Restart page keys @ config.restart_page.keys: 13 keys {id rs_*, label, pos, fix, optional watch [fix ids or 'conn:<label>']} | today: Edit config.json, python3 make_page.py, python3 push_page.py --which restart, install (watchdog reads the keys for FORCE and the flashing 'watch'). | id must not collide with tile ids; fix must exist; watch entries must be fix ids or conn:<label>. | other campus: Per site.
|
||||||
|
- [config-key] config: fixes :: Fix catalogue (26 entries) and their kinds @ config.fixes; status_panel.py:1921-1990 execute_fix; tests:1355 valid kinds ssh/local/hyperdeck/midi_connect/reconnect/atem (netgear_poe exists in code but is not in the test's list) | today: Edit config.json + install. Keys: kind (default ssh), host, run (shell: zsh on Macs, cmd.exe on Windows), short, label, conn, reconnect, timeout (60 default), restart_ok, verify_seconds, hung_note, part. | 'run' strings are long shell one-liners (ProPresenter newest version, Ableton last set from Live's Log.txt, Windows schtasks /Run \StatusPanel\Start X, etc.). | other campus: Per site, but most run strings are reusable verbatim if hosts and task names match.
|
||||||
|
- [config-key] config: watchdog :: network (NET key) settings @ config.network {interface en0, gateway 10.1.0.1, burst_connections 4, burst_groups 3, burst_window_seconds 10, remember_seconds 300} | today: Edit config.json + install. | gateway may be null (tests) to disable the gateway ping. | other campus: Per site.
|
||||||
|
- [config-key] config: watchdog :: alert_webhook (Companion unreachable 30 s) @ config.alert_webhook ''; status_panel.py:2333-2341 post_webhook | today: Edit config.json; empty = off. Posts {text} JSON (Slack-style). | URL string. | other campus: Per site.
|
||||||
|
- [hardcoded] hardcoded :: Colours and display levels @ status_panel.py:42-60 ORDER/COLORS (ok green 0x007A2E, warn amber, notice flashing amber, quiet, ack dark red, err red/black flash, off grey, idle, service blue, fixing blue, failed orange); make_page.py:35 PURPLE | today: Edit status_panel.py (no rebuild: colours travel in sp_state). | other campus: Same. | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded :: Fix cooldown 60 s, hold max age 15 s, auto-fix defaults @ status_panel.py:1041 run_fixes cooldown=60; :2338 HOLD_MAX_AGE=15 | today: Edit status_panel.py. | other campus: Same. | (need not be editable)
|
||||||
|
- [workflow-step] hardcoded :: Project folder on the MacBook and its layout @ /Users/christian/Documents/Status Indicators: status_panel.py, coremidi.py, netgear.py, atem_watch.js, atem_state.js, atem-reference.xml, config.json, make_page.py, push_page.py, nav_keys.py, nav_templates.json, nav_locations.json, install.sh, status-page.companionconfig, restart-page.companionconfig, README.md, remote-access/, tascam-quiet/, tests/, .claude/settings.local.json, .gitignore (__pycache__, *.pyc, .DS_Store) | today: All editing happens here; NA Ingest only receives copies. | other campus: Needs a sites/<campus>/ split or per-campus repos. | (need not be editable)
|
||||||
|
- [companion-side] runtime-data :: Companion custom variable values are not persisted @ README.md:130 ('persist' off) | today: On Companion restart the three variables start empty; the watchdog refills within a second; keys are purple until then. | other campus: Same. | (need not be editable)
|
||||||
|
- [workflow-step] workflow: order of operations :: Full change sequence for a layout change (today) @ README.md:137-171; guideline 16 | today: 1 edit config.json; 2 run tests; 3 python3 make_page.py; 4 scp files; 5 ssh install.sh; 6 python3 push_page.py (and --which restart); 7 python3 nav_keys.py if nav settings/pages changed; 8 verify tablet + log; 9 README; 10 git commit + push; 11 memory/artifact. Watchdog-only changes: 1,2,4,5,8,9,10. | Nothing stops this during a live Sunday service except the user (guideline 10). | other campus: Per site.
|
||||||
|
|
||||||
|
### Lens: Site-specific and layout-specific things that are HARD-CODED in .py/.js/.sh/.ps1/.swift files (or exist only as one-time setup outside the repo) rather than in config.json: what a second campus with similar-but-different gear would have to change by editing code today.
|
||||||
|
- [hardcoded] hardcoded: site names in status_panel.py :: "NA Ingest" in module docstring and check docstrings @ /Users/christian/Documents/Status Indicators/status_panel.py:2, :427, :449, :1701, :1749 | today: edit the .py, re-run install.sh | free text; cosmetic only | other campus: rename to the campus's Ingest Mac or make the wording generic ("this Mac") | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: site names in status_panel.py :: Auto-fix daily log header text: "(NA Ingest fixes these by itself on Sundays 06:30-13:30; ...)" @ /Users/christian/Documents/Status Indicators/status_panel.py:680-683 | today: edit AutoLog.write in status_panel.py, re-run install.sh | literal string; the window is duplicated here even though auto_fix.windows is config | other campus: machine name and window must be built from config (site name + auto_fix.windows) or the header lies at the other campus | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: site names in status_panel.py :: Auto-fix log line "NA Ingest's view of Dante or the Dante network" @ /Users/christian/Documents/Status Indicators/status_panel.py:1198-1202 | today: edit status_panel.py | literal string | other campus: use a configured site/machine name | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: site names in status_panel.py :: Webhook texts "NA Ingest: Companion is answering again." / "NA Ingest: Companion has not answered for 30 seconds." and the 30 s threshold @ /Users/christian/Documents/Status Indicators/status_panel.py:2431, :2437-2438 | today: edit status_panel.py (alert_webhook URL itself is config.json:26, empty today) | text + 30 s; payload format {"text": ...} (Slack-style) at :2338 | other campus: site name in the message; threshold and webhook URL per campus
|
||||||
|
- [hardcoded] hardcoded: logging/paths in status_panel.py :: Watchdog log path default ~/Library/Logs/status-panel.log (CLI --log) and rotation 1,000,000 bytes x 3 backups @ /Users/christian/Documents/Status Indicators/status_panel.py:2485, :2491; echoed by install.sh:60 | today: edit status_panel.py or pass --log in the LaunchAgent plist (install.sh) | path; int bytes; int count | other campus: same path works anywhere; the UI should show/tail it | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: logging/paths in status_panel.py :: Default --config = <script dir>/config.json and --data-dir = <script dir> @ /Users/christian/Documents/Status Indicators/status_panel.py:2481-2482 (install.sh:40-41 passes --data-dir) | today: CLI flags set by install.sh | paths | other campus: unchanged; a UI needs to know the live data dir (~/Library/Application Support/status-panel) | (need not be editable)
|
||||||
|
- [runtime-data] hardcoded: logging/paths in status_panel.py :: service-mode.json (ALARMS override state) in the data dir @ /Users/christian/Documents/Status Indicators/status_panel.py:2384 | today: written by the watchdog; filename fixed in code | {override, last_sched} | other campus: unchanged; UI could show current ALARMS state | (need not be editable)
|
||||||
|
- [runtime-data] hardcoded: logging/paths in status_panel.py :: dante-map.json (self-updating Dante name->ip->mac->switch/port/poe map) in the data dir @ /Users/christian/Documents/Status Indicators/status_panel.py:1839-1840, :1902-1906 | today: written by the watchdog every dante_map_seconds; filename fixed | {name: {ip, mac, switch, port, shared, poe, seen}} | other campus: unchanged mechanism; UI should display it (it is the only place the Dante wiring is visible)
|
||||||
|
- [runtime-data] hardcoded: logging/paths in status_panel.py :: atem-reference.json (generated from the restore .xml at start) in the data dir @ /Users/christian/Documents/Status Indicators/status_panel.py:2107, :2114-2115 | today: regenerated each watchdog start from atem.reference | {audio: {input/source: {mixOption, faderGain}}, camera: {cam/cat/param: [values]}} | other campus: unchanged; derived from that campus's restore file | (need not be editable)
|
||||||
|
- [config-default] hardcoded: logging/paths in status_panel.py :: Auto-fix log folder default ~/Desktop/Auto-Fix Logs, file name pattern "YYYY-MM-DD Weekday Auto-Fix.log", one-calendar-month retention @ /Users/christian/Documents/Status Indicators/status_panel.py:630, :637-663, :666, :775 | today: auto_fix.log_dir key exists in code but is not in config.json; pattern and retention are code | path (default ~/Desktop/Auto-Fix Logs); retention fixed at 1 month | other campus: other campus may want a different folder/retention; expose log_dir and retention
|
||||||
|
- [hardcoded] hardcoded: colours and display in status_panel.py :: COLORS table: 11 display levels (ok, warn, notice, quiet, ack, err, off, idle, service, fixing, failed) each with (bg,fg) for flash-on and flash-off phases @ /Users/christian/Documents/Status Indicators/status_panel.py:49-61 | today: edit the dict, re-run install.sh (no page re-import needed) | 24-bit RGB ints, e.g. ok 0x007A2E/0xFFFFFF, err 0xFF0000/0xFFFFFF then 0x000000/0xFF3030 | other campus: same by guideline (every key green/flashing red), but should be editable in one place
|
||||||
|
- [hardcoded] hardcoded: colours and display in status_panel.py :: ORDER / RANK level priority (off < ok < idle < warn < notice < quiet < ack < err) @ /Users/christian/Documents/Status Indicators/status_panel.py:45-46 | today: edit status_panel.py | ordered list | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: colours and display in status_panel.py :: Key text fitting: LINE_CAPACITY=145, LINE_HEIGHT=1.15, fit_size largest=30 smallest=12, 95% height budget @ /Users/christian/Documents/Status Indicators/status_panel.py:1331-1356 | today: edit constants | measured on Companion 5.0.7 web buttons for a Stream Deck XL key | other campus: a different surface (Mini/+/plus) or Companion build may need re-measuring | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: colours and display in status_panel.py :: Stale-watchdog size 20 and purple used by the page expressions when sp_hb is old @ /Users/christian/Documents/Status Indicators/make_page.py:35, :62, :65 | today: edit make_page.py, rebuild and re-import both pages | PURPLE 0x6A1B9A, fg 0xFFFFFF, size 20 | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: key wording in status_panel.py :: Hold-feedback texts: NOTHING TO FIX, NO AUTO FIX, JUST TRIED WAIT 1 MIN, CHECKING, FIXING, AUTO-FIXING, WAKING PC, FIX FAILED, RESTARTING, DONE, FAILED, JUST DONE WAIT 1 MIN, STARTING WAIT 1 MIN, RECORDING LEFT ALONE, NOTHING TO CLEAR, CLEARED @ /Users/christian/Documents/Status Indicators/status_panel.py:1045-1070, :1115-1145, :1269-1294 | today: edit status_panel.py | English literals, with their display seconds (4, 15, 20, 30, 60, 120, 180) | other campus: same unless the other campus wants other words/language | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: key wording in status_panel.py :: Rendered texts: ALL OK, "%d DOWN", "%d WARN", OK, OFF, ON, manual, HOLD TO FIX, HOLD 2s, "+%d" @ /Users/christian/Documents/Status Indicators/status_panel.py:1372-1421 | today: edit render() | "HOLD 2s" assumes make_page HOLD_MS=2000 | other campus: keep in sync with hold duration if it changes | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: key wording in status_panel.py :: Check fault words: NO DATA, NOT IN COMPANION, DISABLED, STARTING, WARNING, OFFLINE, NO DISK, FULL x, LOW x, NOT REC, <alias> GONE, <ch> BATT, <ch> INTERF, <ch> RF MUTE, NO PING, NOT LOADED, STOPPED, UNREACH, SPOTIFY CLOSED, NOT PLAYING, WRONG PLAYLIST, PLAYING, DISK nGB, NO SESSION, MIDI LOST, MIDI <state>, SILENT, LINK DOWN, NO GATEWAY, n DROPPED, BLIP HH:MM, CHECK BUG, "x%d" flapping @ /Users/christian/Documents/Status Indicators/status_panel.py:200-520, :616, :803, :817-860 | today: edit each check function | English literals; Shure channel names cut to 9 chars (:326), Tascam messages to 14 (:378,:382) | other campus: unchanged; a UI glossary should list them so operators can be taught | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: key wording in status_panel.py :: short_reason mapping of Companion messages (ETIMEDOUT/TIMED OUT/TIMEOUT -> NO ANSWER, ECONNREFUSED -> REFUSED, EHOSTUNREACH/ENETUNREACH/EHOSTDOWN -> UNREACHABLE, AUTH/LOGIN/PASSWORD/401/403 -> LOGIN) @ /Users/christian/Documents/Status Indicators/status_panel.py:186-195 | today: edit the tuple | pattern -> word | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Poller intervals: connections 1 s, variables 1 s, slow-variables 3 s, conn-hosts 60 s, spotify 3 s, pings 3 s, midi 0.5 s, system 10 s, bonjour 15 s, http 5 s, remote 10 s, dante-map 30 s @ /Users/christian/Documents/Status Indicators/status_panel.py:2304-2315 | today: edit start_pollers | seconds (float) | other campus: a campus with more SSH hosts or slower links may need different intervals
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Main loop: 2.5 s start grace, redraw every 0.5 s, sp_state rewritten at least every 10 s, loop sleep 0.25 s @ /Users/christian/Documents/Status Indicators/status_panel.py:2387, :2400, :2417, :2440 | today: edit run() | seconds | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Companion HTTP timeout 3 s; connections considered fresh for 10 s @ /Users/christian/Documents/Status Indicators/status_panel.py:83, :181 | today: edit Companion.__init__ / Snapshot.conns_fresh | seconds | other campus: unchanged | (need not be editable)
|
||||||
|
- [config-default] hardcoded: timing in status_panel.py :: Debounce overrides: network 0 s, midi_peer 1 s (per-check debounce_seconds key allowed); general debounce from config (default 3) @ /Users/christian/Documents/Status Indicators/status_panel.py:748, :882-884 | today: debounce_seconds in config.json or per check; midi/network specials are code | seconds | other campus: expose global and per-check debounce
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Remote-Mac MIDI report considered stale after 12 s (no verdict) @ /Users/christian/Documents/Status Indicators/status_panel.py:472 | today: edit check_midi_peer | 12 s | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Ping: /sbin/ping -c 1 -t 1 -q, 3 s subprocess timeout; a host alarms after 2 consecutive misses (ping check default failures=2; gateway fixed at 2) @ /Users/christian/Documents/Status Indicators/status_panel.py:396, :820, :1445-1449 | today: edit ping_once / check_ping (per-check "failures" key exists) | int misses; the -t 1 wait is macOS ping syntax | other campus: expose misses; Linux ping would need -W instead of -t
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Link check: /sbin/ifconfig <network.interface> must print "status: active" @ /Users/christian/Documents/Status Indicators/status_panel.py:1467-1472 | today: interface name is config (network.interface = en0); the method is code | interface name | other campus: en0 vs enX differs per Mac; the UI should list the Mac's interfaces to pick from
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Bonjour browse: /usr/bin/dns-sd -B _apple-midi._udp local. collected for 3 s @ /Users/christian/Documents/Status Indicators/status_panel.py:1510-1518 | today: edit poll_mdns | only RTP-MIDI instances can be an "mdns" check; other service types need code | other campus: a campus wanting to watch another Bonjour type (e.g. _dante) needs the type made configurable | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: HTTP checks: HEAD request, 6 s timeout, TLS unverified, any HTTP answer = OK; default interval 30 s @ /Users/christian/Documents/Status Indicators/status_panel.py:1521-1541, :2291 | today: interval_seconds per check is config; method/timeout are code | seconds | other campus: unchanged
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Hold freshness: HOLD_MAX_AGE 15 s (older queued holds are dropped) @ /Users/christian/Documents/Status Indicators/status_panel.py:2345 | today: edit constant | 15 s | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: timing in status_panel.py :: Fix cooldown 60 s per fix identity (holds, restart keys and auto-fix share it); auto_fix throttle 5 s; Dante-wide warning suppressed 600 s @ /Users/christian/Documents/Status Indicators/status_panel.py:1041, :1074, :1161, :1197, :1219, :1256 | today: edit defaults | seconds | other campus: expose cooldown
|
||||||
|
- [config-default] hardcoded: timing in status_panel.py :: auto_fix defaults in code: after_seconds 20, max_tries 2, retry_seconds 90, pause_after_network_seconds 60, forget_seconds 600, restart_connections, fixes list, windows @ /Users/christian/Documents/Status Indicators/status_panel.py:1164-1246 (config.json:98-105 sets most; forget_seconds only in code) | today: config.json auto_fix block, re-run install.sh | ints; windows [{days,start,end}]; fixes = list of fix ids that are 'gentle' | other campus: service times and gentle-fix list differ per campus
|
||||||
|
- [config-default] hardcoded: timing in status_panel.py :: network defaults in code: burst_connections 4, burst_groups 3, burst_window_seconds 10, remember_seconds 300, gateway, interface @ /Users/christian/Documents/Status Indicators/status_panel.py:815-865 (config.json:17-24) | today: config.json network block | ints, IP, interface | other campus: gateway and interface differ; thresholds may
|
||||||
|
- [config-default] hardcoded: timing in status_panel.py :: flap defaults: count 3 within window_seconds 600 @ /Users/christian/Documents/Status Indicators/status_panel.py:749-750 (config.json:15) | today: config.json flap | ints | other campus: same
|
||||||
|
- [config-default] hardcoded: check thresholds in status_panel.py :: HyperDeck defaults: full_minutes 5, low_minutes 30, rec_sync True, disk_service_only True, optional False; variables status/slotId/recordingTime/ip from Companion's hyperdeck module @ /Users/christian/Documents/Status Indicators/status_panel.py:254-289, :558-560 | today: per-check keys in config.json; variable names are code | minutes; booleans | other campus: a campus with decks 2 and 4 only: remove hd1/3/5/6 tiles from config AND the REC group members list; thresholds per deck
|
||||||
|
- [config-default] hardcoded: check thresholds in status_panel.py :: Shure defaults: channels 1, battery_err_bars 1, battery_warn_bars 2; variables ch_N_name / ch_N_battery_bars / ch_N_interference_status (shure-wireless module) @ /Users/christian/Documents/Status Indicators/status_panel.py:324-352, :565-568 | today: per-check keys; variable names are code | ints 0-5 | other campus: channel counts per receiver differ (4/4/1 at NA)
|
||||||
|
- [config-default] hardcoded: check thresholds in status_panel.py :: PSM defaults: channels 1, muted_is_error False; variable ch_N_rf_mute == MUTED @ /Users/christian/Documents/Status Indicators/status_panel.py:355-366, :569-571 | today: per-check keys | ints; bool | other campus: channel counts differ
|
||||||
|
- [hardcoded] hardcoded: check thresholds in status_panel.py :: Tascam: variables error/caution; OK strings "no error"/"no caution" @ /Users/christian/Documents/Status Indicators/status_panel.py:369-385, :563-564 | today: edit check_tascam | tascam-da-6400 module variable names | other campus: unchanged if same recorder | (need not be editable)
|
||||||
|
- [config-default] hardcoded: check thresholds in status_panel.py :: Disk defaults: err_gb 5, warn_gb 20 @ /Users/christian/Documents/Status Indicators/status_panel.py:456-465 | today: per-check keys (config.json:216 sets 25/8) | GB ints | other campus: same
|
||||||
|
- [hardcoded] hardcoded: check thresholds in status_panel.py :: Dante check: Companion variable "devices" (JSON list) and "<name>_ip" from the audinate-dantecontroller module; first dante check's conn is the one the map uses @ /Users/christian/Documents/Status Indicators/status_panel.py:292-321, :562, :1861-1866, :1880-1886 | today: edit code | module-specific variable names | other campus: unchanged if the same Companion Dante module is used | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: check thresholds in status_panel.py :: Art-Net listener: UDP 6454 on all interfaces with SO_REUSEPORT, magic "Art-Net\0", default max_silence_seconds 5 @ /Users/christian/Documents/Status Indicators/status_panel.py:510-520, :2184-2208 | today: source IP and max_silence per check in config; port in code | port 6454; seconds | other campus: source IP of the lighting PC differs
|
||||||
|
- [hardcoded] hardcoded: check thresholds in status_panel.py :: ATEM check: watcher report older than 30 s = no verdict; fix wait 8 s; watcher restart wait 5 s @ /Users/christian/Documents/Status Indicators/status_panel.py:2145, :2147, :2173 | today: edit code | seconds | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: check thresholds in status_panel.py :: midi_rx: "max_silence_seconds" only alarms while service mode is on; shows last-message age @ /Users/christian/Documents/Status Indicators/status_panel.py:496-507 | today: per-check key | seconds | other campus: source name differs ("Network NA Ingest Listen" is the NA session name)
|
||||||
|
- [hardcoded] hardcoded: check registry in status_panel.py :: CHECKS registry: conn, hyperdeck, dante, shure, psm, tascam, ping, launchd, process, http, disk, midi_peer, pref, spotify, mdns, midi_rx, remote, artnet, atem @ /Users/christian/Documents/Status Indicators/status_panel.py:534-539, :2181 | today: a new check type is Python code + a test | the UI's "add check" menu must offer exactly these with each one's keys | other campus: same set; a campus wanting e.g. a UniFi or OSC check needs code
|
||||||
|
- [hardcoded] hardcoded: check registry in status_panel.py :: Per-check keys the code understands: type, conn, fix (string or {sub: id, "*": id}), tag, name, msg, service_only, ignore_warning, optional, full_minutes, low_minutes, rec_sync, disk_service_only, ip, channels, battery_err_bars, battery_warn_bars, muted_is_error, host, failures, label, match, url, interval_seconds, path, warn_gb, err_gb, session, peer, address, domain, key, want, playlist, instance, source, max_silence_seconds, cmd, part, devices, count_all, debounce_seconds @ /Users/christian/Documents/Status Indicators/status_panel.py:244-531, :883, :987-999 | today: known only by reading the code | schema per check type; no JSON schema exists today | other campus: the UI must carry this schema so another campus can build checks without reading Python
|
||||||
|
- [hardcoded] hardcoded: check registry in status_panel.py :: RESTARTABLE check types (conn, hyperdeck, shure, psm, tascam) whose failing connection a hold restarts without a "fix" @ /Users/christian/Documents/Status Indicators/status_panel.py:542, :1005-1009; make_page.py:167 | today: edit tuple | tuple of type names | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: check registry in status_panel.py :: Reserved fix ids "restart-connection" and "dante-auto" @ /Users/christian/Documents/Status Indicators/status_panel.py:545, :549, :963-977 | today: edit constants | two magic strings a check's "fix" may name besides entries in "fixes" | other campus: UI fix picker must offer them
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: Fix kinds: ssh (default), local, atem, reconnect, midi_connect, netgear_poe, hyperdeck; and their keys host, run, short, label, timeout(60), reconnect, conn, restart_ok, part, verify_seconds(20), off_seconds(2), hung_note("STILL DOWN"), switch, port, mac @ /Users/christian/Documents/Status Indicators/status_panel.py:1921-2037 | today: fixes live in config.json; the kinds and their meaning are code | enum + per-kind schema | other campus: UI "add fix" must offer these kinds; a new kind (e.g. UniFi PoE) is code
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: "local" fixes run with /bin/zsh -c; "ssh" fixes run the raw string on the remote's default shell (cmd.exe on the Windows PCs) @ /Users/christian/Documents/Status Indicators/status_panel.py:1928 | today: edit code | shell choice | other campus: Windows fixes must be written in cmd syntax; a PowerShell-default sshd would break them | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: ssh exit 255 handling: with hosts[h].mac -> Wake-on-LAN and WAKING; else "<SHORT> NOT REACHABLE" @ /Users/christian/Documents/Status Indicators/status_panel.py:1932-1942 | today: edit code; mac/wake_broadcast per host in config | MAC, broadcast IP per host | other campus: each wakeable PC's MAC and subnet broadcast
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: Wake-on-LAN: UDP port 9, sends to wake_broadcast and 255.255.255.255 @ /Users/christian/Documents/Status Indicators/status_panel.py:1759-1769 | today: edit code | port 9 | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: reconnect_and_verify outcome strings: STILL OFF IN COMPANION, DISABLED IN COMPANION, STILL CONNECTING, NO NETWORK CHECK POWER, default hung_note STILL DOWN @ /Users/christian/Documents/Status Indicators/status_panel.py:2012, :2019, :2031, :2035, :2037 | today: edit code (hung_note per fix is config) | literals | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: hyperdeck_probe: control port 9993, 3 s, "transport info" protocol (present in code; no longer used by the hyperdeck fix kind) @ /Users/christian/Documents/Status Indicators/status_panel.py:1802-1834 | today: dead-ish code | port 9993 | other campus: n/a; candidate for removal or a 'probe' check type | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: fix engine in status_panel.py :: load_config validation: unknown check type/fix/host/member, duplicate ids, restart key ids, watch entries, and refusal of pkill/taskkill/killall in auto_fix fixes unless restart_ok @ /Users/christian/Documents/Status Indicators/status_panel.py:2234-2274 | today: the only validator; runs at start and in install.sh (JSON parse only) | rules | other campus: the UI must run the same validation before saving (R8)
|
||||||
|
- [hardcoded] hardcoded: tiles/layout logic in status_panel.py :: Tile keys the code understands: id, label, pos [row,col], kind (master | service | network), members [ids], checks [], hold_always (fix id); tiles without pos are hidden members @ /Users/christian/Documents/Status Indicators/status_panel.py:744-783, :1364-1423; make_page.py:151-192, :269-278 | today: config.json tiles; schema only in code | ids unique; members must be tile ids; pos must not collide (make_page.place raises) | other campus: the whole tiles list is the layout; UI needs a grid editor
|
||||||
|
- [hardcoded] hardcoded: tiles/layout logic in status_panel.py :: restart_page keys schema: page, page_name, jump_pos, back_pos, keys [{id, label, pos, fix, watch [fix ids or "conn:<label>"]}] @ /Users/christian/Documents/Status Indicators/status_panel.py:771, :1012-1027, :1256-1296, :1367-1376; make_page.py:131-148, :281-288 | today: config.json restart_page | restart key ids must not equal tile ids | other campus: different restart keys per campus
|
||||||
|
- [hardcoded] hardcoded: tiles/layout logic in status_panel.py :: Group tiles repeat members' faults; master counts detail tiles; service kind draws ON/OFF/manual; network kind draws link/gateway/burst @ /Users/christian/Documents/Status Indicators/status_panel.py:1383-1423, :815-865 | today: code | three special kinds | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: tiles/layout logic in status_panel.py :: Service windows / auto-fix windows day names DAYS = mon..sun, times "HH:MM" local, compared as strings @ /Users/christian/Documents/Status Indicators/status_panel.py:622, :689-693, :711-715 | today: config.json service_windows / auto_fix.windows | days list; "HH:MM"; end exclusive; no overnight windows | other campus: service times differ per campus
|
||||||
|
- [hardcoded] hardcoded: OS tools in status_panel.py :: Absolute tool paths: /sbin/ping, /sbin/ifconfig, /bin/launchctl, /usr/bin/pgrep, /usr/bin/defaults, /usr/bin/dns-sd, /usr/sbin/arp, /usr/bin/ssh, /usr/bin/osascript, /bin/zsh, /usr/bin/python3 (remote) @ /Users/christian/Documents/Status Indicators/status_panel.py:1447, :1469, :1478, :1488, :1494, :1511, :1632, :1636, :1708, :1741, :1750, :1857, :1928 | today: edit code | macOS paths | other campus: unchanged on any Mac; the Ingest machine must be a Mac | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: OS tools in status_panel.py :: launchd check reads `launchctl list` of the GUI user; process check uses pgrep -f <match>; pref check uses `defaults read <domain> <key>` @ /Users/christian/Documents/Status Indicators/status_panel.py:1475-1497 | today: labels/domains per check in config | LaunchAgent labels (org.waterschurch.* at NA), process paths, defaults domains | other campus: the Ingest Mac's own helper agents (resi bridge, captions, ProdCom, ProdDash) are NA-specific; another campus has its own list
|
||||||
|
- [companion-side] hardcoded: Companion API in status_panel.py :: Companion HTTP API paths: GET /api/connections, GET /api/variable/<label>/<name>/value, GET/POST /api/custom-variable/<name>/value (?value= to clear), POST /api/connections/<id>/restart|disable|enable @ /Users/christian/Documents/Status Indicators/status_panel.py:102-138 | today: code; Companion 5.0.7 API | host/port from config companion (127.0.0.1:8000 default at :2296) | other campus: same if the same Companion major version; the UI should show Companion version | (need not be editable)
|
||||||
|
- [companion-side] hardcoded: Companion API in status_panel.py :: Companion settings DB read: ~/Library/Application Support/companion/v<X.Y>/db.sqlite (newest), table instances, JSON field config.host, read-only, 2 s timeout (override key companion.db) @ /Users/christian/Documents/Status Indicators/status_panel.py:1552-1582 | today: companion.db key (not set) or code | path | other campus: Companion must run on the same Mac as the watchdog; schema may change with Companion versions | (need not be editable)
|
||||||
|
- [companion-side] hardcoded: Companion API in status_panel.py :: Custom variables sp_state / sp_hb / sp_cmd (names from config "variables") must be CREATED BY HAND in Companion; the API returns 404 for a missing one (code only logs "create it in Companion") @ /Users/christian/Documents/Status Indicators/status_panel.py:2418-2427; config.json:8 | today: Companion admin UI > Variables > Custom | three names | other campus: one-time step at every campus; the UI should detect and say which are missing
|
||||||
|
- [hardcoded] hardcoded: Companion API in status_panel.py :: Command protocol over sp_cmd: ack <tile>; ackall; service [on|off]; restart <tile> <unix>; force <key> <unix>; ';'-separated @ /Users/christian/Documents/Status Indicators/status_panel.py:2348-2376; make_page.py:83-97, :144-146, :166-182 | today: code on both sides | grammar | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: Spotify in status_panel.py :: AppleScript SPOTIFY_STATE: tell application "Spotify" ... player state, id/name/artist of current track; only when `pgrep -xq Spotify` (never opens it) @ /Users/christian/Documents/Status Indicators/status_panel.py:1593-1594, :1631-1641 | today: edit code | app name "Spotify" fixed; needs macOS Automation permission python3 -> Spotify (external) | other campus: a campus using Apple Music or another player needs a different check type | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: Spotify in status_panel.py :: Playlist reader: https://open.spotify.com/embed/playlist/<id> (__NEXT_DATA__, first 100 songs, anonymous token) then https://spclient.wg.spotify.com/playlist/v2/playlist/<id>?from=0&length=1000; UA Mozilla/5.0; refresh every 3600 s and on an unknown song after 60 s; last-3-songs rule @ /Users/christian/Documents/Status Indicators/status_panel.py:1597-1616, :1629, :1643, :156, :436-444 | today: edit code | undocumented Spotify endpoints; playlist must be public | other campus: same mechanism; fragile if Spotify changes its embed page | (need not be editable)
|
||||||
|
- [config-key] hardcoded: Spotify in status_panel.py :: Spotify playlist URI appears TWICE: check "playlist" and inside the spotify_play fix's run command (play track "spotify:playlist:2uTpJVYQLFnKF1WnSOuco8"), plus the name "Christian EDM 2026" in the fix label and check name @ /Users/christian/Documents/Status Indicators/config.json:75-76, :173; README.md:92, :123 | today: edit both places in config.json by hand, re-run install.sh | spotify:playlist:<22 chars> | other campus: one field in the UI must write both the check and the fix command (or the fix should read the check's playlist)
|
||||||
|
- [config-key] hardcoded: Spotify in status_panel.py :: spotify_play fix: pgrep Spotify || open -g -a Spotify; sleep 8; osascript play track; sleep 3; verify playing @ /Users/christian/Documents/Status Indicators/config.json:76 | today: config.json fixes.spotify_play.run | shell string with embedded AppleScript and the playlist URI | other campus: template it: UI generates the run string from playlist + delays
|
||||||
|
- [hardcoded] hardcoded: ATEM in status_panel.py :: MIX_OPTIONS Off=1 On=2 AudioFollowVideo=4 and CCU_PARAMS: the 13 camera settings compared (Lens/ApertureFstop, Video/ManualWhiteBalance, Exposure, DetailLevel, SensorGainInDecibels, SensorNDFilter, ColorCorrection Lift/Gamma/Gain/Offset/Contrast/LumaMix/ColorAdjust) with category/parameter ids and value order @ /Users/christian/Documents/Status Indicators/status_panel.py:2042-2058 | today: edit the dict | which camera settings count as 'wrong' | other campus: a campus that lets operators change e.g. iris live must drop ApertureFstop; should be a checklist in the UI
|
||||||
|
- [hardcoded] hardcoded: ATEM in status_panel.py :: Restore-file parser: expects ATEM Software Control XML with FairlightAudioMixer/AudioInput/AudioSource (mixOption, faderGain dB -> x100) and CameraControl entries (device, category, parameter, fields) @ /Users/christian/Documents/Status Indicators/status_panel.py:2061-2086 | today: code | XML schema of ATEM Software Control 9.x | other campus: unchanged for any ATEM with Fairlight; a classic-audio ATEM would need another parser | (need not be editable)
|
||||||
|
- [config-key] hardcoded: ATEM in status_panel.py :: atem config keys and their Companion-version-bound globs: node "/Applications/Companion [0-9]*.app/Contents/Resources/node-runtimes/node22/bin/node", library "~/Library/Application Support/companion/modules/bmd-atem-*/node_modules/atem-connection", reference "atem-reference.xml" (relative to script dir), host @ /Users/christian/Documents/Status Indicators/config.json:91-96; status_panel.py:2101-2115, :2089-2095 | today: config.json atem block | globs; newest version picked by numbers in the path | other campus: switcher IP differs; node22 / app-name globs break on a Companion upgrade; the UI should show which node/library resolved
|
||||||
|
- [external-setup] hardcoded: ATEM in status_panel.py :: atem-reference.xml: a copy of "4k plus 2026-10-07 14-59-55.xml" from Google Drive Backup/NA/ATEM (product "ATEM 4 M/E Constellation 4K Plus"), 1.6 MB committed to git, copied by install.sh @ /Users/christian/Documents/Status Indicators/atem-reference.xml; README.md:114; install.sh:28 | today: copy the new restore file over atem-reference.xml, re-run install.sh (watchdog rebuilds the JSON at start) | file upload | other campus: each campus has its own restore file; the UI needs an 'upload new restore file' action and shows its date/product
|
||||||
|
- [hardcoded] hardcoded: atem_watch.js :: CLI argv order <switcher ip> <atem-connection path> <reference.json>; report every 1 s, repeat every 10 s; float tolerance 0.002; MIX labels Off/On/AFV; CCdP command name; type 128 = float; disableMultithreaded @ /Users/christian/Documents/Status Indicators/atem_watch.js:13-21, :39, :76, :96-100, :113 | today: edit the .js, re-run install.sh | constants | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: atem_state.js :: Read-only snapshot tool: 15 s connect timeout, default 3 s CCU collection; not deployed by install.sh @ /Users/christian/Documents/Status Indicators/atem_state.js:6, :26, :39 | today: run by hand with Companion's node | argv <ip> <lib> [secs] | other campus: useful as a UI 'what does the switcher look like now' button | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: SSH in status_panel.py :: ssh_cmd options: -i <ssh_key> (default ~/.ssh/id_status_panel), BatchMode=yes, ConnectTimeout=5, IdentitiesOnly=yes, LogLevel=ERROR, StrictHostKeyChecking=accept-new, ControlMaster=auto, ControlPath=/tmp/status-panel-ssh-%C, ControlPersist=120; target string hosts[h].ssh @ /Users/christian/Documents/Status Indicators/status_panel.py:1748-1753 | today: ssh_key is config.json:28; the options are code | OpenSSH options | other campus: unchanged; the key file must exist on that campus's Ingest Mac | (need not be editable)
|
||||||
|
- [config-key] hardcoded: SSH in status_panel.py :: hosts schema: {alias: {ssh: "user@ip", mac?, wake_broadcast?}} (7 aliases at NA: prop, tracks, translation, bibletv, lighting, studiob, livestream) @ /Users/christian/Documents/Status Indicators/config.json:31-39; status_panel.py:1931-1935, :2254-2258 | today: config.json hosts | alias; user@host; MAC; broadcast | other campus: entirely per campus; these aliases are what fixes/checks/midi_peer refer to (R5 machine list)
|
||||||
|
- [hardcoded] hardcoded: SSH in status_panel.py :: Remote MIDI over SSH: coremidi.py source is sent over stdin to /usr/bin/python3 on the other Mac (streamed; ServerAliveInterval=2/CountMax=3; 0.5 s poll, 5 s keepalive print, 3 s reconnect wait; one-shot 20 s timeout; connect_peer 30 s) @ /Users/christian/Documents/Status Indicators/status_panel.py:1663-1746, :1959-1976 | today: code | requires macOS python3 + CoreMIDI on the remote Mac (Macs only; a Windows rtpMIDI host cannot be a midi_peer "host") | other campus: any Mac whose sessions are watched needs Remote Login on and the key authorized | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: SSH in status_panel.py :: remote checks batched per host every 10 s as one shell line "(cmd) >/dev/null 2>&1; echo SPn=$?" with 20 s timeout @ /Users/christian/Documents/Status Indicators/status_panel.py:1772-1799 | today: code | POSIX-sh syntax; would not work on a cmd.exe host | other campus: remote checks only for Macs/Linux today | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: Dante/Netgear in netgear.py :: SWITCHES default list ["10.66.1.100", "10.66.1.102"] used by the `lookup` CLI (the watchdog passes config dante_switches) @ /Users/christian/Documents/Status Indicators/netgear.py:21, :74 | today: edit netgear.py (duplicate of config.json:30) | list of IPs | other campus: other campus's switch IPs; CLI default should read config
|
||||||
|
- [secret] hardcoded: Dante/Netgear in netgear.py :: KEYCHAIN_SERVICE "netgear-status-panel" (login keychain item; account = switch username, e.g. admin) @ /Users/christian/Documents/Status Indicators/netgear.py:22-34 | today: once in Terminal on the Ingest Mac: security add-generic-password -s netgear-status-panel -a admin -w (never in files; only readable in the GUI session, so the watchdog must be a LaunchAgent) | service name fixed; username/password in keychain | other campus: one-time step per campus; the UI should show present/missing and the account name, never the password
|
||||||
|
- [hardcoded] hardcoded: Dante/Netgear in netgear.py :: Netgear M4250 CLI driver: telnet port 23, 8 s timeout, prompt regex [>#], User:/Password: prompts, `enable` with blank password, `terminal length 0`, commands show version / show mac-addr-table [interface X] / show poe port info X / configure / interface X / poe reset / logout @ /Users/christian/Documents/Status Indicators/netgear.py:37-68, :77-131 | today: code | vendor-specific | other campus: a campus on UniFi/Cisco/other PoE switches needs a different driver (fix kind netgear_poe and the map survey both depend on it) | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: Dante/Netgear in netgear.py :: MAX_SHARED = 2 (a port with more MACs is an uplink and is never power-cycled); port format \d+/\d+ @ /Users/christian/Documents/Status Indicators/netgear.py:118, :143, :185, :201 | today: edit constant | int | other campus: same; expose as a safety setting
|
||||||
|
- [external-setup] hardcoded: Dante/Netgear in status_panel.py :: Dante map survey uses this Mac's ARP cache (`arp -an`), i.e. the Ingest Mac must sit on the Dante VLAN (NA: Dante port en17, switch management via en20 10.66.1.2 per memory); those interfaces are not in config @ /Users/christian/Documents/Status Indicators/status_panel.py:1855-1858, :1869-1906; memory companion-status-page.md | today: physical/network setup; nothing to edit | interface names for display | other campus: the UI should show which interface reaches the Dante subnet and whether the switches answer
|
||||||
|
- [config-default] hardcoded: Dante/Netgear in status_panel.py :: dante_map_seconds default 600; first map attempted every 30 s until success; map entries kept forever for devices that vanish @ /Users/christian/Documents/Status Indicators/status_panel.py:1875-1877, :2315 | today: dante_map_seconds key (not in config.json) | seconds | other campus: same
|
||||||
|
- [hardcoded] hardcoded: coremidi.py :: NETWORK_DRIVERS = (com.apple.AppleMIDINetworkDriver, com.apple.AppleMIDIRTPDriver) - macOS 26.5 vs 26.2 names @ /Users/christian/Documents/Status Indicators/coremidi.py:57-59 | today: edit tuple | driver bundle ids | other campus: a Mac on another macOS release may need a third name; the UI should show 'no network MIDI driver found' clearly | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: coremidi.py :: CoreMIDI client/port names "status-panel" / "status-panel in"; LIVE_STATES running/connected; session property apple.midirtp.session / dns-sd-name; clock/active-sensing (0xF8/0xFE) ignored @ /Users/christian/Documents/Status Indicators/coremidi.py:120, :151, :163, :251, :300, :330 | today: code | constants | other campus: unchanged | (need not be editable)
|
||||||
|
- [config-key] hardcoded: coremidi.py :: midi_peer identity = (host alias, session name, peer name) + address "ip:5004" or Bonjour "\tName\tlocal." form; session and peer names are the human names set in Audio MIDI Setup / rtpMIDI ("NA Ingest", "NA Tracks", "NA ProP", "Lighting PC", "SuperRack-LiveBox", "Studio B Mac Studio") @ /Users/christian/Documents/Status Indicators/config.json:161-166; coremidi.py:171-193; status_panel.py:468-487, :993-1000 | today: config.json midi checks | session (must exist on that Mac), peer, address | other campus: all names/addresses differ; the UI should list live sessions/peers read from coremidi to pick from
|
||||||
|
- [hardcoded] config: layout in make_page.py :: EXPORT_VERSION 12 and COMPANION_BUILD "5.0.7+9763-stable-cec2f88e6f" written into every page export @ /Users/christian/Documents/Status Indicators/make_page.py:31-32, :257-259 | today: edit make_page.py, rebuild, re-import | must be acceptable to the campus's Companion | other campus: a campus on another Companion build may need these bumped; UI should read Companion's version and warn | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: GRID {minColumn 0, maxColumn 8, minRow 0, maxRow 5} in the page export (Stream Deck XL shows 8x4 of it) @ /Users/christian/Documents/Status Indicators/make_page.py:33, :260; README.md:10-16 | today: edit make_page.py | grid bounds | other campus: a campus with a different surface (Stream Deck +, Mini, two XLs) needs different bounds and a different tile map
|
||||||
|
- [hardcoded] config: layout in make_page.py :: STALE_SECONDS 15 (sp_hb older -> purple WATCHDOG DOWN) in the button expressions @ /Users/christian/Documents/Status Indicators/make_page.py:34, :53, :209 | today: edit, rebuild, re-import both pages and re-run nav_keys.py | seconds | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: HOLD_MS 2000 (hold duration for restart/force) and nav hold default 1000 ms / tap page 1 @ /Users/christian/Documents/Status Indicators/make_page.py:36, :144-146, :181-182, :199; config.json:6-7 (nav_hold_ms, nav_tap_page) | today: HOLD_MS is code (rebuild + re-import); nav values are config | ms | other campus: same by guideline; if changed, status_panel's "HOLD 2s" text must follow
|
||||||
|
- [companion-side] config: layout in make_page.py :: Stale texts "WATCHDOG\nDOWN\n<instance_errors> ERR" (master) and "<label>\n?" (others); uses $(internal:instance_errors) and $(custom:flash_tick) @ /Users/christian/Documents/Status Indicators/make_page.py:9, :61-62, :157, :161 | today: code; flash_tick custom variable + its 500 ms trigger must already exist in Companion | flash_tick must be created by hand at a new campus (trigger "flash_tick_500") | other campus: one-time Companion step; UI should detect a missing flash_tick
|
||||||
|
- [hardcoded] config: layout in make_page.py :: Button style: font companion-sans, outlineColor 4278190080, decoration border, fontsizeAllowShrink, per-key size from sp_state 's' @ /Users/christian/Documents/Status Indicators/make_page.py:66-80, :107-118 | today: edit, rebuild, re-import | Companion 5 layered-button fields | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: Static jump keys RESTART PAGE / STATUS PAGE: colour 0x37474F, white text, positions from restart_page.jump_pos / back_pos (both [2,6] at NA) @ /Users/christian/Documents/Status Indicators/make_page.py:274-277, :284-285; config.json:108 | today: colour/text in code; positions in config | pos; colour | other campus: positions depend on the campus's tile map
|
||||||
|
- [hardcoded] config: layout in make_page.py :: NAV_LOCATIONS pageup (0,0), pagenum (1,0), pagedown (2,0): column 0 is reserved for nav keys on both pages; NAV_MARKER "STATUS nav key" @ /Users/christian/Documents/Status Indicators/make_page.py:195-196, :238-245; push_page.py:122-123; nav_keys.py:29 | today: edit make_page.py (and the tile map must avoid those cells) | row/col tuples | other campus: Companion default; a campus that moved its nav keys needs this changed | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: nav_templates.json: Companion 5.0.7's converted UP / page number / DOWN keys captured from page 19 (ids like izGK69mOF_3K_vast-75-) @ /Users/christian/Documents/Status Indicators/nav_templates.json; make_page.py:207 | today: re-capture from Companion after 'convert to regular button' if the version changes | JSON blob | other campus: re-capture at a campus on a different Companion version | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: Hold decision per tile: hold exists only if a check is RESTARTABLE with a conn, or any check has a fix, or members, or hold_always, or kind network @ /Users/christian/Documents/Status Indicators/make_page.py:167-169 | today: code; a tile gaining/losing a hold requires rebuild + re-import | rule | other campus: UI must know when a config change needs a page re-import (R8)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: Button notes text mentioning "status_panel.py on NA Ingest" and "clears itself after 5 minutes" (remember_seconds is config) @ /Users/christian/Documents/Status Indicators/make_page.py:140-141, :171, :188 | today: code | help text seen in Companion's editor | other campus: site name; '5 minutes' should come from config | (need not be editable)
|
||||||
|
- [hardcoded] config: layout in make_page.py :: Output file names status-page.companionconfig / restart-page.companionconfig (gzipped JSON, committed to git) @ /Users/christian/Documents/Status Indicators/make_page.py:298-303; push_page.py:180-184 | today: python3 make_page.py | paths | other campus: per-site outputs would collide in one repo | (need not be editable)
|
||||||
|
- [companion-side] config: layout in make_page.py :: Companion internal action ids custom_variable_set_value and set_page (surfaceId self), command appended as plain text "$(custom:sp_cmd);<cmd>" @ /Users/christian/Documents/Status Indicators/make_page.py:83-103 | today: code | Companion 5 internals | other campus: unchanged | (need not be editable)
|
||||||
|
- [hardcoded] config: pages in push_page.py :: Defaults --host na-ingest.local, --port 8000; page/page_name from config (19 "19\nStatus", 20 "20\nRestart") @ /Users/christian/Documents/Status Indicators/push_page.py:172-173; config.json:3-4, :108 | today: CLI flags; config.json page numbers (page_name embeds the number so both must change together) | hostname; port; page ints | other campus: other campus's Ingest hostname and free page numbers
|
||||||
|
- [companion-side] config: pages in push_page.py :: Import mechanics: tRPC over WebSocket /trpc, importExport.prepareImport.start/uploadChunk(256 KB)/complete(sha1)/importSinglePage(sourcePage 1), 40 x 3 s retry on an open upload; GET /int/export/page/N; /api/variable/internal/page_number_N_name/value @ /Users/christian/Documents/Status Indicators/push_page.py:32, :103-134, :162-165, :195-224 | today: code; Companion 5.0.7 internal API | internal API | other campus: may break on a Companion upgrade; UI should fall back to 'import by hand' instructions | (need not be editable)
|
||||||
|
- [hardcoded] config: pages in push_page.py :: Safety rule: refuse to import unless the target page holds only nav keys or is already the page of that name (--force overrides) @ /Users/christian/Documents/Status Indicators/push_page.py:137-159, :191-193 | today: code | rule | other campus: UI must surface the refusal reason and never auto-force
|
||||||
|
- [hardcoded] config: pages in nav_keys.py :: Defaults --host na-ingest.local --port 8000; walks pages 1..N until export 404; copies from status page col 0 via controls.copyControl / controls.resetControl; ?includeSecrets=false @ /Users/christian/Documents/Status Indicators/nav_keys.py:32-33, :61-75, :81-82, :110-115 | today: CLI; run after every page move (guideline 9) | hostname; nav_keys list in config (pagenum only today) | other campus: page count and nav key positions differ
|
||||||
|
- [runtime-data] config: pages in nav_keys.py :: nav_locations.json: record of every page/row/col converted (96 entries at NA incl. the odd 15/0/1), committed to git @ /Users/christian/Documents/Status Indicators/nav_locations.json; nav_keys.py:28, :89, :121 | today: written by nav_keys.py | {"page/row/col": kind} | other campus: site-specific; must not be shared between campuses in one repo (UI: 'which pages were touched' view + revert button)
|
||||||
|
- [hardcoded] NA Ingest install: install.sh :: LaunchAgent label org.waterschurch.status-panel; plist ~/Library/LaunchAgents/<label>.plist; domain gui/<uid> @ /Users/christian/Documents/Status Indicators/install.sh:9, :12-13, :35 | today: edit install.sh | reverse-DNS label | other campus: another org/campus may want its own label; UI (R5) must show this agent's state on the Ingest Mac
|
||||||
|
- [hardcoded] NA Ingest install: install.sh :: DEST ~/Library/Application Support/status-panel (not ~/Documents: launchd python3 cannot read it); backup config.json.bak-<timestamp> only on install when changed @ /Users/christian/Documents/Status Indicators/install.sh:10-11, :24-26 | today: edit install.sh | path; one-shot .bak files (no history viewer) | other campus: same path; the UI's history/backups (R8) should live here
|
||||||
|
- [hardcoded] NA Ingest install: install.sh :: Deployed file list: status_panel.py coremidi.py netgear.py config.json atem_watch.js (+ atem-reference.xml if present); atem_state.js, make_page.py, push_page.py, nav_keys.py, nav_templates.json are NOT deployed @ /Users/christian/Documents/Status Indicators/install.sh:27-28 | today: edit the cp line whenever a file is added | file list | other campus: a UI server file must be added here, and page-building tools must be deployed if the UI runs on Ingest | (need not be editable)
|
||||||
|
- [hardcoded] NA Ingest install: install.sh :: Plist: /usr/bin/python3 status_panel.py --data-dir DEST; RunAtLoad; KeepAlive; ThrottleInterval 10; StandardErrorPath ~/Library/Logs/status-panel.err.log; bootout/wait-up-to-10 s/bootstrap x3 @ /Users/christian/Documents/Status Indicators/install.sh:30-58 | today: edit install.sh | launchd keys | other campus: unchanged | (need not be editable)
|
||||||
|
- [workflow-step] NA Ingest install: workflow :: Deploy path from the MacBook: scp files to checklist-ingest:status-panel-install/ then ssh checklist-ingest 'zsh ~/status-panel-install/install.sh'; the alias "checklist-ingest" lives in the user's own ~/.ssh/config (not in the repo) @ /Users/christian/Documents/Status Indicators/README.md:142, :148; .claude/settings.local.json; memory companion-status-page.md | today: by hand from the MacBook | ssh alias; staging folder name | other campus: each campus's Ingest needs its own alias; an on-Ingest UI removes the need to scp at all
|
||||||
|
- [workflow-step] NA Ingest install: workflow :: Page import workflow: python3 make_page.py -> push_page.py (--which restart) -> nav_keys.py; or by hand via Import/Export into page 19/20 (README says the file is on NA Ingest's Desktop) @ /Users/christian/Documents/Status Indicators/README.md:134, :157-163; make_page.py:19; push_page.py:12-14 | today: three commands on the MacBook against na-ingest.local | rebuild only when layout/hold-ability changed | other campus: UI 'Apply layout' button runs all three locally on Ingest
|
||||||
|
- [secret] NA Ingest install: external-setup :: NA Ingest SSH key ~/.ssh/id_status_panel (+.pub, comment status-panel@NA-Ingest) generated once by hand; config ssh_key points to it @ /Users/christian/Documents/Status Indicators/config.json:28; status_panel.py:1750; memory companion-status-page.md | today: ssh-keygen by hand on the Ingest Mac | private key never leaves Ingest; public key is copied into mac-authorize.sh / windows-setup.ps1 | other campus: new key per campus; UI should show the public key to paste and test each host
|
||||||
|
- [external-setup] NA Ingest install: external-setup :: macOS Automation permission: python3 (LaunchAgent) allowed to control Spotify (osascript) @ /Users/christian/Documents/Status Indicators/status_panel.py:1636 (implied); README/memory | today: macOS prompt once on the Ingest screen; System Settings > Privacy & Security > Automation | allowed/denied | other campus: one-time per campus; UI should test with a harmless osascript and report
|
||||||
|
- [external-setup] NA Ingest install: external-setup :: Keychain item netgear-status-panel must be stored in the Ingest login keychain (GUI session only) @ /Users/christian/Documents/Status Indicators/netgear.py:4-10 | today: security add-generic-password in Terminal on Ingest | present/absent | other campus: per campus (different switch login)
|
||||||
|
- [external-setup] NA Ingest install: external-setup :: NA Ingest's own DVS helper + sudoers (mac-dvs-helper.sh run on Ingest too) for the dvs_ingest local fix @ /Users/christian/Documents/Status Indicators/config.json:51; remote-access/mac-dvs-helper.sh:1 | today: ARD Send UNIX Command as root | installed/not | other campus: only if that campus's Ingest runs DVS
|
||||||
|
- [companion-side] NA Ingest install: external-setup :: Companion app itself on Ingest: pages 19/20 free, custom variables sp_state/sp_hb/sp_cmd/flash_tick, trigger flash_tick_500, connections with the labels config names, bmd-atem module installed, quiet Tascam module imported @ /Users/christian/Documents/Status Indicators/README.md:130-165; make_page.py:5-12; tascam-quiet/make_quiet_tascam.py:14-15 | today: Companion admin UI by hand | checklist | other campus: every item re-done per campus; UI should verify each (variables exist? labels exist? module versions?)
|
||||||
|
- [companion-side] Companion side: labels referenced by config :: Connection labels config refers to (HyperDeck_1..6, tascam, x32, audinate-dantecontroller, lighting-midi, Propresenter-API, ableton, BibleTV, ATEM, OBS-Youtube, RESI_Decoder, resi-studio-cues, VX1000, UHDJr, arena, Axient_1, Axient_2, ULXD-4, PSM_1..3) must match Companion's labels exactly (case-sensitive) @ /Users/christian/Documents/Status Indicators/config.json:135-237; status_panel.py:198-214 (NOT IN COMPANION) | today: rename in config.json to match Companion | strings | other campus: UI should offer a dropdown of live labels from GET /api/connections
|
||||||
|
- [hardcoded] Windows PC: windows-setup.ps1 :: $Key literal: NA Ingest's public key "ssh-ed25519 AAAAC3...thbgY status-panel@NA-Ingest"; the comment is also the grep marker @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:17, :52, :59 | today: edit the .ps1 before copying it to each PC | one public key | other campus: other campus's Ingest key; UI should generate this script with the right key filled in
|
||||||
|
- [hardcoded] Windows PC: windows-setup.ps1 :: $IngestIPs = 10.1.1.1, 10.6.3.222 (firewall RemoteAddress allowlist for port 22) @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:18, :46 | today: edit the .ps1 | list of IPs | other campus: that campus's Ingest addresses on every network it touches
|
||||||
|
- [hardcoded] Windows PC: windows-setup.ps1 :: App discovery list: OBS <drive>\Program Files\obs-studio\bin\64bit\obs64.exe (--disable-shutdown-check); Vista <drive>\Vista3\Vista.exe; Resolume Arena*\Arena.exe; Tobias Erichsen\rtpMIDI\rtpMIDI.exe; Bible app = first *.exe matching 'bible' (AtLogon PT15S) @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:64-82 | today: edit the .ps1 | name, exe candidates, args, at-logon flag | other campus: other PCs run other apps (e.g. ProPresenter on Windows, Chamsys); the UI should hold this app table and emit the script
|
||||||
|
- [hardcoded] Windows PC: windows-setup.ps1 :: Scheduled task folder \StatusPanel\ and names "Start OBS", "Start Vista", "Start Arena", "Start rtpMIDI", "Start BibleApp" - referenced verbatim by config fixes (schtasks /Run /TN "\StatusPanel\Start OBS" etc.) @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:102; config.json:57, :59, :61, :63, :65 | today: both files by hand | task names must agree in two places | other campus: UI should derive the fix's schtasks name from the app table
|
||||||
|
- [hardcoded] Windows PC: windows-setup.ps1 :: Elevated task when the exe has RUNASADMIN in AppCompatFlags\Layers (OBS on the LiveStream PC); otherwise Limited interactive principal; settings no time limit, IgnoreNew @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:83-108 | today: script logic | auto-detected | other campus: unchanged | (need not be editable)
|
||||||
|
- [external-setup] Windows PC: windows-setup.ps1 :: sshd: capability install, service Automatic + failure restart 5s/5s/30s; firewall rule OpenSSH-Server-In-TCP port 22 profile Any; keys written to C:\ProgramData\ssh\administrators_authorized_keys and %USERPROFILE%\.ssh\authorized_keys @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:21-61 | today: run once per PC as admin (needs the user's OK: system/firewall change) | done/not per PC | other campus: per PC per campus; UI shows per-PC status (ssh reachable? tasks exist?)
|
||||||
|
- [external-setup] Windows PC: windows-setup.ps1 :: rtpMIDIService failure-restart policy and Wake-on-LAN adapter setting (BIOS WoL must be enabled by hand) @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:112-125 | today: same script | per PC | other campus: per PC
|
||||||
|
- [workflow-step] Windows PC: windows-setup.ps1 :: Manual steps: save as C:\Users\<you>\windows-setup.ps1, PowerShell as admin, Set-ExecutionPolicy RemoteSigned -Scope CurrentUser, run, paste RESULT block back (pc, user, domain, sshd, nic/mac/ip, tasks) @ /Users/christian/Documents/Status Indicators/remote-access/windows-setup.ps1:1-14, :127-134 | today: by hand on each PC | RESULT block feeds hosts.<pc>.ssh user and .mac | other campus: UI should accept the RESULT block and fill hosts
|
||||||
|
- [hardcoded] Mac helpers: mac-authorize.sh :: Public key literal "ssh-ed25519 AAAAC3...thbgY status-panel@NA-Ingest" appended to the console user's ~/.ssh/authorized_keys via ARD Send UNIX Command as root; prints user/ComputerName/ssh-group @ /Users/christian/Documents/Status Indicators/remote-access/mac-authorize.sh:1-5 | today: edit the key in the file, paste into ARD | key; requires Remote Login on each Mac (com.apple.access_ssh) | other campus: other campus's key; UI generates the one-liner and records the printed user name into hosts
|
||||||
|
- [external-setup] Mac helpers: mac-dvs-helper.sh :: /usr/local/sbin/status-panel-dvs-start (defaults write /Library/Preferences/com.audinate.dante.dvs wantToRun 1; launchctl kickstart -k system/com.audinate.dante.DanteVirtualSoundcard) + /etc/sudoers.d/status-panel (<console user> NOPASSWD for that script only); target Macs listed in comment: NA ProPresenter Mac, NA Tracks, NA Translation, NA Ingest @ /Users/christian/Documents/Status Indicators/remote-access/mac-dvs-helper.sh:1-29 | today: paste into ARD as root on each DVS Mac | installed? (ls the two files over ssh) | other campus: any Mac running DVS at the other campus; path must match the dvs_* fixes' "sudo -n /usr/local/sbin/status-panel-dvs-start" in config.json:48-51
|
||||||
|
- [external-setup] Mac helpers: Studio B solo-mode agent :: install-solo-mode.sh: LABEL org.waterschurch.status-panel.solo-mode, DIR ~/Library/Application Support/status-panel, compiles with /usr/bin/swiftc (needs Xcode CLT on that Mac), scp/ssh target studiob@10.1.13.175 in the comment @ /Users/christian/Documents/Status Indicators/remote-access/install-solo-mode.sh:4-12 | today: scp + ssh by hand from Ingest | installed? agent running? (launchctl print over ssh) | other campus: only a campus with a Pro Tools room; host alias differs
|
||||||
|
- [hardcoded] Mac helpers: Studio B solo-mode agent :: solo-mode-agent.swift: bundle id com.avid.ProTools; menu path Options > Solo Mode > AFL* / Latch; state file solo-mode.json and trigger file solo-mode.fix in ~/Library/Application Support/status-panel; 1 s loop; needs Accessibility permission (prompted once on that Mac's screen) @ /Users/christian/Documents/Status Indicators/remote-access/solo-mode-agent.swift:15-17, :29, :34-41, :53-55, :86 | today: edit .swift, re-run install-solo-mode.sh | the two file paths are duplicated in config.json:69, :188-189 (remote checks + solo_afl fix) and must agree; 20 s staleness in config | other campus: menu titles differ per Pro Tools version/language; Accessibility is a per-Mac one-time step the UI should show as trusted/untrusted
|
||||||
|
- [workflow-step] tascam-quiet :: make_quiet_tascam.py: VERSION 1.3.1-quiet; stock module path ~/Library/Application Support/companion/modules/tascam-da-6400-1.3.0; two string patches anchored on the minified stock main.js; HELP.md note "Waters Church build"; output tascam-da-6400-1.3.1-quiet.tgz imported by hand in Companion > Modules @ /Users/christian/Documents/Status Indicators/tascam-quiet/make_quiet_tascam.py:9-10, :24-36, :56, :59 | today: run once, import package, pick 1.3.1-quiet on the tascam connection | module version selection is Companion-side | other campus: only a campus with a DA-6400; a newer stock module version breaks the patch anchors
|
||||||
|
- [hardcoded] .gitignore / repo hygiene :: .gitignore only ignores __pycache__/, *.pyc, .DS_Store: config.json, nav_locations.json, atem-reference.xml (1.6 MB), both .companionconfig outputs and .claude/settings.local.json are committed; dante-map.json / service-mode.json / .bak files exist only in the Ingest data dir @ /Users/christian/Documents/Status Indicators/.gitignore:1-3 | today: git | three patterns | other campus: one repo cannot hold two campuses' config.json/nav_locations.json/atem-reference.xml without per-site folders or branches; the UI's site identity must decide which files are per-site | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: data model gaps :: No site/campus name, Ingest hostname or machine display names anywhere in config (hosts have aliases only); machine names exist only in comments, README, button notes and log strings @ /Users/christian/Documents/Status Indicators/config.json:31-39; status_panel.py:2, :681; make_page.py:141, :188; remote-access/*:1-2 | today: n/a | needs a new top-level "site" block (name, ingest hostname, networks) and per-host display name/role/OS | other campus: this is the root of R3/R5: without it the UI cannot label machines or logs per campus
|
||||||
|
- [hardcoded] hardcoded: data model gaps :: No per-host inventory of what runs there (helpers, LaunchAgents, scheduled tasks, DVS helper, solo agent, sshd, WoL); that knowledge is spread across fixes' run strings, remote checks, the two helper scripts and the memory file @ /Users/christian/Documents/Status Indicators/config.json:41-89, :178-190; remote-access/* | today: in the user's head / README | needs hosts.<alias>.{os, role, helpers:[...], tasks:[...]} plus live probes (ssh ok? task exists? agent running?) | other campus: R5 requirement; each campus's map differs
|
||||||
|
- [hardcoded] hardcoded: data model gaps :: Duplicated values that must agree but are edited separately: Spotify URI (check + fix), schtasks names (ps1 + fixes), DVS helper path (sh + fixes), solo-mode paths (swift + checks + fix), switch IPs (netgear.py + config), public key (two scripts), page number (page + page_name), 5-minute blip text (make_page + remember_seconds), window text in AutoLog header @ see the individual items above | today: by hand in each place | single source of truth per value with generated outputs | other campus: the UI should own each value once and generate scripts/commands from it
|
||||||
|
|
||||||
|
### Lens: EVERY key of config.json and every key the code reads from it (status_panel.py, make_page.py, push_page.py, nav_keys.py), with type/default/validation, plus the hardcoded constants that change what those keys mean
|
||||||
|
- [config-key] config: companion :: companion.host @ config.json:2; status_panel.py:2296,1948,2000 | today: edit config.json, zsh install.sh (watchdog restarts) | string host/IP; default "127.0.0.1" (cfg["companion"].get("host")); no validation; the "companion" object itself is required (KeyError otherwise) | other campus: same value if the watchdog runs on the Companion Mac; NOTE push_page.py/nav_keys.py do NOT read it (they use --host na-ingest.local)
|
||||||
|
- [config-key] config: companion :: companion.port @ config.json:2; status_panel.py:2296,1948,2000 | today: edit config.json, zsh install.sh | int; default 8000; no validation | other campus: only if that site's Companion HTTP port differs
|
||||||
|
- [config-default] config: companion :: companion.db @ status_panel.py:1553-1560 (companion_db_path) | today: not set today; would be edit config.json, zsh install.sh | optional path (~ expanded) to Companion's db.sqlite; default = newest ~/Library/Application Support/companion/vN.N/db.sqlite; read read-only every 60 s for ping checks with "conn" and for reconnect_and_verify's ping; silently falls back to check "host" on any error | other campus: only if Companion keeps its data elsewhere
|
||||||
|
- [config-key] config: pages :: page @ config.json:3; make_page.py:242,278,284; push_page.py:182; nav_keys.py:86 | today: edit config.json, python3 make_page.py, python3 push_page.py (and --which restart), python3 nav_keys.py | int Companion page number (19); required; no validation (push_page refuses a target page that holds real buttons unless already this page) | other campus: each site picks a free page number
|
||||||
|
- [config-key] config: pages :: page_name @ config.json:4; make_page.py:278; push_page.py:182,188-190 | today: edit config.json, make_page.py, push_page.py | string, literal backslash-n for a line break on the key ("19\nStatus"); required; push_page.py refuses import if the built file's page name differs and uses it to recognise its own page | other campus: should match the page number
|
||||||
|
- [config-key] config: nav keys :: nav_keys @ config.json:5; make_page.py:240; nav_keys.py:88 | today: edit config.json, make_page.py, push_page.py, then python3 nav_keys.py (colours every page) / --revert | list of "pageup"|"pagenum"|"pagedown"; default all three when the key is absent; unknown names silently ignored; today ["pagenum"] | other campus: same
|
||||||
|
- [config-key] config: nav keys :: nav_hold_ms @ config.json:6; make_page.py:242,199 | today: edit config.json, make_page.py, push_page.py, nav_keys.py | int milliseconds; default 1000; hold on the page-number key jumps to the status page | other campus: same
|
||||||
|
- [config-key] config: nav keys :: nav_tap_page @ config.json:7; make_page.py:243,227 | today: edit config.json, make_page.py, push_page.py, nav_keys.py | int page number; default 1; tap on the page-number key (on release) goes there | other campus: site's home page
|
||||||
|
- [config-key] config: variables :: variables.state @ config.json:8; status_panel.py:2381,2418-2423; make_page.py:52,56,210 | today: edit config.json + create the custom variable in Companion by hand, make_page.py, push_page.py, install.sh | string custom-variable name ("sp_state"); required (KeyError); the variable must already exist in Companion or the watchdog logs "cannot set custom:... create it in Companion" and the page stays purple | other campus: same names fine; must be created in each Companion
|
||||||
|
- [config-key] config: variables :: variables.heartbeat @ config.json:8; status_panel.py:2424-2427; make_page.py:53,209 | today: same as variables.state | string ("sp_hb"); required; unix time written every redraw; page turns purple when older than 15 s (STALE_SECONDS) | other campus: same
|
||||||
|
- [config-key] config: variables :: variables.command @ config.json:8; status_panel.py:2395-2398; make_page.py:144,177,181 | today: same as variables.state | string ("sp_cmd"); required; buttons append "ack <id>" / "ackall" / "service toggle" / "restart <id> <unix>" / "force <id> <unix>" separated by ";" | other campus: same
|
||||||
|
- [companion-side] hardcoded: Companion side :: custom variable flash_tick + trigger flash_tick_500 @ make_page.py:9,61-62,211-212 ($(custom:flash_tick) == 1) | today: exists in Companion already (1 Hz blink clock); not configurable | hardcoded name; every key's flash phase depends on it; without it keys never flash | other campus: each site's Companion needs the same trigger/variable, or make the name a config key
|
||||||
|
- [config-key] config: windows :: service_windows @ config.json:10-12; status_panel.py:752,711-715 (ServiceMode.scheduled) | today: edit config.json, zsh install.sh | list of {days,start,end}; default [] (= never scheduled on; only the ALARMS button override); today every day 00:00-23:59 | other campus: per site; guideline says alarms any day so keep all-day
|
||||||
|
- [config-key] config: windows :: window shape: days / start / end (service_windows and auto_fix.windows) @ status_panel.py:622 (DAYS), 689-693 (in_windows), 711-715 | today: edit config.json, install.sh | days: list of lowercase "mon".."sun" (anything else never matches, no error); start/end: "HH:MM" strings compared as text, inside when start <= now < end (so "23:59" excludes the last minute; no overnight windows); no validation at all | other campus: per site service times
|
||||||
|
- [config-key] config: timing :: debounce_seconds @ config.json:14; status_panel.py:748,878-884 | today: edit config.json, install.sh | number of seconds a fault must persist before it is confirmed; default 3; overridden per check by check.debounce_seconds (midi_peer defaults to 1, network checks 0) | other campus: same
|
||||||
|
- [config-key] config: timing :: flap.count @ config.json:15; status_panel.py:749,615-616 | today: edit config.json, install.sh | int; default 3; that many confirmed fault starts inside flap.window_seconds shows amber "<msg> xN" | other campus: same
|
||||||
|
- [config-key] config: timing :: flap.window_seconds @ config.json:15; status_panel.py:750,606 | today: edit config.json, install.sh | int seconds; default 600 | other campus: same
|
||||||
|
- [config-key] config: network :: network.interface @ config.json:18; status_panel.py:2302,1467-1472 | today: edit config.json, install.sh | string interface name ("en0"); default none = no LINK DOWN check; ifconfig must print "status: active" | other campus: per Mac (en0 vs en1)
|
||||||
|
- [config-key] config: network :: network.gateway @ config.json:19; status_panel.py:818-820,2281-2282 | today: edit config.json, install.sh | IP string; default none = no NO GATEWAY check; pinged every 3 s, 2 misses = NO GATEWAY (hardcoded 2) | other campus: per site subnet
|
||||||
|
- [config-key] config: network :: network.burst_connections @ config.json:20; status_panel.py:844 | today: edit config.json, install.sh | int; default 4; watched Companion connections that must drop together (within burst_window_seconds) to call it a network burst | other campus: same
|
||||||
|
- [config-key] config: network :: network.burst_groups @ config.json:21; status_panel.py:844 | today: edit config.json, install.sh | int; default 3; distinct group tiles those drops must span | other campus: same
|
||||||
|
- [config-key] config: network :: network.burst_window_seconds @ config.json:22; status_panel.py:839 | today: edit config.json, install.sh | int seconds; default 10 | other campus: same
|
||||||
|
- [config-key] config: network :: network.remember_seconds @ config.json:23; status_panel.py:857 | today: edit config.json, install.sh | int seconds; default 300; how long NET shows the flashing "BLIP HH:MM" after a burst clears (hold NET clears it early) | other campus: same
|
||||||
|
- [config-key] config: alerts :: alert_webhook @ config.json:26; status_panel.py:2334-2342,2431,2438 | today: edit config.json, install.sh | URL string; "" or absent = off; POST JSON {"text": ...} when Companion has not answered for 30 s and when it answers again; message text hardcoded "NA Ingest: ..." | other campus: per site URL; the "NA Ingest" wording needs a site-name key
|
||||||
|
- [config-key] config: ssh :: ssh_key @ config.json:28; status_panel.py:1750 | today: edit config.json, install.sh; the key itself made once (remote-access/mac-authorize.sh) | path string, ~ expanded; default "~/.ssh/id_status_panel"; used with BatchMode=yes IdentitiesOnly=yes for every ssh | other campus: same path per ingest Mac; a new key pair per site
|
||||||
|
- [config-key] config: dante :: dante_switches @ config.json:30; status_panel.py:1872,1913; netgear.py:174,193 | today: edit config.json, install.sh; switch login stored once in keychain | list of switch IP strings; absent/empty = Dante map and PoE power-cycle off (dante-auto then only restarts the Companion connection); telnet port 23 hardcoded | other campus: per site switches; netgear.py's own SWITCHES list (netgear.py:21) is only for its CLI
|
||||||
|
- [config-default] config: dante :: dante_map_seconds @ status_panel.py:1877 | today: not set; edit config.json, install.sh | int seconds between switch surveys; default 600; the poller runs every 30 s regardless | other campus: same
|
||||||
|
- [config-key] config: hosts :: hosts (map alias -> machine) @ config.json:31-39; status_panel.py:1753,1931,2255,2257 | today: edit config.json, install.sh; plus one-time ssh authorisation on that machine | object keyed by alias (prop, tracks, translation, bibletv, lighting, studiob, livestream); aliases referenced by fixes.<id>.host, remote checks' host, midi_peer checks' host | other campus: the core per-site list; every machine the panel talks to
|
||||||
|
- [config-key] config: hosts :: hosts.<alias>.ssh @ config.json:32-38; status_panel.py:1753 | today: edit config.json, install.sh | "user@host" string; required for every alias; validated to exist only for ssh-kind fixes referenced by a tile check (:2254-2256) and remote checks (:2257); a midi_peer "host" or a restart-page-only ssh fix with a missing alias fails at runtime (KeyError in ssh_cmd) | other campus: per site users/IPs
|
||||||
|
- [config-key] config: hosts :: hosts.<alias>.mac @ config.json:38; status_panel.py:1931-1937,1759-1769 | today: edit config.json, install.sh | MAC string (any separator); optional; when an ssh fix exits 255 a Wake-on-LAN packet is sent and the key says WAKING PC; wake_on_lan raises ValueError if not 6 bytes (fix then reports FAILED) | other campus: per PC that may be asleep
|
||||||
|
- [config-key] config: hosts :: hosts.<alias>.wake_broadcast @ config.json:38; status_panel.py:1935,1767 | today: edit config.json, install.sh | IP string; optional; packet also always goes to 255.255.255.255; UDP port 9 hardcoded | other campus: per site subnet broadcast
|
||||||
|
- [config-key] config: fixes :: fixes (map id -> fix) @ config.json:41-89; status_panel.py:983,1100,1924,2252-2273 | today: edit config.json, install.sh | object keyed by fix id; ids referenced by check.fix, tile.hold_always, auto_fix.fixes, restart_page.keys[].fix and .watch; load_config checks every reference exists | other campus: largely reusable; host aliases and app names differ
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.kind @ status_panel.py:1925,1927-1987 | today: edit config.json, install.sh | string; default "ssh"; allowed ssh | local | atem | reconnect | midi_connect | netgear_poe | hyperdeck; an unknown kind logs a warning and the key shows FIX FAILED; NOT validated by load_config | other campus: same
|
||||||
|
- [config-key] config: fixes :: fix kind = ssh (fields) @ status_panel.py:1927-1952 | today: edit config.json, install.sh | uses host (required, hosts alias), run (required), timeout (60), reconnect (false), conn, short, label, restart_ok; runs `ssh <hosts.alias.ssh> '<run>'` in the remote login shell (cmd.exe on Windows PCs); exit 255 + hosts.mac -> WAKING, exit 255 otherwise -> "<SHORT> NOT REACHABLE"; exit 0 = success | other campus: same mechanics
|
||||||
|
- [config-key] config: fixes :: fix kind = local (fields) @ status_panel.py:1928-1952 | today: edit config.json, install.sh | uses run (required), timeout (60), reconnect (false), conn, short, label, restart_ok; runs `/bin/zsh -c <run>` on the ingest Mac as the logged-in user | other campus: same
|
||||||
|
- [config-key] config: fixes :: fix kind = atem (fields) @ status_panel.py:1953-1956,2147-2164 | today: edit config.json, install.sh | uses part (required: "audio"|"camera"), short, label; sends "fix <part>" to atem_watch.js and waits up to 8 s (hardcoded) for the switcher to match the restore file; fails if the ATEM watcher is not running | other campus: needs that site's atem section
|
||||||
|
- [config-key] config: fixes :: fix kind = reconnect (fields) @ status_panel.py:1957-1958,1993-2037 | today: edit config.json, install.sh | uses conn (required Companion label), verify_seconds (20), hung_note ("STILL DOWN"), short, label; restarts the Companion connection (or enables it if disabled), waits verify_seconds, then True if good, "STILL CONNECTING" if still starting, hung_note if the device still pings, else "NO NETWORK CHECK POWER" | other campus: same
|
||||||
|
- [config-key] config: fixes :: fix kind = midi_connect (fields) @ status_panel.py:1959-1976,993-1000,1107-1112 | today: edit config.json, install.sh | no fields of its own beyond short/label; session, peer, host and address come from the midi_peer check it is attached to; address = where the peer was last seen else check.address; local via coremidi.connect_peer, remote via ssh + /usr/bin/python3 - ; ok when result is asked|already|kicked; several run one after another | other campus: same
|
||||||
|
- [config-key] config: fixes :: fix kind = netgear_poe (fields) @ status_panel.py:1977-1982; netgear.py:110 | today: not used in config.json today; edit config.json, install.sh | uses switch (required IP), port (required "0/14" form), mac (optional expected MAC; refuses if that MAC is elsewhere); power-cycles one PoE port over telnet with the keychain login | other campus: per site switch/port
|
||||||
|
- [config-key] config: fixes :: fix kind = hyperdeck (fields) @ status_panel.py:1983-1986,981-992,1057-1063,1271-1272 | today: edit config.json, install.sh | uses verify_seconds (20), off_seconds (2), hung_note, short, label; conn and ip come from the hyperdeck check (ip: check.ip, else Companion variable "ip", else last remembered); disables then enables the Companion connection; per-deck 60 s cooldown; never runs while that deck is recording; never reboots the deck | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.host @ status_panel.py:1928,1931,1936,1942,2254-2256 | today: edit config.json, install.sh | hosts alias string; required when kind is ssh (default kind); validated only when the fix is referenced from a tile check; fixes used only by restart_page keys or hold_always are not validated (runtime KeyError -> FIX FAILED) | other campus: per site alias
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.run @ status_panel.py:1928,2264 | today: edit config.json, install.sh | shell command string; required for ssh/local; remote shell for ssh, /bin/zsh -c for local; if the fix is in auto_fix.fixes it must not contain pkill|taskkill|killall unless restart_ok is true (load_config raises); today also embeds site data: app names, LaunchAgent labels, \StatusPanel\ task names, the Spotify playlist URI, /usr/local/sbin/status-panel-dvs-start | other campus: commands change per site; the UI should expose the embedded values (playlist, labels, task names) as fields
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.timeout @ config.json:81,83; status_panel.py:1929 | today: edit config.json, install.sh | seconds; default 60; ssh/local only; subprocess killed after it (fix reports FAILED) | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.short @ config.json:42-88; status_panel.py:1102,1106,1112,1942 | today: edit config.json, install.sh | short key text; default fix id upper-cased; shown as "FIXING <short>" / "<SHORT> NOT REACHABLE"; on a group button prefixed to the outcome | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.label @ status_panel.py:1101,1253,1281,1925; make_page.py:173 | today: edit config.json, install.sh (+ make_page/push_page for hold_always notes) | plain-words description; default fix id in the watchdog (logs, auto-fix log); REQUIRED by make_page.py for any fix used as a tile's hold_always (KeyError otherwise) | other campus: wording mentions site machines ("NA Translation"); per site
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.reconnect @ config.json:58,60,81; status_panel.py:1945-1951 | today: edit config.json, install.sh | bool; default false; ssh/local only: after exit 0 restart the Companion connection named by the check's conn, else fixes.<id>.conn | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.conn @ config.json:81,85; status_panel.py:1945,1998-2001 | today: edit config.json, install.sh | Companion connection label; required for kind reconnect; fallback target for reconnect:true when the check has no conn; ignored by kind hyperdeck (uses the check's conn); not validated against Companion | other campus: per site connection labels
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.restart_ok @ config.json:72; status_panel.py:2264 | today: edit config.json, install.sh | bool; default false; lets a run containing pkill/taskkill/killall be listed in auto_fix.fixes (atem_midi today) | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.part @ config.json:70-71; status_panel.py:1954 | today: edit config.json, install.sh | "audio"|"camera"; required for kind atem; not validated (anything else just never matches) | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.verify_seconds @ config.json:85,87; status_panel.py:2023,2030 | today: edit config.json, install.sh | seconds; default 20; reconnect and hyperdeck kinds: wait before judging the connection | other campus: same
|
||||||
|
- [config-default] config: fixes :: fixes.<id>.off_seconds @ status_panel.py:2009 | today: not set; edit config.json, install.sh | seconds; default 2; hyperdeck kind: time the connection stays disabled before re-enabling | other campus: same
|
||||||
|
- [config-key] config: fixes :: fixes.<id>.hung_note @ config.json:85,87; status_panel.py:2035 | today: edit config.json, install.sh | key text (\n allowed); default "STILL DOWN"; shown when the connection is still bad but the device answers ping | other campus: same
|
||||||
|
- [config-default] config: fixes :: fixes.<id>.switch / .port / .mac (netgear_poe) @ status_panel.py:1980 | today: not set today | switch: IP string required; port: "unit/port" string required; mac: optional MAC guard; no load-time validation | other campus: per site
|
||||||
|
- [config-key] config: fixes :: special fix id "restart-connection" (RESTART_CONNECTION) @ status_panel.py:545,972-977,1005-1009,2252 | today: use the literal string as a check's fix (or a dict value); install.sh | allowed in check.fix instead of a fixes entry; restarts the check's own Companion connection (needs check.conn); checks of type conn/hyperdeck/shure/psm/tascam (RESTARTABLE :542) get this automatically when they have no fix | other campus: same
|
||||||
|
- [config-key] config: fixes :: special fix id "dante-auto" (DANTE_AUTO) @ config.json:151; status_panel.py:549,963-971,1909-1918 | today: edit config.json, install.sh | allowed in check.fix (dante checks); if dante-map.json says the device is on a PoE port (found by MAC) its port is power-cycled, else falls back to restart-connection on the Dante connection; one PoE cycle per MAC per plan | other campus: needs dante_switches + keychain login
|
||||||
|
- [config-key] config: fixes :: check.fix as a per-sub map {sub: fix id, "*": fallback} @ config.json:135,149-151; status_panel.py:961-962,1021-1022,2250-2253 | today: edit config.json, install.sh | keys are the check's sub-result names: dante = device names; hyperdeck = conn|disk|rec; shure = chN_batt|chN_rf; psm = chN_mute; tascam = conn|error|caution; "*" matches every other sub; each value validated like a string fix | other campus: dante device names differ per site
|
||||||
|
- [hardcoded] config: fixes :: implicit "enable" plan for a disabled Companion connection @ status_panel.py:953-959,1083-1088 | today: not configurable | any failing check whose conn is disabled in Companion is fixed by enabling it (TURN ON), never by its fix; good to document in the UI | other campus: same | (need not be editable)
|
||||||
|
- [config-key] config: tiles :: tiles (list) @ config.json:126-238; status_panel.py:747,755-760; make_page.py:271-273 | today: edit config.json, make_page.py, push_page.py, install.sh | ordered list of tile objects; order matters only for rendering/iteration; today 7 groups + 1 master + service + network + 25 detail tiles (6 hidden RF tiles) | other campus: the main per-site layout
|
||||||
|
- [config-key] config: tiles :: tiles[].id @ status_panel.py:2238-2241,2267; make_page.py:56-65 (jsonpath $.<id>.x) | today: edit config.json, make_page.py, push_page.py, install.sh | string; required; unique among tiles and not equal to any restart key id (validated); used as a JSON key in sp_state and inside Companion jsonpath expressions, so it must be a plain identifier (letters/digits/_) - NOT validated; the master tile's id must be "all" (make_page.py:210 nav keys read $.all.*, status_panel.py:2357 ackall acks "all") | other campus: same
|
||||||
|
- [config-key] config: tiles :: tiles[].label @ status_panel.py:886,1381,1385,1407-1421; make_page.py:154 | today: edit config.json, make_page.py, push_page.py, install.sh | string; required; first line of the key, prefix of fault messages ("VISTA SILENT"), name in logs; keep short (fit_size shrinks text to 12%) | other campus: same
|
||||||
|
- [config-key] config: tiles :: tiles[].pos @ config.json:127-224; make_page.py:272,248-252; status_panel.py:2466-2475 | today: edit config.json, make_page.py, push_page.py | [row, col] ints; optional - a tile without pos is hidden and only shows through a group's members (axient1.., psm1..); make_page raises on two buttons at one position; grid is GRID minColumn 0..8, minRow 0..5 (make_page.py:33); column 0 rows 0-2 are the nav keys; restart_page.jump_pos also occupies a slot; no range validation | other campus: per site Stream Deck layout
|
||||||
|
- [config-key] config: tiles :: tiles[].kind @ config.json:127,192,208; status_panel.py:912,1042,1383,1388; make_page.py:155-171 | today: edit config.json, make_page.py, push_page.py, install.sh | absent (normal/group) | "master" (ALL: press = ackall, shows ALL OK / N DOWN, no hold) | "service" (ALARMS toggle, press = service toggle) | "network" (NET: link/gateway/burst, hold clears a blip); not validated; exactly one of each expected | other campus: same
|
||||||
|
- [config-key] config: tiles :: tiles[].members @ config.json:128-133; status_panel.py:756,914,942,1402,2246-2248 | today: edit config.json, make_page.py, push_page.py, install.sh | list of tile ids; validated to exist; makes a group button (worst member, "+N"); hold fixes every member; a tile may be in several groups (first match wins for burst grouping) | other campus: per site
|
||||||
|
- [config-key] config: tiles :: tiles[].checks @ config.json:135-237; status_panel.py:789-810,2242-2244 | today: edit config.json, install.sh (make_page/push_page only if the hold-ability changes) | list of check objects; each type must be in CHECKS (19 types); a tile with checks or kind network is a "detail" tile counted by ALL; a check that throws shows CHECK BUG | other campus: per site
|
||||||
|
- [config-key] config: tiles :: tiles[].hold_always @ config.json:135-141,172; status_panel.py:1053-1063,2259-2260; make_page.py:169,173 | today: edit config.json, make_page.py, push_page.py, install.sh | fix id; validated to exist; hold runs it even when the button is green (HD1-6, TASCAM, SPOTIFY); for a hyperdeck-kind fix the first check with a conn supplies conn/ip and a recording deck is left alone; make_page needs fixes[hold_always].label | other campus: same
|
||||||
|
- [config-key] config: checks (common) :: checks[].type @ status_panel.py:534-539,2181,2243 | today: edit config.json, install.sh | one of conn, hyperdeck, dante, shure, psm, tascam, ping, launchd, process, http, disk, midi_peer, pref, spotify, mdns, midi_rx, remote, artnet, atem; validated | other campus: same
|
||||||
|
- [config-key] config: checks (common) :: checks[].conn @ status_panel.py:245,259,294,331,357,370,391,760,932,953,2256 | today: edit config.json, install.sh | Companion connection label (case-sensitive, as shown in Companion); required for conn/hyperdeck/dante/shure/psm/tascam, optional for ping; any check with conn is a "watched" connection for burst detection and gets the enable/restart plans; NOT validated against Companion (shows NOT IN COMPANION live) | other campus: per site labels; the UI should list Companion's live connections to pick from
|
||||||
|
- [config-key] config: checks (common) :: checks[].fix @ status_panel.py:960-962,1020-1022,2250-2256; make_page.py:167 | today: edit config.json, install.sh (+ make_page/push_page when it changes whether the key has a hold) | fix id string | "restart-connection" | "dante-auto" | per-sub dict; validated; absent = restart its Companion connection for RESTARTABLE types, nothing otherwise (hold says NO AUTO FIX) | other campus: same
|
||||||
|
- [config-key] config: checks (common) :: checks[].tag @ config.json:161-167,179-187,201,219-220; status_panel.py:805-808 | today: edit config.json, install.sh | string; optional; prefixed to fault text that does not already name its device ("PROPRES OFFLINE"); use on tiles with several devices | other campus: same
|
||||||
|
- [config-key] config: checks (common) :: checks[].service_only @ config.json:199; status_panel.py:245,313,398,1306 | today: edit config.json, install.sh | bool; default false; read only by conn, dante, ping; the fault shows grey "idle" while ALARMS is off instead of dark red "quiet" | other campus: same
|
||||||
|
- [config-default] config: checks (common) :: checks[].debounce_seconds @ status_panel.py:882-884 | today: not set; edit config.json, install.sh | seconds; default = global debounce_seconds (3), except midi_peer = 1; network sub-checks always 0 | other campus: same
|
||||||
|
- [config-key] config: check conn :: conn.ignore_warning @ config.json:179; status_panel.py:246-250 | today: edit config.json, install.sh | bool; default false; treats Companion's yellow "warning" state as OK (ProPresenter API flicker); error/offline/disabled still alarm | other campus: same
|
||||||
|
- [hardcoded] config: check conn :: conn fault texts (hardcoded) @ status_panel.py:186-214 | today: edit source | NO DATA (no Companion data for 10 s), NOT IN COMPANION, DISABLED (warn), NO ANSWER/REFUSED/UNREACHABLE/LOGIN mapped from the module message, OFFLINE, WARNING, STARTING | other campus: same | (need not be editable)
|
||||||
|
- [config-key] config: check hyperdeck :: hyperdeck.conn + polled variables @ status_panel.py:254-289,558-560 | today: edit config.json, install.sh | Companion label (HyperDeck_1..6); reads module variables status, slotId, recordingTime every 1 s and ip every 3 s; conn must be a Blackmagic HyperDeck module instance | other campus: use only the decks that site has (e.g. HyperDeck_2 and _4) - just list fewer tiles
|
||||||
|
- [config-key] config: check hyperdeck :: hyperdeck.optional @ config.json:136,138; status_panel.py:267-270 | today: edit config.json, install.sh | bool; default false; true = a deck with no disk/no recordingTime sits grey "NO DISK" with no alarm and is excluded from REC sync until a disk is in | other campus: per site
|
||||||
|
- [config-default] config: check hyperdeck :: hyperdeck.disk_service_only @ status_panel.py:272 | today: not set; edit config.json, install.sh | bool; default true; NO DISK on a non-optional deck is idle outside service | other campus: same
|
||||||
|
- [config-default] config: check hyperdeck :: hyperdeck.full_minutes @ status_panel.py:276 | today: not set; edit config.json, install.sh | int minutes left; default 5; below = red "FULL <left>" | other campus: same
|
||||||
|
- [config-default] config: check hyperdeck :: hyperdeck.low_minutes @ status_panel.py:278 | today: not set; edit config.json, install.sh | int minutes left; default 30; below = amber "LOW <left>" | other campus: same
|
||||||
|
- [config-default] config: check hyperdeck :: hyperdeck.rec_sync @ status_panel.py:283-287,794-796 | today: not set; edit config.json, install.sh | bool; default true; when any rec_sync deck is recording, every other rec_sync deck that is not shows red NOT REC | other campus: same
|
||||||
|
- [config-default] config: check hyperdeck :: hyperdeck.ip @ status_panel.py:987,1062,1986 | today: not set; edit config.json, install.sh | IP string; optional override of the deck's address used by the hyperdeck fix's ping; default = Companion variable "ip" of the connection, remembered if it disappears | other campus: normally not needed
|
||||||
|
- [config-key] config: check dante :: dante.conn + polled variables @ status_panel.py:292-303,562,1880-1886 | today: edit config.json, install.sh | Companion label of the audinate-dantecontroller module; reads variable "devices" (JSON list) every 1 s and "<name>_ip" for the Dante map; only the FIRST dante check's conn feeds the Dante map (dante_label) | other campus: same module per site
|
||||||
|
- [config-key] config: check dante :: dante.devices @ config.json:152-158; status_panel.py:304-313,791 | today: edit config.json, install.sh | dict {Dante device name: short alias} or list of names (alias = name); required; each missing device = red "<ALIAS> GONE"; count "seen/total" shown when >1 device; aliases should be short (14 chars) | other campus: the per-site Dante device list; the UI should offer the live list from Companion's devices variable
|
||||||
|
- [config-default] config: check dante :: dante.count_all @ status_panel.py:314-318 | today: not set; edit config.json, install.sh | bool; default false; true = the count covers every Dante device watched on any tile (so several dante checks can split devices across tiles) | other campus: same
|
||||||
|
- [config-key] config: check shure :: shure.conn + polled variables @ status_panel.py:329-352,565-568 | today: edit config.json, install.sh | Companion label of a Shure module (Axient_1, Axient_2, ULXD-4); reads ch_N_name, ch_N_battery_bars, ch_N_interference_status every 3 s | other campus: per site receivers
|
||||||
|
- [config-key] config: check shure :: shure.channels @ config.json:226-230; status_panel.py:335,566 | today: edit config.json, install.sh | int; default 1; channels 1..N read | other campus: per receiver
|
||||||
|
- [config-default] config: check shure :: shure.battery_err_bars @ status_panel.py:340 | today: not set; edit config.json, install.sh | int 0-5; default 1; bars <= this = red "<CH> BATT" | other campus: same
|
||||||
|
- [config-default] config: check shure :: shure.battery_warn_bars @ status_panel.py:342 | today: not set; edit config.json, install.sh | int 0-5; default 2; bars <= this = amber | other campus: same
|
||||||
|
- [config-key] config: check psm :: psm.conn + polled variables @ status_panel.py:355-366,569-571 | today: edit config.json, install.sh | Companion label (PSM_1..3); reads ch_N_name, ch_N_rf_mute every 3 s | other campus: per site
|
||||||
|
- [config-key] config: check psm :: psm.channels @ config.json:232-236; status_panel.py:362,570 | today: edit config.json, install.sh | int; default 1 | other campus: per unit
|
||||||
|
- [config-default] config: check psm :: psm.muted_is_error @ status_panel.py:361 | today: not set; edit config.json, install.sh | bool; default false (RF MUTE is amber); true = red | other campus: same
|
||||||
|
- [config-key] config: check tascam :: tascam.conn + polled variables @ status_panel.py:369-385,563-564 | today: edit config.json, install.sh | Companion label; reads variables error and caution every 1 s; no other fields; "no error"/"no caution" = OK, else text upper-cased to 14 chars | other campus: same
|
||||||
|
- [config-key] config: check ping :: ping.host @ config.json:143,184,202-203,221-222,227-237; status_panel.py:388-399,2277-2282 | today: edit config.json, install.sh | IP/hostname; the address pinged (every 3 s, 1 s timeout, /sbin/ping); when conn is set, Companion's own host setting for that connection wins and host is only the fallback; chk["host"] is read for the default message so a ping check with neither host nor name/msg crashes that check (CHECK BUG) | other campus: per site IPs
|
||||||
|
- [config-key] config: check ping :: ping.conn @ config.json:143,227-237; status_panel.py:388-391,1585-1590 | today: edit config.json, install.sh | optional Companion label; its "host" setting is read from Companion's db.sqlite every 60 s so an address changed in Companion needs no config change | other campus: same
|
||||||
|
- [config-key] config: check ping :: ping.name @ status_panel.py:397 | today: edit config.json, install.sh | string; default = host; fault text "<name> NO PING" | other campus: same
|
||||||
|
- [config-default] config: check ping :: ping.msg @ status_panel.py:397 | today: not set; edit config.json, install.sh | string; optional full fault text replacing "<name> NO PING" | other campus: same
|
||||||
|
- [config-default] config: check ping :: ping.failures @ status_panel.py:396 | today: not set; edit config.json, install.sh | int consecutive misses; default 2 (~6 s at the 3 s poll) | other campus: same
|
||||||
|
- [config-key] config: check launchd :: launchd.label @ config.json:210-212; status_panel.py:402-410,1477-1484 | today: edit config.json, install.sh | LaunchAgent label string (org.waterschurch.lobby-captions); required; `launchctl list` every 10 s: missing = "<name> NOT LOADED", loaded with no pid = "<name> STOPPED" | other campus: per site agents
|
||||||
|
- [config-key] config: check launchd :: launchd.name @ status_panel.py:405 | today: edit config.json, install.sh | string; default = label | other campus: same
|
||||||
|
- [config-key] config: check process :: process.match @ config.json:214; status_panel.py:413-417,1486-1489 | today: edit config.json, install.sh | `pgrep -f` pattern (regex against the full command line); required; polled every 10 s | other campus: per app path
|
||||||
|
- [config-key] config: check process :: process.name @ status_panel.py:416 | today: edit config.json, install.sh | string; default "PROCESS"; fault "<name> STOPPED" | other campus: same
|
||||||
|
- [config-key] config: check http :: http.url @ config.json:185-186,204,206,213,215; status_panel.py:420-423,1527-1541,2285-2292 | today: edit config.json, install.sh | URL string; required; HEAD request, 6 s timeout, TLS not verified; any HTTP answer (even 4xx/5xx) = up | other campus: per site
|
||||||
|
- [config-key] config: check http :: http.msg @ status_panel.py:422 | today: edit config.json, install.sh | string; default "<name> UNREACH" | other campus: same
|
||||||
|
- [config-key] config: check http :: http.name @ config.json:206; status_panel.py:422 | today: edit config.json, install.sh | string; default "WEB" | other campus: same
|
||||||
|
- [config-key] config: check http :: http.interval_seconds @ config.json:185,204,213,215; status_panel.py:2291,2313 | today: edit config.json, install.sh | seconds; default 30; the smallest value wins when the same URL appears twice; the http poller runs every 5 s so values below 5 behave as 5 | other campus: same
|
||||||
|
- [config-key] config: check disk :: disk.path @ config.json:216; status_panel.py:456-465,1490-1492 | today: edit config.json, install.sh | filesystem path; required; free GB read every 10 s; shows "<n>GB free" | other campus: same
|
||||||
|
- [config-key] config: check disk :: disk.warn_gb @ config.json:216; status_panel.py:463 | today: edit config.json, install.sh | number; default 20; below = amber "DISK nGB" | other campus: same
|
||||||
|
- [config-key] config: check disk :: disk.err_gb @ config.json:216; status_panel.py:461 | today: edit config.json, install.sh | number; default 5; below = red | other campus: same
|
||||||
|
- [config-key] config: check midi_peer :: midi_peer.session @ config.json:161-166; status_panel.py:476-478,995,1961 | today: edit config.json, install.sh | network MIDI session name as in Audio MIDI Setup ("NA Ingest", "NA Tracks"); required; missing session = red NO SESSION | other campus: per Mac session names
|
||||||
|
- [config-key] config: check midi_peer :: midi_peer.peer @ config.json:161-166; status_panel.py:479-487,995,1961 | today: edit config.json, install.sh | participant name as the session lists it ("Lighting PC"); required; state running|connected = OK, other = "MIDI <STATE>", absent = MIDI LOST | other campus: per site
|
||||||
|
- [config-key] config: check midi_peer :: midi_peer.host @ config.json:163-166; status_panel.py:471-473,1663-1746,1962-1966 | today: edit config.json, install.sh; the other Mac needs the ssh key authorised | optional hosts alias; the session is read on that Mac over one long-lived ssh running coremidi.py with /usr/bin/python3 (nothing installed there); no verdict if it has not reported for 12 s; NOT validated by load_config (a bad alias kills that stream thread) | other campus: per site Macs
|
||||||
|
- [config-key] config: check midi_peer :: midi_peer.address @ config.json:161-166; status_panel.py:996-999 | today: edit config.json, install.sh | "ip:port" or Bonjour "\tName\tlocal." form; optional fallback for the midi_reconnect fix when the peer was never seen; last-seen address wins | other campus: per site
|
||||||
|
- [config-default] config: check midi_peer :: midi_peer.debounce_seconds (implicit) @ status_panel.py:881-884 | today: not set | default 1 s for this type (a peer leaving is a clean event) | other campus: same
|
||||||
|
- [config-key] config: check pref :: pref.domain @ config.json:197; status_panel.py:448-453,1493-1496 | today: edit config.json, install.sh | macOS defaults domain string; required; `defaults read <domain> <key>` every 10 s on the ingest Mac | other campus: same app = same domain
|
||||||
|
- [config-key] config: check pref :: pref.key @ config.json:197; status_panel.py:450,1494 | today: edit config.json, install.sh | defaults key string; required | other campus: same
|
||||||
|
- [config-key] config: check pref :: pref.want @ config.json:197; status_panel.py:451 | today: edit config.json, install.sh | value compared as a string to what defaults prints ("0"); required; unreadable key = OK (no verdict) | other campus: same
|
||||||
|
- [config-key] config: check pref :: pref.msg @ config.json:197; status_panel.py:453 | today: edit config.json, install.sh | fault text; required (KeyError otherwise) | other campus: same
|
||||||
|
- [config-key] config: check spotify :: spotify.playlist @ config.json:173; status_panel.py:1619-1630,1597-1616 | today: edit config.json (TWICE: here and inside fixes.spotify_play.run), install.sh | "spotify:playlist:<id>" URI; required; only the FIRST spotify check's playlist is fetched (public embed page + anonymous token, hourly and on an unknown song); WRONG PLAYLIST when the current song is not in it (3 strangers in a row if only part of the list is known) | other campus: per site playlist; the UI must write both places (or the fix should read it from the check)
|
||||||
|
- [config-key] config: check spotify :: spotify.name @ config.json:173 | today: edit config.json | string; NOT read by any code today (documentation only); the fix label hardcodes "Christian EDM 2026" | other campus: per site
|
||||||
|
- [hardcoded] config: check spotify :: spotify hardcoded behaviour @ status_panel.py:426-445,1593-1594,1631-1645,156-158 | today: edit source | polled every 3 s via pgrep + osascript (never opens Spotify); CLOSED/NOT PLAYING red; refresh 3600 s; remembers last 3 tracks; needs Automation permission for Spotify on the ingest Mac | other campus: same | (need not be editable)
|
||||||
|
- [config-key] config: check mdns :: mdns.instance @ config.json:168-169; status_panel.py:490-493,1510-1518 | today: edit config.json, install.sh | Bonjour _apple-midi._udp instance name; required; browsed 3 s every 15 s; missing = "<name> GONE" | other campus: per site
|
||||||
|
- [config-key] config: check mdns :: mdns.name @ status_panel.py:492 | today: edit config.json, install.sh | string; default = instance | other campus: same
|
||||||
|
- [config-key] config: check midi_rx :: midi_rx.source @ config.json:170; status_panel.py:496-507,1652-1658 | today: edit config.json, install.sh | CoreMIDI source name on the ingest Mac ("Network NA Ingest Listen"); required; a coremidi.Listener is attached; grey OFF while not connected, else "<name> <age>" | other campus: per Mac source name
|
||||||
|
- [config-key] config: check midi_rx :: midi_rx.name @ status_panel.py:498 | today: edit config.json, install.sh | string; default "MIDI" | other campus: same
|
||||||
|
- [config-default] config: check midi_rx :: midi_rx.max_silence_seconds @ status_panel.py:504-506; README.md:185 | today: not set; edit config.json, install.sh | seconds; default none = informational only; set = amber "<name> SILENT" when no MIDI for that long, only while service mode is on | other campus: same
|
||||||
|
- [config-key] config: check remote :: remote.host @ config.json:180-189; status_panel.py:523-531,1782-1799,2257 | today: edit config.json, install.sh | hosts alias; required; validated; all remote cmds for one host are batched into one ssh every 10 s | other campus: per site
|
||||||
|
- [config-key] config: check remote :: remote.cmd @ config.json:180-189; status_panel.py:524,1772-1774 | today: edit config.json, install.sh | shell command run on that machine; required; exit 0 = OK, non-zero = fault, unreachable = no verdict; duplicates per host collapsed | other campus: per site
|
||||||
|
- [config-key] config: check remote :: remote.msg @ status_panel.py:530 | today: edit config.json, install.sh | fault text; required (KeyError otherwise) | other campus: same
|
||||||
|
- [config-key] config: check artnet :: artnet.source @ config.json:176; status_panel.py:510-520,2184-2208 | today: edit config.json, install.sh | IP string of the Art-Net sender; required; passive listener on UDP 6454 (SO_REUSEPORT), started only when an artnet check exists | other campus: per site lighting PC IP
|
||||||
|
- [config-key] config: check artnet :: artnet.max_silence_seconds @ config.json:176; status_panel.py:512 | today: edit config.json, install.sh | seconds; default 5 | other campus: same
|
||||||
|
- [config-default] config: check artnet :: artnet.msg @ status_panel.py:519 | today: not set; edit config.json, install.sh | string; default "SILENT" (tile label is prefixed); a custom msg is shown as-is | other campus: same
|
||||||
|
- [config-key] config: check atem :: atem check .part @ config.json:195-196; status_panel.py:2170-2178 | today: edit config.json, install.sh | "audio"|"camera"; required; no verdict unless atem_watch.js reported within 30 s and is connected | other campus: same
|
||||||
|
- [config-key] config: check atem :: atem check .msg @ config.json:195-196; status_panel.py:2177 | today: edit config.json, install.sh | fault text; required (KeyError otherwise); info shows the first 3 differences | other campus: same
|
||||||
|
- [config-key] config: atem section :: atem (section presence) @ config.json:91-96; status_panel.py:2320-2328 | today: edit config.json, install.sh | object; the watcher starts only when the section exists AND at least one atem check exists; if node/library are not found the ATEM checks are silently off (warning in log) | other campus: omit at a site without an ATEM
|
||||||
|
- [config-key] config: atem section :: atem.host @ config.json:92; status_panel.py:2120 | today: edit config.json, install.sh | switcher IP; required | other campus: per site
|
||||||
|
- [config-key] config: atem section :: atem.node @ config.json:93; status_panel.py:2108,2089-2095 | today: edit config.json, install.sh | glob pattern to Companion's bundled node binary; required; newest version match wins; ~ expanded | other campus: same unless Companion is installed elsewhere
|
||||||
|
- [config-key] config: atem section :: atem.library @ config.json:94; status_panel.py:2108 | today: edit config.json, install.sh | glob pattern to the atem-connection module inside Companion's bmd-atem module; required; newest wins | other campus: same
|
||||||
|
- [config-key] config: atem section :: atem.reference @ config.json:95; status_panel.py:2111-2115,2061-2086; install.sh:28 | today: copy a new ATEM Software Control .xml over atem-reference.xml, zsh install.sh | path to the restore .xml; relative paths are relative to the script folder, ~ expanded; converted to atem-reference.json in the data dir at start; which settings are compared is hardcoded (MIX_OPTIONS/CCU_PARAMS :2042-2058) | other campus: per site restore file (today from Google Drive Backup/NA/ATEM)
|
||||||
|
- [config-key] config: auto_fix :: auto_fix (section) @ config.json:98-105; status_panel.py:772-775,1154-1246 | today: edit config.json, install.sh | object; absent or empty = auto-fix completely off; runs every 5 s while inside a window AND service mode is on | other campus: per site
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.windows @ config.json:99; status_panel.py:1164-1174 | today: edit config.json, install.sh | list of {days,start,end} like service_windows; default []; today Sunday 06:30-13:30 | other campus: per site service times
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.after_seconds @ config.json:100; status_panel.py:1206 | today: edit config.json, install.sh | seconds a red fault must have lasted; default 20 | other campus: same
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.max_tries @ config.json:100; status_panel.py:1211-1218,1234 | today: edit config.json, install.sh | int; default 2; then "gave up" in the auto-fix log until the fault is forgotten | other campus: same
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.retry_seconds @ config.json:100; status_panel.py:1219 | today: edit config.json, install.sh | seconds between tries; default 90 (plus the global 60 s fix cooldown) | other campus: same
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.pause_after_network_seconds @ config.json:100; status_panel.py:1178-1186 | today: edit config.json, install.sh | seconds; default 60; no auto-fixes while link/gateway are down or a burst is on/just cleared | other campus: same
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.restart_connections @ config.json:101; status_panel.py:1207 | today: edit config.json, install.sh | bool; default false; true = plain Companion reconnects are also done automatically | other campus: same
|
||||||
|
- [config-key] config: auto_fix :: auto_fix.fixes @ config.json:102-104; status_panel.py:1187,1207,2261-2265 | today: edit config.json, install.sh | list of fix ids that auto-fix may run ("gentle"); validated to exist and to not contain pkill/taskkill/killall in run unless restart_ok; everything else stays a hold | other campus: per site
|
||||||
|
- [config-default] config: auto_fix :: auto_fix.forget_seconds @ status_panel.py:1240-1245 | today: not set; edit config.json, install.sh | seconds; default 600; a fault's try count resets once it has been gone this long | other campus: same
|
||||||
|
- [config-default] config: auto_fix :: auto_fix.log_dir @ status_panel.py:775,625-686 | today: not set; edit config.json, install.sh | directory, ~ expanded; default "~/Desktop/Auto-Fix Logs"; one "YYYY-MM-DD Day Auto-Fix.log" per day, deleted after a calendar month; header text hardcodes "NA Ingest ... Sundays 06:30-13:30" (:680-682) | other campus: per site; header wording needs a site-name key
|
||||||
|
- [config-key] config: restart_page :: restart_page (section) @ config.json:107-124; status_panel.py:771; make_page.py:274-277,281-288,301-304; push_page.py:179 | today: edit config.json, make_page.py, push_page.py --which restart, install.sh | optional object; when present make_page also builds restart-page.companionconfig and places a RESTART PAGE jump key on the status page | other campus: per site
|
||||||
|
- [config-key] config: restart_page :: restart_page.page @ config.json:108; make_page.py:277,288; push_page.py:179 | today: edit config.json, make_page.py, push_page.py --which restart | int Companion page number (20); required when the section exists | other campus: per site
|
||||||
|
- [config-key] config: restart_page :: restart_page.page_name @ config.json:108; make_page.py:288; push_page.py:179,188 | today: edit config.json, make_page.py, push_page.py --which restart | string with literal \n ("20\nRestart"); required; push_page matches on it | other campus: per site
|
||||||
|
- [config-key] config: restart_page :: restart_page.jump_pos @ config.json:108; make_page.py:276 | today: edit config.json, make_page.py, push_page.py | [row,col] on the STATUS page for the grey "RESTART PAGE" key; required; must not collide with a tile pos | other campus: per layout
|
||||||
|
- [config-key] config: restart_page :: restart_page.back_pos @ config.json:108; make_page.py:284 | today: edit config.json, make_page.py, push_page.py --which restart | [row,col] on the RESTART page for the "STATUS PAGE" key; required; must not collide with a restart key pos | other campus: per layout
|
||||||
|
- [config-key] config: restart_page :: restart_page.keys[].id @ config.json:110-122; status_panel.py:771,1260,2266-2268; make_page.py:137,144-145 | today: edit config.json, make_page.py, push_page.py --which restart, install.sh | string; required; must not equal a tile id (validated); same identifier rules as tile ids (jsonpath); command "force <id>" | other campus: same
|
||||||
|
- [config-key] config: restart_page :: restart_page.keys[].label @ config.json:110-122; status_panel.py:1372-1376; make_page.py:134 | today: edit config.json, make_page.py, push_page.py --which restart, install.sh | key text; required; shows "<label>\nHOLD 2s" / "HOLD TO FIX" / RESTARTING / DONE / FAILED | other campus: same
|
||||||
|
- [config-key] config: restart_page :: restart_page.keys[].pos @ config.json:110-122; make_page.py:287 | today: edit config.json, make_page.py, push_page.py --which restart | [row,col] on the restart page; required; collision raises | other campus: per layout
|
||||||
|
- [config-key] config: restart_page :: restart_page.keys[].fix @ config.json:110-122; status_panel.py:1264,1369,2269-2270; make_page.py:141 | today: edit config.json, make_page.py, push_page.py --which restart, install.sh | fix id; required; validated to exist; runs whatever the status says (shares the 60 s guard with holds) | other campus: per site
|
||||||
|
- [config-key] config: restart_page :: restart_page.keys[].watch @ config.json:115-116,120,122; status_panel.py:1265-1270,1369-1374,2271-2273 | today: edit config.json, install.sh | list of fix ids or "conn:<Companion label>"; default []; the key flashes HOLD TO FIX when its fix or any watched fix/connection is failing; a watched fix that ran <60 s ago makes the key say "STARTING WAIT 1 MIN"; fix ids validated, conn labels not | other campus: per site
|
||||||
|
- [hardcoded] config: restart_page :: make_page "options/steps/style" are NOT restart-key config @ make_page.py:207-232 (nav_key), nav_templates.json | today: n/a | key["style"], key["steps"], key["options"] in make_page.py are read from nav_templates.json (Companion's own pageup/pagenum/pagedown exports), not from restart_page.keys; restart keys accept only id, label, pos, fix, watch | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: page build :: HOLD_MS = 2000 @ make_page.py:36,144-146,181-182 | today: edit make_page.py, make_page.py, push_page.py | hold time in ms for every status tile and restart key | other campus: same; candidate config key
|
||||||
|
- [hardcoded] hardcoded: page build :: STALE_SECONDS = 15 and PURPLE = 0x6A1B9A @ make_page.py:34-35,53,61-65,209-212 | today: edit make_page.py | heartbeat age after which every key turns purple WATCHDOG DOWN | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: page build :: GRID = columns 0..8, rows 0..5 @ make_page.py:33 | today: edit make_page.py | page grid size written into the export; a site with a different surface needs it changed | other campus: per surface; candidate config key
|
||||||
|
- [hardcoded] hardcoded: page build :: NAV_LOCATIONS pageup (0,0) pagenum (1,0) pagedown (2,0) @ make_page.py:195,241-244; nav_keys.py:24,113-114 | today: edit make_page.py | column 0 rows 0-2 are always the nav keys; tiles cannot use them | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: page build :: EXPORT_VERSION 12 / COMPANION_BUILD 5.0.7 @ make_page.py:31-32,257-259 | today: edit make_page.py when Companion's export format changes | export format stamps; push_page/nav_keys written against Companion 5.0.7's tRPC API | other campus: same Companion version needed per site | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: page build :: push_page.py / nav_keys.py --host na-ingest.local --port 8000 @ push_page.py:172-173; nav_keys.py:84-85 | today: pass --host/--port on the command line | CLI defaults; these scripts read config.json only for page numbers/names and ignore companion.host/port | other campus: another site must pass --host; should read companion.host
|
||||||
|
- [hardcoded] hardcoded: page build :: "on NA Ingest" wording in key notes and docstrings @ make_page.py:141,188; status_panel.py:2,427,680-682,2431,2438 | today: edit source | site name appears in Companion button notes, auto-fix log header and webhook text | other campus: needs a site_name config key
|
||||||
|
- [hardcoded] hardcoded: watchdog timing :: poller intervals @ status_panel.py:2303-2316 | today: edit source | connections 1 s, fast variables 1 s, slow variables 3 s, Companion db hosts 60 s, spotify 3 s, pings 3 s, local MIDI 0.5 s, system (launchd/process/disk/pref) 10 s, Bonjour 15 s, http 5 s, remote ssh 10 s, dante-map 30 s; redraw 0.5 s (:2400), command poll 0.25 s (:2440) | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog timing :: fix cooldown 60 s @ status_panel.py:1041,1074,1256,1219 | today: edit source | the same fix (per deck / per MIDI peer) never re-runs within 60 s; key says JUST TRIED WAIT 1 MIN | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog timing :: HOLD_MAX_AGE 15 s @ status_panel.py:2345,2365-2370 | today: edit source | a hold/force stamped more than 15 s ago (queued while the watchdog was down) is dropped | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog timing :: freshness limits @ status_panel.py:181 (conns 10 s), 472 (remote MIDI 12 s), 2173 (ATEM 30 s), 2387 (2.5 s start grace), 2437 (webhook after 30 s) | today: edit source | NO DATA when Companion data is older than 10 s; remote MIDI no verdict after 12 s; ATEM no verdict after 30 s | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: COLORS table / display levels @ status_panel.py:41-61,1299-1310 | today: edit source | ok green, warn amber, notice flashing amber, quiet dark red (fault while ALARMS off), ack dark red, err flashing red, off/idle grey, service blue, fixing blue, failed orange | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: RESTARTABLE = conn, hyperdeck, shure, psm, tascam @ status_panel.py:542,932,1005; make_page.py:29,167 | today: edit source | check types whose Companion connection a hold restarts when they have no fix; also decides whether make_page gives the key a hold action | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: ssh options @ status_panel.py:1748-1753,1707 | today: edit source | /usr/bin/ssh -i <ssh_key> BatchMode=yes ConnectTimeout=5 IdentitiesOnly=yes StrictHostKeyChecking=accept-new ControlMaster=auto ControlPath=/tmp/status-panel-ssh-%C ControlPersist=120; remote python is /usr/bin/python3 (:1708,1741,1963) | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: ports: WoL 9, Art-Net 6454, HyperDeck 9993, telnet 23 @ status_panel.py:1759,2184,1802; netgear.py:38 | today: edit source | hyperdeck_probe (9993) is defined but only used by tests, not by any fix path | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: ATEM compared settings (MIX_OPTIONS, CCU_PARAMS) @ status_panel.py:2042-2058 | today: edit source | Fairlight mixOption + faderGain per source; camera iris, white balance, exposure, detail, gain, ND, lift/gamma/gain/offset, contrast, luma mix, colour | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: text fitting LINE_CAPACITY 145 / sizes 30..12 @ status_panel.py:1331-1356 | today: edit source | key text size chosen per key so words never break; measured on Companion web buttons | other campus: same | (need not be editable)
|
||||||
|
- [hardcoded] hardcoded: watchdog :: CLI: --config, --data-dir, --log, --once, --dry-run @ status_panel.py:2479-2497; install.sh:33-39 | today: LaunchAgent passes --data-dir "$HOME/Library/Application Support/status-panel"; --config defaults to config.json beside the script; --log ~/Library/Logs/status-panel.log (1 MB x3) | the running config is the COPY in App Support, not the repo file; a UI must edit that copy (or re-run install.sh) | other campus: same
|
||||||
|
- [runtime-data] runtime-data :: data dir files: service-mode.json, dante-map.json, atem-reference.json, config.json.bak-* @ status_panel.py:2384-2385,1839,2107; install.sh:24-26 | today: written by the watchdog / install.sh | ALARMS override + last schedule; Dante name->ip/mac/switch/port/poe/seen; converted ATEM restore; timestamped config backups on each install | other campus: same
|
||||||
|
- [companion-side] companion-side :: Companion connection labels referenced by checks @ config.json tiles (HyperDeck_1-6, tascam, x32, audinate-dantecontroller, lighting-midi, Propresenter-API, ableton, BibleTV, ATEM, OBS-Youtube, RESI_Decoder, resi-studio-cues, VX1000, UHDJr, arena, Axient_1/2, ULXD-4, PSM_1-3) | today: rename in Companion AND in config.json | exact label match; a renamed connection shows NOT IN COMPANION; the UI should read /api/connections to offer the live list and flag mismatches | other campus: per site
|
||||||
|
- [companion-side] companion-side :: custom variables sp_state, sp_hb, sp_cmd must be created by hand @ status_panel.py:2421-2427; README.md:128 | today: Companion web UI once | names come from variables.*; watchdog only warns when missing | other campus: per Companion
|
||||||
|
- [secret] secret :: Netgear switch login in the login keychain (service "netgear-status-panel") @ netgear.py:4-10,22,25-35 | today: security add-generic-password -s netgear-status-panel -a <user> -w (in Terminal on the ingest Mac, logged-in session) | account = username, password never printed; needed for dante_switches survey and PoE cycle; the UI should only show present/absent and never the value | other campus: per site switches
|
||||||
|
- [external-setup] external-setup referenced from config strings :: things fixes.run strings depend on @ config.json:48-50 (/usr/local/sbin/status-panel-dvs-start via sudo -n), 53-55,67 (LaunchAgents org.waterschurch.*), 57-65 (schtasks \StatusPanel\Start BibleApp/OBS/Arena/Vista/rtpMIDI), 69,188-189 (~/Library/Application Support/status-panel/solo-mode.json on studiob), 76 (Spotify Automation permission) | today: remote-access/mac-dvs-helper.sh, windows-setup.ps1, install-solo-mode.sh; by hand | each is a plain string inside run; nothing verifies it exists until the fix fails | other campus: per machine; the UI should show per host which helpers are installed
|
||||||
|
- [workflow-step] workflow-step :: install.sh copies status_panel.py coremidi.py netgear.py config.json atem_watch.js atem-reference.xml to ~/Library/Application Support/status-panel and restarts LaunchAgent org.waterschurch.status-panel @ install.sh:9-11,22-28,41-60 | today: zsh install.sh on the ingest Mac after any config change | validates config.json is JSON only (no load_config); backs up the old config; atem_state.js/nav_templates.json not needed at runtime | other campus: same per ingest Mac
|
||||||
|
|
||||||
|
### Lens: WHAT RUNS WHERE: every machine the status panel touches, what is installed/configured on each, how it got there, how to tell it is alive, and which panel keys depend on it
|
||||||
|
- [workflow-step] Your MacBook (dev) :: Project folder (source of truth) + git @ MacBook: /Users/christian/Documents/Status Indicators (git, branch main) | today: edit files here, git commit, git push | Verify: git status clean; git log. Panel: nothing at runtime (NA Ingest runs copies) | other campus: one repo per site, or one repo with per-site config files; code must stay shared | (need not be editable)
|
||||||
|
- [external-setup] Your MacBook (dev) :: Gitea remote christian/status-indicators (private) @ MacBook: origin http://chronicles-ab.waterschurch.org:3000/christian/status-indicators.git | today: git push with credential helper reading keychain item gitea-mcp (see .claude/settings.local.json) | Verify: git remote -v; git ls-remote origin | other campus: same repo; keep private (guideline 13) | (need not be editable)
|
||||||
|
- [external-setup] Your MacBook (dev) :: SSH alias checklist-ingest -> NA Ingest @ MacBook: ~/.ssh/config Host checklist-ingest (HostName 10.1.1.1, User Studio, IdentityFile ~/.ssh/id_ed25519_checklist_ingest) | today: edited by hand in ~/.ssh/config; key authorized on NA Ingest by hand | Verify: ssh -o BatchMode=yes checklist-ingest 'hostname'. Used by scp/install deploy and all remote checks from the MacBook | other campus: one alias per site Ingest Mac (checklist-<site>) | (need not be editable)
|
||||||
|
- [workflow-step] Your MacBook (dev) :: Memory files (project history + guidelines) @ MacBook: ~/.claude/projects/-Users-christian-Documents-Status-Indicators/memory/companion-status-page.md and status-panel-guidelines.md | today: written by Claude sessions | Verify: files exist. Panel: none | other campus: n/a | (need not be editable)
|
||||||
|
- [external-setup] NA Ingest: watchdog :: LaunchAgent org.waterschurch.status-panel (the watchdog) @ NA Ingest (Studio@10.1.1.1): ~/Library/LaunchAgents/org.waterschurch.status-panel.plist; runs /usr/bin/python3 '~/Library/Application Support/status-panel/status_panel.py' --data-dir <that folder>; RunAtLoad, KeepAlive, ThrottleInterval 10 | today: install.sh:30-58 writes the plist and bootout/bootstraps gui/<uid>; 'zsh install.sh --remove' stops it | Verify: launchctl list | grep status-panel (PID shown, e.g. 99852); curl 127.0.0.1:8000/api/custom-variable/sp_hb/value within 15 s of now; page turns PURPLE 'WATCHDOG DOWN' when it stops. Panel: everything | other campus: same install.sh per site; label could carry the site name
|
||||||
|
- [workflow-step] NA Ingest: watchdog :: Install staging folder @ NA Ingest: ~/status-panel-install/ (status_panel.py, coremidi.py, netgear.py, atem_watch.js, config.json, atem-reference.xml, install.sh) | today: scp from the MacBook (README 'Changing things'), then ssh checklist-ingest 'zsh ~/status-panel-install/install.sh' | Verify: ls ~/status-panel-install; cmp with App Support copies | other campus: same path per site | (need not be editable)
|
||||||
|
- [external-setup] NA Ingest: watchdog :: Data dir ~/Library/Application Support/status-panel @ NA Ingest: that folder (install.sh:11 DEST; App Support because launchd python cannot read ~/Documents) | today: install.sh:23-28 copies status_panel.py, coremidi.py, netgear.py, config.json, atem_watch.js, atem-reference.xml into it | Verify: ls -la the folder; mtime of config.json matches the deployed one | other campus: same
|
||||||
|
- [external-setup] NA Ingest: watchdog :: /usr/bin/python3 3.9.6 (macOS Xcode python, stdlib only) @ NA Ingest: /usr/bin/python3 (Xcode Python3.framework 3.9); macOS 26.5.1 | today: comes with Xcode / Command Line Tools; nothing installed (R9) | Verify: /usr/bin/python3 --version. Panel: whole watchdog; telnetlib (netgear.py) needs 3.9-era stdlib | other campus: every Ingest Mac needs Xcode CLT python3; telnetlib is removed in newer pythons
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Watchdog log (rotating) @ NA Ingest: ~/Library/Logs/status-panel.log (1 MB x 3, status_panel.py:2485,2491) and ~/Library/Logs/status-panel.err.log (launchd stderr, install.sh:46) | today: written by the watchdog; every fault/clear/HOLD/FIX line | Verify: tail -f ~/Library/Logs/status-panel.log; err.log should stay 0 bytes | other campus: same
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Auto-Fix daily logs @ NA Ingest: ~/Desktop/Auto-Fix Logs/<YYYY-MM-DD Weekday> Auto-Fix.log (AutoLog status_panel.py:625-686; dir from auto_fix.log_dir default ~/Desktop/Auto-Fix Logs) | today: written by auto_fix(); files deleted a calendar month after their date | Verify: ls '~/Desktop/Auto-Fix Logs' (today: 2026-10-11 Sunday, 2026-10-13 Tuesday). Double-click opens Console | other campus: same; folder name configurable
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: service-mode.json (ALARMS override) @ NA Ingest: ~/Library/Application Support/status-panel/service-mode.json ({override, last_sched}; ServiceMode status_panel.py:696-739) | today: written when ALARMS key is pressed or a window edge passes | Verify: cat the file (now {"override": null, "last_sched": true}). Panel: ALARMS key, auto-fix on/off | other campus: same
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: dante-map.json (self-updating Dante device map) @ NA Ingest: ~/Library/Application Support/status-panel/dante-map.json (poll_dante_map status_panel.py:1869-1906; every 30 s until first success then dante_map_seconds=600) | today: built automatically from Companion Dante vars + arp -an + Netgear MAC/PoE tables | Verify: python3 -c to print it (19 devices today; PoE: STUDIO-B-MON 0/15, SOLO-MON 0/14, TB 0/16, TB-MON 0/17 on 10.66.1.102); log line 'dante map: N devices'. Panel: DANTE hold (dante-auto) | other campus: automatic per site
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: atem-reference.json (derived from the restore XML) @ NA Ingest: ~/Library/Application Support/status-panel/atem-reference.json (AtemWatcher.__init__ status_panel.py:2107-2115 writes it on every start from atem-reference.xml) | today: regenerated at watchdog start; source XML copied by install.sh:28 | Verify: file mtime == last watchdog start; ATEM key green. Panel: ATEM AUDIO/CAMERA WRONG | other campus: each site has its own restore XML
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: config.json backups @ NA Ingest: ~/Library/Application Support/status-panel/config.json.bak-YYYYmmdd-HHMMSS (install.sh:24-26; 46 of them today) | today: install.sh makes one whenever the config differs | Verify: ls config.json.bak-*. Useful as the history/rollback source the UI needs (R8) | other campus: same
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: SSH multiplex sockets @ NA Ingest: /tmp/status-panel-ssh-%C (ssh_cmd status_panel.py:1748-1753: ControlMaster=auto, ControlPersist=120, BatchMode, ConnectTimeout=5, IdentitiesOnly, StrictHostKeyChecking=accept-new) | today: created by ssh on first use per host | Verify: ls /tmp/status-panel-ssh-*; pgrep -lf 'ssh.*id_status_panel'. Panel: all remote checks/fixes | other campus: same | (need not be editable)
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Long-lived MIDI session streams to NA Tracks and ProPresenter Mac @ NA Ingest: one ssh per host (stream_remote_midi status_panel.py:1700-1734) running /usr/bin/python3 -u - with coremidi.py piped over stdin; nothing installed on the far Mac | today: started by start_pollers for every midi_peer check with a 'host' | Verify: pgrep -lf 'ServerAliveInterval=2' on NA Ingest (one per host, e.g. PID 6558); MIDI key shows no 'NO SESSION'. Panel: MIDI (FX LIGHTS, TRK LIVEBOX, TRK STUDIO B, PROP LIGHTS) | other campus: any Mac with Remote Login + /usr/bin/python3
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: ATEM watcher subprocess (atem_watch.js on Companion's node) @ NA Ingest: <Companion 5.0.7.app>/Contents/Resources/node-runtimes/node22/bin/node + ~/Library/Application Support/companion/modules/bmd-atem-3.19.1/node_modules/atem-connection (config atem.node / atem.library globs; newest() status_panel.py:2089; AtemWatcher 2098-2164) | today: globs resolve to the newest Companion app and newest bmd-atem module; restarts itself every 5 s if it dies | Verify: pgrep -lf atem_watch.js; log 'ATEM watcher stopped' absent; ATEM key green. Panel: ATEM AUDIO/CAMERA; atem_audio/atem_camera fixes | other campus: depends on the Companion app version and bmd-atem module being installed there
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Art-Net listener UDP 6454 @ NA Ingest: watchdog socket on 0.0.0.0:6454 with SO_REUSEPORT (start_artnet_listener status_panel.py:2184-2208) | today: started when any 'artnet' check exists | Verify: lsof -nP -iUDP:6454; log 'Art-Net listener could not start' absent. Panel: VISTA (SILENT) | other campus: the lighting console's Art-Net must reach the Ingest Mac's subnet
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Bonjour browse of _apple-midi._udp @ NA Ingest: /usr/bin/dns-sd -B _apple-midi._udp local. every 15 s for 3 s (poll_mdns status_panel.py:1510) | today: built in | Verify: dns-sd -B _apple-midi._udp local. lists 'Lighting PC' and 'NA Tracks'. Panel: MIDI (LIGHT PC MIDI GONE, TRACKS MIDI GONE) | other campus: instance names differ per site (config mdns.instance)
|
||||||
|
- [external-setup] NA Ingest: watchdog :: Local CoreMIDI session read + 'NA Ingest Listen' session (optional) @ NA Ingest: coremidi.py via ctypes reads the network driver's apple.midirtp.session; session 'NA Ingest' (peers Lighting PC, SuperRack-LiveBox); optional 'NA Ingest Listen' session created in Audio MIDI Setup (README 'Optional') | today: sessions created/connected by hand in Audio MIDI Setup > MIDI Studio > Network | Verify: python3 -c 'import coremidi; print(coremidi.sessions())' in the data dir. Panel: MIDI (LIGHT PC, SUPERRACK), TRACKS IN line | other campus: session and peer names per site
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Local shell probes used by checks @ NA Ingest: /sbin/ping, /sbin/ifconfig en0, /bin/launchctl list, /usr/bin/pgrep, /usr/bin/defaults read, /usr/sbin/arp -an, shutil.disk_usage (poll_pings 1453, poll_system 1475, arp_table 1855) | today: built in | Verify: python3 status_panel.py --once prints every tile. Panel: NET, INGEST, ATEM SW MIDI, DANTE map | other campus: interface name (network.interface) and gateway per site | (need not be editable)
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Spotify control via osascript @ NA Ingest: /usr/bin/osascript 'tell application "Spotify"' every 3 s (poll_spotify status_panel.py:1619-1645, SPOTIFY_STATE 1593) + playlist list from open.spotify.com/embed and spclient.wg.spotify.com (playlist_tracks 1597) | today: built in; playlist URI in config tiles.spotify.checks[0].playlist and fixes.spotify_play.run (hardcoded twice) | Verify: SPOTIFY key green 'PLAYING'; log 'Spotify playlist list' warnings absent. Panel: SPOTIFY, rs_spotify | other campus: playlist URI per site; needs internet for the song list
|
||||||
|
- [runtime-data] NA Ingest: watchdog :: Read-only read of Companion's settings database @ NA Ingest: ~/Library/Application Support/companion/v5.0/db.sqlite opened mode=ro every 60 s (companion_db_path 1552, companion_conn_hosts 1563; table instances) | today: built in; override with config companion.db | Verify: log 'could not read connection addresses' absent. Panel: Shure/Tascam/LED pings and HyperDeck/Tascam hung-vs-off verdicts use the address set on the Companion connection | other campus: path follows the newest vN.N folder automatically | (need not be editable)
|
||||||
|
- [external-setup] NA Ingest: Companion :: Companion 5.0.7 (login item) @ NA Ingest: /Applications/Companion 5.0.7.app (also 5.0.3/5.0.4/5.0.5 and Companion.app present); HTTP API 127.0.0.1:8000; web UI http://na-ingest.local:8000 | today: installed by hand; macOS login item 'Companion' | Verify: curl -s 127.0.0.1:8000/api/connections | head -c 100; pgrep -lf 'Companion 5.0.7'. Panel: all keys; watchdog logs 'Companion unreachable' and posts alert_webhook after 30 s | other campus: version pinned in make_page.py (EXPORT_VERSION 12, COMPANION_BUILD 5.0.7+9763); old app copies could be deleted
|
||||||
|
- [external-setup] NA Ingest: Companion :: Custom variables sp_state, sp_hb, sp_cmd @ NA Ingest Companion: Variables > Custom Variables (names from config variables.state/heartbeat/command) | today: created BY HAND (README 'One-time Companion setup'); the API cannot create them (POST to a missing one -> 404) | Verify: curl -s -o /dev/null -w '%{http_code}' 127.0.0.1:8000/api/custom-variable/sp_hb/value -> 200 (all three 200 today). Panel: everything | other campus: create the three by hand on each site's Companion
|
||||||
|
- [external-setup] NA Ingest: Companion :: Custom variable flash_tick + trigger flash_tick_500 (blink clock) @ NA Ingest Companion: custom var flash_tick, trigger 'flash_tick_500' (controls table id trigger:324fEl4_yJyr6Uu71jvId, enabled) | today: pre-existing trigger; must stay enabled (README step 3) | Verify: custom-variable/flash_tick/value -> 200 and toggles 0/1; trigger enabled in Triggers page. Panel: blinking of every key and nav key | other campus: recreate the trigger on each site (not exported by make_page.py)
|
||||||
|
- [external-setup] NA Ingest: Companion :: Page 19 'Status' (8x4 keys) @ NA Ingest Companion page 19 (config page, page_name); built by make_page.py -> status-page.companionconfig; imported by push_page.py (tRPC importSinglePage) or by hand | today: python3 make_page.py; python3 push_page.py (refuses unless page empty or already the status page) | Verify: http://na-ingest.local:8000/tablet?pages=19&cols=8&rows=4; page_export via /int/export/page/19. Panel: the whole status page | other campus: page number per site in config
|
||||||
|
- [external-setup] NA Ingest: Companion :: Page 20 'Restart' @ NA Ingest Companion page 20 (config restart_page.page); restart-page.companionconfig | today: python3 make_page.py; python3 push_page.py --which restart | Verify: tablet view pages=20. Panel: restart keys rs_* | other campus: page number per site
|
||||||
|
- [external-setup] NA Ingest: Companion :: Coloured page-number nav key on all other pages (97 pages) @ NA Ingest Companion: location 1/0 on every page replaced by a copy of page 19's converted pagenum key; record in repo nav_locations.json; templates nav_templates.json; marker 'STATUS nav key' | today: python3 nav_keys.py (tRPC controls.copyControl); --revert resets; re-run after adding pages (guideline 9) | Verify: nav_keys.py --dry-run lists nothing to change; cat nav_locations.json. Panel: ALL colour visible from every page; tap -> page nav_tap_page, hold nav_hold_ms -> page 19 | other campus: run nav_keys.py on each site; nav_locations.json is per site
|
||||||
|
- [external-setup] NA Ingest: Companion :: Tascam quiet module 1.3.1-quiet @ NA Ingest: ~/Library/Application Support/companion/modules/tascam-da-6400-1.3.1-quiet (built by tascam-quiet/make_quiet_tascam.py from tascam-da-6400-1.3.0; tgz imported under Modules > Import module package; connection 'tascam' set to that version) | today: by hand in the Companion UI | Verify: ls the modules folder; Connections > tascam shows 1.3.1-quiet. Panel: TASCAM key (log flood otherwise) | other campus: import the tgz on each site that has a DA-6400
|
||||||
|
- [external-setup] NA Ingest: Companion :: Companion module folders used by the panel @ NA Ingest: ~/Library/Application Support/companion/modules/ (bmd-atem-3.19.1 used by atem_watch.js; bmd-hyperdeck-3.2.0, shure-wireless-2.3.1, shure-psm1000-2.0.3, audinate-dantecontroller-2.0.3-heartbeat-auto, novastar-controller-2.6.4, resi-decoders-2.0.0, resi-studio-cues-0.1.5, obs-studio-3.15.3, resolume-arena-3.12.0, generic-midi-1.4.0, ableton-osc-2.0.0, renewedvision-propresenter-api-1.2.0-rc4 etc.) | today: Companion module store / manual imports | Verify: ls that folder; each connection's version in the UI. Panel: all conn/variable checks | other campus: module set differs per site
|
||||||
|
- [external-setup] NA Ingest: Companion :: Companion Satellite (login item) @ NA Ingest: ~/Applications/Companion Satellite.app (running, PID 874) | today: installed by hand; login item | Verify: pgrep -lf 'Companion Satellite'. Panel: not checked today; surfaces it feeds are unknown | other campus: not determined
|
||||||
|
- [external-setup] NA Ingest: Companion :: Stream Deck surfaces (XL; MK.2 and Mini show top-left corner) @ NA Ingest Companion: Surfaces page; db tables surfaces/surface_groups | today: plugged in / configured in Companion UI (surface 'use last page' setting matters for the nav key, memory note) | Verify: Companion > Surfaces lists them (not read in this audit). Panel: which physical keys show pages 19/20 | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: HyperDeck_1..HyperDeck_6 (bmd-hyperdeck) + tcp-HyperDeck-1..6 (generic-tcp-udp) @ NA Ingest Companion connections; deck IPs on each connection (watchdog reads var 'ip' / db); control port 9993 probed by hyperdeck_probe status_panel.py:1802 | today: Companion UI; config tiles hd1..hd6 (HD2/HD4 optional:true) | Verify: /api/connections shows all 6 'good'; HD keys green with record time. Panel: HD1-6, REC group | other campus: which decks exist and which are optional is config; tcp-HyperDeck-n connections are separate (purpose not determined)
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: tascam (tascam-da-6400 1.3.1-quiet) -> DA-6400 10.1.11.74 @ NA Ingest Companion connection 'tascam'; ping 10.1.11.74 (config fallback host) | today: Companion UI; config tiles.tascam | Verify: connection good; ping 10.1.11.74. Panel: TASCAM (HUNG - POWER CYCLE IT / NO NETWORK), rs_tascam | other campus: address per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: x32 (behringer-x32) [m32 connection exists but OFF] @ NA Ingest Companion connection 'x32' | today: Companion UI | Verify: connection good. Panel: X32, AUDIO group | other campus: console model/label per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: ATEM (bmd-atem) -> switcher 10.1.10.2 @ NA Ingest Companion connection 'ATEM'; config atem.host 10.1.10.2 | today: Companion UI; config atem | Verify: connection good; atem_watch reports connected. Panel: ATEM, VIDEO group | other campus: switcher IP per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: audinate-dantecontroller (module 2.0.3-heartbeat-auto) -> 19 Dante devices @ NA Ingest Companion connection 'audinate-dantecontroller'; vars devices, <name>_ip; config tiles.dante.devices (19 names -> short labels) | today: Companion UI; config dante check 'devices' map and 'fix' map | Verify: /api/variable/audinate-dantecontroller/devices/value lists 19. Panel: DANTE (19/19, X GONE), AUDIO group | other campus: device list per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: Axient_1 10.1.1.101, Axient_2 10.1.1.102, ULXD-4 10.1.1.106 (shure-wireless); PSM_1 .103, PSM_2 .104, PSM_3 .105 (shure-psm1000) @ NA Ingest Companion connections; addresses read from Companion db every 60 s, config host = fallback | today: Companion UI; config hidden tiles axient1/2, ulxd, psm1-3 (channels 4/4/1/2/2/2) | Verify: connections good; ping each. Panel: RF group (battery, interference, NO PING) | other campus: RF inventory per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: ableton (ableton-osc), AbletonOSC (generic-osc), AbleSet (OFF) -> Tracks Mac @ NA Ingest Companion connections | today: Companion UI | Verify: 'ableton' good. Panel: APPS (ABLETON), rs_ableton watch conn:ableton | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: Propresenter-API (renewedvision-propresenter-api) -> ProPresenter Mac @ NA Ingest Companion connection 'Propresenter-API' (ignore_warning:true in config) | today: Companion UI | Verify: connection good. Panel: APPS (PROPRES OFFLINE) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: BibleTV (generic-osc) -> Bible TV PC @ NA Ingest Companion connection 'BibleTV' | today: Companion UI | Verify: connection good. Panel: APPS (BIBLE OSC) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: lighting-midi (generic-midi) -> Lighting PC @ NA Ingest Companion connection 'lighting-midi' | today: Companion UI | Verify: connection good. Panel: MIDI (COMPANION MIDI) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: OBS-Youtube (obs-studio) -> LiveStream PC @ NA Ingest Companion connection 'OBS-Youtube' (service_only:true) | today: Companion UI | Verify: connection good. Panel: OBS, STREAM group; obs fix reconnects it | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: arena (resolume-arena) -> Lighting PC :8080 @ NA Ingest Companion connection 'arena' | today: Companion UI | Verify: connection good. Panel: ARENA, VIDEO group | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: RESI_Decoder (resi-decoders, RossTalk) 10.1.10.203 and resi-studio-cues @ NA Ingest Companion connections | today: Companion UI | Verify: connections good; ping 10.1.10.203. Panel: RESI, STREAM group | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: Companion connections :: VX1000 10.1.14.116 and UHDJr 10.1.14.115 (novastar-controller) @ NA Ingest Companion connections | today: Companion UI | Verify: connections good AND ping (module keeps saying Connected when off). Panel: LED, VIDEO group | other campus: LED processors per site
|
||||||
|
- [runtime-data] NA Ingest: Companion connections :: Connections NOT watched by the panel @ NA Ingest Companion: 7 Govee lights, DiGiCo_OSC (OFF), m32 (OFF), AbleSet (OFF), tcp-HyperDeck-1..6 | today: Companion UI | Verify: /api/connections. Panel: none (candidates to add via the UI) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Resi bridge LaunchAgent org.waterschurch.resi-propresenter @ NA Ingest: ~/Library/LaunchAgents/org.waterschurch.resi-propresenter.plist -> /usr/bin/python3 '~/Library/Application Support/resi-propresenter/resi_to_propresenter.py' (KeepAlive, RunAtLoad) | today: separate project, installed by hand; fix 'bridge' = launchctl kickstart -k / bootstrap | Verify: launchctl list | grep resi-propresenter (PID 843). Panel: INGEST (BRIDGE), rs_bridge. Never restart during a service (guideline 10) | other campus: only where ProPresenter stage countdown to Resi cues is used
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Lobby captions server LaunchAgent org.waterschurch.lobby-captions @ NA Ingest: ~/Library/LaunchAgents/org.waterschurch.lobby-captions.plist -> /usr/bin/python3 ~/LobbyCaptions/server.py --channels Speaker,MC | today: separate project; fix 'captions' kickstarts both captions agents | Verify: launchctl list | grep lobby-captions (PID 837). Panel: INGEST (CAPTIONS), rs_captions | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Lobby caption screen LaunchAgent org.waterschurch.lobby-captions-display @ NA Ingest: ~/Library/LaunchAgents/org.waterschurch.lobby-captions-display.plist -> ~/LobbyCaptions/LobbyCaptionsDisplay.app --config ~/LobbyCaptions/display.json | today: separate project; fix 'caption_screen' | Verify: launchctl list (PID 838). Panel: INGEST (CAPTION SCREEN) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: ProdCom.app (transcription, HTTP :24480) @ NA Ingest: /Applications/ProdCom.app (running PID 548); check http://127.0.0.1:24480/api/v1/ping every 5 s; fix 'prodcom' pkill/open; also restarted after dvs_ingest | today: how it starts at login NOT determined (not a login item, not a LaunchAgent) | Verify: curl 127.0.0.1:24480/api/v1/ping; pgrep -x ProdCom. Panel: INGEST (PRODCOM DOWN), rs_prodcom | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: ProdDash.app (login item, HTTP :24500) @ NA Ingest: /Applications/ProdDash.app (login item; launchd shows application.org.waterschurch.proddash.*); checks process match + http://127.0.0.1:24500/ every 10 s; fix 'proddash' | today: macOS login item | Verify: curl -I 127.0.0.1:24500; pgrep -x ProdDash. Panel: INGEST (PRODDASH), rs_proddash | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Spotify.app + playlist Christian EDM 2026 @ NA Ingest: ~/Applications/Spotify.app (NOT /Applications); playlist spotify:playlist:2uTpJVYQLFnKF1WnSOuco8 | today: installed by hand; playlist URI hardcoded in config twice (check + fix run string) | Verify: pgrep -x Spotify; SPOTIFY key 'PLAYING'. Panel: SPOTIFY, rs_spotify, SHOW group | other campus: playlist per site; open -g -a Spotify works wherever the app is
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: ATEM Software Control (login item) + midiEnabled preference @ NA Ingest: /Applications/Blackmagic ATEM Switchers/ATEM Software Control.app; defaults domain 'com.blackmagic-design.ATEM Software Control' key midiEnabled must be 0 (pref check; fix atem_midi quits/reopens the app) | today: installed with ATEM Switchers package; login item | Verify: defaults read 'com.blackmagic-design.ATEM Software Control' midiEnabled -> 0. Panel: ATEM (ATEM SW MIDI ON) | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Dante Virtual Soundcard on NA Ingest (root LaunchDaemon) @ NA Ingest: /Library/LaunchDaemons com.audinate.dante.DanteVirtualSoundcard (dvsd, apec, conmon_server, ptp running); state wantToRun in /Library/Preferences/com.audinate.dante.dvs.plist; Dante name STUDIO-A-Ingest on en17 | today: Audinate installer; fix dvs_ingest = sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a DVS, then ProdCom restart | Verify: pgrep -f DanteVirtualSoundcard/apec; defaults read /Library/Preferences/com.audinate.dante.dvs wantToRun -> 1. Panel: DANTE (INGEST GONE), rs_dvs_ingest | other campus: per Mac with DVS
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Church Translate DISABLED on NA Ingest @ NA Ingest: ~/Library/LaunchAgents.disabled/org.waterschurch.translate.plist (open -a Church Translate; must NOT run here, it runs on NA Translation) | today: moved out of ~/Library/LaunchAgents by hand 2026-10-08 | Verify: launchctl list | grep translate -> nothing; pgrep -x 'Church Translate' -> nothing on NA Ingest | other campus: document which Mac owns each app
|
||||||
|
- [external-setup] NA Ingest: apps the panel watches :: Google Drive (ATEM restore file source) @ NA Ingest: ~/Library/CloudStorage/GoogleDrive-media@waterschurch.org (login item 'Google Drive'); restore file Backup/NA/ATEM/'4k plus 2026-10-07 14-59-55.xml' copied to repo atem-reference.xml | today: copy a new restore XML over atem-reference.xml in the repo, re-run install.sh | Verify: ls that CloudStorage folder; cmp repo atem-reference.xml with the Drive file. Panel: ATEM AUDIO/CAMERA reference | other campus: each site keeps its own restore XML
|
||||||
|
- [runtime-data] NA Ingest: apps the panel watches :: Free disk on / @ NA Ingest: shutil.disk_usage('/') every 10 s; warn_gb 25, err_gb 8 | today: config tiles.ingest disk check | Verify: df -g /. Panel: INGEST (DISK xGB) | other campus: thresholds per site
|
||||||
|
- [runtime-data] NA Ingest: other background tasks (not the panel) :: org.waterschurch.checklist-build-runner (Gitea act-runner daemon) @ NA Ingest: ~/Library/LaunchAgents/org.waterschurch.checklist-build-runner.plist -> ~/act-runner/gitea-runner daemon (KeepAlive) | today: other project (checklist app); not touched by the panel | Verify: launchctl list (PID 839). Shows on the machine map only | other campus: n/a
|
||||||
|
- [runtime-data] NA Ingest: other background tasks (not the panel) :: org.waterschurch.checklist-drive-backup (calendar) and checklist-weekly-health (calendar) @ NA Ingest: ~/Library/LaunchAgents/... -> ~/checklist-app/backup-to-drive.sh and ~/checklist-app/scripts/weekly-health.sh (StartCalendarInterval, RunAtLoad false) | today: other project | Verify: launchctl list shows them with '-' (idle). Machine map only | other campus: n/a
|
||||||
|
- [runtime-data] NA Ingest: other background tasks (not the panel) :: Other login items / agents: Itsycal, Alfred 5, Google Drive, Google Updater/keystone, Signiant (local.launch.signiantapp) @ NA Ingest: login items list + ~/Library/LaunchAgents | today: by hand | Verify: osascript 'tell application "System Events" to get the name of every login item'. Machine map only | other campus: n/a
|
||||||
|
- [external-setup] NA Ingest: network :: en0 10.1.1.1 (main LAN, gateway 10.1.0.1) and second address 10.6.3.222 @ NA Ingest: config network.interface en0, network.gateway 10.1.0.1; windows-setup.ps1:18 allows SSH only from 10.1.1.1 and 10.6.3.222 (which interface carries 10.6.3.222 NOT determined) | today: macOS network settings; IPs hardcoded in windows-setup.ps1 | Verify: ipconfig getifaddr en0; ifconfig | grep 10.6.3.222; ping 10.1.0.1. Panel: NET (link, gateway, blips) | other campus: Ingest IPs per site must be edited in windows-setup.ps1 and config
|
||||||
|
- [external-setup] NA Ingest: network :: en17 Dante port 169.254.144.168 (STUDIO-A-Ingest) @ NA Ingest: en17; arp -an on it feeds the Dante map | today: physical cabling + DVS interface choice | Verify: ipconfig getifaddr en17; dante-map.json lists STUDIO-A-Ingest on 10.66.1.102 0/3 | other campus: per site
|
||||||
|
- [external-setup] NA Ingest: network :: en20 10.66.1.2 Netgear management link @ NA Ingest: en20 -> switches 10.66.1.100 and 10.66.1.102 (telnet 23); NETGEAR Engage.app also on this Mac | today: physical link + static IP by hand; config dante_switches | Verify: ipconfig getifaddr en20; nc -z 10.66.1.100 23. Panel: DANTE PoE power-cycle, map | other campus: switch IPs per site
|
||||||
|
- [external-setup] ProPresenter Mac :: Host 'prop' napropresenter@10.1.14.225 ('NA ProPresenter Mac (2)', macOS 26.2) @ config hosts.prop.ssh; Remote Login ON (System Settings > Sharing) | today: Remote Login by hand; key via mac-authorize.sh (ARD Send UNIX Command as root) | Verify: ssh -i ~/.ssh/id_status_panel napropresenter@10.1.14.225 hostname (ok today). Panel: APPS (PROPRES NOT RUNNING, PROP TV DOWN), MIDI (PROP LIGHTS), DANTE (PROP MAC) | other campus: user@ip per site
|
||||||
|
- [secret] ProPresenter Mac :: authorized_keys entry status-panel@NA-Ingest @ ProPresenter Mac: ~napropresenter/.ssh/authorized_keys (1 line today) | today: remote-access/mac-authorize.sh pasted into ARD (public key hardcoded in the script) | Verify: grep -c status-panel ~/.ssh/authorized_keys -> 1 | other campus: new site = new key pair -> edit the hardcoded key in mac-authorize.sh and windows-setup.ps1
|
||||||
|
- [secret] ProPresenter Mac :: DVS helper /usr/local/sbin/status-panel-dvs-start + /etc/sudoers.d/status-panel @ ProPresenter Mac: root-owned 755 script (defaults write wantToRun 1; launchctl kickstart -k system/com.audinate.dante.DanteVirtualSoundcard) + sudoers NOPASSWD for that one script (installed 2026-10-08 12:05) | today: remote-access/mac-dvs-helper.sh via ARD as root; undo: rm both files | Verify: sudo -n -l /usr/local/sbin/status-panel-dvs-start (sudo-ok today); ls -l both. Panel: fix dvs_prop, rs_dvs_prop | other campus: run the same script on each DVS Mac
|
||||||
|
- [external-setup] ProPresenter Mac :: Dante Virtual Soundcard engine (NA-ProPresenter-Mac) @ ProPresenter Mac: root LaunchDaemon com.audinate.dante.DanteVirtualSoundcard; wantToRun=1; engine running today | today: Audinate installer | Verify: pgrep -f DanteVirtualSoundcard/apec; Companion Dante devices list has NA-ProPresenter-Mac. Panel: DANTE PROP MAC GONE | other campus: per Mac
|
||||||
|
- [external-setup] ProPresenter Mac :: ProPresenter app (newest /Applications/ProPresenter*.app, 21.4) + API :1025 @ ProPresenter Mac: fix propresenter = pkill + open newest; checks pgrep -xq ProPresenter (SSH every 10 s) and http://10.1.14.225:1025/ every 5 s | today: installed by hand; API enabled in ProPresenter prefs | Verify: curl -I http://10.1.14.225:1025/; ssh ... pgrep -x ProPresenter. Panel: APPS, rs_propres | other campus: per site
|
||||||
|
- [external-setup] ProPresenter Mac :: Network MIDI session 'NA ProP' with peer Lighting PC (10.1.10.206:5004) @ ProPresenter Mac: CoreMIDI network driver (read via the ssh python stream; reconnect via connect_peer over ssh) | today: Audio MIDI Setup on that Mac by hand; config tiles.midi check PROP LIGHTS | Verify: MIDI key has no 'PROP LIGHTS'; log of the stream. Panel: MIDI | other campus: session/peer names per site
|
||||||
|
- [external-setup] Tracks Mac :: Host 'tracks' waterschurchmedia@10.1.3.227 (Ableton Live 11 Suite + DVS) @ config hosts.tracks.ssh; Remote Login turned ON 2026-10-08 (was off) | today: Remote Login by hand; key via mac-authorize.sh (ARD) | Verify: ssh ... hostname. TODAY: 'Host is down' at 10.1.3.227 (Mac off or asleep). Panel: APPS (ABLETON NOT RUNNING, TRACKS MAC ping), MIDI (FX LIGHTS, TRK LIVEBOX, TRK STUDIO B, TRACKS MIDI), DANTE (TRACKS) | other campus: per site
|
||||||
|
- [secret] Tracks Mac :: authorized_keys entry + DVS helper + sudoers @ Tracks Mac: ~/.ssh/authorized_keys; /usr/local/sbin/status-panel-dvs-start; /etc/sudoers.d/status-panel (verified installed 2026-10-08 per memory; NOT re-verified today, host down) | today: mac-authorize.sh and mac-dvs-helper.sh via ARD as root | Verify when up: grep -c status-panel ~/.ssh/authorized_keys; sudo -n -l /usr/local/sbin/status-panel-dvs-start. Panel: fix dvs_tracks, rs_dvs_tracks | other campus: same scripts
|
||||||
|
- [external-setup] Tracks Mac :: Ableton Live 11 Suite + last-set lookup @ Tracks Mac: /Applications/Ableton Live 11 Suite.app; last set parsed from ~/Library/Preferences/Ableton/Live*/Log.txt ('Loading document', templates skipped) by fixes ableton / ableton_restart | today: app installed by hand; app name hardcoded in both fix strings | Verify: ssh ... pgrep -x Live; Companion 'ableton' good. Panel: APPS, rs_ableton | other campus: Live version/app name per site
|
||||||
|
- [external-setup] Tracks Mac :: Network MIDI session 'NA Tracks' (peers Lighting PC 10.1.10.206:5004, SuperRack-LiveBox 10.1.13.140:5004, Studio B Mac Studio via Bonjour) + Bonjour advert 'NA Tracks' @ Tracks Mac: CoreMIDI network driver; read by the ssh stream; mDNS instance seen from NA Ingest | today: Audio MIDI Setup on the Tracks Mac by hand; config midi_peer checks with host tracks | Verify: dns-sd -B _apple-midi._udp shows 'NA Tracks'; MIDI key clean. Panel: MIDI (FX LIGHTS, TRK LIVEBOX, TRK STUDIO B, TRACKS MIDI GONE) | other campus: per site
|
||||||
|
- [external-setup] Tracks Mac :: Dante Virtual Soundcard (NA-Tracks) @ Tracks Mac: root LaunchDaemon com.audinate.dante.DanteVirtualSoundcard | today: Audinate installer | Verify: Dante devices list has NA-Tracks. Panel: DANTE TRACKS GONE | other campus: per Mac
|
||||||
|
- [external-setup] NA Translation Mac mini :: Host 'translation' studio@10.1.7.39 ('NA Translation', macOS 26.5.1) @ config hosts.translation.ssh; Remote Login ON | today: Remote Login by hand; key via mac-authorize.sh | Verify: ssh ... hostname (ok today). Panel: APPS (TRANSLATE NOT RUNNING), DANTE (TRANSL) | other campus: per site
|
||||||
|
- [secret] NA Translation Mac mini :: authorized_keys + DVS helper + sudoers @ NA Translation: ~/.ssh/authorized_keys (1); /usr/local/sbin/status-panel-dvs-start; /etc/sudoers.d/status-panel (installed 2026-10-08 12:05, sudo-ok today) | today: mac-authorize.sh and mac-dvs-helper.sh via ARD as root | Verify: sudo -n -l /usr/local/sbin/status-panel-dvs-start. Panel: fix dvs_translation, rs_dvs_transl | other campus: same scripts
|
||||||
|
- [external-setup] NA Translation Mac mini :: Church Translate.app (+ its translation server) @ NA Translation: /Applications/Church Translate.app; checks pgrep -xq 'Church Translate'; fixes translate (open if closed) / translate_restart (kills process group then open) | today: installed by hand | Verify: ssh ... pgrep -x 'Church Translate'. Panel: APPS, rs_translate | other campus: per site
|
||||||
|
- [external-setup] NA Translation Mac mini :: Dante Virtual Soundcard (NA-Translation) @ NA Translation: root LaunchDaemon; wantToRun=1, engine running today | today: Audinate installer | Verify: pgrep -f DanteVirtualSoundcard/apec. Panel: DANTE TRANSL GONE | other campus: per Mac
|
||||||
|
- [external-setup] Studio B Mac Studio :: Host 'studiob' studiob@10.1.13.175 ('NA Studio B (4)', macOS 26.5) @ config hosts.studiob.ssh; Remote Login ON; authorized_keys (1) | today: Remote Login by hand; key via mac-authorize.sh | Verify: ssh ... hostname (ok today). Panel: APPS (PT SOLO NOT AFL, PT SOLO CHECK OFF), MIDI (TRK STUDIO B) | other campus: per site
|
||||||
|
- [external-setup] Studio B Mac Studio :: Solo-mode helper LaunchAgent org.waterschurch.status-panel.solo-mode @ Studio B: ~/Library/LaunchAgents/org.waterschurch.status-panel.solo-mode.plist -> ~/Library/Application Support/status-panel/solo-mode-agent (compiled with /usr/bin/swiftc from remote-access/solo-mode-agent.swift; KeepAlive); running PID 1051 | today: scp solo-mode-agent.swift + install-solo-mode.sh to /tmp on that Mac, then ssh 'zsh /tmp/install-solo-mode.sh' (needs swiftc = Xcode CLT there) | Verify: launchctl list | grep solo-mode; solo-mode.json younger than 20 s with trusted:true. Panel: APPS PT SOLO checks, fix solo_afl | other campus: only where Pro Tools solo mode must be watched
|
||||||
|
- [runtime-data] Studio B Mac Studio :: solo-mode.json (state) and solo-mode.fix (trigger file) @ Studio B: ~/Library/Application Support/status-panel/solo-mode.json written every 1 s ({afl, latch, bad, running, trusted, time, error}); touching solo-mode.fix makes the helper tick AFL+Latch | today: helper writes json; fix solo_afl touches .fix over ssh then greps json | Verify: cat solo-mode.json (today afl:true latch:true trusted:true). Panel: APPS | other campus: same
|
||||||
|
- [secret] Studio B Mac Studio :: Accessibility permission for solo-mode-agent (TCC) @ Studio B: System Settings > Privacy & Security > Accessibility > solo-mode-agent allowed | today: allowed by hand once on that Mac's screen (the helper prompts via AXIsProcessTrustedWithOptions), then helper restart | Verify: solo-mode.json trusted:true (true today); otherwise panel says PT SOLO CHECK OFF | other campus: must be re-granted after recompiling the binary
|
||||||
|
- [external-setup] Studio B Mac Studio :: Pro Tools.app @ Studio B: /Applications/Pro Tools.app (bundle com.avid.ProTools); Options > Solo Mode must be AFL + Latch | today: installed by hand | Verify: solo-mode.json running:true. Panel: APPS | other campus: per site
|
||||||
|
- [external-setup] Studio B Mac Studio :: MIDI peer 'Studio B Mac Studio' in the NA Tracks session (Bonjour address '\tStudio B Mac Studio\tlocal.') @ config tiles.midi check TRK STUDIO B; reconnect via Tracks Mac | today: Audio MIDI Setup on Studio B / Tracks by hand | Verify: MIDI key clean. Panel: MIDI | other campus: per site
|
||||||
|
- [external-setup] Lighting PC :: Host 'lighting' media@10.1.10.206 (LIGHTING-PC, Windows; firewall blocks ping) @ config hosts.lighting.ssh; no MAC (no Wake-on-LAN entry) | today: windows-setup.ps1 run by hand as administrator (Set-ExecutionPolicy RemoteSigned CurrentUser first) | Verify: ssh ... 'echo %COMPUTERNAME%' (ok today). Panel: VISTA, ARENA, MIDI (LIGHT PC, FX LIGHTS, PROP LIGHTS, LIGHT PC MIDI GONE) | other campus: per site
|
||||||
|
- [external-setup] Lighting PC :: OpenSSH Server service sshd (Automatic, restarts on failure) @ Lighting PC: Windows capability OpenSSH.Server; sc failure sshd restart/5000 x3 (windows-setup.ps1:21-40) | today: windows-setup.ps1 step 1 | Verify: sc query sshd -> RUNNING (yes today) | other campus: same script
|
||||||
|
- [external-setup] Lighting PC :: Firewall rule OpenSSH-Server-In-TCP (Profile Any, RemoteAddress 10.1.1.1,10.6.3.222) @ Lighting PC: Windows Defender Firewall inbound rule Name OpenSSH-Server-In-TCP / DisplayName 'OpenSSH Server (sshd)' (windows-setup.ps1:41-47) | today: windows-setup.ps1 step 1 (needs the user's OK for firewall changes, guideline 12) | Verify: PowerShell Get-NetFirewallRule -Name OpenSSH-Server-In-TCP | Get-NetFirewallAddressFilter (netsh by display name found nothing today, so NOT confirmed) | other campus: Ingest IPs hardcoded in the script
|
||||||
|
- [secret] Lighting PC :: authorized keys: C:\ProgramData\ssh\administrators_authorized_keys + %USERPROFILE%\.ssh\authorized_keys @ Lighting PC: both files hold status-panel@NA-Ingest; ACL Administrators+SYSTEM only (windows-setup.ps1:49-61) | today: windows-setup.ps1 step 2 | Verify: key login works (it does today) | other campus: public key hardcoded in the script
|
||||||
|
- [external-setup] Lighting PC :: Scheduled task \StatusPanel\Start Vista @ Lighting PC: task runs <drive>\Vista3\Vista.exe (G:) as the interactive user, Limited; fix 'vista' = schtasks /Run (Status: Running today; Vista.exe up) | today: windows-setup.ps1 step 3 (app auto-discovered) | Verify: schtasks /Query /TN \StatusPanel\Start Vista; tasklist | findstr Vista.exe. Panel: VISTA hold + auto-fix | other campus: the script finds the apps present on that PC
|
||||||
|
- [external-setup] Lighting PC :: Vista autoload preference (opens Prop Takeover) @ Lighting PC: E:\media\Documents\Vista 3\.preferences <autoloadpath> -> E:\media\Documents\Vista 3\Shows (set by hand 2026-10-08) | today: edited by hand on the PC (not in any script) | Verify: open the .preferences file; starting Vista loads the show. Panel: VISTA fix opens the right show (guideline 6) | other campus: per site show path
|
||||||
|
- [external-setup] Lighting PC :: Scheduled task \StatusPanel\Start Arena (Resolume Arena, web :8080) @ Lighting PC: C:\Program Files\Resolume Arena*\Arena.exe; fix 'arena' then Companion reconnect (Running today) | today: windows-setup.ps1 step 3 | Verify: schtasks /Query /TN \StatusPanel\Start Arena; tasklist | findstr Arena.exe; Companion arena good. Panel: ARENA | other campus: same
|
||||||
|
- [external-setup] Lighting PC :: Scheduled task \StatusPanel\Start rtpMIDI (tray app) + rtpMIDIService (restarts itself on failure) @ Lighting PC: C:\Program Files (x86)\Tobias Erichsen\rtpMIDI\rtpMIDI.exe; service rtpMIDIService sc failure restart/5000 (windows-setup.ps1:112-116); fix rtpmidi_start = net start service, then task; NEVER restart a running rtpMIDI | today: windows-setup.ps1 step 3 | Verify: sc query rtpMIDIService -> RUNNING (yes); tasklist rtpMIDI.exe -> NOT running today (tray app closed; sessions live in the service). Panel: MIDI LIGHT PC MIDI GONE fix | other campus: same
|
||||||
|
- [external-setup] Lighting PC :: Network profile must stay Public (rtpMIDISvc Bonjour firewall rules are Public-only) @ Lighting PC: Windows network category; firewall rules named rtpMIDISvc (26 rules seen today) | today: by hand; README MIDI row (2026-10-08) | Verify: Get-NetConnectionProfile NetworkCategory = Public; dns-sd on NA Ingest shows 'Lighting PC' | other campus: same gotcha on any Windows rtpMIDI PC
|
||||||
|
- [runtime-data] Lighting PC :: Art-Net polls from 10.1.10.206 to UDP 6454 (Vista heartbeat) @ config tiles.vista artnet check source 10.1.10.206 max_silence 5 s | today: Vista sends ArtPoll every 0.5 s by itself | Verify: VISTA key green. Panel: VISTA SILENT | other campus: source IP per site
|
||||||
|
- [external-setup] Lighting PC :: rtpMIDI session peer 'Lighting PC' 10.1.10.206:5004 in NA Ingest / NA Tracks / NA ProP sessions @ config tiles.midi midi_peer checks LIGHT PC, FX LIGHTS, PROP LIGHTS (address 10.1.10.206:5004) | today: rtpMIDI UI on the PC + Audio MIDI Setup on the Macs by hand | Verify: MIDI key clean. Panel: MIDI, hold = midi_reconnect from the Mac side | other campus: peer name/address per site
|
||||||
|
- [external-setup] LiveStream PC :: Host 'livestream' media@10.1.9.126 (LIVESTREAM, Windows) + Wake-on-LAN mac 6c:24:08:d7:fb:57 broadcast 10.1.255.255 @ config hosts.livestream (ssh, mac, wake_broadcast); wake_on_lan status_panel.py:1759 sends UDP 9 magic packet when ssh exits 255 | today: windows-setup.ps1 (sshd, key, WoL adapter) + BIOS WoL by hand | Verify: ssh ... 'echo %COMPUTERNAME%' (ok today); Get-NetAdapterPowerManagement Ethernet WakeOnMagicPacket Enabled (yes today). Panel: OBS (WAKING PC) | other campus: MAC/broadcast per site
|
||||||
|
- [secret] LiveStream PC :: sshd + firewall rule + authorized keys @ LiveStream PC: sshd RUNNING today; OpenSSH-Server-In-TCP rule; administrators_authorized_keys + user authorized_keys | today: windows-setup.ps1 steps 1-2 | Verify: sc query sshd; key login works | other campus: same script
|
||||||
|
- [external-setup] LiveStream PC :: Scheduled task \StatusPanel\Start OBS (RunLevel Highest, --disable-shutdown-check) @ LiveStream PC: D:\Program Files\obs-studio\bin\64bit\obs64.exe ticked 'Run as administrator' so the task runs elevated (windows-setup.ps1:84-86,101); fix 'obs' then Companion reconnect (task Ready, obs64.exe running today) | today: windows-setup.ps1 step 3 | Verify: schtasks /Query /TN \StatusPanel\Start OBS; tasklist obs64.exe; Companion OBS-Youtube good. Panel: OBS | other campus: same
|
||||||
|
- [external-setup] Bible TV PC :: Host 'bibletv' media@10.6.7.13 (SANCTUARYTV, Windows 11, Wi-Fi only, blocks ping) @ config hosts.bibletv.ssh; checked via http://10.6.7.13:8080/ every 5 s | today: windows-setup.ps1 by hand | Verify: ssh ... (ok today); curl -I http://10.6.7.13:8080/. Panel: APPS (BIBLE APP DOWN, BIBLE OSC) | other campus: per site
|
||||||
|
- [secret] Bible TV PC :: sshd + firewall rule + authorized keys @ Bible TV PC: sshd RUNNING today; rule + keys per windows-setup.ps1 | today: windows-setup.ps1 steps 1-2 | Verify: sc query sshd; key login works | other campus: same script
|
||||||
|
- [external-setup] Bible TV PC :: Scheduled task \StatusPanel\Start BibleApp (also AtLogOn + 15 s) @ Bible TV PC: 'Bible App.exe' found by the script; task Running today (4 Bible App.exe processes); fix bibleapp = taskkill then schtasks /Run | today: windows-setup.ps1 step 3 (AtLogon true) | Verify: schtasks /Query /TN \StatusPanel\Start BibleApp; tasklist | findstr Bible. Panel: APPS hold (never auto) | other campus: same
|
||||||
|
- [external-setup] Bible TV PC :: Wi-Fi power settings (power plan wireless = max performance; adapter PnPCapabilities=24) @ Bible TV PC: powercfg + registry (set by hand 2026-10-11; not in any script) | today: by hand | Verify: powercfg /q (wireless adapter settings); Ethernet recommended instead | other campus: only Wi-Fi machines
|
||||||
|
- [external-setup] Devices: video :: ATEM switcher 10.1.10.2 @ Companion 'ATEM' connection + atem_watch.js direct connection; reference atem-reference.xml | today: config atem.host; restore file copied by hand | Verify: ATEM key green; ping 10.1.10.2. Panel: ATEM | other campus: IP + XML per site
|
||||||
|
- [external-setup] Devices: video :: HyperDecks 1-6 (IPs set on the Companion connections; control port 9993) @ Companion HyperDeck_n; watchdog reads var 'ip' and the db; hold = connection off/on + 15 s check; never reboots | today: Companion UI; config tiles hd1..hd6 (optional:true on HD2/HD4) | Verify: nc -z <deck ip> 9993; HD keys green. Panel: HD1-6 | other campus: deck count/optional flags per site
|
||||||
|
- [external-setup] Devices: video :: LED processors VX1000 10.1.14.116, UHDJr 10.1.14.115 @ Companion novastar connections + pings | today: config tiles.led | Verify: ping both. Panel: LED | other campus: per site
|
||||||
|
- [external-setup] Devices: audio/RF :: Tascam DA-6400 10.1.11.74 @ Companion 'tascam' + ping | today: config tiles.tascam | Verify: ping; Companion good. Panel: TASCAM | other campus: per site
|
||||||
|
- [external-setup] Devices: audio/RF :: X32 (STUDIO-A-X32 on Dante too) @ Companion 'x32' (address on the connection) | today: Companion UI | Verify: Companion good. Panel: X32 | other campus: per site
|
||||||
|
- [external-setup] Devices: audio/RF :: Shure AD4Q x2 (10.1.1.101/.102), ULXD4 (.106), PSM1000 x3 (.103-.105) @ Companion Shure connections + 3 s pings | today: config hidden RF tiles | Verify: ping each; RF key green. Panel: RF | other campus: per site
|
||||||
|
- [external-setup] Devices: audio/RF :: SuperRack LiveBox 10.1.13.140:5004 (MIDI peer) / FOH-SuperRack-LiveBox (Dante) @ config midi_peer SUPERRACK + TRK LIVEBOX; Dante map 10.66.1.102 0/9 | today: config | Verify: MIDI key clean; Dante list. Panel: MIDI, DANTE | other campus: per site
|
||||||
|
- [external-setup] Devices: Resi/LED/network :: Resi decoder 10.1.10.203 (RossTalk) and encoder 10.1.10.202 (LAOEncoder web), Resi cloud https://central.resi.io/ @ Companion RESI_Decoder + resi-studio-cues; pings; HEAD http checks every 10 s / 30 s | today: config tiles.resi | Verify: ping .203 .202; curl -I http://10.1.10.202/; curl -I https://central.resi.io/. Panel: RESI | other campus: per site
|
||||||
|
- [external-setup] Devices: Resi/LED/network :: Gateway 10.1.0.1 + NA Ingest link en0 @ config network (gateway, interface, burst_* thresholds, remember_seconds 300) | today: config | Verify: ping 10.1.0.1; ifconfig en0 'status: active'. Panel: NET (BLIP) | other campus: per site
|
||||||
|
- [external-setup] Dante network :: Netgear M4250 switches 10.66.1.100 (8G2XF-PoE+) and 10.66.1.102 (26G4XF-PoE+), telnet CLI port 23, fw 13.0.5.24 @ config dante_switches; netgear.py (Switch class, survey, poe_cycle_device; MAX_SHARED 2) | today: switches managed in NETGEAR Engage; IPs in config | Verify (GUI session on NA Ingest, not over ssh): python3 netgear.py lookup <mac>; python3 netgear.py poe-status 10.66.1.102 0/14. Panel: DANTE hold power-cycle (dante-auto), dante-map | other campus: switch IPs + keychain login per site
|
||||||
|
- [runtime-data] Dante network :: Studio B PoE-powered Dante boxes (MON 0/15, SOLO-MON 0/14, TB 0/16, TB-MON 0/17 on 10.66.1.102) + other 15 hardware devices @ dante-map.json; config dante check fix map ('*': dante-auto) | today: automatic by MAC; no config edit when re-patched | Verify: dante-map.json shows poe:true on those four. Panel: DANTE hold = PoE reset for them, Companion module restart for the rest | other campus: automatic
|
||||||
|
- [secret] Secrets & permissions :: SSH key pair id_status_panel (private on NA Ingest) @ NA Ingest: ~/.ssh/id_status_panel + .pub (config ssh_key); comment status-panel@NA-Ingest; public key HARDCODED in remote-access/mac-authorize.sh:5 and windows-setup.ps1:17 | today: generated 2026-10-08 by hand; never leaves NA Ingest | Verify: ls -l ~/.ssh/id_status_panel; ssh-keygen -lf id_status_panel.pub matches the key in the scripts | other campus: each site Ingest Mac makes its own key; the UI must generate/show the public key and feed it into the setup scripts instead of the hardcoded line
|
||||||
|
- [secret] Secrets & permissions :: known_hosts (accept-new) @ NA Ingest: ~/.ssh/known_hosts (+ .old); ssh_cmd uses StrictHostKeyChecking=accept-new | today: first connection records the host key automatically | Verify: ssh-keygen -F 10.1.14.225 etc. A reinstalled machine with a new host key makes every check fail until the old line is removed | other campus: same
|
||||||
|
- [secret] Secrets & permissions :: Netgear login in NA Ingest login keychain (service netgear-status-panel, account admin) @ NA Ingest: login keychain generic password; read by netgear.credentials() via /usr/bin/security; only readable in the GUI session (LaunchAgent), not over ssh | today: security add-generic-password -s netgear-status-panel -a admin -w, typed by the user in Terminal on NA Ingest | Verify: security find-generic-password -s netgear-status-panel (attributes only; exists today, acct admin); dante-map log line appears. Panel: DANTE PoE, dante-map | other campus: store the item on each site's Ingest Mac; never typed by Claude (guideline 11)
|
||||||
|
- [secret] Secrets & permissions :: sudoers NOPASSWD for the DVS helper (4 Macs) @ NA Ingest, ProPresenter Mac, Tracks Mac, NA Translation: /etc/sudoers.d/status-panel '<user> ALL=(root) NOPASSWD: /usr/local/sbin/status-panel-dvs-start' (visudo-checked) | today: mac-dvs-helper.sh via ARD as root (NA Ingest 2026-10-08 12:38, others 12:05) | Verify: sudo -n -l /usr/local/sbin/status-panel-dvs-start on each (ok on Ingest, ProP, Translation today; Tracks not reachable). Panel: DVS fixes | other campus: same script
|
||||||
|
- [secret] Secrets & permissions :: Accessibility (TCC) for solo-mode-agent on Studio B @ Studio B: Privacy & Security > Accessibility | today: granted by hand on that Mac's screen | Verify: solo-mode.json trusted:true | other campus: per Mac; re-grant after rebuild
|
||||||
|
- [secret] Secrets & permissions :: Automation (TCC) for the watchdog to control Spotify via osascript @ NA Ingest: Privacy & Security > Automation (python3 / launchd context -> Spotify); prompt appears on NA Ingest's screen | today: NOT determined how/when it was granted; Spotify hold works per the user, and poll_spotify logs an error string if osascript is refused | Verify: log has no 'spotify' osascript errors; SPOTIFY key shows PLAYING not '?'. Panel: SPOTIFY | other campus: grant once per Ingest Mac
|
||||||
|
- [secret] Secrets & permissions :: Remote Login (SSH) enabled on each Mac + ssh access group membership @ ProPresenter Mac, Tracks Mac, NA Translation, Studio B, NA Ingest: System Settings > General > Sharing > Remote Login (com.apple.access_ssh group; mac-authorize.sh prints ssh-group) | today: by hand (Tracks turned on 2026-10-08) | Verify: ssh from NA Ingest works; sudo systemsetup -getremotelogin on the Mac | other campus: per Mac
|
||||||
|
- [workflow-step] Secrets & permissions :: Apple Remote Desktop (ARD) used to push root commands to the Macs @ User's admin Mac: ARD > Manage > Send UNIX Command as root (used for mac-authorize.sh and mac-dvs-helper.sh) | today: user pastes the script text by hand | Verify: script prints 'OK user=... mac=...'. The UI can only show the text to paste, never run it | other campus: same
|
||||||
|
- [secret] Secrets & permissions :: Windows: run PowerShell as administrator + Set-ExecutionPolicy RemoteSigned (CurrentUser) @ Lighting, LiveStream, Bible TV PCs (windows-setup.ps1 header steps 2-3) | today: user does it at the PC; system/firewall changes need the user's OK (guideline 12) | Verify: the script's RESULT block (pc, user, sshd, nic mac/ip/network, tasks) | other campus: same
|
||||||
|
- [secret] Secrets & permissions :: MacBook keychain item gitea-mcp (git push credential) and key id_ed25519_checklist_ingest @ MacBook: login keychain; ~/.ssh/ | today: by hand | Verify: git push works; ssh checklist-ingest works | other campus: n/a | (need not be editable)
|
||||||
|
- [config-key] Secrets & permissions :: alert_webhook (empty today) @ config alert_webhook ''; post_webhook status_panel.py:2334 posts JSON {text} when Companion is down >30 s | today: config edit | string URL or ''. Verify: log 'webhook failed' absent | other campus: per site
|
||||||
|
- [workflow-step] Workflow steps :: Deploy the watchdog: scp + install.sh @ MacBook -> NA Ingest: scp status_panel.py coremidi.py netgear.py config.json install.sh atem_watch.js atem-reference.xml checklist-ingest:status-panel-install/; ssh checklist-ingest 'zsh ~/status-panel-install/install.sh' | today: by hand from the MacBook (README 'Changing things'); install.sh validates JSON, backs up config, restarts the agent | Verify: 'installed org.waterschurch.status-panel' echo; sp_hb fresh | other campus: same per site; the UI on the Ingest Mac should replace the scp step
|
||||||
|
- [workflow-step] Workflow steps :: Rebuild + import pages when the layout changes @ python3 make_page.py (imports status_panel.load_config); python3 push_page.py [--which restart] (tRPC WebSocket /trpc, Companion 5.0.7; refuses non-empty foreign pages) | today: by hand; only needed for tile positions/labels/new keys, not for check or fix edits | Verify: tablet view of pages 19/20; page_export name '19\nStatus' | other campus: same
|
||||||
|
- [workflow-step] Workflow steps :: Re-colour nav keys after pages move @ python3 nav_keys.py (--dry-run / --revert); nav_locations.json committed | today: by hand (guideline 9) | Verify: --dry-run reports nothing | other campus: same
|
||||||
|
- [workflow-step] Workflow steps :: Offline tests @ MacBook: python3 -m unittest discover -s tests (tests/test_status_panel.py, fake Companion, MIDI loopback; stubs ssh/fixes) | today: by hand before deploy (guideline 15) | Verify: all tests pass | other campus: same | (need not be editable)
|
||||||
|
- [workflow-step] Workflow steps :: One-shot poll without writing @ NA Ingest or MacBook: python3 status_panel.py --once (and --dry-run) | today: by hand | prints every tile row/col/level/text; useful as the UI's 'test this config' button | other campus: same
|
||||||
|
- [workflow-step] Workflow steps :: New ATEM restore file @ Copy the ATEM Software Control .xml from Google Drive Backup/NA/ATEM over repo atem-reference.xml, run install.sh (watchdog rebuilds atem-reference.json on start) | today: by hand | Verify: ATEM key green after restart | other campus: per site
|
||||||
|
- [workflow-step] Workflow steps :: Companion one-time setup per site @ Companion UI: create sp_state/sp_hb/sp_cmd, keep flash_tick_500 enabled, import pages, import tascam quiet module, name page '19\nStatus' | today: by hand (README 'One-time Companion setup') | Verify: custom-variable endpoints 200; trigger enabled | other campus: repeat per site
|
||||||
|
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
# How the settings-page plan was made and audited
|
||||||
|
|
||||||
|
Four inventory readers went through the repo first, one lens each: operator workflow (97 items), hard-coded site facts in code and scripts (132), config keys and the code that reads them (182), and what runs where (133): 544 items in all. The "what runs where" reader also verified NA Ingest live, read-only, on 2026-10-13 (LaunchAgents, data dir, Companion connections and the flash trigger, keychain item presence, interfaces, the DVS helper, sshd and the StatusPanel tasks on the three PCs).
|
||||||
|
Three designers then wrote independent plans from the same inventory: simplest, operations-first and teach-first.
|
||||||
|
Three judges scored each on completeness, simplicity, safety, multi-site and feasibility. Totals over the three judges: simplest 126, operations 127, teaching 117. Two judges picked operations and one picked simplest, so operations won and the ideas listed under "Judges' reasoning" were grafted onto it.
|
||||||
|
One synthesis produced the plan (second full revision, 844 lines; a partial third revision of sections 10-14).
|
||||||
|
Three audit rounds followed, each with four critics (completeness, simplicity, safety, feasibility), finding 47, 59 and 51 gaps. Each round's fixes were written into the next revision.
|
||||||
|
The loop did not converge: every revision added machinery (apply classes with SIGHUP reload, a queued-job runner, a stop flag, live-signal triples, a full-inventory diff with ignore lists) that the next round found unsafe, unteachable or infeasible, and the gap count stayed flat.
|
||||||
|
So instead of a fourth revision the plan was consolidated against a decisions list dated 2026-10-13: K1-K18 are the mechanisms kept, D1-D10 details pinned after this audit, C1-C13 the deliberate cuts, P0-P6 the phases, Q1-Q4 the questions only the user can answer. Those decisions override the source wherever they differ.
|
||||||
|
The table below judges every one of the 157 findings against that list, not against the revision that first answered it. Round 1 and 2 findings were addressed by the source's own revisions; where the decisions keep that mechanism the row says ADOPTED, where they cut it the row says CUT, and where a later critic replaced the ask the row says SUPERSEDED.
|
||||||
|
|
||||||
|
## Judges' reasoning
|
||||||
|
|
||||||
|
### Simplest (126)
|
||||||
|
|
||||||
|
Strengths
|
||||||
|
|
||||||
|
- The most exhaustive coverage table: every config key, check field, fix-kind field and hard-coded constant is either editable or "shown, not editable" with a reason, and the new keys pull constants out of code (hold time, grid, stale seconds, flash variable, cooldown, Dante max shared, export stamps, settings port).
|
||||||
|
- Machines are data, not code: hosts.<alias>.runs[] with a closed kind list and a probe per kind; the Ingest Mac is a host too, so its interfaces, Satellite and other agents show on the map.
|
||||||
|
- A safe apply order: validate (schema, load_config, make_page dry run), atomic write, restart the watchdog, prove it is up, only then import pages and run nav keys; page imports refused outright during a service; rollback re-runs the page and nav steps.
|
||||||
|
- Least invasive to the watchdog: an AST test checks the code's literal defaults against the schema instead of refactoring them out; a Phase 0 round-trip test proves today's config still builds byte-identical pages.
|
||||||
|
- A password-free edit gate (Unlock editing, typed APPLY, an address allow-list), a "Try it on a copy" sandbox, a generated "Where things are" page, a dedicated Spotify screen, and an import wizard whose cascade drops and lists everything that depended on a dropped computer.
|
||||||
|
|
||||||
|
Weaknesses
|
||||||
|
|
||||||
|
- No automatic rollback: if the watchdog does not come back the page only "offers Rollback", leaving the deck purple until someone clicks.
|
||||||
|
- Page freshness by hashing Companion's live export against the built file cannot work (Companion normalises what it imports); a stored build hash is what works.
|
||||||
|
- Fix templates fill the command box once and the string becomes a copy again, so task names, the playlist and the DVS path can drift.
|
||||||
|
- The draft lives only in the browser tab; "who changed it" is only a browser address; History auto-prunes.
|
||||||
|
- A text-only Overview with no live deck picture; "bridge" refused by name in shared code; 13 screens with Site, Companion and Schedules as grab-bags; NA moved into sites/na in Phase 0 before any UI exists.
|
||||||
|
|
||||||
|
### Operations (127, winner)
|
||||||
|
|
||||||
|
Strengths
|
||||||
|
|
||||||
|
- The fullest per-machine model: runs[] with kinds, watched:false, networks[] per interface, pingable:false for PCs that block ping and self:true for the Ingest Mac, seeded for all eight NA machines, with the per-OS probe commands spelled out, cached, and never waking a PC.
|
||||||
|
- Fix builders store template + params beside the generated run with a regenerate test, which structurally fixes the duplicated Spotify URI, task names and DVS path.
|
||||||
|
- The safest apply pipeline: server-side draft.json, History with a sidecar, atomic replace, automatic rollback on a traceback, typed APPLY during a service, layout re-import refused then, and install.sh never overwriting a page-edited config; pages and nav keys are detected by diffing the built page JSON rather than a hand-kept rule list.
|
||||||
|
- site.live_services separate from auto_fix.windows; a "What this key will say" preview on every key; the Companion screen flags vanished labels with a "rename to" dropdown and offers "Add a key for it"; the Dante screen offers "Watch it".
|
||||||
|
- An editor name per browser for History attribution, "Things only a person can do" as a live checklist, a walkthrough that ticks itself, a cheat sheet with contacts, phases that split editing/apply from layout and fleet setup, and the guideline fit enumerated 1-16.
|
||||||
|
|
||||||
|
Weaknesses
|
||||||
|
|
||||||
|
- Apply imports pages before restarting the watchdog, and rollback restores only the config, so a bad config can leave new pages on the deck over the old settings.
|
||||||
|
- No edit gate beyond a self-declared first name; anyone on the LAN can Apply.
|
||||||
|
- test_defaults_single_source demands ripping some fifty literal defaults out of the 2,500-line watchdog in Phase 0, the most invasive refactor of the three; config.json leaves the repo root in the same phase.
|
||||||
|
- 14 screens with two grab-bags ("Site & times", "Timing & limits" whose global thresholds have no config key); three window tables; deleting a computer is blocked until every reference is removed by hand.
|
||||||
|
- The playlist is only a field inside the SPOTIFY key's check (no screen, no link paste, no search); no sandbox; export stamps and the settings port stay constants; no nav_locations viewer or revert; Restart and Mute not said to respect the guard.
|
||||||
|
|
||||||
|
### Teaching (117)
|
||||||
|
|
||||||
|
Strengths
|
||||||
|
|
||||||
|
- Wizards that map one-to-one onto the user's own examples: Add a MIDI link with live pickers, Add or remove a HyperDeck adjusting the REC members too, Change the playlist by pasting a link, Add a computer, Add a Windows app task, New campus; plus a demo-mode walkthrough.
|
||||||
|
- The live Stream Deck picture on the Overview from sp_state with click-through to the key, a global search box, and a "Stream Deck" (where it sits) versus "Keys" (what it does) split a volunteer gets in one sentence.
|
||||||
|
- The safest Apply order (restart the watchdog, prove it, then pages and nav with a dry run shown), the key guide regenerated on every Apply, History never auto-pruned, and "Undo last apply" on the Overview.
|
||||||
|
- Makes alert_after_seconds and the fix cooldown editable with the key texts following; validates fix kinds in load_config; requires host or conn on ping checks; the settings server imports only load_config, CHECKS and make_page so a page bug cannot take the panel down.
|
||||||
|
- Per-field help with an example and "what you will see on the key"; "done by hand" ticks for things no probe can see; the nav_locations list with a Revert button.
|
||||||
|
|
||||||
|
Weaknesses
|
||||||
|
|
||||||
|
- The per-machine inventory is a code catalogue (machines.py) with NA facts in it, not data: weak on R3 and R5.
|
||||||
|
- The live-service guard is only a typed phrase and is conditioned on ALARMS being on, so muting alarms disables it; page re-imports are allowed during a service.
|
||||||
|
- No edit gate (a PIN is only an open question); drafts live in localStorage.
|
||||||
|
- Phase 4 is a single 1,800-line, 4-5 day big bang; tests are rebuilt from scratch rather than made config-driven; the regex-based schema sync is the weakest of the three.
|
||||||
|
- The Site screen is a grab-bag (ATEM, grid, hold time, cooldown, thresholds, the Windows app table, SSH key, webhook); no Logs screen; no install.sh non-clobber rule; {playlist} substitution has no fallback for restart keys.
|
||||||
|
|
||||||
|
### Ideas the judges asked to graft (and where they landed)
|
||||||
|
|
||||||
|
- From teaching: restart the watchdog and prove it started before pages are rebuilt or imported, and roll pages back too (K10).
|
||||||
|
- From teaching: the job wizards for the named asks (K4, P4) and "New campus from this one" (K17).
|
||||||
|
- From teaching: the live deck on the Overview from sp_state with click-through (K4); the "what this key will say" preview (K5); the key guide rebuilt on every Apply (K15); History never auto-pruned (K14); Undo the last change (K14); "done by hand" ticks (K8 manual_done); alert_after_seconds and the fix cooldown editable with the key texts following (K12); a global search box (D10).
|
||||||
|
- From simplest: the AST-based schema sync test and "defaults in code must equal the schema" instead of removing the defaults from the watchdog (K16); the Phase 0 round-trip test (K16, P0).
|
||||||
|
- From simplest: the password-free edit gate, later reduced from allow-list plus unlock plus typed APPLY to one editing code (K2, C3).
|
||||||
|
- From simplest: the new-campus cascade that drops and lists dependants, sites/_template, and export stamps as config with a mismatch warning (K17, K9).
|
||||||
|
- From simplest: the dedicated Spotify screen with link paste (K9), the generated "Where things are" page (K15, C12), the "Try it on a copy" sandbox (deferred, C4).
|
||||||
|
- From operations, kept as the base: draft.json on the server with the editor's name (K10), pages/nav detected by diffing the built JSON (K10), site.live_services (K9, K12), the runs[] model with watched:false, networks[] and pingable (K8), the Companion screen's rename-to and "Add a key for it" (K9), builders with template + params (K6), "Things only a person can do" (K15).
|
||||||
|
- Reviewer additions: probe whether tRPC can create custom variables and import the flash trigger before declaring those steps by hand (P1 spike); an Ingest self-check that python3 is 3.9-3.12 because telnetlib disappears in 3.13 (K12); explain the Stream Deck "use last page" setting beside the nav settings (K9 Advanced); a generic never_auto flag instead of "bridge" by name (K12); split settings.html into files; a dated folder for previous ATEM files (K14).
|
||||||
|
|
||||||
|
## Audit findings and what happened to them
|
||||||
|
|
||||||
|
Round 1 critics read the first full plan, round 2 the revision that answered them, round 3 the second full revision. Lenses are listed in the order completeness, simplicity, safety, feasibility. K, C, P and Q numbers refer to the decisions list (K = kept, C = cut, P = phase, Q = open question).
|
||||||
|
|
||||||
|
| Round | Lens | Severity | Finding | Disposition | How the plan handles it now |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | Completeness | major | Machine cards only echo what was typed into runs[]; a background task nobody listed stays invisible, so the drift R5 is about is reproduced one level up. | SUPERSEDED | Built as a full-inventory diff with Add/Ignore in revision 2; round 2 Simplicity showed it is vendor noise and it was cut (C5). K8 keeps a collapsed raw "Everything found on this computer" list on the card, so drift is visible on inspection but never counted on Overview. |
|
||||||
|
| 1 | Completeness | major | Checks have no builders: remote, process and pref checks still carry hand-typed app names and helper paths that fixes no longer do. | ADOPTED | K5 "Add a check" asks a question and picks the type; checks.*.{template,params} (K12) regenerate the command; the K16 regenerate test covers checks as well as fixes. |
|
||||||
|
| 1 | Completeness | major | The new-campus walkthrough omits the bootstrap (Command Line Tools, copying the folder), Companion pages existing, the trigger export, the quiet Tascam package, nav templates, surfaces. | SIMPLIFIED | K17 companion-objects/ (trigger export, quiet Tascam module, nav_templates per Companion version) is captured in P1 and deployed by install.sh (K13); K9 Companion lights cover pages exist, our pages and the quiet module; surfaces are not shown (C11). Copying the staged folder to a new Mac stays scp by hand (K13). |
|
||||||
|
| 1 | Completeness | minor | Changing the page number or name is refused by push_page's name rule, or leaves the old page live; no path forward. | SUPERSEDED | Replaced by round 3 Safety's marker-only rule: K10 accepts a page when every non-nav key carries our marker, whatever its name. Old-page cleanup waits on the P1 resetControl spike; until then Review lists the keys to delete by hand. |
|
||||||
|
| 1 | Completeness | minor | "Test this fix now" is offered for every fix, including PoE power-cycle, ATEM restore, dante-auto and pkill fixes. | ADOPTED | K7: Run this fix now only for start/reconnect kinds, never during a live service, always through the watchdog. |
|
||||||
|
| 1 | Completeness | minor | "HOLD 2s" and "WAIT 1 MIN" are hard-coded in the watchdog; the "config loaded" log line Apply waits for does not exist. | ADOPTED | K12 reads page_hold_ms and timing.fix_cooldown_seconds with defaults; started.json with the config sha (K10, K12) replaces the log line. |
|
||||||
|
| 1 | Completeness | minor | site.live_services is new and has no stated default, so the guard is empty on day one. | ADOPTED | K12: it defaults to auto_fix.windows and the P0 migration writes it into NA's config. |
|
||||||
|
| 1 | Completeness | minor | No builders for rtpmidi_start, ableton / ableton_restart or translate_restart; they fall to Custom with free-text names. | ADOPTED | K6 adds Start a Windows service (+task), Reopen Ableton with its last set, and Restart a Mac program with "also stop everything it started"; all 27 fixes map to builders. |
|
||||||
|
| 1 | Completeness | minor | solo-mode-agent.swift is "deployed unchanged" yet its paths are said to come from site.solo_dir. | ADOPTED | P5 generates the swift agent from a template with the Pro Tools parameters; K13 deploys remote-access/*.template. |
|
||||||
|
| 1 | Completeness | minor | Phase 0 removes hyperdeck_probe but TestHyperDeckProbe and the fix-kinds assertion are untouched, so Phase 0 cannot be green. | ADOPTED | K16 asserts CHECKS/FIX_KINDS equality against the exported FIX_KINDS (K12); D8 removes hyperdeck_probe and TestHyperDeckProbe/FakeDeck in P0. |
|
||||||
|
| 1 | Completeness | minor | The settings page and Companion's tablet view cannot link "to each other"; only one direction is possible. | ADOPTED | P0 writes the page address into the ALL key's note; the K15 cheat sheet carries the address. |
|
||||||
|
| 1 | Completeness | minor | The wireframe example "x32 not in Companion, rename to m32" contradicts the inventory. | NOT A GAP | A wireframe illustration only; K9 draws the connection list live from /api/connections and no canned example survives into the plan. |
|
||||||
|
| 1 | Simplicity | major | Engineering vocabulary (ids, hold_always, sp_state, --once, file names, raw shell) sits on nearly every primary surface. | ADOPTED | K15 wording rule and test over ui/strings.json, schema help and the Help pages: no underscored names, flags, extensions, Companion internals or launchd labels; raw strings only under Advanced. |
|
||||||
|
| 1 | Simplicity | major | "What runs where" is not the default view; Computers opens on dense cards. | ADOPTED | K8 Computers opens on the Map: one row per computer with a status word, what it does for the panel and the keys that turn red; click a row for the card. |
|
||||||
|
| 1 | Simplicity | major | Five gating concepts stack (per-tab unlock, editors list, name, typed APPLY, shared draft) plus a sandbox switch on two screens. | SIMPLIFIED | K2 one editing code plus a first name remembered by the browser; K3 one Editing toggle; a typed word only for Emergency and Stop (C3); one shared draft whose 409 names the person (K10); the training copy is deferred (C4). |
|
||||||
|
| 1 | Simplicity | major | Sixteen menu entries in five groups; the wizards for the user's own asks are buried last. | ADOPTED | K3 eight always-visible entries, Site setup and Fixes under a "Set up once" divider; K4 puts the job buttons on Overview. |
|
||||||
|
| 1 | Simplicity | major | Review & Apply exposes the pipeline (check names, a seven-step list, --once, raw diff, typed word). | ADOPTED | K10 Review is a plain-words diff with command edits in red and "what Apply will do"; K14 History rows are "Go back to this". |
|
||||||
|
| 1 | Simplicity | major | The key editor shows every field for every check type at full depth. | ADOPTED | K5 progressive fields with the two previews at the top; per-type raw fields and the 19 types under Advanced. |
|
||||||
|
| 1 | Simplicity | minor | Overview packs twelve blocks and puts Undo last apply on the home screen. | ADOPTED | K4 Overview: problems first, the deck, computers, jobs, status lines; Undo lives in History (K14). |
|
||||||
|
| 1 | Simplicity | minor | Four unexplained status marks on the checklists. | ADOPTED | K8 Setup lights are words: OK / Not done / Can't see from here / Not needed / Done by hand. |
|
||||||
|
| 1 | Simplicity | minor | The Companion screen is a page of internals (export stamps, grid, purple-after, trigger names). | ADOPTED | K9 Companion shows lights and live connections; address, variable names, nav, grid, purple-after and export stamps under Advanced. |
|
||||||
|
| 1 | Simplicity | minor | Three words for the same thing (key/tile/button, machine/computer/host, fix/builder/task); "Alias" is the first field on a card. | ADOPTED | K15 four words only (key, computer, fix, connection), enforced by the wording test. |
|
||||||
|
| 1 | Simplicity | minor | Help has no "start here"; the glossary is alphabetical. | ADOPTED | K15 Help opens on four cards; the glossary is grouped by action (hold fixes it / check the device / ask Chris). |
|
||||||
|
| 1 | Simplicity | minor | The read-aloud test is scheduled only in Phase 5. | ADOPTED | P1 exit criterion: a newcomer explains Overview, Computers and Help back in 20 minutes; D9 adds the same for Review & Apply, the Spotify section and one key editor at the end of P2. |
|
||||||
|
| 1 | Simplicity | minor | Routine code updates still need MacBook, scp, ssh install.sh; no "Update program" path on the page. | SIMPLIFIED | K13 "Install the staged folder" from the page as a one-shot launchd job; the staged folder still arrives by scp (no git credential on Ingest); pull_site.py is replaced by Export site bundle (K17). |
|
||||||
|
| 1 | Simplicity | minor | Schedules offers an editable "Alarms are armed" window; the Mute button does not say what it does. | ADOPTED | K9 one schedule and "alarms are always on" as one sentence everywhere; K4 button "Silence alarms until midnight". |
|
||||||
|
| 1 | Safety | blocker | The settings server is unauthenticated remote code execution on every private range with no CSRF defence; a draft's remote.cmd runs before Apply via --once and the probes. | SIMPLIFIED | K2 one editing code (keychain, lockout, origin check on writes); K6 command edits are red in Review and need the code again; K8 probes run a fixed set with quoted parameters; there is no --once preview. The address allow-list was cut (C3). Residual: one code over plain HTTP on the LAN. |
|
||||||
|
| 1 | Safety | major | Every Apply kickstarts the watchdog with no SIGTERM handler: a fix in flight is killed (HyperDeck left disabled), cooldowns, acks and tries lost, UI-only edits restart too. | ADOPTED | K12 SIGTERM handler lets the fix finish, fixes-running.json, state.json persists acks/tries/cooldowns, the HyperDeck re-enable sits in a finally; K10 waits for no fix and page-only changes skip the restart. The SIGHUP alternative was cut (C1). |
|
||||||
|
| 1 | Safety | major | Apply success and rollback hinge on sp_hb, which needs Companion: spurious rollbacks and a loop with no terminal state. | ADOPTED | K10 success = started.json newer than T with our sha, no traceback, alive after 10 s; rollback once; red notice until the next good Apply. |
|
||||||
|
| 1 | Safety | major | "Test this fix now" runs outside the watchdog, bypassing the recording guard, cooldown and logging; it can PoE-cycle. | ADOPTED | K7 test verb through the watchdog with the recording guard, cooldown and TEST logging; start/reconnect kinds only. |
|
||||||
|
| 1 | Safety | major | One draft.json, last PUT wins, no base version, no Apply lock. | ADOPTED | K10 one shared draft with base_sha and a 409 naming the person and the field; D1 adds a server-side apply.lock so a second Apply is refused outright. |
|
||||||
|
| 1 | Safety | major | Page rules incomplete: name-only safe_target, a renumbered page leaves an orphan, a missing variable is not a hard error. | SIMPLIFIED | K10 marker-only rule replaces the name rule; Companion-dependent checks are soft warnings so Apply still works with Companion down; old-page cleanup waits on the P1 resetControl spike (by hand until then). |
|
||||||
|
| 1 | Safety | major | The live-service guard is clock-only; add recording, stream and Resi signals. | SUPERSEDED | Round 2 Safety and Feasibility showed the stream and Resi signals are always true; K11 keeps the one real signal (a HyperDeck that follows the recording is recording) and C7 defers OBS to the spike. |
|
||||||
|
| 1 | Safety | major | An ATEM file upload replaces the switcher reference during a service; only "XML parses" is checked, no size cap, no entity guard. | ADOPTED | K9 upload or pick-newest with a diff and a tick; K11 refuses Apply while live; K10 writes config and xml together; K14 keeps dated copies; D3 caps uploads at 20 MB and refuses XML with a DOCTYPE or entities. |
|
||||||
|
| 1 | Safety | minor | Builders interpolate names and paths into shell strings without quoting. | ADOPTED | K6 shlex-quoted Mac parameters, the Windows regex, zsh -n / sh -n, byte-identical regenerate test; K16 metacharacter case. |
|
||||||
|
| 1 | Safety | minor | Secrets leak: the webhook in config, draft, history and diffs; module passwords from db.sqlite; fetch_page without includeSecrets=false. | ADOPTED | K2 webhook masked on every readable surface; K14 history bodies masked; K10 re-fetches with includeSecrets=false; C11 module id from /api/connections only, no DB rows. |
|
||||||
|
| 1 | Safety | minor | Page rollback re-runs the failing push; build hashes recorded before verification; "pages out of sync" is not persisted. | ADOPTED | K10 records a build only after a verified import; the K4 "Needs Chris" strip shows pages out of sync and failed-apply restored; red notice until the next good Apply. |
|
||||||
|
| 1 | Safety | minor | The typed word normalises a watchdog restart during a service; kickstart fails if the agent is not loaded. | SUPERSEDED | Its "queue the restart" fix was found unsafe by round 2 and 3 Safety and cut (C2); K11 refuses while live with an Emergency override that logs who and why; K10 bootstraps the plist if it is not loaded. |
|
||||||
|
| 1 | Feasibility | major | Apply success waits for a log line that does not exist and passes on the old process's heartbeat. | ADOPTED | K10 and K12 started.json {pid, started_at, config_sha} newer than T, no traceback, alive 10 s, polled 25 s (refined by round 3 Feasibility's log-rotation point). |
|
||||||
|
| 1 | Feasibility | major | "The whole test suite per site" ignores some sixty NA-specific assertions. | ADOPTED | P0 freezes tests/fixture-config.json; only test_sites_validate runs over every site (K16, K17). |
|
||||||
|
| 1 | Feasibility | major | Running run_once in the server process blocks 10-30 s with CoreMIDI and ssh and must scrape stdout. | SUPERSEDED | The --once preview was dropped after round 2 Safety (TCC prompt) and round 3 Feasibility (no-op flags); K10 validates with load_config on a temp file and make_page in memory only. |
|
||||||
|
| 1 | Feasibility | minor | The schema-sync test misses chained receivers and expression defaults; the glossary scan collects log strings. | ADOPTED | K16 receiver-aware AST walk, defaults compared where literal, the glossary test over fault words only. |
|
||||||
|
| 1 | Feasibility | minor | A restart loses acks, debounce, auto-fix tries and cooldowns; Review does not say so. | ADOPTED | K12 persists acks, tries and cooldowns in state.json keyed by check identity. |
|
||||||
|
| 1 | Feasibility | minor | build-hashes.json has no seed; a page-number change leaves the old page populated. | SUPERSEDED | P3 seeds the hashes from the live exports (round 3 Safety); the old-page reset depends on the P1 resetControl spike (round 2 Feasibility). |
|
||||||
|
| 1 | Feasibility | minor | Phase 0 flips install.sh to non-clobber before the UI exists, so an old-style config deploy silently does nothing. | ADOPTED | K13 and P2: install.sh stops overwriting site-owned files only once the page exists, with a diff notice. |
|
||||||
|
| 1 | Feasibility | minor | The page tools load config from their own folder, settings/ needs sys.path, the module version is not in /api/connections. | SIMPLIFIED | K10 make_page writes into the data dir and K13 deploys the tools there; C11 shows the version only if the spike finds it trivially; pull_site.py is replaced by Export site bundle (K17). |
|
||||||
|
| 1 | Feasibility | minor | Stop watchdog has no Start; the shared Spotify Automation permission is an assumption. | SIMPLIFIED | K11 allows Start while the watchdog is down and K10 bootstraps a missing agent; K9 shows one permission light; the P1 spike checks whether one Automation grant covers both agents. |
|
||||||
|
| 2 | Completeness | major | The live-now signals are hard-coded NA facts (the stream connection, the Resi encoder); add site.live_signals[]. | CUT | C7: the conn/var/value signal list was cut after round 3 Simplicity; K11 keeps the built-in "a HyperDeck is recording", derived from each site's own checks; OBS only if the spike finds the variable. |
|
||||||
|
| 2 | Completeness | major | The builder mapping is wrong for translate (open-only), captions (two agents) and dvs_ingest (chained ProdCom restart). | ADOPTED | K6 splits Start from Restart a Mac program, Restart helpers takes a list, dvs_ingest renders the ProdCom step inline; all 27 regenerate byte-identical. |
|
||||||
|
| 2 | Completeness | minor | "Sunday may run it by itself" bakes NA's day into strings.json. | ADOPTED | K15 wording test forbids day and site names in typed text; K9 builds the one schedule sentence from the windows. |
|
||||||
|
| 2 | Completeness | minor | No key can alarm on "a program is running on a Windows PC". | ADOPTED | K12 Windows remote checks batched per PC; K5 "Is a program running?" picks the type from the computer chosen. |
|
||||||
|
| 2 | Completeness | minor | spotify.name has a field but nothing reads it; labels keep the old playlist name. | ADOPTED | K12 reads spotify.{playlist,name,open_wait,play_wait}; K9 one link field plus name; the K6 Play builder derives the label. |
|
||||||
|
| 2 | Completeness | minor | Add or remove a HyperDeck leaves the kept decks' optional flag untouched. | ADOPTED | P4: the job asks per kept deck "may it have no disk?". |
|
||||||
|
| 2 | Completeness | minor | hosts.ssh is classed R, but the MIDI streams and pollers keep the old address after SIGHUP. | CUT | C1: no partial reload; every Apply restarts the watchdog. |
|
||||||
|
| 2 | Completeness | minor | The edit key written to the login keychain by install.sh over ssh may fail silently. | ADOPTED | K2: the agent makes the code on first start in the GUI session, with a 0600 file fallback. |
|
||||||
|
| 2 | Completeness | minor | The surfaces light has no data source. | CUT | C11: surfaces are not shown. |
|
||||||
|
| 2 | Completeness | minor | A changed SSH host key needs a Terminal step. | ADOPTED | K8 Setup tab: Forget the old login key. |
|
||||||
|
| 2 | Completeness | minor | The nav walk stops at the first 404; a new page never prompts a re-run. | SIMPLIFIED | C6 drops periodic detection; K9 shows "coloured on N of M pages" when the section opens, with Colour them now and Undo; the walk rule is a P1 spike item. |
|
||||||
|
| 2 | Completeness | minor | The ATEM file's origin folder is NA-specific screen text with no key. | ADOPTED | K12 atem.source_folder; K9 "pick newest from folder". |
|
||||||
|
| 2 | Completeness | minor | solo-mode-agent.swift's menu titles and bundle id are literals. | ADOPTED | P5 templates the swift agent with the Pro Tools parameters. |
|
||||||
|
| 2 | Completeness | minor | Add a MIDI link only picks an existing peer; no new-peer entry or "also watch" options. | SIMPLIFIED | P4 adds a typed fallback for session and peer; the extra Bonjour and Companion-connection tick boxes are not in the decisions. |
|
||||||
|
| 2 | Simplicity | major | History (and Undo) is reachable only through the unsaved-changes badge. | ADOPTED | K3 History is always in the menu; K14 Go back and Undo live there. |
|
||||||
|
| 2 | Simplicity | major | Editing needs an IP allow-list, the key and a name; a device on DHCP is refused without explanation. | ADOPTED | K2 code plus name only; no address allow-list (C3). |
|
||||||
|
| 2 | Simplicity | major | Four schedule notions (live, auto-fix's own windows, alarms armed, mute) plus timing on one screen. | ADOPTED | K9 one schedule, alarms always on, timing numbers under Advanced; K4 Silence alarms until midnight. |
|
||||||
|
| 2 | Simplicity | major | Two fix pickers per key; the preview answers only what the key says, not what holding does. | ADOPTED | K5 "HOLDING 2 s WILL" preview, one "When held" section with the builder inline, "also when the key is green". |
|
||||||
|
| 2 | Simplicity | major | Two edit homes each for Dante devices, the playlist, page numbers and HyperDecks. | ADOPTED | K9 the DEVICE LIST is the only home of the DANTE key's devices and Spotify has one link field; K5 the page number lives in the Keys tab header. |
|
||||||
|
| 2 | Simplicity | major | Half the keys point at gear that is not a computer and has no home. | ADOPTED | K8 Gear filter (address, kind, where, watched by, light); K15 "Which computer or device is that" card; C13. |
|
||||||
|
| 2 | Simplicity | major | The full-inventory probe shows vendor noise and counts it on Overview. | CUT | C5: the diff, ignore lists and Add/Ignore were cut; a collapsed raw list on the card (K8); no counts on Overview. |
|
||||||
|
| 2 | Simplicity | major | A change queued during a service is invisible; nothing says it is pending. | CUT | C2: no queue; K11 refuses while live, the draft stays saved and Review says when the service ends. |
|
||||||
|
| 2 | Simplicity | major | "Edit key", "SSH key", "nav keys" and "connection" collide with the three-word rule. | ADOPTED | K15: "editing code", "login key", "page-number buttons"; connection becomes the fourth defined word. |
|
||||||
|
| 2 | Simplicity | major | The settings page needs its own Spotify permission, a Test button and an extra Allow step. | ADOPTED | K9 one permission light; K7 Run now goes through the watchdog so the page never drives Spotify; the P1 spike checks the grant. |
|
||||||
|
| 2 | Simplicity | minor | Map cells are walls of text. | ADOPTED | K4 and K8: one status word per row, what it does, the keys that turn red. |
|
||||||
|
| 2 | Simplicity | minor | Six different "test" buttons with unclear effects. | ADOPTED | K9 read-only buttons say "Check ..." ; K7 "Run this fix now" is the one that acts. |
|
||||||
|
| 2 | Simplicity | minor | Update the program and the campus export sit on the normal Site screen; pull_site.py is a terminal step. | SIMPLIFIED | C12 Install the staged folder and Export site bundle sit under Site setup (set up once); the K17 export replaces pull_site.py; scp remains (K13). |
|
||||||
|
| 2 | Simplicity | minor | Per-editor drafts with take-over and discard add a multi-user merge concept. | ADOPTED | K10 one shared draft; the 409 names who changed what. |
|
||||||
|
| 2 | Simplicity | minor | The Keys grid draws three overlays; "+ New key" offers ALL, ALARMS and NET. | ADOPTED | K5 draws the 8x4 deck; fixed chips for ALL / ALARMS / NET. |
|
||||||
|
| 2 | Simplicity | minor | Overview carries admin wizards, gear, a log tail and duplicate lights. | ADOPTED | K4 problems, the deck, computers, three job buttons plus More jobs, status lines (round 3 Simplicity set the three). |
|
||||||
|
| 2 | Safety | major | kickstart SIGKILLs after launchd's 5 s; the 30 s join exceeds it; check-then-act race on fixes-running.json. | ADOPTED | K12 ExitTimeOut derived at install, join bounded, new fixes refused once SIGTERM arrives (RESTARTING SOON), HyperDeck re-enable in a finally; K10 waits first. |
|
||||||
|
| 2 | Safety | major | The stream and Resi signals are always true; a stale live-now.json is undefined. | ADOPTED | K11 HyperDeck recording only, live-now.json timestamped, older than 30 s = schedule only and said in Review; OBS deferred (C7). |
|
||||||
|
| 2 | Safety | major | Writing config during a service and queuing the restart: KeepAlive can relaunch on the unverified config mid-service. | CUT | C2: no queue; K11 refuses while live so disk and process never diverge. |
|
||||||
|
| 2 | Safety | major | The R class includes keys read only at start (pollers, ServiceMode, run()). | CUT | C1. |
|
||||||
|
| 2 | Safety | major | Code deploys validate only JSON; no code rollback; the page-driven install kills its own agent. | ADOPTED | K13 validates with the new code before bootout, keeps prev/ with --rollback, restarts the watchdog first and settings last, as a one-shot launchd job. |
|
||||||
|
| 2 | Safety | major | Reads (logs, the fleet inventory, history bodies, generated scripts) are open to every private address including Wi-Fi. | SIMPLIFIED | K2 deliberately keeps reads open so teaching needs no code (round 3 Simplicity); K1 narrows the bind to hosts.ingest.networks; the webhook is masked. Residual: the fleet map is visible to anyone on the LAN. |
|
||||||
|
| 2 | Safety | major | `test <fix>` carries no key or check context and could toggle the wrong deck or peer. | ADOPTED | K7 `test <key> <fix> <identity> <time>`, resolved exactly as a hold. |
|
||||||
|
| 2 | Safety | minor | atem-reference.xml is outside the transaction, replaced in place and not restored on rollback. | ADOPTED | K10 config and xml written together (.tmp + os.replace), hash in History, rollback restores both; K14 Go back names the file. |
|
||||||
|
| 2 | Safety | minor | Remote command strings are not syntax-checked; Windows `!` and `^` are allowed. | ADOPTED | K6 sh -n / zsh -n; the Windows regex excludes exclamation, caret, ampersand, pipe, percent and quotes; Custom under Advanced in red. |
|
||||||
|
| 2 | Safety | minor | Two writers for dante-map.json; sp_cmd read-modify-write from the page can clobber a hold. | SIMPLIFIED | K12 resurvey verb through the watchdog; C10 keeps page requests on the command variable like holds. Residual: a request can race a deck hold on sp_cmd. |
|
||||||
|
| 2 | Safety | minor | Hard validation needs Companion up; resetControl to blank a cell is untested. | ADOPTED | K10 soft live warnings; P1 spike "resetControl blanks a cell?" with a by-hand fallback. |
|
||||||
|
| 2 | Safety | minor | Stop, Revert nav, Prune, Re-import and Install have no confirmation; Mute sits under the guard. | ADOPTED | C3 one dialog style stating the consequence, a typed word for Stop and Emergency; K11 Silence alarms is never guarded. |
|
||||||
|
| 2 | Safety | minor | The --once preview triggers a TCC prompt; the ATEM snapshot opens a second client with no guard. | SIMPLIFIED | There is no --once preview in K10; "Check the switcher now" is in K11's guarded list. The ATEM client limit is not noted beside the button. |
|
||||||
|
| 2 | Safety | minor | wmic is gone on Windows 11 24H2; the edit key is made over ssh; Pull needs a stored git credential. | ADOPTED | K2 agent-made code; K13 no git credential and no Pull button; D4 Windows probes use Get-CimInstance and schtasks /Query /FO CSV, never wmic. |
|
||||||
|
| 2 | Safety | minor | The nav walk stops at a gap; hand-moved pages are not detected. | SUPERSEDED | Round 3 Feasibility put the walk rule in the P1 spike; periodic detection was cut (C6) in favour of the K9 count plus Colour them now. |
|
||||||
|
| 2 | Feasibility | major | The R apply class cannot work: start_pollers and Engine capture cfg once. | CUT | C1. |
|
||||||
|
| 2 | Feasibility | major | The 30 s graceful stop exceeds launchd's 5 s exit timeout; fix threads are anonymous daemon threads. | ADOPTED | K12 ExitTimeOut derived at install, a thread registry behind fixes-running.json, join at most ExitTimeOut-5; K16 SIGTERM mid-fix test. |
|
||||||
|
| 2 | Feasibility | major | The live signals are mostly always-true; use the OBS streaming variable. | SIMPLIFIED | K11 HyperDeck recording only; C7 adds OBS as a built-in kind only if the P1 spike finds the variable. |
|
||||||
|
| 2 | Feasibility | major | The test verb with an empty chk raises KeyError for HyperDeck and MIDI fixes. | ADOPTED | K7 verb carries key and identity; K12 identity per check type. |
|
||||||
|
| 2 | Feasibility | major | Phase 2 allows hold-ability edits a phase before pages can be re-imported. | ADOPTED | P2 refuses layout changes until P3 ("available after the layout step"). |
|
||||||
|
| 2 | Feasibility | major | Phase 0 "byte-identical, no re-import" contradicts its note changes; split into 0a/0b. | SIMPLIFIED | K16 roundtrip strips notes and P0 is "deck identical apart from key notes"; P0 stays one phase of about 1.5 days (no 0a/0b split). |
|
||||||
|
| 2 | Feasibility | minor | The marker rule misses the two static jump keys. | ADOPTED | K10 every non-nav key must carry our marker; P0 adds the markers in make_page. |
|
||||||
|
| 2 | Feasibility | minor | /api/program/install kills the agent that is running it. | ADOPTED | K13 one-shot launchd job, settings agent last, AbandonProcessGroup. |
|
||||||
|
| 2 | Feasibility | minor | "Pull from repo" does not fit the real setup (no clone, the alias points the other way). | ADOPTED | K13 drops Pull; scp staging plus Install the staged folder. |
|
||||||
|
| 2 | Feasibility | minor | install.sh cannot write the keychain over ssh. | ADOPTED | K2. |
|
||||||
|
| 2 | Feasibility | minor | A receiver-less AST walk will never pass against this code. | ADOPTED | K16 receiver-aware walk with a UI-only allowlist. |
|
||||||
|
| 2 | Feasibility | minor | moduleVersionId, resetControl and tRPC variable creation are unverified Companion facts. | ADOPTED | P1 spike with recorded answers and fallbacks; C11 version only if trivial. |
|
||||||
|
| 2 | Feasibility | minor | Acks persisted by check index re-attach to the wrong check after an edit. | SUPERSEDED | Round 3 Feasibility showed type-plus-sub keys collide too; K12 keys state by a per-type identity and drops it for keys whose checks changed. |
|
||||||
|
| 2 | Feasibility | minor | Two Automation grants are assumed; one very likely covers both agents. | ADOPTED | P1 spike; K9 one light. |
|
||||||
|
| 3 | Completeness | major | The server binds only hosts.ingest.networks; the template has no ingest record; an empty list is undefined. | ADOPTED | K1 always 127.0.0.1 plus every private-address interface when the list is empty (with a grey notice); K17 the template has an ingest self record with networks []. |
|
||||||
|
| 3 | Completeness | major | install.sh's deploy list omits the remote-access templates and companion-objects. | ADOPTED | K13 deploys both; K17 nav_templates/<version>.json. |
|
||||||
|
| 3 | Completeness | major | Windows batching with %ERRORLEVEL% expands before the command runs, so every check reports OK. | ADOPTED | K12 generates per-command conditional echo pairs (SPn=0 on success, SPn=1 otherwise), never a bare %ERRORLEVEL%, verified once on the Lighting PC. |
|
||||||
|
| 3 | Completeness | minor | No expected module per check type; a HyperDeck check on a generic TCP connection is silently wrong. | ADOPTED | K12 EXPECTED_MODULE filters the pickers and warns on mismatch; it lives in the K16 registry. |
|
||||||
|
| 3 | Completeness | minor | restart_page is optional but the Keys screen always has the tab and chips. | ADOPTED | K9 "this campus has no Restart page" switch (built in P3). |
|
||||||
|
| 3 | Completeness | minor | Windows cannot cross midnight. | ADOPTED | K18. |
|
||||||
|
| 3 | Completeness | minor | Add a MIDI link has no typed fallback when the Mac is off. | ADOPTED | P4. |
|
||||||
|
| 3 | Completeness | minor | The Ingest self record can be named by ssh fixes, remote checks and midi_peer (a runtime KeyError). | ADOPTED | K12 host validated (exists, has ssh, not self); builders switch to local for the self record. |
|
||||||
|
| 3 | Completeness | minor | The OBS streaming variable is seeded without verification. | ADOPTED | P1 spike reads it on and off air; C7 adds the kind only then. |
|
||||||
|
| 3 | Simplicity | major | About 25 destinations; Help doubles as the admin console. | SIMPLIFIED | K3 eight entries, the two set-up-once screens under a divider and readable by anyone (one rule: the code changes things); C12 admin actions sit there, Help keeps the recipes. Residual: a volunteer can open Site setup and see it. |
|
||||||
|
| 3 | Simplicity | major | Schedules asks for a Companion connection, variable and value to define a live signal. | CUT | C7. |
|
||||||
|
| 3 | Simplicity | major | "+ Add a check" lists 19 implementation types. | ADOPTED | K5 asks a question first; the 19 types sit under Advanced. |
|
||||||
|
| 3 | Simplicity | major | The walkthrough needs the editing code (Logs, History, the training copy). | ADOPTED | K2 reads are open; K15 the walkthrough needs no code; C4 defers the training copy. |
|
||||||
|
| 3 | Simplicity | major | The Overview problem line mixes room problems with admin notices. | ADOPTED | K4 room problems versus a grey "Needs Chris" strip. |
|
||||||
|
| 3 | Simplicity | minor | Three status vocabularies across Overview, Setup and Gear. | ADOPTED | D7 one legend for the whole page (OK / Problem + reason / Can't see from here / Not needed / Done by hand), printed once under the top bar's "?". |
|
||||||
|
| 3 | Simplicity | minor | The plan's own wireframes contain forbidden tokens; extend the wording test to the rendered DOM. | SIMPLIFIED | C8 keeps the test over strings, help and Help pages; K5 shows "Page 19" and chips. Residual: live Companion labels can appear raw in table cells. |
|
||||||
|
| 3 | Simplicity | minor | "Alarms are always on" next to a Pause button. | ADOPTED | K9 one sentence everywhere; K4 "Silence alarms until midnight". |
|
||||||
|
| 3 | Simplicity | minor | The page is "Status Panel Settings" and one menu entry is "Settings". | ADOPTED | K3 "Site setup". |
|
||||||
|
| 3 | Simplicity | minor | The Fixes catalogue is a second mental model beside "When held". | ADOPTED | K5 the builder sits inline under "When held"; K3 the Fixes catalogue sits under the "Set up once" divider. |
|
||||||
|
| 3 | Simplicity | minor | The Map lacks "keys that turn red"; Gear is a separate tab. | ADOPTED | K8 Map rows carry the keys that turn red; Gear is a filter (C13). |
|
||||||
|
| 3 | Simplicity | minor | ssh-keygen for the panel's own key is still a Terminal step. | ADOPTED | K8 Setup tab: Make the login key. |
|
||||||
|
| 3 | Simplicity | minor | Tuning numbers on the main screens. | ADOPTED | K9 every number under Advanced with its default beside it. |
|
||||||
|
| 3 | Simplicity | minor | Add a MIDI link is hidden under More jobs. | ADOPTED | K4 three job buttons. |
|
||||||
|
| 3 | Simplicity | minor | The "Not on the deck" tray is unexplained. | ADOPTED | K5 tray labelled "Shown inside the RF group (no key of their own)". |
|
||||||
|
| 3 | Simplicity | minor | Watchdog controls in two places, two magic words, second confirmations. | ADOPTED | C3 one confirmation style; D6 Start / Stop / Restart the watchdog live on Overview only, Stop behind the typed word. |
|
||||||
|
| 3 | Safety | blocker | No loopback listener, so /edit-key is unreachable and nobody can ever switch Editing on. | ADOPTED | K1 binds 127.0.0.1 always; K2 serves /edit-key only there; K16 loopback-only test. |
|
||||||
|
| 3 | Safety | blocker | setsid does not exist on macOS; the installer dies with the agent's process group half-way. | ADOPTED | K13 one-shot launchd job, AbandonProcessGroup on the settings plist, the watchdog bootstrapped first. |
|
||||||
|
| 3 | Safety | major | safe_target's name shortcut silently wipes hand-added keys. | ADOPTED | K10 marker-only rule; Review lists foreign keys by position; Apply refuses. |
|
||||||
|
| 3 | Safety | major | Raw commands behind one plaintext reusable code; no lockout; the allow-list off by default. | SIMPLIFIED | K2 lockout (5 tries, 10 min) and origin check; K6 Custom commands are red and re-ask the code. Admin code, RUN word and allow-list were cut (C3). Residual: one code on a LAN is a remote-command surface. |
|
||||||
|
| 3 | Safety | major | Probes interpolate names and paths from config into ssh lines. | ADOPTED | K8 quoted and regex-checked parameters; K16 metacharacter test proving the ssh line is inert. |
|
||||||
|
| 3 | Safety | major | A stale stop flag disables every fix forever. | CUT | C10: no stop flag; the SIGTERM handler plus the fixes-running.json wait cover it. |
|
||||||
|
| 3 | Safety | major | SIGHUP sent to old code kills the watchdog. | CUT | C1; K12 keeps the "(code X)" log line for install and rollback tracking. |
|
||||||
|
| 3 | Safety | major | The queued job has no hysteresis and no base_sha recheck; Start should be exempt from Emergency. | CUT | C2; K11 allows Start while the watchdog is down. |
|
||||||
|
| 3 | Safety | minor | A 409 merges silently. | ADOPTED | K10 "<name> changed this a moment ago - your change to <field> was not kept", with the field re-offered. |
|
||||||
|
| 3 | Safety | minor | history, draft and pending copies hold the unmasked webhook. | ADOPTED | D2 every file the settings agent writes in the data dir (draft, history, cache, log, code fallback) is 0600; K14 serves bodies masked; pending.json is gone (C2). |
|
||||||
|
| 3 | Safety | minor | Seeded build hashes are "assumed current". | ADOPTED | P3 seeds them from the live exports. |
|
||||||
|
| 3 | Safety | minor | A PC whose DefaultShell is not cmd returns garbage silently. | ADOPTED | K12 light plus refusal of cmd-batched checks for that PC (setting DefaultShell stays a by-hand step). |
|
||||||
|
| 3 | Safety | minor | Holds queued during the restart gap fire on the new process. | ADOPTED | K12 sp_cmd cleared once at start and logged. |
|
||||||
|
| 3 | Feasibility | major | cmd.exe %ERRORLEVEL% batching always reports OK. | ADOPTED | K12 (the same fix as round 3 Completeness). |
|
||||||
|
| 3 | Feasibility | major | A (key, type, sub) identity collides on the MIDI key; the test verb cannot pick a peer. | ADOPTED | K12 per-type identity (midi_peer = host, session, peer; remote = host, cmd; ...); K7 the verb carries it. |
|
||||||
|
| 3 | Feasibility | major | test_apply_class contradicts the R rows. | CUT | C1. |
|
||||||
|
| 3 | Feasibility | major | The LAN-only bind leaves /edit-key unreachable; interface names cannot be bound, only addresses. | ADOPTED | K1 and D5: bind by address (127.0.0.1 always, the only listener serving /edit-key, plus each LAN interface's IPv4), re-checked every minute and rebound on change. |
|
||||||
|
| 3 | Feasibility | major | install.sh copies atem-reference.xml (and nav_locations, built pages) over the data dir. | ADOPTED | K13 never overwrites site-owned files without --config, --site or --force-site-files, with a diff notice. |
|
||||||
|
| 3 | Feasibility | major | Phase 0b and Phase 2 bundle too much to test as single steps. | SIMPLIFIED | C1, C2, C4, C7 and C10 remove most of the load; P0 and P2 stay single phases (about 1.5 and 4 days). |
|
||||||
|
| 3 | Feasibility | minor | --skip-spotify is a no-op; the Preview shows unjudged keys green. | SIMPLIFIED | There is no preview run in K10, so there are no flags to get wrong. |
|
||||||
|
| 3 | Feasibility | minor | A `then` chain breaks byte-identical regeneration for dvs_ingest; restart_ok is misstated. | ADOPTED | C9 no chain executor; K6 renders the second step inline; byte-identical for all 27. |
|
||||||
|
| 3 | Feasibility | minor | ServiceMode._save clobbers persisted acks; live-now.json written every tick. | ADOPTED | K12 one atomic state.json writer that ServiceMode merges into; K11 live-now on change or every 5 s. |
|
||||||
|
| 3 | Feasibility | minor | ExitTimeOut 40 s is shorter than fixes legitimately run. | ADOPTED | K12 derived at install (max fix timeout + verify + 10, floor 60); re-enable in a finally. |
|
||||||
|
| 3 | Feasibility | minor | The page_number_N_name walk is unproven. | ADOPTED | P1 spike item; C6. |
|
||||||
|
| 3 | Feasibility | minor | The self record is referenceable by ssh fixes. | ADOPTED | K12 (the same fix as round 3 Completeness). |
|
||||||
|
| 3 | Feasibility | minor | Tailing a rotating log can miss the lines and trigger a false rollback. | ADOPTED | K10 and K12 started.json; the log is read only for tracebacks. |
|
||||||
|
|
||||||
|
Disposition totals over the 157 findings: ADOPTED 115, SIMPLIFIED 20, CUT 13, SUPERSEDED 8, NOT A GAP 1.
|
||||||
|
|
||||||
|
## Deliberate cuts and why
|
||||||
|
|
||||||
|
C1, partial reload. The watchdog will not re-read parts of its settings on a signal; every Apply restarts it, so the deck goes purple for about five seconds, outside services. What was lost is the comfort of changing a timing number without a restart. It is acceptable because the reload could never rebuild the pollers and the service mode (round 2 Feasibility), a signal sent to old code kills the process, and a whole family of apply-class tests disappears.
|
||||||
|
|
||||||
|
C2, queued apply. A change made during a live service is refused rather than saved for later; the draft stays and a person comes back to press Apply. What was lost is "it will go live by itself at 13:30". It is acceptable because the queue meant the file on disk disagreeing with the running process for hours, a job restarting the watchdog unattended in the gap between two services, and a pending state nobody could see; the Emergency override covers a real need.
|
||||||
|
|
||||||
|
C3, extra gates. There is no admin code, no address allow-list, no RUN re-typing and no second confirmations; one code, one dialog style, and a typed word only for Emergency during a service and for stopping the watchdog. What was lost is defence in depth around command edits. It is acceptable because an IP list is invisible to the person it blocks and one code is what volunteers can actually hold; raw command edits still turn red in Review and ask for the code again.
|
||||||
|
|
||||||
|
C4, training copy. The sandbox where a trainee could press Apply against a copy is deferred. What was lost is a risk-free place to practise editing. It is acceptable because reads are open to the LAN, so the whole walkthrough works with Editing off, and the copy can be added in a day once the page is driven by the config.
|
||||||
|
|
||||||
|
C5, full-inventory diff. Computers are not scanned for every agent, login item, port and service and diffed against the list, and there are no vendor ignore lists or Add/Ignore buttons. What was lost is an automatic "something new appeared on this computer". It is acceptable because on a real Mac or PC the list is Google, Adobe and Microsoft noise; the raw "Everything found on this computer" list on the card still shows it all when somebody looks.
|
||||||
|
|
||||||
|
C6, page-button probe. Nothing polls Companion to notice pages that were moved or added by hand. What was lost is an Overview line that appears by itself. It is acceptable because the count "coloured on N of M pages" when the Companion section opens, with Colour them now and Undo, covers the step the user keeps forgetting, and the background walk rule it would need is unproven.
|
||||||
|
|
||||||
|
C7, live-signal triples. The Schedules screen does not let anyone define "live" as a Companion connection, variable and value. What was lost is OBS or Resi counting as live. It is acceptable because only "a HyperDeck is recording" was ever a real signal, the triple was Companion jargon on a volunteer screen, and OBS can be added as a built-in kind once the spike finds its variable.
|
||||||
|
|
||||||
|
C8, DOM wording test. The wording test runs over the typed strings, schema help and Help pages, not over a rendered page. What was lost is catching jargon in table cells built from live data. It is acceptable because typed text is where the words live; a Companion label is a name, not a word; and a headless render test was heavy for the gain.
|
||||||
|
|
||||||
|
C9, chain executor. The watchdog gets no "then also run this fix" step. What was lost is a generic second-step mechanism. It is acceptable because only dvs_ingest needs it, the builder renders the ProdCom step inline, regeneration stays byte-identical and no new code path exists in the watchdog.
|
||||||
|
|
||||||
|
C10, stop flag and command folder. There is no stop.json and no commands/ folder for the watchdog to poll. What was lost is a file-based command channel from the page. It is acceptable because a stale flag could silently disable every fix; the SIGTERM handler and the fixes-running wait cover restarts, and page requests travel on the same command variable holds already use.
|
||||||
|
|
||||||
|
C11, surfaces and module versions. The page shows no surfaces table, no Satellite labels and no module version read from Companion's database. What was lost is "which Stream Decks are plugged in" and a version column. It is acceptable because the module id from /api/connections is what the expected-module check needs, the database read would expose module passwords, and a version appears only if the spike finds it trivially.
|
||||||
|
|
||||||
|
C12, admin pages. "Where things are" stays as one generated page, and "Things only Chris does" is folded into Site setup under the "Set up once" divider rather than living in Help. What was lost is a separate admin section in Help. It is acceptable because Help stays read-only and the admin actions sit in one labelled place that only the editing code can act on, which is the volunteer/admin split round 3 asked for.
|
||||||
|
|
||||||
|
C13, gear as a filter. Gear has no data model of its own beyond location and notes; it is a filter of the Computers list derived from the checks. What was lost is a separate gear register. It is acceptable because every device is already described by the check that watches it, and one list is one thing to teach.
|
||||||
|
|
||||||
|
C14, preview run. Review has no "Preview the deck with these settings" button that runs the watchdog once against the draft. What was lost is a dry run of the checks before Apply. It is acceptable because such a run could not judge the ATEM, Dante-map, Spotify and remote checks and would have shown those keys green (round 3 Feasibility), it would have run CoreMIDI and ssh from the settings process, and the key editor's "WHAT THIS KEY WILL SAY" line plus the in-memory page build at validate already answer what the key will show.
|
||||||
|
|
||||||
|
## Residual risks
|
||||||
|
|
||||||
|
- Every Apply restarts the watchdog (C1): about five seconds of purple deck, and holds pressed in that gap are dropped. Mitigation: Apply is refused during live services (K11), page-only changes skip the restart (K10), Review says it will happen, the SIGTERM handler lets a running fix finish (K12) and sp_cmd is cleared and logged at start.
|
||||||
|
- No queue (C2): a change wanted during a service waits for a person to come back and press Apply. Mitigation: the draft is saved, Review says when the service ends, the Emergency override (typed LIVE plus a reason, logged) exists, and Q2 pins the windows.
|
||||||
|
- Raw Custom commands (K6) are a remote-command surface on seven computers, guarded by one code over plain HTTP on a LAN. Mitigation: LAN-only binding narrowed by hosts.ingest.networks (K1), origin check and lockout (K2), the code asked again and the command shown red in Review (K6), zsh -n / sh -n, History naming the editor (K14), and Q1 limiting who holds the code.
|
||||||
|
- Probes are parameterised, not fixed (K8): names and paths from the config reach ssh lines. Mitigation: shlex-quoted and regex-checked parameters, a fixed command set, the metacharacter test (K16), never run during a fix, 6 s timeout, never wake a PC.
|
||||||
|
- Windows DefaultShell (K12): the batched checks assume cmd.exe. Mitigation: a light on the PC's card and refusal of cmd-batched checks when the shell is not cmd; verified once on the Lighting PC; setting DefaultShell stays a by-hand system change for the user.
|
||||||
|
- Python 3.13 removes telnetlib, which netgear.py uses (K12 self-check for 3.9-3.12). Mitigation: the self-check light; a newer macOS at another campus would lose the Dante map and PoE cycling until telnetlib is replaced, which is not in this plan.
|
||||||
|
- Companion tRPC spike unknowns (P1): custom-variable creation, trigger import, resetControl blanking a cell, the OBS variable, the page walk, whether one Automation grant covers both agents. Mitigation: recorded answers before any Apply step depends on them, with fallbacks (by hand, "unknown", schedule-only).
|
||||||
|
- Reads are open on the LAN (K2): logs, history bodies and the fleet map are visible to anyone who joins the network. Mitigation: the webhook is masked everywhere, no secret is in History, the bind is narrowed to the named LAN interfaces (K1).
|
||||||
|
- The only live signal is a recording HyperDeck (K11, C7), and a stale live-now.json falls back to the schedule. Mitigation: Review says "schedule only" when stale, Q2 fills the windows, OBS follows the spike.
|
||||||
|
- Page requests share sp_cmd with deck holds (C10): a test or resurvey request can race a hold. Mitigation: it is the same read-then-clear path holds use today, Run now is rare and never live (K7), and the watchdog logs what it drops.
|
||||||
|
- The settings server binds addresses, not interface names (K1, D5): a DHCP change on the LAN port is picked up within a minute by the rebind check. Mitigation: the Ingest addresses are static today and a grey notice shows when the LAN list is empty.
|
||||||
|
|
||||||
|
## Still open (needs the user)
|
||||||
|
|
||||||
|
- Q1 Who gets the editing code besides you?
|
||||||
|
- Q2 Live services at NA: only Sunday 06:30-13:30, or also midweek services or rehearsals that changes must wait for?
|
||||||
|
- Q3 The other campuses: their names, and does each have a Mac that runs Companion (where the page and the watchdog would live)?
|
||||||
|
- Q4 Unwatched Companion connections at NA (Govee x7, DiGiCo_OSC, m32, AbleSet, tcp-HyperDeck-1..6): leave them?
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
# Status Panel settings page — engineering details (appendix to settings-plan.md)
|
||||||
|
|
||||||
|
This is the reference for building the settings page; the plain-words plan is `settings-plan.md`. Every item here follows the decisions list of 2026-10-13: it keeps K1-K18 and leaves out everything C1-C13 cut (no SIGHUP partial reload or apply classes, no queued or pending jobs, no admin code or address allow-list, no training copy, no vendor ignore lists or Add/Ignore bookkeeping, no page-set probe, no live-signal triples, no wording test over the rendered DOM, no `then` chain executor, no stop flag or commands folder, no surfaces table). Where the second full revision of the plan and the final revision's sections 10-14 differed, the final revision won. It keeps the ATEM saved-file handling, the page markers, the rollback, the identity-keyed state, the Windows echo pairs, `EXPECTED_MODULE` and overnight live-service windows.
|
||||||
|
|
||||||
|
Conventions used below:
|
||||||
|
|
||||||
|
- **Apply words** (what Apply does for a changed value): **restart** = the watchdog is restarted (Apply always restarts it, about 5 s of purple, outside services); **pages** = the Status or Restart page is rebuilt and re-imported because the built page JSON, with the keys' notes stripped, differs from the last verified build (never a hand-kept list; a changed note alone never forces a re-import, D11), and the page-number buttons are re-run after any page change; **none** = page-only data (notes, gear notes): no restart, no import; **settings** = the settings agent restarts.
|
||||||
|
- **Screens**: always Overview, Keys, Computers (Map, card, Gear filter, Setup tab), Logs, History, Help; then under a divider "Set up once": Site setup (Site . Schedules . Companion . Network & Dante . Spotify . ATEM) and Fixes; all eight are always visible and readable by anyone, and the editing code is what lets you change things (K3). The top bar holds the site name, the search box (finds any key, fix, computer, device or setting by name), the unsaved-changes badge, the Editing toggle and a "?".
|
||||||
|
- **Status words**: one legend for the whole page, printed once under the top bar's "?": OK / Problem (+ one-line reason) / Can't see from here - ask at the computer / Not needed / Done by hand (who, when). Every light anywhere on the page uses these words.
|
||||||
|
- **NEW** = a config key added by this plan (its default keeps today's behaviour). **DNE** = deliberately not editable (how the panel works, not what it watches; guideline 1), documented in Help > Glossary / Where things are.
|
||||||
|
- Paths: the data dir is `~/Library/Application Support/status-panel/`; the watchdog agent is `org.waterschurch.status-panel`, the settings agent `org.waterschurch.status-settings`.
|
||||||
|
|
||||||
|
## A. Everything you can change
|
||||||
|
|
||||||
|
Values are typed once: every string that names a program, path, task or helper lives in one owner (`hosts.<alias>.runs`, `site.*`, `spotify.name`) and checks, fixes and scripts are regenerated from it. One owner screen per value; every other place shows it read-only with a link.
|
||||||
|
|
||||||
|
| Thing | Screen | Field(s) | Plain-words help | Validation / apply |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `site.id` NEW | Site setup > Site (Advanced) | Site id | Short name for folders and files ("na"). | `[a-z0-9_-]+`; restart |
|
||||||
|
| `site.name`, `site.ingest_name`, `site.ingest_hostname`, `site.ingest_ips[]` NEW | Site setup > Site | Site name, this Mac's name, network name, addresses | Used in log headers, alert text, key notes and generated setup scripts (replaces "NA Ingest" and the IPs in windows-setup.ps1). [Check this Mac's network ports] fills the addresses. | non-empty; IPs valid; restart (notes: pages) |
|
||||||
|
| `site.settings_port` NEW | Site setup > Site (Advanced) | Port | Where this page listens: loopback plus every private-address port, narrowed by this Mac's LAN list. Default 8020. | 1024-65535; the dialog states the consequence (the page moves to the new address); settings |
|
||||||
|
| editing code (keychain item `status-settings-edit-key`, 0600 file fallback `edit-key` in the data dir) NEW | Site setup > Site (Advanced) | "editing code: set <date>", [Make a new editing code] | Random word-list code the settings agent makes on its first start in the logged-in session; shown only on this Mac's screen at `http://127.0.0.1:8020/edit-key`; browsers remember it with the person's first name. The one code: no admin code, no address list. | never shown over the LAN; the dialog says every browser must enter the new code; settings |
|
||||||
|
| `site.live_services[]` NEW | Site setup > Schedules | Live services | When a service is on: Apply, Run now, Restart the watchdog, Re-import, Check the switcher and Install are refused and your draft stays saved; auto-fix may run; alarms are always on. Defaults to `auto_fix.windows`; the Phase 0 migration writes it. | days in mon..sun; `HH:MM`; end <= start = runs past midnight (allowed); warn if empty; restart (ServiceMode is built at start) |
|
||||||
|
| built-in live signal: a watched HyperDeck that "follows the recording" is recording | Site setup > Schedules ("Also counts as live", read-only line with "Live now?") | - | Derived from every hyperdeck check with "follows the recording"; the watchdog writes `live-now.json` on change / every 5 s with a timestamp; older than 30 s = schedule only (said in Review). No connection/variable/value triple on the page; "OBS is streaming" is added as a built-in kind only if the Phase 1 spike finds the module variable. | DNE; warn when no watched deck follows the recording |
|
||||||
|
| `site.contacts` NEW | Site setup > Site | Contacts | Printed on the cheat sheet. | free text; none |
|
||||||
|
| `site.dvs_helper`, `site.solo_dir` NEW | Site setup > Site (Advanced) | DVS helper path, Studio B helper folder | One owner for paths that fixes, checks and scripts repeat; builders and templates regenerate from them. | absolute path; restart |
|
||||||
|
| `alert_webhook`, `site.alert_prefix` NEW, `timing.alert_after_seconds` NEW | Site setup > Site / Schedules (Advanced) | Alert message (web hook), message starts with, alert after | Posts "<prefix>: Companion has not answered for N s" and the all-clear; also a line for every command edit applied. Blank = off. Shown masked everywhere ("set, 44 chars"); [Change] [Send a test alert]. | http(s) or blank; 5-600; restart (captured in run()) |
|
||||||
|
| `ssh_key` | Site setup > Site (Advanced) | Login key | Private key on this Mac for every other computer; light + public half with [Copy]. [Make the login key] on the Setup tab makes the panel's own pair at a new campus (asks first). | file exists, 0600; `.pub` readable; restart |
|
||||||
|
| `companion.host/port/db` | Site setup > Companion (Advanced) | Address, port, settings database | Where Companion answers. The DB is read read-only for labels and addresses (module version only if the Phase 1 spike finds it trivially); blank = newest `vN.N/db.sqlite`. push_page and nav_keys read these too. | host non-empty; 1-65535; path exists; live probe; restart |
|
||||||
|
| `variables.state/heartbeat/command`, `variables.flash` NEW | Site setup > Companion (Advanced) | Variable names (state `sp_state`, heartbeat `sp_hb`, command `sp_cmd`, blink `flash_tick`) | Must exist in Companion: a hard error when Companion answers, a warning ("could not check") when it does not. Renaming the blink variable means renaming it inside the trigger in Companion too (said beside the field). | `[A-Za-z0-9_]+`; exists; pages + restart |
|
||||||
|
| `page`, `page_name`; `restart_page.page`, `restart_page.page_name` | Keys tab header (only home; the Companion section links here) | Page numbers and names | Name derived "<n>\nStatus" / "<n>\nRestart" unless overridden. Number change: the old page keeps its keys; Apply resets our marked keys on it (asks; if the spike shows `controls.resetControl` cannot blank a cell, Review lists "delete these N keys on page 19 by hand") and re-runs the page-number buttons. Name-only change on an existing page: rename it in Companion first (live name shown). "Pages exist" light. | int >= 1; distinct; name contains the number; live checks; pages |
|
||||||
|
| `nav_keys[]`, `nav_hold_ms`, `nav_tap_page` | Site setup > Companion (Advanced) | Page-number buttons that follow ALL, hold ms, tap page | Which of up / page number / down are coloured; a tap goes to page N; a hold jumps to the Status page. "Use last page" surface note shown. | subset of up/pagenum/down; 200-5000; page exists; pages |
|
||||||
|
| `page_hold_ms` NEW (today's HOLD_MS) | Site setup > Schedules (Advanced, Timing) | Holding a key takes | Guideline: 2 s. The watchdog reads it for the "HOLD 2s" text. | 500-5000; pages + restart |
|
||||||
|
| `page_grid` NEW (GRID) | Site setup > Companion (Advanced) | Grid | Export grid for the surface (XL 8x4 inside 0-8 x 0-5); the Keys screen draws the 8x4 by default, "Show more" shows the whole grid. | ints; every position inside; pages |
|
||||||
|
| `page_stale_seconds` NEW (STALE_SECONDS) | Site setup > Companion (Advanced) | Purple after | Keys turn purple when the watchdog is silent this long. | 5-120; pages |
|
||||||
|
| `companion.export_version`, `companion.build` NEW | Site setup > Companion (Advanced) | Export stamps | What make_page writes into page files and which nav template it picks (`companion-objects/nav_templates/<version>.json`); warning when Companion's live version differs; "Recapture the nav templates" recipe linked. Change only after a Companion upgrade. | int; string; pages |
|
||||||
|
| `service_windows[]` + window shape | not on the page (Help: deliberately not editable) | Alarms are armed | Always every day all day (guideline 1). A narrowed value in an old file loads with a Review warning. | days in mon..sun; `HH:MM`; restart |
|
||||||
|
| `auto_fix.windows[]` | derived from live services (not shown) | - | Same windows as the live services; "May run by itself on Sundays 06:30-13:30" is built from them. | derived; restart |
|
||||||
|
| `auto_fix` on/off, `after_seconds`, `max_tries`, `retry_seconds`, `pause_after_network_seconds`, `forget_seconds`, `restart_connections`, `fixes[]`, `log_dir`, `keep_months` NEW | Site setup > Schedules | Auto-fix block: tick list and "also restart Companion connections by itself" on the screen; numbers, folder and months under Advanced with defaults beside them | Gentle fixes only; a fix that closes a program needs "may close"; a "never by itself" fix is refused. | ints; fix ids exist; load_config gentle rule; folder creatable; 1-12; restart (the Engine builds its auto state at start) |
|
||||||
|
| `debounce_seconds`, `flap.count`, `flap.window_seconds` | Site setup > Schedules (Advanced, Timing) | A fault must last; flapping | One bad reading does not alarm; repeated faults show amber xN. Defaults 3 s, 3 in 600 s. | 0-60; ints; restart |
|
||||||
|
| `timing.fix_cooldown_seconds` NEW (60) | Site setup > Schedules (Advanced, Timing) | The same fix never re-runs within | Guards against hammering a device; the watchdog reads it as one `self.cooldown` for the "WAIT 1 MIN" text. | 10-600; restart |
|
||||||
|
| `network.interface/gateway/burst_connections/burst_groups/burst_window_seconds/remember_seconds` | Site setup > Network & Dante | NET key: port and gateway on the screen; blip rule under Advanced | Port from a live list; gateway blank = off; a "blip" is N connections from M groups dropping within W s; NET shows BLIP for remember_seconds. | exists in ifconfig; IP or blank; ints >= 1; restart (pollers built at start; NET note: pages) |
|
||||||
|
| `dante_switches[]`, `dante_map_seconds`, `dante_max_shared` NEW (MAX_SHARED) | Site setup > Network & Dante | Switches, survey every, never power-cycle a port carrying more than | Netgear M4250 only; empty = no map, no PoE. | IPs with telnet light; 60-86400; 1-10; restart |
|
||||||
|
| keychain `netgear-status-panel` | Site setup > Network & Dante | light + account | Present/absent and user name only; the Terminal line to store it is shown ([How to add it]). | never read or written by the page |
|
||||||
|
| `hosts.<alias>` | Computers (card) | Short name used inside the settings | Shown below the display name. Delete shows what refers to it and removes those too (cascade, listed). | `[a-z0-9_]+` unique; rename updates references; restart |
|
||||||
|
| `hosts.<alias>.ssh/mac/wake_broadcast` | Computers (card) | Log in as, wake MAC, broadcast | How this Mac logs in; wake a PC that is off (key says WAKING PC). The MIDI stream and the batched checks for that computer restart with the new address. | `user@host`; MAC 6 bytes; IP; reach light; restart |
|
||||||
|
| `hosts.<alias>.name/os/role/notes/pingable/self` NEW | Computers (card) | Display name, kind, what it does for the panel, notes for operators, does not answer pings, this Mac | For people, logs, scripts and probes; `pingable:false` skips the ping light; exactly one `self` record (no ssh), refused as a host for fixes, remote checks and MIDI links (builders switch to local for it). | os in mac/windows/other; one self; none |
|
||||||
|
| `hosts.<alias>.runs[]` NEW | Computers (card: "What runs here") | What runs here | One entry per thing, with kind `sshd`, `task`, `service`, `launchagent`, `daemon`, `app`, `web`, `helper_dvs`, `helper_solo`, `midi_session`, `artnet`, `disabled` or `other`; fields per kind: name, exe, candidates, args, at_logon, label, path, http, source, dante_name, folder, restart_on_failure, `watched` (false = listed, no light); `helper_solo` also carries `pt_bundle_id` and `solo_menu_path` (defaults = today's Pro Tools values). Feeds probes, scripts, check and fix builders: a program is named once here. | per-kind required fields; task names `[A-Za-z0-9 ]+`; Windows values cmd-safe `^[A-Za-z0-9 ._()\\:*-]+$` (no `!` or `^` either); `PROBE_SAFE` whitelist of fields that may reach a probe line; none (dependent checks/fixes regenerate -> restart) |
|
||||||
|
| `hosts.<alias>.networks[]` NEW | Computers (Ingest card, Advanced) | Ports | iface + use (LAN / Dante / switch management), filled by [Check this Mac's network ports]; the page binds 127.0.0.1 plus the IPv4 address of each LAN-use port (resolved from ifconfig at start, re-checked every minute, rebound on change); an empty list = every private-address port and a grey notice. | iface exists (light); settings (for the `self` record) |
|
||||||
|
| `hosts.<alias>.manual_done{}` NEW | Computers > Setup | Done-by-hand ticks | BIOS WoL, Public profile, Vista autoload, Wi-Fi power settings: no probe can see them, you tick them and the light says "Done by hand (Chris, 2026-10-08)". | bool + who/when; none |
|
||||||
|
| `gear.<id>.location/notes` NEW | Computers > Gear filter | Where it is, notes | Plain words for people; the device itself is defined by the check that watches it (the Gear filter is a view of the Computers list, not its own data model). | free text; none |
|
||||||
|
| `fixes.<id>` | Fixes (Advanced) | Id | Referenced by keys, restart keys and auto-fix; delete cascades with a list. | `[A-Za-z0-9_]+`; not restart-connection / dante-auto; restart |
|
||||||
|
| `fixes.<id>.kind` | Fixes (Built as) | Built as | ssh (default), local, atem, reconnect, midi_connect, netgear_poe, hyperdeck, `spotify` NEW; `FIX_KINDS` exported and validated by load_config. | enum; restart |
|
||||||
|
| `fixes.<id>.host` | Fixes | Computer | ssh kind; validated for every fix (restart keys, hold-always, MIDI links too): exists, has ssh, not the `self` record. | alias rules; restart |
|
||||||
|
| `fixes.<id>.run`, `.template`, `.params` NEW | Fixes (run under Advanced) | Generated / Custom command | zsh on Macs, cmd.exe on PCs. The builder stores template + params beside the generated `run`; the watchdog runs only `run`; every Mac value is `shlex.quote`d, every Windows value regex-checked; the dvs_ingest -> prodcom step is rendered inline by the builder (no chain executor). A raw Custom command is a "command edit": red in Review, asks for the editing code again, never run before Apply. | non-empty; `zsh -n` for local and Mac ssh commands; pkill/taskkill/killall => not gentle unless "may close"; restart |
|
||||||
|
| `fixes.<id>.timeout` | Fixes | Give up after | Default 60 s. The largest timeout sizes the watchdog's ExitTimeOut at install. | 5-600; restart |
|
||||||
|
| `fixes.<id>.short`, `.label` | Fixes | Key text, plain words | "FIXING <short>"; plain words required for every fix; the Play builder derives both from `spotify.name`. | <= 14; required; restart (a changed note alone never forces a re-import, D11) |
|
||||||
|
| `fixes.<id>.reconnect`, `.conn` | Fixes | Reconnect Companion connection after | conn required for kind reconnect; picked from live labels. | warning if not live; restart |
|
||||||
|
| `fixes.<id>.restart_ok` | Fixes | May close a running program | Lets a closing fix be gentle (atem_midi, prodcom, proddash). Removes "Run now". | bool; restart |
|
||||||
|
| `fixes.<id>.never_auto` NEW | Fixes | Never runs by itself | Generic flag (bridge: never during a service). Removes "Run now". | bool; refused in auto_fix.fixes; restart |
|
||||||
|
| `fixes.<id>.part` | Fixes | Switcher part | audio or camera. | enum; restart |
|
||||||
|
| `fixes.<id>.verify_seconds/off_seconds/hung_note` | Fixes (More settings) | Wait before judging, off time, still-bad text | reconnect / hyperdeck kinds. | 5-120; 1-10; <= 2 lines; restart |
|
||||||
|
| `fixes.<id>.switch/port/mac` | Fixes | PoE switch, port, MAC guard | netgear_poe kind. Prefer Dante auto. No Run now. | IP in switches; `\d+/\d+`; MAC; restart |
|
||||||
|
| `spotify.open_wait`, `spotify.play_wait` NEW (read by the `spotify` fix kind) | Site setup > Spotify | Play fix: open Spotify and wait N s, then wait M s before checking it plays | The fix reads playlist and name from `spotify.*` (one owner); the same for the restart key, which has no check. | 1-60; restart |
|
||||||
|
| built-ins `restart-connection`, `dante-auto` | key editor picker | "Restart its Companion connection", "Dante auto" | Always offered where they apply. No Run now. | - |
|
||||||
|
| per-sub fix map `{sub: fix, "*": fix}` | key editor (Advanced) | table per check | Sub names per type (device names live; conn/disk/rec; chN_batt/chN_rf; chN_mute; conn/error/caution). | values are fix ids or built-ins; restart |
|
||||||
|
| implicit "enable a disabled connection" | Help; HOLDING 2 s WILL line | documented | Any held key whose connection is off in Companion turns it on. | DNE |
|
||||||
|
| `tiles[].id` | Keys (Advanced) | Id | Used inside Companion; master is `all`. | `[A-Za-z0-9_]+`; unique vs restart keys; pages |
|
||||||
|
| `tiles[].label` | Keys | Label | First line and prefix of faults. | <= 10 warn; pages |
|
||||||
|
| `tiles[].pos` | Keys (drag) | Position / tray | Blank = in the tray "Shown inside the RF group (no key of their own)". | in grid; not col 0 rows 0-2; no overlap incl. jump/back pos; pages |
|
||||||
|
| `tiles[].kind` | Keys | Kind | single key / group; ALL and ALARMS are fixed chips (moved and edited, never created or deleted); NET may be removed at a campus that does not want it (at most one; D13). | exactly one ALL, one ALARMS, at most one NET; pages |
|
||||||
|
| `tiles[].members[]` | Keys | Group members | Worst member shown ("worst member + N"); hold fixes all. | ids exist; not self; a hidden key must be in a group (warn); restart (pages when hold-ability changes, detected by the build) |
|
||||||
|
| `tiles[].checks[]` | Keys | Checks | See per type. Changing a key's check list drops that key's remembered acknowledgements and auto-fix tries (said in Review). | type in CHECKS; restart (pages when the build differs) |
|
||||||
|
| `tiles[].hold_always` | Keys > When held | also when the key is green | Even when green (HD, TASCAM, SPOTIFY). | fix exists with label; pages |
|
||||||
|
| check `type` | "+ Add a check" | question first | The menu asks a question (Is a device answering? Is a program running? Is a MIDI link up? Is a recorder OK? Is a setting right? Is Spotify playing? Is Dante complete? Is the lighting console sending?) and picks the type from the answer; the 19 raw types (conn, hyperdeck, dante, shure, psm, tascam, ping, launchd, process, http, disk, midi_peer, pref, spotify, mdns, midi_rx, remote, artnet, atem) only under Advanced. | enum |
|
||||||
|
| check `conn` | check row | Companion connection | Live dropdown filtered by `EXPECTED_MODULE` for the type; a mismatch warns in Review. | required for conn/hyperdeck/dante/shure/psm/tascam; restart |
|
||||||
|
| check `fix`, `tag`, `service_only`, `debounce_seconds` | check row (When held / More settings) | Fix, tag, only during a service (Advanced; warned: guideline 1; no effect while alarms are armed all day), wait | | exists; <= 12; bool; 0-60; restart |
|
||||||
|
| check `template`, `params` NEW (remote, process, pref) | check row | Built as (A program is running on a Mac / on a Windows PC / A helper file is fresh or says X / A program is running on this Mac / A macOS preference equals / Custom) | Regenerates `cmd` / `match` / `domain,key` from what runs on the computer and `site.solo_dir`, so renaming a program in Computers updates the checks that watch it. | regenerated on save; test_builders_regenerate; restart |
|
||||||
|
| conn `ignore_warning` | check row | Ignore Companion's yellow warning | ProPresenter flicker. | bool; restart |
|
||||||
|
| hyperdeck `conn/optional/disk_service_only/full_minutes/low_minutes/rec_sync/ip` | check row | Connection, this deck may have no disk, NO DISK only in service, FULL below, LOW below, follows the recording, address override | Which HyperDecks are in use = which HD keys exist (the Add or remove a HyperDeck job also asks "may this deck have no disk?" per kept deck); "follows the recording" decks feed the live-service guard. Expects bmd-hyperdeck. | ints; IP; restart |
|
||||||
|
| dante `conn/devices{}/count_all` | Site setup > Network & Dante (only home; the check row links there) | Connection, device list, every device counts | Names from the live devices variable; key texts <= 14; the first dante check feeds the map. Expects audinate-dantecontroller. | non-empty; restart |
|
||||||
|
| shure `conn/channels/battery_err_bars/battery_warn_bars` | check row | Receiver, channels, red at <=, amber at <= | | 1-8; 0-5; err <= warn; restart |
|
||||||
|
| psm `conn/channels/muted_is_error` | check row | Receiver, channels, a muted channel is an error | | 1-8; bool; restart |
|
||||||
|
| tascam `conn` | check row | Connection | Needs the quiet module (light on the Companion section). | restart |
|
||||||
|
| ping `host/conn/name/msg/failures` | check row | Address or Companion connection, name, text, misses | With conn set, Companion's address wins. | host or conn required (fixes today's CHECK BUG); 1-10; restart |
|
||||||
|
| launchd `label/name` | check row | Helper (picker), name | | restart |
|
||||||
|
| process `match/name` (+ template/params) | check row | Program (picker from what runs on this Mac), name | | regex compiles; restart |
|
||||||
|
| http `url/msg/name/interval_seconds` | check row | Address, text, name, every N s | Any answer counts (so it is never a live signal). | URL; >= 5; restart |
|
||||||
|
| disk `path/warn_gb/err_gb` | check row | Folder, amber below, red below | | exists; err < warn; restart |
|
||||||
|
| midi_peer `session/peer/host/address` | check row / Add a MIDI link job | Session, peer (live pickers, local or over ssh, or a typed name when the Mac is off), Mac, fallback address | Macs only as host. The identity (host, session, peer) keys acknowledgements and Run now. | alias exists, is a Mac, has ssh, not self; restart |
|
||||||
|
| pref `domain/key/want/msg` (+ template/params) | check row | Domain and key (picker from `defaults domains`), wanted value, text | live `defaults read` shown | all required; restart |
|
||||||
|
| spotify `playlist/name` -> `spotify.playlist`, `spotify.name` NEW (one owner) | Site setup > Spotify (only home; the SPOTIFY key's check row links) | one link field + name | The page derives the URI and fetches the name and song count from open.spotify.com only; `spotify.name` owns the display name used by the fix label, the key guide and the notes; one permission light from the watchdog's last successful Spotify read in its log. | playlist URI regex; song-list fetch warning (private playlist); restart |
|
||||||
|
| mdns `instance/name` | check row | Bonjour MIDI name (picker) | RTP-MIDI only; the Bonjour type is fixed. | restart |
|
||||||
|
| midi_rx `source/name/max_silence_seconds` | check row | Source (picker), name, amber after | | restart |
|
||||||
|
| remote `host/cmd/msg/shell` NEW (+ template/params) | check row | Computer, built as, text | `shell` NEW: `sh` (Macs, batched with `; echo SPn=$?`) or `cmd` (Windows PCs, batched as `(cmd) >nul 2>&1 && echo SPn=0 \|\| echo SPn=1` pairs joined with `&`, never a bare `%ERRORLEVEL%`; verified once on the Lighting PC); `cmd` is generated; Custom only under Advanced with "cannot be checked before Apply"; a PC whose OpenSSH DefaultShell is not cmd.exe gets a light and cmd-batched checks are refused; a changed remote check is marked "not run before Apply" in Review. | alias exists, has ssh, not self; `sh -n` on each command and the batched line (Mac); cmd-safe regex (Windows); restart |
|
||||||
|
| artnet `source/max_silence_seconds/msg` | check row | Sender, silence, text | Port 6454 is fixed. | IP; 1-60; restart |
|
||||||
|
| atem check `part/msg` | check row | Part, text | Needs the ATEM section. | restart |
|
||||||
|
| `atem.host/node/library/reference`, `atem.compare` NEW, `atem.source_folder` NEW | Site setup > ATEM | Switcher address, patterns (resolved paths shown), saved file (upload or pick the newest from the folder), folder, what to keep in line | The upload (capped at 20 MB; XML with a DOCTYPE or entity declarations refused; parsed in the settings process with the watchdog's own parser) replaces `atem-reference.xml` atomically together with the config; its hash goes into History; the old file is kept dated under `history/atem/`; refused during a live service; Review shows "product name matches / N audio and M camera values differ from the current file" and needs the tick "I checked this is the right file for this switcher"; [Check the switcher now] reads only (3 s, a second connection, guarded). | IP; globs resolve; XML parses with the watchdog's own parser, no DOCTYPE or entity declarations, <= 20 MB; compare is a subset of MIX_OPTIONS/CCU_PARAMS; folder exists (light); restart |
|
||||||
|
| "this campus has no ATEM" switch (the `atem` section absent) | Site setup > ATEM | tick box | Hides the section; an atem check or a saved-switcher-file fix then fails validation with the key named. | cascade listed; restart |
|
||||||
|
| `restart_page.jump_pos/back_pos`, `restart_page.keys[].id/label/pos/fix/watch[]` | Keys (Restart tab) | jump/back chips; restart keys: label, position, fix, "keep flashing HOLD TO FIX while these are down" | watch = fix ids and "conn:<label>". | id unique vs tiles; fix exists; labels live-checked; pages (fix/watch: restart) |
|
||||||
|
| "this campus has no Restart page" switch (the `restart_page` section absent) | Keys (Restart tab) | tick box | make_page builds no Restart page and no jump key; the restart keys and the jump chip go with it (cascade listed). | cascade listed; pages |
|
||||||
|
| Companion connection labels | Site setup > Companion | live list with module id, status, watched by; rename-to for a vanished label; [Add a key for it] on unwatched ones | | warning only |
|
||||||
|
| custom variables / blink trigger / modules / pages exist / our pages / page-number buttons | Site setup > Companion | lights; "coloured on N of M pages" (counted when the section is opened) + [Colour them now] + [Undo]; [Re-import the pages now] | Set up once by the person who installed Companion. | by hand except nav revert and re-import |
|
||||||
|
| `companion-objects/` (trigger export, quiet Tascam module, nav templates per version) NEW | Help > Set up a new campus | shown with the import steps | Captured from NA once (Phase 1); not editable on the page. | repo files, deployed to the data dir |
|
||||||
|
| Windows program table (`runs[].kind=task`) | Computers (card) | task name, exe candidates, args, at logon | Generates windows-setup.ps1, task names in fixes and the "is running on a Windows PC" check. | cmd-safe regex; none (dependents -> restart) |
|
||||||
|
| `$Key`, `$IngestIPs` in scripts | Computers > Setup | from `ssh_key.pub` and `site.ingest_ips` | | - |
|
||||||
|
| `{{PT_BUNDLE_ID}}`, `{{SOLO_MENU_PATH}}` in the solo agent | Computers (Studio B card, Advanced) | Pro Tools bundle id, solo menu path | Differ per Pro Tools version/language; defaults are today's values. | non-empty; regenerating shows the agent needs a rebuild + Accessibility re-grant |
|
||||||
|
| python version on this Mac | Computers (Ingest card) | light | Must be 3.9-3.12: telnetlib (Netgear) is gone in 3.13. | self-check at start |
|
||||||
|
| `state.json` (acknowledged keys, auto-fix tries, fix cooldowns) NEW | Review note; Overview ALARMS line | shown, not edited | Keyed by (key, check identity): midi_peer (host, session, peer) / remote (host, cmd) / ping (host or conn) / http url / mdns instance / launchd label / process match / artnet source / else conn. Acks survive a restart and are dropped only for a key whose checks changed; one atomic writer (ServiceMode merges into it); `service-mode.json` migrated once. | the watchdog's file; the page never writes it |
|
||||||
|
| `started.json`, `live-now.json`, `fixes-running.json` NEW | Overview (watchdog line, live-service line) | shown | Written by the watchdog: `{pid, started_at, config_sha}` at start; live-now on change / every 5 s with a timestamp; the running-fix registry. Apply reads them for its success test and its waits. | the watchdog's files; the page never writes them |
|
||||||
|
| `netgear.py` CLI SWITCHES; push_page/nav_keys `--host` defaults | - | read config | | code change (Phase 0) |
|
||||||
|
| COLORS, ORDER, fault words, hold-feedback texts, poller intervals, HOLD_MAX_AGE, freshness limits, text fitting, ssh options, ports (WoL 9, 6454, 9993, 23), OS tool paths, Companion API paths, command grammar, RESTARTABLE, NAV_LOCATIONS, button style, notes markers, nav templates, ATEM parser, Spotify endpoints, Netgear driver, coremidi constants | Help > Glossary / Where things are | shown, not editable | Guideline 1 wants identical keys everywhere; these define how the panel works, not what it watches. | DNE |
|
||||||
|
|
||||||
|
## B. Computers: data model, probes, setup checklist
|
||||||
|
|
||||||
|
**The view.** The Map comes first: one row per computer, problems sorted to the top, one status word at the left from the one legend (the worst of its items: OK / Problem with its one-line reason / Can't see from here), what it does for the panel as up to three plain names then "+N", the keys that turn red if it dies, and the only per-item status shown is a problem ("Problem: rtpMIDI tray stopped"). Clicking a row opens the card: display name, its status word from the legend, "What runs here" with a light per listed item, "Used by" (keys, fixes, restart keys as links), notes for operators, and a collapsed raw **"Everything found on this computer"** list straight from the probe (every agent, task, service, login item and listening port the probe saw, shown as it came back). There is no Add/Ignore bookkeeping, no vendor list and nothing from that list is counted on Overview. The **Gear** filter shows the devices that are not computers (address, kind, where it is, watched by, light; notes typed once). The **Setup** tab holds the one-time checklist. The Overview shows one line per computer with one status word.
|
||||||
|
|
||||||
|
**The data.** `hosts` grows from `{ssh, mac, wake_broadcast}` to a computer record; the Ingest Mac is a record too (`self: true`, no ssh). The watchdog keeps using only `ssh/mac/wake_broadcast`; the rest is for the page, probes and generators. Phase 1 seeds all 8 NA computers from today's inventory. Record fields: `name`, `os`, `ssh`, `mac`, `wake_broadcast`, `role`, `notes`, `pingable`, `self`, `runs[]`, `networks[]`, `manual_done{}`. `runs[]` kinds: `sshd`, `task`, `service`, `launchagent`, `daemon`, `app`, `web`, `helper_dvs`, `helper_solo`, `midi_session`, `artnet`, `disabled`, `other` (plus `watched: false` for things that are listed but get no light).
|
||||||
|
|
||||||
|
```json
|
||||||
|
"ingest": {"self": true, "name": "NA Ingest", "os": "mac", "role": "watchdog, Companion, captions, transcription",
|
||||||
|
"runs": [
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.status-panel", "name": "watchdog"},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.status-settings", "name": "Settings page"},
|
||||||
|
{"kind": "app", "path": "/Applications/Companion 5.0.7.app", "name": "Companion", "http": "http://127.0.0.1:8000/api/connections"},
|
||||||
|
{"kind": "app", "path": "~/Applications/Companion Satellite.app", "name": "Companion Satellite", "watched": false},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.resi-propresenter", "name": "Resi bridge"},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.lobby-captions", "name": "lobby captions"},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.lobby-captions-display", "name": "caption screen"},
|
||||||
|
{"kind": "app", "path": "/Applications/ProdCom.app", "name": "ProdCom", "http": "http://127.0.0.1:24480/api/v1/ping"},
|
||||||
|
{"kind": "app", "path": "/Applications/ProdDash.app", "name": "ProdDash", "http": "http://127.0.0.1:24500/"},
|
||||||
|
{"kind": "app", "path": "~/Applications/Spotify.app", "name": "Spotify"},
|
||||||
|
{"kind": "app", "path": "/Applications/Blackmagic ATEM Switchers/ATEM Software Control.app", "name": "ATEM Software Control"},
|
||||||
|
{"kind": "daemon", "label": "com.audinate.dante.DanteVirtualSoundcard", "name": "Dante Virtual Soundcard", "dante_name": "STUDIO-A-Ingest"},
|
||||||
|
{"kind": "helper_dvs"},
|
||||||
|
{"kind": "app", "path": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org", "name": "Google Drive (saved switcher files)", "folder": true},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.checklist-build-runner", "name": "Gitea runner (not the panel)", "watched": false},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.checklist-drive-backup", "name": "checklist backup (not the panel)", "watched": false},
|
||||||
|
{"kind": "launchagent", "label": "org.waterschurch.checklist-weekly-health", "name": "checklist health (not the panel)", "watched": false},
|
||||||
|
{"kind": "other", "name": "login items: Itsycal, Alfred 5, Google Drive, Signiant", "watched": false},
|
||||||
|
{"kind": "disabled", "label": "org.waterschurch.translate", "name": "Church Translate (must NOT run here)"}
|
||||||
|
],
|
||||||
|
"networks": [{"iface": "en0", "use": "LAN"}, {"iface": "en17", "use": "Dante"}, {"iface": "en20", "use": "Netgear management"}]},
|
||||||
|
"studiob": {"name": "Studio B Mac Studio", "os": "mac", "ssh": "studiob@10.1.13.175", "role": "Pro Tools",
|
||||||
|
"runs": [{"kind": "sshd"},
|
||||||
|
{"kind": "helper_solo", "pt_bundle_id": "com.avid.ProTools", "solo_menu_path": ["Options", "Solo Mode", "AFL"]},
|
||||||
|
{"kind": "app", "path": "/Applications/Pro Tools.app", "name": "Pro Tools"},
|
||||||
|
{"kind": "midi_session", "name": "Studio B Mac Studio (peer of NA Tracks)"}]},
|
||||||
|
"lighting": {"name": "Lighting PC", "os": "windows", "ssh": "media@10.1.10.206", "role": "Vista, Resolume Arena, rtpMIDI",
|
||||||
|
"pingable": false, "notes": "Keep the network profile Public (rtpMIDI Bonjour rules).",
|
||||||
|
"manual_done": {"public_profile": {"by": "Chris", "on": "2026-10-08"}, "vista_autoload": {"by": "Chris", "on": "2026-10-08"}},
|
||||||
|
"runs": [{"kind": "sshd"},
|
||||||
|
{"kind": "task", "name": "Start Vista", "exe": "Vista.exe", "candidates": ["*:\\Vista3\\Vista.exe"]},
|
||||||
|
{"kind": "task", "name": "Start Arena", "exe": "Arena.exe", "candidates": ["C:\\Program Files\\Resolume Arena*\\Arena.exe"]},
|
||||||
|
{"kind": "task", "name": "Start rtpMIDI", "exe": "rtpMIDI.exe", "candidates": ["C:\\Program Files (x86)\\Tobias Erichsen\\rtpMIDI\\rtpMIDI.exe"]},
|
||||||
|
{"kind": "service", "name": "rtpMIDIService", "restart_on_failure": true},
|
||||||
|
{"kind": "artnet", "name": "Vista Art-Net", "source": "10.1.10.206"}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
The other NA records: **prop** (sshd, DVS daemon NA-ProPresenter-Mac, helper_dvs, app ProPresenter = newest `/Applications/ProPresenter*.app` with http :1025, midi_session "NA ProP"); **tracks** (sshd, DVS NA-Tracks, helper_dvs, app `Ableton Live 11 Suite` - the "Reopen Ableton with its last set" builder and the "A program is running on a Mac" check read the app name from here, so it is typed once; midi_session "NA Tracks" + Bonjour advert); **translation** (sshd, DVS NA-Translation, helper_dvs, app Church Translate); **livestream** (sshd, task Start OBS exe obs64.exe `--disable-shutdown-check` elevated-detected, WoL mac/broadcast, `manual_done.bios_wol`); **bibletv** (sshd, task Start BibleApp at_logon, web http://10.6.7.13:8080/, `pingable:false`, `manual_done.wifi_power`, note "Wi-Fi only; Ethernet recommended").
|
||||||
|
|
||||||
|
**Devices that are not computers** (ATEM, HyperDecks, Tascam, X32, Shure, LED, Resi boxes, SuperRack, Netgear switches, Studio B PoE boxes) have one home: the Gear filter of the Computers list, derived from the checks (address, kind, ping/connection light, keys that watch it) plus `gear.<id>.location/notes` typed by a person; the key editors, the Dante map, the ATEM section and the Help card "Which computer or device is that?" link to it.
|
||||||
|
|
||||||
|
**Liveness** (`settings/probes.py`): read-only; it only ever runs the fixed command set in probes.py, never a string from the settings file; the only parameters (`<app>`, `<helper>`, `<solo_dir>`, `<ip>`, the listed task and service names) come from `runs` / `hosts` through the schema regexes and `shlex.quote`, and `PROBE_SAFE` names which `runs` fields may reach a probe line at all. Probes never run during a fix (they wait while `fixes-running.json` is non-empty), never wake a PC, time out after 6 s per command, run in parallel threads, and cache their results with timestamps in `machines-live.json` so the page shows "last look 2 min ago" instead of waiting. They run when Computers or Overview is open (every 60 s) and on [Check computers now] / [Check this computer now]. Per computer: ping (skipped and marked "does not answer pings" when `pingable:false`), `ssh ... true` with the panel key (same `ssh_cmd` options and ControlMaster socket, so it is cheap), then one batched command per computer, by OS, that returns the full inventory. Each listed `runs` item is matched against that inventory for its light; everything else is shown raw under the collapsed "Everything found on this computer" list.
|
||||||
|
|
||||||
|
- Mac: `launchctl list` (every agent label with pid/state), `ls ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons` (every plist), login items via `osascript -e 'tell application "System Events" to get the name of every login item'`, `pgrep -x <app>` per listed app, `ls -l /usr/local/sbin/status-panel-dvs-start /etc/sudoers.d/status-panel` + `sudo -n -l <helper>` (helper_dvs), `cat <solo_dir>/solo-mode.json` age and `trusted` (helper_solo), `pgrep -f DanteVirtualSoundcard/apec` + `defaults read /Library/Preferences/com.audinate.dante.dvs wantToRun` (daemon), `grep -c status-panel ~/.ssh/authorized_keys` (login key), `/usr/bin/python3 - < coremidi.py` sessions list (MIDI sessions and peers shown on the card, expected beside live), `ssh-keygen -F <ip>` on this Mac (host key known; "host key changed since last time" is detected from the ssh error text and offers the confirmed [Forget the old login key] = `ssh-keygen -R <ip>`).
|
||||||
|
- Windows (cmd.exe; the probes use `schtasks /Query /FO CSV` and PowerShell `Get-CimInstance`, never wmic, which is gone on Windows 11 24H2): `schtasks /Query /FO CSV` filtered to `\StatusPanel\` (every task, not only the listed ones), `tasklist /FO CSV`, `sc query type= service state= all` + `powershell -c "Get-CimInstance Win32_Service | ? StartMode -eq Auto | select Name,PathName"` filtered to non-Microsoft paths (wmic is gone on Windows 11 24H2), `powershell -c Get-NetAdapterPowerManagement` (WoL), `Get-NetFirewallRule -Name OpenSSH-Server-In-TCP | Get-NetFirewallAddressFilter` (firewall allow-list vs `site.ingest_ips`), `Get-NetConnectionProfile` (Public profile light), and the DefaultShell probe `echo %COMPUTERNAME%` (expanded = cmd.exe is the OpenSSH DefaultShell; echoed literally = PowerShell: the card shows the light and cmd-batched checks for that PC are refused until the generated script has set it).
|
||||||
|
- This Mac: everything in the Mac list plus `lsof -nP -iTCP -sTCP:LISTEN` (bound ports), http pings of the listed `http` entries, `ifconfig` per network in `networks`, keychain item attributes only (never the secret), the watchdog's Spotify permission from its log (last successful read; never an osascript from the settings process), the login key pair present, `/usr/bin/python3 --version` in 3.9-3.12, node/library glob resolution for ATEM, `pgrep -lf atem_watch.js`, `lsof -nP -iUDP:6454` (Art-Net port bound), `pgrep -lf ServerAliveInterval=2` (MIDI streams per host), `dns-sd -B _apple-midi._udp` (3 s), `atem.source_folder` mounted, Remote Login on (`systemsetup -getremotelogin`, falling back to `launchctl print system/com.openssh.sshd` when it needs admin), `arp -an` sees the Dante devices, the switches answer on port 23, `launchctl print gui/<uid>/org.waterschurch.status-panel` (agent loaded / running / last exit / exit timeout) and the age of `started.json`.
|
||||||
|
|
||||||
|
**One-time setup checklist per computer** (Setup tab; every light uses the one status legend of the whole page, printed once under the top bar's "?": OK / Problem (+ one-line reason) / Can't see from here - ask at the computer / Not needed / Done by hand (who, when); every line has a [how] with the exact steps or the generated script):
|
||||||
|
|
||||||
|
- Every Mac: Remote Login on; Ingest login key authorised (generated `mac-authorize.sh`); host key known (with "changed since last time" + [Forget the old login key]); `/usr/bin/python3` present (for MIDI reads); DVS helper + sudoers (computers with `helper_dvs`); Audio MIDI Setup sessions named as the checks expect (expected beside live; a peer added by the Add a MIDI link job shows "will be added on first hold"; a typed session shows NO SESSION until it exists); apps at the listed paths; Studio B: solo agent running, `solo-mode.json` fresh, `trusted: true` (Accessibility), swiftc present for a rebuild.
|
||||||
|
- Every Windows PC: sshd Automatic + running; OpenSSH DefaultShell is cmd.exe (light); firewall rule allows this site's Ingest IPs; key login works (both authorized_keys files); a `\StatusPanel\` task per `task` run; services' failure-restart; WoL on the adapter; BIOS WoL (Done by hand); Public profile when rtpMIDI is listed (light + Done by hand); Vista autoload (Done by hand); Wi-Fi power (Done by hand); RESULT block pasted (fills ssh user, MAC, IP); the host-key-changed line as on Macs.
|
||||||
|
- This Mac: Command Line Tools / python 3.9-3.12; watchdog agent (loaded, running, ExitTimeOut set to the derived value); settings agent; Companion checklist (variables, blink trigger, pages exist, our pages, quiet Tascam module, ATEM module, "use last page" noted); keychain item (if `dante_switches`); the watchdog's Spotify permission (if a spotify check); DVS helper + sudoers (if dvs_ingest); login key pair ([Make the login key] at a new campus); editing code present; ports for LAN / Dante / switch management up; Art-Net port bound (if artnet); saved-file folder mounted (if `atem.source_folder` is set); Remote Login on (for the MacBook's `checklist-<id>` alias); Church Translate not loaded.
|
||||||
|
|
||||||
|
**Generated scripts** (`settings/generate.py`): "Generate" fills `remote-access/mac-authorize.sh.template`, `mac-dvs-helper.sh.template`, `windows-setup.ps1.template`, `install-solo-mode.sh.template` and `solo-mode-agent.swift.template` with the placeholders `{{PUBLIC_KEY}}`, `{{INGEST_IPS}}`, `{{APPS}}`, `{{DVS_HELPER}}`, `{{SOLO_DIR}}`, `{{SOLO_LABEL}}`, `{{PT_BUNDLE_ID}}`, `{{SOLO_MENU_PATH}}` and `{{SITE}}` from the site's public key, Ingest IPs, program table, helper paths and site name; every Mac value is shell-quoted, every Windows value passes the cmd-safe regex (and contains no `!` or `^`); the Windows script also sets the OpenSSH DefaultShell to cmd.exe. Each script is shown with Copy / Download and the exact place to paste it (ARD "Send UNIX Command as root" / PowerShell as administrator / scp + ssh for Studio B; a rebuilt solo agent needs Accessibility re-granted on that Mac). The page never runs them (guideline 12). The schema check compares `site.solo_dir` against the generated strings of the Studio B checks, the solo fix and the swift template, so all three agree by construction. The templates are deployed to the data dir by `install.sh`, and `test_generate_from_data_dir` (Phase 5) proves generate.py finds and fills every one of them there.
|
||||||
|
|
||||||
|
## C. Saving and applying — the pipeline
|
||||||
|
|
||||||
|
1. **Draft.** One shared draft per site, saved to `draft.json` in the data dir (0600, like every file the settings agent writes there) on every change (a closed tab loses nothing). It carries `base_sha` (hash of the live `config.json` it started from) and who started it; the server answers `PUT /api/draft` with an ETag and `409` when another tab wrote in between: the page merges field-wise and, when the same field was touched, says "<name> changed this a moment ago - your change to <field> was not kept" and re-offers the field. When you switch Editing on and a draft exists, the page says "Sam started changes at 14:02 - [continue them] [throw them away]". The live config is untouched until Apply.
|
||||||
|
2. **Validate** (on every change and again at Apply), in process: (a) schema rules from `schema.py`: types, enums, ranges, id characters, unique ids, master id `all`, one ALARMS / at most one NET, positions in the grid and off the nav cells, no overlaps including jump/back pos, windows well-formed (overnight allowed), MACs, URLs, playlist URI, fix label present, fix kind in `FIX_KINDS`, every `host` alias exists, has ssh and is not the `self` record for every fix, remote check and MIDI link, hidden key in a group, `never_auto` not in `auto_fix.fixes`, Windows params cmd-safe, local and Mac-ssh commands pass `zsh -n`, remote check commands and the batched line the poller builds pass `sh -n`, cmd-batched checks refused for a PC whose DefaultShell light is red, every `variables.*` name exists in Companion (a hard error when Companion answers, the warning "could not check - Companion is not answering" when it does not, so a rollback is never blocked by a Companion outage); (b) `status_panel.load_config()` on the draft written to a temp file; (c) `make_page.build()` + `build_restart()` in memory; (d) soft warnings from live data: label not in Companion, computer unreachable, playlist not public, module mismatch against `EXPECTED_MODULE`, a HyperDeck without a disk becoming non-optional, `live_services` empty, no watched deck follows the recording, `service_windows` narrowed; (e) a saved switcher file that is part of the draft: the upload is capped at 20 MB, XML with a DOCTYPE or entity declarations is refused, and the file is parsed in the settings process with the watchdog's own parser so Review can show what differs from the current file.
|
||||||
|
3. **Review.** The plain-words diff from the schema (raw JSON under Details); command edits (`fixes.*.run`, `remote.cmd`, `process.match`, `pref.*`) listed in red with the generated text and "not run before Apply"; what Apply will do, computed by building both page JSONs from the live config and from the draft, stripping the keys' notes, and comparing them with the stored hash of the last verified import (a changed note alone never forces a re-import; notes catch up at the next one, D11): "the deck will go purple for about 5 seconds and come back with your changes" (always, unless every change is page-only data), "the Status page will be re-imported" when the build differs, "the page-number buttons will be re-run" when a page changes, "the key guide will be rebuilt"; the live-service state with the exact reason (window or recording deck) and "watchdog silent: using the schedule only" when `live-now.json` is older than 30 s; the foreign keys on our pages listed by position when the marker-only rule would refuse; the state note "acknowledged alarms and auto-fix counts carry over, except on keys whose check list changed" (state is keyed by (key, check identity), never by index); a note for History; one APPLY button. There is no preview run: what a key will say after the change is shown on the key's editor (WHAT THIS KEY WILL SAY), and validate already builds both pages in memory.
|
||||||
|
4. **Apply** (`settings/apply.py`, one request under a server-side file lock `apply.lock` - a second Apply is refused with "an Apply is already running"; each step logged to `apply.log`, each must succeed before the next; refused up front when `sha(config.json) != base_sha`, and refused while live unless the Emergency override is used):
|
||||||
|
1. copy the live config to `history/<timestamp>-<slug>.json` (0600) + `.meta.json` (note, by, address, diff summary with secrets masked, actions, the hash of the switcher file it ran with, the code version); the 46 existing `config.json.bak-*` files are imported once;
|
||||||
|
2. write `config.json.tmp`, validate again, `os.replace` (never a half-written file); when a new switcher file is part of the change, write `atem-reference.xml.tmp` and `os.replace` it in the same step, keeping the old one in `history/atem/<date>/`;
|
||||||
|
3. if every change is page-only data (notes, gear notes): regenerate the key guide and stop. Otherwise **restart the watchdog**: wait until `fixes-running.json` says no fix is in progress (refuse after 60 s with "a fix is running on <key>"), read the old PID from `launchctl print`, then `launchctl kickstart -k gui/<uid>/org.waterschurch.status-panel` (if the agent is not loaded, `launchctl bootstrap gui/<uid> <plist>` first) and record T. The SIGTERM handler lets a fix that is already running finish (join up to ExitTimeOut-5) and refuses new ones meanwhile (keys say RESTARTING SOON), so a running fix finishes before the process is gone. **Success** = `started.json` `{pid, started_at, config_sha}` newer than T with the sha we wrote (an old PID's file never counts; a log rotation during the wait does not fail it), no new traceback in `status-panel.err.log`, process alive after 10 s; polled up to 25 s. A fresh heartbeat (`sp_hb` > T + 2) is a second, non-fatal check reported as "Companion not answering, settings are live" when missing. At start the watchdog clears the command variable once (logged), so a hold queued before the restart is dropped and logged;
|
||||||
|
4. **only then**, when the built page JSON (notes stripped) differs from the last verified build: `make_page.py` into the data dir, then `push_page.py` (Status page, then Restart page) with the **marker-only rule**: every non-nav key on the target page must carry one of our markers (`STATUS tile`, `RESTART key`, `STATUS jump key`, `RESTART back key`, `NAV_MARKER` = "STATUS nav key"; one `MARKERS` constant in make_page.py) whatever the page's name; otherwise Review already listed the foreign keys by position and Apply refuses with the verbatim reason and "import by hand" steps; `--force` never. If the page **number** changed, the same Apply resets every control on the old page that carries our markers (tRPC `controls.resetControl` once the Phase 1 spike proves it blanks a cell; otherwise Review lists "delete these N keys on page 19 by hand" and the Needs Chris strip keeps it until done). The import is **verified** by re-fetching the page with `includeSecrets=false` and comparing control ids; build hashes (`build-hashes.json`) are stored only after that. Then, because pages changed, the **nav keys**: `nav_keys.py --dry-run` shown, then applied (walking pages by `page_number_N_name` up to 200 with the 5-miss end, writing `nav_locations.json` in the data dir);
|
||||||
|
5. verify: no ERR in the last 20 log lines; regenerate `key-guide.html`; clear `pages-out-of-sync.json`. Nothing is copied to the staging folder `~/status-panel-install/` by Apply;
|
||||||
|
6. if step 3 fails (process died, traceback, no `started.json` with our sha): **rollback, once**: restore the previous config **and the switcher file recorded in that History entry** from the history entry, kickstart again, wait with the same test, re-run the page/nav steps for the restored config when the pages had changed, and report "restored previous settings: <error>"; if the restored config fails too, leave it in place and raise the red notice on Overview ("Restored the previous settings after a failed Apply - [See why]") until the next good Apply. If step 4 fails after a successful restart: write `pages-out-of-sync.json` (what to import by hand), roll back the config and re-run the page/nav steps for the restored config; if that also fails the notice stays until the next verified import, so the deck never silently shows new pages over an old config.
|
||||||
|
5. **Live-service guard** (independent of ALARMS). "Live now" = inside a `site.live_services` window (overnight windows count past midnight) **or** any watched HyperDeck that "follows the recording" is recording, read from the watchdog's `live-now.json` (written on change / every 5 s with a timestamp; older than 30 s = schedule only, said in Review). While live, **Apply, Run this fix now, Restart the watchdog, Re-import the pages now, Check the switcher now, Install the staged folder, Go back to this and Undo the last change are refused** with the reason and when it ends ("A live service is on (HD2 is recording). Apply is not allowed until it ends at 13:30. Your change is saved as a draft."); the draft stays saved and the live `config.json` is never touched, so the watchdog, if launchd relaunches it mid-service, always starts on the config it was verified with. There is no queue and no job runner. The **Emergency override** is a dialog that states the consequence ("the deck goes purple for 5 s during the service") and asks you to type `LIVE` plus a reason; both are logged with who and the address. **Start the watchdog** while it is down is allowed during a live service; the three watchdog buttons (Start / Stop behind the typed word / Restart) live on Overview only. **Silence alarms until midnight** (the ALARMS key's override, written through the watchdog) is never guarded. Viewing, probes and logs are always allowed (guideline 10).
|
||||||
|
6. **Go back / Undo.** History > Go back to this (or Undo the last change): the chosen version becomes your draft together with the switcher file its History entry names (refused with a sentence when that file is missing), and goes through the same pipeline (validation, guard, restart, pages when the build differs), bumping `base_sha`. History is never pruned automatically; [Prune older than N months] asks with the count. ATEM files are kept dated under `history/atem/`.
|
||||||
|
7. **Program updates.** `install.sh` first runs the **new** `status_panel.py`'s `load_config` and `schema.validate` over the config it will run with and refuses on error (so a stricter validator never crash-loops the watchdog); keeps the previous code as `prev/` with `install.sh --rollback`; writes both LaunchAgents (the watchdog plist with `ExitTimeOut` derived from the config = max fix timeout + verify + 10, floor 60; the settings plist with `AbandonProcessGroup`); restarts the watchdog first and waits for `started.json`, then the settings agent last, detached; writes `apply.log` and a History entry ("program update", with the code version). Once the page exists it **never overwrites site-owned files** in the data dir (`config.json`, `atem-reference.xml`, `nav_locations.json`, the built pages) unless `--config <file>`, `--site <id>` or `--force-site-files` is given, printing a diff notice when the staged file differs (`--keep-config` is the Phase 0-1 opt-out while the copy behaviour is still the default). It deploys `settings/`, `schema.py`, `make_page.py`, `push_page.py`, `nav_keys.py`, `remote-access/*.template`, `companion-objects/` and `atem_state.js`. **Install the staged folder** from the page (Site setup > Site) runs install.sh as a one-shot launchd job, so it survives the settings agent's own restart; refused while live; the page polls the new agents' health. The staged folder still arrives by `scp` to `~/status-panel-install` from the MacBook; there is no git clone, deploy key or Pull button on the Ingest Mac. `install.sh --site <id>` deploys `sites/<id>/` when the repo has it, otherwise `sites/_template/` (D12).
|
||||||
|
|
||||||
|
## D. Safety and access
|
||||||
|
|
||||||
|
- **Who can read and change what.** The server binds IP addresses, not interface names: at start it resolves the IPv4 address of each LAN-use interface in `hosts.ingest.networks` from `ifconfig` (every private-address interface when the list is empty, with a grey notice), re-checks every minute and rebinds on change; 127.0.0.1 is always bound and is the only listener that serves `/edit-key`; never 0.0.0.0, and it answers only private ranges (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8); everything else gets 403. **Everything is readable on the LAN without the code**: Overview, Keys, Computers (inventories, generated scripts, the Dante map), Logs, History (list and bodies), the key guide, Help and every Site setup screen, with the alert web hook masked. **Every change needs the editing code** (`X-Edit-Key` header equal to the code in this Mac's keychain item `status-settings-edit-key`, with the 0600 file fallback), a `Host`/`Origin` header naming this server (CSRF), and a name; lockout for 10 minutes after 5 wrong tries; every write is logged with address and name and History records "changed by <name> from <address>". The code is shown only at `http://127.0.0.1:8020/edit-key` on the Ingest Mac's own screen (404 on a LAN socket) and is rotated from Site setup > Site > Advanced. Changing the port or the code shows a dialog that states the consequence. There is no admin code, no address allow-list and no second code; the editing code is not a password for any other system and is never typed by Claude.
|
||||||
|
- **Commands never run from the page before Apply.** `fixes.*.run`, `remote.cmd`, `process.match` and `pref.*` are "command edits": generated by builders, shown in red in Review, executed only by the watchdog after Apply; a raw Custom command (Advanced) asks for the editing code again before it is accepted into the draft. Probes run only the fixed set in `probes.py` with shlex-quoted / regex-checked parameters. "Run this fix now" never calls `execute_fix` in the settings process: it sends `test <key> <fix> <identity> <time>` on the command variable (`sp_cmd`), exactly as a hold arrives, and the watchdog resolves the check from that key (deck address from the snapshot, MIDI address from the last-seen peer address, recording-deck guard, cooldown, one MIDI fix at a time), logs it as TEST, and refuses a `test` whose fix is attached to no check on that key, a netgear_poe / dante-auto / atem fix, a recording deck, or a bare `test <fix>`; it is offered only for the start/reconnect kinds (start-only Windows/Mac/helper/DVS builders, Reconnect a Companion connection, Switch a HyperDeck off and on, Reconnect a MIDI peer, Play the Spotify playlist) and never during a live service. "Check the Dante network now" sends the `resurvey` verb the same way. The page never writes a runtime file the watchdog also writes: `dante-map.json`, `live-now.json`, `fixes-running.json`, `started.json`, `state.json` and the command variable are the watchdog's alone.
|
||||||
|
- **Server surface.** `GET /api/logs/<name>` serves only names on its list; `/api/live/spotify?url=` fetches only `open.spotify.com`; ATEM uploads are capped at 20 MB, XML with a DOCTYPE or entity declarations is refused, and the file is parsed with the watchdog's own parser in the settings process; the Companion DB reader returns label, module id and `config.host` only (the module version only if the spike finds it trivially; no surfaces, never a raw row); every page export is fetched with `includeSecrets=false`.
|
||||||
|
- **Secrets:** presence only (keychain items exist + account, login key exists, public key text, alert web hook "set, N chars"). `alert_webhook` and any key named `*token`, `*secret`, `*password` are masked in every API response, diff, History meta and site bundle, and every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600; the real value lives only in `config.json` and is re-attached on restore. The page never reads, shows or writes the Netgear password, never stores a credential for any computer or for git, and never runs `security add-generic-password` or makes a key for a person; it shows the exact line to type. The only `ssh-keygen` calls it makes are the confirmed `-R <ip>` (Forget the old login key) and the confirmed creation of the panel's own login key pair (Make the login key).
|
||||||
|
- **Never automated:** setup scripts on other computers (ARD / PowerShell by the user, guideline 12), firewall or system changes, BIOS WoL, Remote Login, macOS permissions (Automation, Accessibility), creating Companion custom variables or triggers unless the Phase 1 tRPC spike proves it safe (then only with explicit confirmation and never a full import), module imports, `push_page --force`, full Companion imports, rebooting any device, power-cycling a PoE port or pushing the switcher file from a Run button (those fixes have no Run now), restarting the Resi bridge during a service ("never by itself"), any Apply or watchdog restart during a live service without the Emergency override, git pulls on the Ingest Mac.
|
||||||
|
- **Confirmations, one style:** a dialog that states the consequence and asks once; a typed word only for the Emergency override during a live service (`LIVE` + reason) and for Stop the watchdog (`STOP`: "the deck goes purple and every alarm is off until Start"). The dialogs: deleting a computer, key, fix or check shows everything that refers to it ("Delete Lighting PC and also remove 3 fixes, 4 checks, 1 restart key?"); "Run this fix now" names the computer or device; Restart the watchdog; Go back to this / Undo; a page-number change before the old page is cleared; a saved switcher file must be ticked after its diff is shown; Undo the page-number buttons on every page (rewrites 97 pages); Prune history (with the count); Re-import the pages now; Install the staged folder (shows the staged version); Forget the old login key (names the computer); Make the login key; Make a new editing code and changing the port (state the consequence); a Custom command edit asks for the editing code again.
|
||||||
|
- **Watchdog restart safety:** the Engine keeps a registry of running fix threads (every thread started in `run_plan`, `run_force` and the test verb) and publishes it as `fixes-running.json`; its SIGTERM handler sets the stop event, refuses new fixes (hold, force, auto or test answer RESTARTING SOON on the key) and joins the registry for up to ExitTimeOut-5 s before exiting; `install.sh` writes the derived `ExitTimeOut` (max fix timeout + verify + 10, floor 60; today launchd's deadline is 5 s) into the watchdog plist before the handler ships; Apply waits until the registry is empty (60 s) before the kickstart; a HyperDeck off/on fix re-enables the connection in a `finally`, so a kill between its off and on never leaves a deck connection switched off; acknowledged keys, auto-fix tries and fix cooldowns persist in `state.json` keyed by (key, check identity) so a restart never re-runs a fix early or forgets an acknowledgement; the command variable is cleared once at start. There is no SIGHUP partial reload and no stop flag: every Apply is a full restart on the new config.
|
||||||
|
- **Guideline fit:** no per-key colour options in the schema; the legacy `service_only` flag sits under Advanced with a warning and has no effect while alarms are armed all day; the alarms window is not on the page at all (1); one schedule, labelled so only auto-fix and the change guard use it and alarms stay armed, the one sentence everywhere (2); the validator warns when a key has nothing to fix and no "also when green", and the HOLDING 2 s WILL line shows the answer (3); restart keys always have a fix and the watch list keeps them flashing (4); builders generate real, tested fixes with a guarded Run now (5, 6); debounce defaults stay 3/1/0 s (7); the gentle list is enforced by load_config and "never by itself" (8); page-number buttons re-run inside Apply whenever a page changes, and the Companion section counts "coloured on N of M pages" when opened, with [Colour them now] (9); the live-service guard uses the clock and the built-in recording signal, refuses rather than queues, leaves the live config untouched and has one logged override (10); no passwords (11); scripts only shown (12); the repo stays private (13); help texts short (14); Apply waits for proof (`started.json`, verified import) before saying done (15); History and the generated key guide replace most README tables (16).
|
||||||
|
- The settings server imports only `load_config`, `CHECKS`, `RESTARTABLE`, `FIX_KINDS`, the ATEM parser and `make_page.build` from the watchdog code (never `run_once`), so a bug in the page cannot take the panel down; it runs as the GUI user with no sudo, as a separate process, so the page keeps working while the watchdog restarts or when the panel is purple.
|
||||||
|
|
||||||
|
## E. Architecture: files, server routes, watchdog changes
|
||||||
|
|
||||||
|
```
|
||||||
|
schema.py the registry: top-level keys, site/host/run/gear records, 19 check types, 8 fix kinds, tile and
|
||||||
|
restart-key fields; per field: type, default, enum/range/regex, required-when, help, example,
|
||||||
|
shows-on-key, screen (one owner screen), advanced flag, secret flag, page-only flag (notes, gear notes).
|
||||||
|
DEFAULTS, GLOSSARY (grouped by action), SUBS (per-type sub names), KEY_RECORD ((receiver kind, key) -> record),
|
||||||
|
IDENTITY (per check type: which fields key the persisted state), EXPECTED_MODULE (module id per check type),
|
||||||
|
BUILDERS (fix and check templates with param rules and quoting; the dvs_ingest second step rendered inline),
|
||||||
|
QUESTIONS (the question-first "Add a check" menu), PROBE_SAFE (runs fields that may reach a probe line),
|
||||||
|
validate(cfg, live=None) -> errors, warnings; plain_diff(a, b); mask_secrets(obj); phrases(cfg) (the
|
||||||
|
"may run by itself ..." and window sentences built from the config)
|
||||||
|
settings/__init__.py adds the parent folder to sys.path so status_panel/make_page import from the data dir
|
||||||
|
settings/server.py stdlib ThreadingHTTPServer bound to IP addresses, not interface names: 127.0.0.1 always (the only listener
|
||||||
|
that serves /edit-key) plus the IPv4 address of each LAN-use interface in hosts.ingest.networks resolved
|
||||||
|
from ifconfig at start (every private-address interface when the list is empty), re-checked every minute
|
||||||
|
and rebound on change; one thread per address; editing code + Origin check on every write; reads open
|
||||||
|
GET / /app.js /style.css /key-guide.html /cheat.html /where.html GET /edit-key (127.0.0.1 only)
|
||||||
|
GET /api/schema /api/config (masked) /api/draft (ETag) PUT /api/draft (If-Match, 409 with the field) DELETE /api/draft
|
||||||
|
POST /api/validate /api/apply (lock; refused while live unless the body carries the Emergency word + reason)
|
||||||
|
POST /api/restore/<id> /api/undo
|
||||||
|
POST /api/watchdog/command (test <key> <fix> <identity> <time> | resurvey, sent on the command variable like a hold)
|
||||||
|
POST /api/watchdog/start|stop|restart (launchctl; the buttons live on Overview only; guarded, Stop behind the typed word, Start allowed while down)
|
||||||
|
GET /api/history /api/history/<id> /api/history/atem
|
||||||
|
GET /api/live/overview (deck from sp_state, heartbeat, state.json summary, live-now with age, started.json, launchd state)
|
||||||
|
GET /api/live/companion (connections with module id and status, lights, page-number buttons coloured on N of M)
|
||||||
|
GET /api/live/machines POST /api/live/machines/probe GET /api/live/dante-map GET /api/live/gear
|
||||||
|
GET /api/live/interfaces /api/live/midi?host= /api/live/launchd /api/live/processes /api/live/bonjour
|
||||||
|
GET /api/live/spotify?url= (open.spotify.com only) GET /api/live/atem-state (guarded)
|
||||||
|
GET /api/logs/<name> (whitelist) POST /api/atem-reference (upload, capped, no DOCTYPE) POST /api/atem-reference/pick-newest
|
||||||
|
GET /api/generate/<script>/<alias> POST /api/hosts/<alias>/forget-host-key (asks)
|
||||||
|
POST /api/companion/nav-keys /api/companion/nav-revert /api/companion/reimport POST /api/program/install (one-shot launchd job)
|
||||||
|
GET /api/site/export (bundle, masked) POST /api/site/import (Phase 6)
|
||||||
|
settings/render.py server-side rendering of the screens from the config, the schema and the live data
|
||||||
|
settings/probes.py per-OS fixed probe commands and parsers incl. the full-inventory lists and the DefaultShell probe (section B),
|
||||||
|
machines-live.json cache; no vendor lists
|
||||||
|
settings/apply.py history (with the switcher-file hash and code version), atomic write of config + xml, base_sha check, the
|
||||||
|
apply.lock file lock (a second Apply is refused with "an Apply is already running"), fix-in-progress wait, kickstart (bootstrap when not loaded), the started.json success test, make/push/nav,
|
||||||
|
verified import + build hashes, old-page reset or by-hand list, pages-out-of-sync, auto-rollback once
|
||||||
|
(config + xml, pages re-run), the live guard that refuses with the reason, the Emergency override;
|
||||||
|
also `python3 settings/apply.py <config> --note "..."` (CLI)
|
||||||
|
settings/generate.py fills remote-access/*.template with site data (quoted / regex-checked)
|
||||||
|
settings/keyguide.py builds key-guide.html, cheat.html, where.html from config + schema
|
||||||
|
settings/ui/index.html app.js style.css vanilla JS, no framework; the grid editor is plain DOM drag/drop; the top bar holds the site name,
|
||||||
|
the search box (finds any key, fix, computer, device or setting by name), the unsaved-changes badge, the
|
||||||
|
Editing toggle and the "?" with the one status legend; strings in ui/strings.json (wording test)
|
||||||
|
remote-access/*.template mac-authorize.sh, mac-dvs-helper.sh, windows-setup.ps1, install-solo-mode.sh, solo-mode-agent.swift with
|
||||||
|
{{PUBLIC_KEY}}, {{INGEST_IPS}}, {{APPS}}, {{DVS_HELPER}}, {{SOLO_DIR}}, {{SOLO_LABEL}}, {{PT_BUNDLE_ID}}, {{SOLO_MENU_PATH}}, {{SITE}}
|
||||||
|
companion-objects/ flash_tick_500.companionconfig, tascam-da-6400-1.3.1-quiet.tgz, nav_templates/<version>.json
|
||||||
|
sites/<id>/, sites/_template/ (Phase 6; until then config.json stays at the repo root)
|
||||||
|
tests/fixture-config.json frozen copy of today's NA config for the behaviour tests
|
||||||
|
install.sh + --site / --config / --keep-config / --force-site-files / --rollback; validates the config with the NEW code
|
||||||
|
before bootout; keeps prev/; deploys settings/, schema.py, make_page.py, push_page.py, nav_keys.py,
|
||||||
|
remote-access/*.template, companion-objects/, atem_state.js; writes both LaunchAgents (watchdog plist with the
|
||||||
|
derived ExitTimeOut, settings plist with AbandonProcessGroup); restarts the watchdog first and waits for
|
||||||
|
started.json, the settings agent last (detached); never overwrites site-owned files once the page exists;
|
||||||
|
prints the /edit-key address; writes apply.log + a History entry
|
||||||
|
tests/test_schema.py test_wording.py test_apply.py test_probes.py test_settings.py (beside the existing tests/test_status_panel.py)
|
||||||
|
```
|
||||||
|
|
||||||
|
The page tools (`make_page.py`, `push_page.py`, `nav_keys.py`) run from and write to the **data dir** on the Ingest Mac (they load `HERE/config.json`; `nav_locations.json` and the `.companionconfig` outputs live there). The data dir holds, besides today's `config.json`, `atem-reference.xml`, `atem-reference.json`, `dante-map.json`, `nav_locations.json`, the two built pages, `status-panel.log`, `status-panel.err.log` and the `config.json.bak-*` files: `draft.json`, `history/` (+ `history/atem/<date>/`), `apply.log`, `apply.lock` (the server-side Apply lock: a second Apply is refused with "an Apply is already running"), `machines-live.json`, `settings.log`, `key-guide.html`, `build-hashes.json`, `fixes-running.json`, `live-now.json`, `started.json`, `state.json` (replaces `service-mode.json`, migrated once), `pages-out-of-sync.json`, `edit-key` (when the keychain is unavailable), and the deployed `remote-access/*.template` and `companion-objects/`. Every file the settings agent writes there (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600.
|
||||||
|
|
||||||
|
**Code changes in the watchdog** (`status_panel.py`, additive, keeps its `.get(key, literal)` defaults): a `VERSION` constant, the "status panel running (code X)" line and `started.json` `{pid, started_at, config_sha}` at start; new keys read with defaults (`site.{id,name,ingest_name,ingest_hostname,ingest_ips,contacts,alert_prefix,live_services (defaults to auto_fix.windows; the migration writes it),dvs_helper,solo_dir,settings_port}`, `timing.{fix_cooldown_seconds,alert_after_seconds}`, `page_hold_ms`, `page_grid`, `page_stale_seconds`, `variables.flash`, `companion.{export_version,build,db}`, `auto_fix.{forget_seconds,keep_months}`, `dante_max_shared`, `atem.{compare,source_folder}`, `spotify.{playlist,name,open_wait,play_wait}`, `fixes.*.{never_auto,template,params}`, `checks.*.{template,params}`, `remote.shell`, `hosts.*.{name,os,role,notes,pingable,self,runs,networks,manual_done}`, `gear.*.{location,notes}`); overnight windows in `in_windows` (end <= start runs past midnight); the "HOLD 2s" and "WAIT 1 MIN" texts built from `page_hold_ms` / `fix_cooldown_seconds` (one `self.cooldown`); a fix-thread registry (started in `run_plan`, `run_force` and the test verb) that writes `fixes-running.json`; a SIGTERM handler (stop event, refuse new fixes with RESTARTING SOON, join the registry up to ExitTimeOut-5); the HyperDeck off/on fix re-enabling in a `finally`; `live-now.json` written on change / every 5 s with a timestamp from the hyperdeck checks that follow the recording (`status == record`); one atomic writer for `state.json` with acknowledgements, auto-fix tries and `fix_last` keyed by (key, check identity) per `IDENTITY`, dropped for a key whose checks changed, with `ServiceMode` merging into it (migrating `service-mode.json` once); the command variable cleared once at start (logged, a queued hold dropped); a `test <key> <fix> <identity> <unix>` verb in `handle_commands` that resolves the check from the key as `run_fixes` does (deck ip from the snapshot, MIDI address from the last-seen peer address, recording guard, cooldown) and refuses netgear_poe / dante-auto / atem / a fix not attached to that key / an identity not on the key; a `resurvey` verb; the Windows (`cmd`) variant of remote-check batching as `(cmd) >nul 2>&1 && echo SPn=0 || echo SPn=1` pairs joined with `&` (never a bare `%ERRORLEVEL%`), with cmd-batched checks refused for a PC whose DefaultShell is not cmd; wording from `site.*` and `schema.phrases` (log header, Dante log line, webhook text); fix kind `spotify` (playlist and name from `spotify.*`); `FIX_KINDS` exported and validated in `load_config`; the host validated for every fix, remote check and MIDI link (exists, has ssh, not self); `never_auto`; `EXPECTED_MODULE` per check type; `hyperdeck_probe` removed in P0 together with `TestHyperDeckProbe`/`FakeDeck` (dead since the HyperDeck hold toggles the connection); a python 3.9-3.12 self-check at start (telnetlib). No SIGHUP reload, no `then` chain, no stop flag, no commands folder poll.
|
||||||
|
|
||||||
|
`make_page.py` reads `page_hold_ms`, `page_grid`, `page_stale_seconds`, `variables.flash`, `companion.export_version/build`, the site name and `remember_seconds` for the notes, writes the page address into the ALL key's note ("Settings: http://na-ingest.local:8020/"), gives the two static jump keys the markers "STATUS jump key" / "RESTART back key", exports `MARKERS` as one constant, reads the per-version nav template from `companion-objects/nav_templates/`, builds no Restart page and no jump key when the section is absent, and takes an output dir. `push_page.py` imports `MARKERS`, extends `safe_target` to the marker-only rule (every non-nav key carries a marker, whatever the page name; foreign keys reported by position) and fetches with `includeSecrets=false`. `nav_keys.py` walks by `page_number_N_name` up to 200 with the 5-miss end and writes `nav_locations.json` in the data dir. `push_page.py` / `nav_keys.py` default `--host/--port` from `companion.*`. `netgear.py`'s CLI reads `dante_switches` from the config.
|
||||||
|
|
||||||
|
## F. Tests
|
||||||
|
|
||||||
|
The source's test 10 (`test_apply_class`) went with the apply classes and is not listed; `python3 -m unittest` runs everything below beside the existing `tests/test_status_panel.py`.
|
||||||
|
|
||||||
|
1. `test_code_keys_in_schema`: a **receiver-aware** AST walk over `status_panel.py`, `make_page.py`, `push_page.py`, `nav_keys.py`, `settings/*.py`: it tracks names bound from `cfg`, `cfg[...]` paths, `chk`/`c`, `fix`/`f`, `t`/`tile`, `k`/`key`, `rp`, `names`, `a` (auto), `self.cfg`/`self.net`/`self.auto`/`self.restart_keys`, `w` (windows), host entries, and collects every `.get("literal"[, default])` and `X["literal"]` whose receiver is one of those; each (receiver kind, key) must be declared in `schema.KEY_RECORD`; subscripts of other receivers (dante-map entries, ATEM reports, tRPC results, nav templates, state.json, Companion status rows) are covered by an explicit per-receiver allowlist. The test asserts "every config read is declared", not "every literal subscript".
|
||||||
|
2. `test_defaults_match_schema`: for every config read whose default node is an `ast.Constant`, the literal equals `schema.DEFAULTS` for that path; expression defaults (`fix.get("label", fix_id)`) are listed in the schema as "derived" and skipped.
|
||||||
|
3. `test_schema_keys_used_in_code`: the reverse, with an explicit allowlist for UI-only keys (`name, os, role, notes, runs, networks, manual_done, pingable, self, template, params, contacts, settings_port, gear, source_folder, ...`) so dead config keys are caught but UI data is not flagged.
|
||||||
|
4. `test_check_types_and_fix_kinds`: `set(schema.CHECKS) == set(status_panel.CHECKS)`; `set(schema.FIX_KINDS) == set(status_panel.FIX_KINDS)`; every per-type sub list in `SUBS` matches the sub names the check returns; every `IDENTITY` field is a declared field of that check type; every `EXPECTED_MODULE` id is one the fixture's live connection list uses for that check type.
|
||||||
|
5. `test_glossary_covers_fault_words`: AST-collect only the text arguments of `Result(...)` constructors, `self.note(...)` calls and `return "..."` statements inside `check_*`, `render`, `run_fixes`, `run_force`, `reconnect_and_verify` (for `%`-formatted strings the template with placeholders stripped), with an allowlist for log-only strings; each distinct upper-case word sequence of two characters or more must appear in `GLOSSARY`.
|
||||||
|
6. `test_page_roundtrip`: today's config + schema defaults builds `status-page`/`restart-page` exports identical to the committed ones **after stripping `options.notes`**, and asserts the notes differ only where intended (the ALL note gains the page address, the static jump keys gain their markers, site wording); a config without `restart_page` builds no Restart page and no jump key. Run in Phase 0 and kept.
|
||||||
|
7. `test_sites_validate`: every `sites/*/config.json` (root `config.json` until Phase 6) passes `schema.validate`, `load_config`, builds both pages, the key guide renders with no "?" placeholders, builders regenerate, wording passes; `sites/_template` has one `self` record with `networks: []`.
|
||||||
|
8. `test_builders_regenerate`: for every fix **and check** with `template`, regenerating `run` / `cmd` / `match` from `params` equals the stored string, for all 27 of today's fixes (vista, arena, obs, bibleapp, rtpmidi_start, propresenter, ableton_restart, translate open-only, translate_restart with "also stop everything it started", ableton, dvs_prop, dvs_tracks, dvs_translation, dvs_ingest with prodcom rendered inline, bridge, caption_screen, captions with two helpers, proddash, prodcom, atem_midi, solo_afl, spotify_play, tascam_reconnect, hyperdeck, midi_reconnect, atem_audio, atem_camera); includes a metacharacter case (`Ableton Live 11 Suite`, a name with `'`, a Windows path with `&` or `!` refused); the "only if not already running" guard of every start builder; the batched Windows line contains no bare `%ERRORLEVEL%` and uses one `&& echo SPn=0 || echo SPn=1` pair per check; a builder given the `self` record emits a local kind; the `site.solo_dir` agreement check.
|
||||||
|
9. `test_apply_dry`: the pipeline against a temp data dir with a fake `launchctl` and the existing fake Companion: history written masked with the switcher hash and code version, secrets re-attached on restore, atomic write of config and xml, base_sha refusal, a second Apply refused by `apply.lock` with "an Apply is already running", fix-in-progress wait, a fix that starts between the empty-registry check and the kickstart is joined by the SIGTERM handler before the old process exits, success judged from `started.json` (an old PID's file must not count; a log rotation during the wait does not fail it; an unversioned or old `started.json` never counts as success), auto-rollback once on a bad config restoring both files, no second rollback, pages rolled back and `pages-out-of-sync.json` written when the page step fails, page / nav-key detection from built-page hashes (incl. the Phase 2 refusal case "available after the layout step"), page-number change resets the old page or lists the by-hand step, name-only change instructs "rename in Companion first", a missing variable is a hard error only when the fake Companion answers, the guard **refuses** and leaves `config.json` and the draft untouched inside a live window (incl. an overnight window) and while a rec_sync deck reports `status == record`, a stale `live-now.json` (> 30 s) falls back to the schedule, the Emergency override applies and is logged, Start-the-watchdog is allowed while live when the agent is down, persisted acks dropped for a key whose checks changed and kept for the other five MIDI peers when one is acknowledged, page-only data (a note) applies without a restart, the install job survives the settings agent's restart, a hold queued before the restart is dropped and logged at start.
|
||||||
|
10. `test_sigterm_mid_fix`: a SIGTERM during a stubbed 10 s fix lets it finish and the registry empties before exit; a kill between a HyperDeck's off and on still leaves the connection enabled (the `finally`); `install.sh`'s plist contains an `ExitTimeOut` equal to the value derived from the fixture config and the settings plist contains `AbandonProcessGroup`.
|
||||||
|
11. `test_probe_parsers`: parsers fed captured outputs of `launchctl list`, `ls LaunchAgents`, login items, `schtasks /Query /FO CSV` (full list), `sc query`, `Get-CimInstance Win32_Service`, `lsof LISTEN`, `Get-NetAdapterPowerManagement`, `Get-NetFirewallRule`, `ifconfig`, `dns-sd`, the nav_keys page walk with the page-96 gap, the `%COMPUTERNAME%` answer from cmd and from PowerShell (the DefaultShell light); every listed run gets the expected light and the raw inventory renders in full on the card; a run named `x; rm -rf ~` is refused by `schema.validate` and, forced in, produces a quoted, inert ssh line.
|
||||||
|
12. `tests/test_wording.py`: scans `settings/ui/strings.json`, every schema help and label, and the Help pages (not the rendered DOM) for the forbidden-token list (underscored words, `--flags`, `.py/.json/.plist`, `sp_state`-style names, launchd labels, `r2c6`-style positions, bare connection labels outside a pill, "tile", "machine", "host", "builder", "edit key", "nav key", "Sunday", "North Attleboro", "always on", "detect", "Refresh") outside Advanced sections and generated text; the four chosen words (key, computer, fix, connection) and "editing code", "login key", "page-number buttons" are allowed; every button label starts with a listed verb and read-only ones with "Check"; the alarms sentence appears verbatim wherever alarms are mentioned.
|
||||||
|
13. `test_push_page_markers`: `TestPage.test_push_page_looks_at_what_is_on_the_page` extended with a page holding only our marked tiles plus the two jump keys (accepted), a page with only nav keys (accepted), a page with the expected name and one foreign key (refused, with the key's position in the reason), and one with a foreign key and another name (refused).
|
||||||
|
14. `test_test_verb`: in `TestFixes` with the stubbed executor, `test HD2 hyperdeck <identity> <now>` resolves HyperDeck_2's conn and ip, `test MIDI midi_reconnect {"host":"tracks","session":"NA Tracks","peer":"Studio B"} <now>` resolves exactly that peer's address and leaves the other five alone, a recording deck is left alone, a bare `test <fix>` is refused, an identity not on the key is refused.
|
||||||
|
15. `test_settings`: routes against the fake Companion: every read answers without the code (logs, history bodies, masked config, inventories, generated scripts) and every write is refused without it; `/edit-key` 404 on a LAN socket and 200 on loopback (the only listener that serves it); the bind set resolved to IPv4 addresses from a canned `ifconfig` with a full / empty / stale `networks` list, 127.0.0.1 always present, and a changed address rebound on the minute re-check; lockout after 5 wrong codes; 409 field-wise merge and the same-field message; a Custom command edit needs the editing code typed again; secrets masked in every response; every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600; the Origin check refuses a write from another host.
|
||||||
|
16. Existing tests: the behaviour suite loads `tests/fixture-config.json`; `TestPage`/`TestFixes` use `find_tile(cfg, id)`; `TestHyperDeckProbe`/`FakeDeck` deleted with `hyperdeck_probe` in P0 (dead since the HyperDeck hold toggles the connection); the valid-fix-kinds assertion reads `status_panel.FIX_KINDS`; the committed-pages test points at `sites/na` after Phase 6.
|
||||||
|
17. Phase 5: `test_generate_from_data_dir`: install.sh's copy step into a temp data dir, then generate.py finds every template and fills it.
|
||||||
|
|
||||||
|
No new dependencies: python3 stdlib (http.server, json, ast, subprocess, sqlite3, gzip, hashlib, threading, difflib, zipfile, urllib, signal, fcntl, secrets, shlex), Companion's node for ATEM as today, browser-side vanilla JS.
|
||||||
|
|
||||||
|
## G. Coverage checklist
|
||||||
|
|
||||||
|
Every inventory item, and where the plan handles it or why it is deliberately not editable. DNE = deliberately not editable because it is how the panel works, not what it watches (guideline 1: identical keys everywhere); documented in Help > Glossary / Where things are.
|
||||||
|
|
||||||
|
**Lens 1: config keys, code reads, hardcoded constants**
|
||||||
|
|
||||||
|
| Item | Handled where |
|
||||||
|
|---|---|
|
||||||
|
| companion.host, port | Site setup > Companion (Advanced); push/nav read them (Phase 0) |
|
||||||
|
| companion.db | Site setup > Companion (Advanced); the reader returns label / module id / config.host only (module version only if the Phase 1 spike finds it trivially; no surfaces) |
|
||||||
|
| page, page_name | Keys tab header ("Page 19"; number under Advanced; the Companion section links); name derived; marker-only safe target incl. the jump keys; foreign keys listed by position; old page reset or by-hand list / rename-first (§C, test 9); pages + nav keys |
|
||||||
|
| nav_keys, nav_hold_ms, nav_tap_page | Site setup > Companion (Advanced) as "page-number buttons" with the "use last page" note; Apply runs nav_keys whenever pages changed (export walk, 5-miss end); Undo button (asks) |
|
||||||
|
| variables.state/heartbeat/command; sp_state/sp_hb/sp_cmd by hand | Site setup > Companion lights; existence is a hard error when Companion answers, a warning when not; §C.7 bootstrap (tRPC spike Phase 1); the command variable is cleared once at every watchdog start |
|
||||||
|
| flash_tick custom variable + trigger flash_tick_500 | `variables.flash` NEW with the rename-the-trigger note; trigger light; `companion-objects/` export deployed to the data dir; by hand in Companion |
|
||||||
|
| service_windows, window shape days/start/end | not on the page (DNE in Help; schema default all day; a narrowed value warned in Review); validation (days, HH:MM, overnight = end <= start allowed) |
|
||||||
|
| debounce_seconds, per-check debounce, flap.count, flap.window_seconds | Site setup > Schedules Advanced Timing (restart, like every Apply); check row More settings |
|
||||||
|
| network.interface (live list), gateway, burst_connections/groups/window, remember_seconds | Site setup > Network & Dante (port and gateway main; blip rule Advanced with defaults) (restart: pollers built at start); NET note from remember_seconds |
|
||||||
|
| alert_webhook, "NA Ingest" text, 30 s threshold | Site setup > Site masked URL + prefix; `timing.alert_after_seconds` Schedules Advanced (restart: captured in run()); also receives a line for every command edit applied |
|
||||||
|
| ssh_key, key pair, public key in scripts | Site setup > Site (Advanced) "login key" light + public half; Make the login key (asks, the panel's own pair) on the Setup tab; templates (§B) |
|
||||||
|
| dante_switches, dante_map_seconds, netgear.py SWITCHES | Site setup > Network & Dante (switches main, survey Advanced); the CLI reads the config (Phase 0) |
|
||||||
|
| hosts map, .ssh, .mac, .wake_broadcast | Computers; validated for every reference (exists, has ssh, not self); an address change restarts the watchdog (MIDI streams and batched checks restart) |
|
||||||
|
| fixes map, .kind (validated), .host, .run, .timeout, .short, .label (required), .reconnect, .conn, .restart_ok, .part, .verify_seconds, .off_seconds, .hung_note, .switch/.port/.mac, .template/.params NEW | builders inline in the key editor and the Fixes catalogue (all 27 mapped exactly in §F test 8, quoted, `zsh -n`, the dvs_ingest -> prodcom step rendered inline, helper lists, self -> local); §A rows; the largest timeout sizes ExitTimeOut |
|
||||||
|
| fix kinds ssh/local/atem/reconnect/midi_connect/netgear_poe/hyperdeck | builders; `spotify` added; Run-now allowed-kinds rule; `test <key> <fix> <identity>` resolves the check; hyperdeck `finally` |
|
||||||
|
| "restart-connection", "dante-auto", per-sub fix map | key editor picker / Advanced table (§A) |
|
||||||
|
| implicit "enable" plan | DNE; Help explains; shown in the HOLDING 2 s WILL line |
|
||||||
|
| tiles list, .id (regex, `all`), .label, .pos, .kind, .members, .checks, .hold_always; hidden tiles; groups | Keys grid (8x4 default, fixed special chips, labelled tray) + key editor (progressive, When held with the inline fix builder, two previews); page detection from Phase 2 |
|
||||||
|
| checks common: type, conn (live, module-filtered), fix, tag, service_only (warned), debounce_seconds | Keys check rows via the question-first menu; persisted acks keyed by IDENTITY |
|
||||||
|
| conn.ignore_warning; conn fault texts; short_reason map | field; Glossary (DNE) |
|
||||||
|
| hyperdeck conn/optional/disk_service_only/full_minutes/low_minutes/rec_sync/ip; decks 2 and 4 only; module variable names | Keys HyperDeck card (expects bmd-hyperdeck, mismatch warned); Add or remove a HyperDeck job end to end incl. the per-deck "may have no disk" question (the emptied cells stay empty; one click moves HD4 next to HD2; D15); variable names DNE; rec_sync decks feed the live-service guard |
|
||||||
|
| dante conn/devices (live)/count_all; module variable names | Site setup > Network & Dante (only home; the check row links there; expects audinate-dantecontroller); names DNE |
|
||||||
|
| shure conn/channels/battery bars; psm conn/channels/muted_is_error; tascam conn (+ expected modules) | Keys rows with EXPECTED_MODULE filtering and the Review warning |
|
||||||
|
| ping host/conn/name/msg/failures (missing-host crash) | Keys; host or conn required |
|
||||||
|
| launchd label/name (picker); process match/name (builder); http url/msg/name/interval (>=5); disk path/warn/err | Keys via the question-first menu; http is never a live signal |
|
||||||
|
| midi_peer session/peer/host/address (+1 s debounce); coremidi identity | Keys MIDI link card; Add a MIDI link job end to end incl. a typed session/peer, a new peer, Bonjour and connection watches, and the question which key it goes on (MIDI when one exists, otherwise a new key; D14); host validated (Mac, has ssh, not self); identity (host, session, peer) for acks and Run now |
|
||||||
|
| pref domain/key/want/msg | Keys with the "A macOS preference equals" builder |
|
||||||
|
| spotify playlist (twice) / name (unread) / hardcoded behaviour / spotify_play run string | Site setup > Spotify one field; `spotify.name` is the single owner read by the Play builder's label/short, the key guide and the notes; `spotify` fix kind; Change the playlist job on Overview end to end; behaviour DNE; the settings agent never touches Spotify itself (no preview run, no Automation permission of its own) |
|
||||||
|
| mdns instance/name (picker); Bonjour type fixed | Keys; type DNE |
|
||||||
|
| midi_rx source/name/max_silence_seconds | Keys |
|
||||||
|
| remote host/cmd/msg/shell NEW (Macs and Windows PCs); artnet source/max_silence/msg (port DNE); atem check part/msg | Keys; remote built from "A program is running on a Mac" / "on a Windows PC" / "A helper file is fresh" builders, Custom under Advanced with the editing code typed again, `sh -n` on the batched Mac line, Windows pairs with no `%ERRORLEVEL%` (verified on the Lighting PC), DefaultShell light, never run before Apply; host not self |
|
||||||
|
| atem section/host/node/library (resolved)/reference (upload, dated history)/source_folder NEW; MIX_OPTIONS/CCU_PARAMS; parser; atem_watch.js constants; atem_state.js | Site setup > ATEM incl. `atem.compare`, pick-newest from the folder, upload cap/DOCTYPE/diff/tick, live refusal, the saved file as part of the applied set (atomic, hashed in History, restored by rollback and Go back, never overwritten by install.sh), guarded "Check the switcher now" with the client-limit note; "this campus has no ATEM" switch; js constants DNE |
|
||||||
|
| auto_fix section/windows/after/tries/retry/pause/restart_connections/fixes/forget_seconds/log_dir/retention/header text | Site setup > Schedules (tick list and restart_connections main; numbers, folder and `keep_months` Advanced with defaults); windows derived from live services; header and "may run by itself" phrase built from site + windows; restart (AutoLog in __init__) |
|
||||||
|
| site.live_services NEW (and the former site.live_signals idea) | Site setup > Schedules (one schedule incl. overnight windows); defaults to `auto_fix.windows`, the migration writes it; "Also counts as live" shows only the built-in recording-deck signal (no conn/var/value triple; "OBS is streaming" only as a built-in kind if the Phase 1 spike finds the module variable); stale handling (30 s); no quiet time and no queue: Apply is refused while live and the draft is kept |
|
||||||
|
| restart_page section/page/page_name/jump_pos/back_pos/keys id/label/pos/fix/watch; section absent | Keys Restart tab (jump/back chips, "this campus has no Restart page" switch with its cascade; make_page builds nothing when absent) |
|
||||||
|
| make_page options/steps/style, nav templates, stale texts, PURPLE, button style, action ids, notes markers | DNE; notes wording from the site (+ the page address on ALL, markers on the jump keys, one `MARKERS` constant); stale seconds editable; nav templates per version in `companion-objects/nav_templates/` (root file dropped) with the recapture recipe |
|
||||||
|
| HOLD_MS, GRID, STALE_SECONDS, NAV_LOCATIONS, EXPORT_VERSION/COMPANION_BUILD | `page_hold_ms` (read by the watchdog for "HOLD 2s"), `page_grid`, `page_stale_seconds`, `companion.export_version/build` (picks the nav template); NAV_LOCATIONS DNE (locked cells) |
|
||||||
|
| push_page/nav_keys --host defaults; "on NA Ingest" wording | read the config; site block (Phase 0) |
|
||||||
|
| poller intervals, fix cooldown, HOLD_MAX_AGE, freshness limits, main loop, HTTP timeout, remote MIDI 12 s, ATEM 8/5/30 s, Dante-wide 600 s | cooldown editable (one `self.cooldown`; restart) and read for "WAIT 1 MIN"; the rest DNE, listed in the Glossary |
|
||||||
|
| COLORS/ORDER, RESTARTABLE, ssh options, ports (WoL 9, 6454, 9993, 23), text fitting, CLI flags, OS tool paths, Companion API paths, db read, command grammar | DNE; hyperdeck_probe removed with its test; the `test <key> <fix> <identity>` and `resurvey` verbs added to the grammar; Where things are shows the data dir / CLI |
|
||||||
|
| runtime data: service-mode.json (-> state.json, one writer, identity-keyed acks/tries/cooldowns), dante-map.json, atem-reference.json, config.json.bak-*, fixes-running.json, live-now.json (timestamped, on change / 5 s), started.json NEW, pages-out-of-sync.json | Overview (ALARMS sentence, live now, the Needs Chris strip: pages out of sync, failed apply restored, page-number buttons missing), Network & Dante map, ATEM, History import (the .bak files once); one writer per file (the watchdog); the page sends `test` / `resurvey` on the command variable and writes no watchdog file; no pending files, no commands folder |
|
||||||
|
| Companion connection labels referenced by checks | Site setup > Companion live list with module id, mismatch rename-to (fictional example), module-mismatch warning, Add a key for it |
|
||||||
|
| Netgear keychain secret | Site setup > Network & Dante presence + account only; typed in Terminal by hand |
|
||||||
|
| external setup referenced from run strings (DVS helper path, LaunchAgent labels, schtasks names, solo-mode paths, Pro Tools bundle id and menu path, Automation) | `site.dvs_helper`, `site.solo_dir`, `runs` incl. `pt_bundle_id`/`solo_menu_path` (§B, PROBE_SAFE whitelist); fix and check builders derive names; swift template with four placeholders; lights |
|
||||||
|
| install.sh workflow and running copy in App Support | the page edits the App Support copy; install.sh validates with the new code, keeps prev/ with VERSION, deploys code + templates + companion-objects, never overwrites site-owned files unless told, derives ExitTimeOut, bootstraps the watchdog first and waits for started.json, restarts the settings agent last, runs as a one-shot launchd job from the page, writes History (§C.7); Install the staged folder under Site setup > Site |
|
||||||
|
|
||||||
|
**Lens 2: hardcoded site/layout things**
|
||||||
|
|
||||||
|
| Item | Handled where |
|
||||||
|
|---|---|
|
||||||
|
| site names in docstrings, AutoLog header, Dante log line, webhook texts, day names in UI phrases | `site.*` + `schema.phrases()` (Phase 0); "Sunday"/"North Attleboro" forbidden in typed strings (test_wording over strings, schema help and Help pages) |
|
||||||
|
| log path/rotation, --config/--data-dir defaults | DNE; Logs (open on the LAN), Where things are; rotation no longer affects the Apply success test (started.json) |
|
||||||
|
| auto-fix log folder/pattern/retention | log_dir + keep_months (Schedules Advanced) |
|
||||||
|
| COLORS, ORDER/RANK, text fitting, stale size/purple | DNE (Glossary > Colours) |
|
||||||
|
| hold-feedback, rendered and fault texts, short_reason | Glossary grouped by action; "HOLD 2s"/"WAIT 1 MIN" follow hold_ms/cooldown in the watchdog; RESTARTING SOON added |
|
||||||
|
| poller intervals, loop timing, Companion HTTP timeout, remote MIDI 12 s, ping method/misses, link method, Bonjour browse, HTTP method, hold freshness, cooldown, auto_fix/network/flap defaults | editable where listed in §A (numbers under Advanced with defaults beside them); methods DNE |
|
||||||
|
| HyperDeck/Shure/PSM/Tascam/Disk/Dante/Art-Net/ATEM/midi_rx defaults and module variable names | per-check fields (§A) with schema defaults; variable names DNE; the expected module per type in `EXPECTED_MODULE`; the built-in recording signal reads the HyperDeck `status` variable of rec_sync decks |
|
||||||
|
| CHECKS registry, per-check keys, RESTARTABLE, reserved fix ids, fix kinds, shells, exit-255/WoL, reconnect strings, hyperdeck_probe, load_config rules | the schema mirrors them with sync tests (§F, receiver-aware walk, IDENTITY); probe removed; validation reused (§C) |
|
||||||
|
| tiles/layout logic, restart_page schema (optional), special kinds, DAYS/HH:MM (overnight) | Keys (fixed chips for ALL/ALARMS/NET, no-Restart-page switch), Schedules |
|
||||||
|
| OS tool paths; launchd/pgrep/defaults methods | DNE (Ingest is a Mac); labels/domains editable through builders |
|
||||||
|
| Companion API paths, DB read, custom variables by hand, sp_cmd grammar | DNE; Site setup > Companion |
|
||||||
|
| Spotify AppleScript, playlist reader, URI twice, spotify_play string | DNE / single source `spotify.*` incl. name / builder (Site setup > Spotify, Change the playlist job); no settings-side permission; one light from the watchdog log |
|
||||||
|
| ATEM MIX_OPTIONS/CCU_PARAMS, parser, globs, restore file, source folder, atem_watch.js, atem_state.js | Site setup > ATEM |
|
||||||
|
| ssh_cmd options, hosts schema, remote MIDI over ssh, remote check batching (sh and cmd), known_hosts, DefaultShell | DNE; Computers; Windows batching with conditional echo pairs; DefaultShell probe + the generated script line; Forget the old login key; the self record refused as a host |
|
||||||
|
| netgear.py SWITCHES, KEYCHAIN_SERVICE, CLI driver, MAX_SHARED, ARP/VLAN interfaces, dante_map_seconds | config; Network & Dante; driver DNE; `dante_max_shared`; `networks` on the Ingest card (also the page's bind list) |
|
||||||
|
| coremidi NETWORK_DRIVERS, client names, identity rules | DNE; "no network MIDI driver found" light on the Ingest card and in the MIDI pickers; typed fallback when a Mac cannot be read |
|
||||||
|
| make_page EXPORT_VERSION/GRID/STALE/HOLD_MS/stale texts/style/jump keys/NAV_LOCATIONS/nav_templates/hold decision/notes/output names/action ids | editable: stamps, grid, stale, hold, jump chips; hold decision = built-page hash detection with verified import (§C, active from Phase 2 as a refusal); jump keys carry markers; nav template per version from companion-objects; outputs to the data dir; the rest DNE |
|
||||||
|
| push_page defaults/import mechanics/safety rule; nav_keys defaults/walk/nav_locations.json | defaults from the config; marker-only `MARKERS` rule in the Apply path (name shortcut removed), foreign keys listed by position, refusal surfaced, never --force; export walk with the 5-miss end; "coloured on N of M pages" counted when the Companion section is opened (no periodic probe), [Colour them now] and [Undo]; per site, data dir |
|
||||||
|
| install.sh label/DEST/file list/plist/deploy path/page workflow | updated (§E): derived ExitTimeOut, AbandonProcessGroup, pre-validation, prev/ + --rollback with VERSION, watchdog first + started.json wait, settings agent last, one-shot launchd install job; Apply replaces the page workflow; `--site/--config/--keep-config/--force-site-files`; site-owned files never overwritten; the editing code made by the settings agent; phase rule (§C.7) |
|
||||||
|
| NA Ingest key, Automation permission, keychain, DVS helper on Ingest, Companion app checklist, connection labels | Ingest card (§B) + Site setup > Companion; one Automation grant (watchdog); Make the login key |
|
||||||
|
| windows-setup.ps1 $Key/$IngestIPs/app list/task names/elevation/sshd/firewall/rtpMIDI/WoL/DefaultShell/manual steps/RESULT | template from `site.ingest_ips` + `runs` (§B), regex-checked (no `!`/`^`), deployed to the data dir; RESULT paste; elevation logic unchanged; DefaultShell set to cmd.exe (the user OKs); Get-CimInstance probes; by hand on the PC |
|
||||||
|
| mac-authorize.sh key, mac-dvs-helper.sh, install-solo-mode.sh, solo-mode-agent.swift paths/menu titles/bundle id/Accessibility | templates in the data dir incl. the swift template with `{{SOLO_DIR}}`, `{{PT_BUNDLE_ID}}`, `{{SOLO_MENU_PATH}}`; Accessibility light and re-grant note; test 17; by hand on that Mac |
|
||||||
|
| tascam-quiet | built module in `companion-objects/` (deployed to the data dir); module light on Site setup > Companion; imported by hand in Companion |
|
||||||
|
| .gitignore / one repo one site; data model gaps (site name, per-host inventory, duplicated values) | `sites/<id>/` (Phase 6) with the self record in the template; `site` block; `runs/networks/manual_done/gear`; single owners with generated checks and fixes (§A, builders, templates); site bundle export instead of a terminal step |
|
||||||
|
|
||||||
|
**Lens 3: what runs where**
|
||||||
|
|
||||||
|
| Item | Handled where |
|
||||||
|
|---|---|
|
||||||
|
| MacBook project folder, Gitea remote, checklist-ingest alias, memory files, gitea keychain item | DNE (dev side); scp staging (code only) + Export site bundle; `checklist-<id>` alias in the bootstrap list; no git credential on Ingest |
|
||||||
|
| NA Ingest LaunchAgent (exit timeout), staging folder, data dir, python3 3.9 (3.13 telnetlib risk), logs, auto-fix logs, state, dante-map, atem-reference.json, config backups, ssh sockets, MIDI streams, ATEM watcher, Art-Net listener, Bonjour, local CoreMIDI sessions, local probes, Spotify osascript, Companion db read | Ingest card lights incl. the python-version self-check, launchd agent state with the derived exit timeout and started.json (§B); Logs; History; Where things are; nothing copied to the staging folder by Apply |
|
||||||
|
| Companion app/version, custom variables, flash trigger, page 19, page 20, nav keys on 97 pages (gap at 96), quiet Tascam module, module folders, Companion Satellite, Stream Deck surfaces | Site setup > Companion (version, variables, pages exist / ours, page-number buttons "coloured on N of M" counted when the section is opened with the gap listed, module id per connection, version only if trivial); surfaces not shown; Satellite on the Ingest card (`watched:false`); Companion set-up by hand; `companion-objects/` for a new campus |
|
||||||
|
| every Companion connection (HyperDeck_1-6, tascam, x32, ATEM, dante, Axient_1/2, ULXD-4, PSM_1-3, ableton, Propresenter-API, BibleTV, lighting-midi, OBS-Youtube, arena, RESI_Decoder, resi-studio-cues, VX1000, UHDJr) and unwatched ones (Govee x7, DiGiCo_OSC, m32, AbleSet, tcp-HyperDeck-1..6) | Site setup > Companion live list with module id, watched-by, module-mismatch warning, "Add a key for it"; the HyperDeck job offers only bmd-hyperdeck connections; Open question Q4 |
|
||||||
|
| Resi bridge, lobby captions, caption screen, ProdCom (start method unknown), ProdDash, Spotify.app, ATEM Software Control + midiEnabled, DVS on Ingest, Church Translate disabled, Google Drive, free disk | Ingest `runs` (`disabled` kind for Translate; Drive folder probe via `atem.source_folder`) + INGEST key checks; bridge "never by itself"; captions fix with two helpers; dvs_ingest with prodcom rendered inline; prodcom/proddash without restart_ok unless ticked |
|
||||||
|
| other Ingest agents (checklist runner, backups, Itsycal, Alfred, Signiant) **and anything installed later** | Ingest `runs` with `watched:false`; the inventory probe lists every agent, login item and listening port under the collapsed "Everything found on this computer" list on the card, raw and unfiltered; no Add/Ignore, no counts on Overview |
|
||||||
|
| en0 / 10.6.3.222 (interface undetermined -> Check this Mac's network ports shows it), en17 Dante, en20 Netgear | Ingest `networks` (the page binds loopback + the LAN ones, every private port when empty); `site.ingest_ips`; port picker (Network & Dante) |
|
||||||
|
| ProPresenter Mac: host, key, DVS helper+sudoers, DVS engine, ProPresenter app + API :1025, session NA ProP | Computers card + Setup (expected session beside live); builders read the app path; check builder "A program is running on a Mac" |
|
||||||
|
| Tracks Mac: host (down today), key/helper/sudoers, Ableton Live 11 Suite + last set, NA Tracks session + Bonjour, DVS | same; "Problem: Ableton stopped since 09:12" on the Map with keys APPS MIDI DANTE; app name typed once in `runs` and read by "Reopen Ableton with its last set" and the running check; the MIDI link job works with the typed fallback while it is down |
|
||||||
|
| NA Translation: host, key/helper/sudoers, Church Translate, DVS | same; `translate` = "Start a Mac program" (gentle), `translate_restart` = "Restart a Mac program" with "also stop everything it started" |
|
||||||
|
| Studio B: host, solo agent, solo-mode.json/.fix, Accessibility, Pro Tools (bundle id, menu path), MIDI peer | same; `site.solo_dir` shared by the check builder, the fix and the swift template (schema check on generated strings; probe-safe); Pro Tools parameters on the helper_solo record |
|
||||||
|
| Lighting PC: host (blocks ping), sshd, DefaultShell, firewall, keys, tasks Vista/Arena/rtpMIDI, rtpMIDIService, Public profile, Vista autoload, Art-Net source, rtpMIDI peer | card with `pingable:false`, remote-shell light, `manual_done` ticks, `artnet` run; "Start a Windows service (+ task)" builder for rtpMIDI; "A program is running on a Windows PC" check with conditional echo pairs, verified here in Phase 0; the full `\StatusPanel\` task list in the raw inventory |
|
||||||
|
| LiveStream PC: host, WoL MAC/broadcast, sshd/firewall/keys, Start OBS elevated, OBS streaming variable | card; WoL light; BIOS tick; the OBS streaming variable is not a live signal in v1 (only the built-in recording-deck signal); "OBS is streaming" is added as a built-in kind only if the Phase 1 spike finds the module variable and its on/off values |
|
||||||
|
| Bible TV PC: host (Wi-Fi, blocks ping), sshd/firewall/keys, Start BibleApp at logon, Wi-Fi power settings | card; `pingable:false`; Wi-Fi tick + note; the page is not served on that Wi-Fi network once the LAN port is named |
|
||||||
|
| devices: ATEM, HyperDecks, LED, Tascam, X32, Shure, SuperRack, Resi decoder/encoder/cloud, gateway, Netgear switches, Studio B PoE boxes | the device list under each Map row and the **Gear** filter (one row each: address, kind, where it is, keys that watch it, light) linked from key editors, the Dante map, the ATEM section and the Help card; recording decks feed the live-service guard; the Resi encoder's HTTP answer is deliberately not a live signal (a Resi live kind is offered only if the spike finds a module variable) |
|
||||||
|
| secrets & permissions: ssh key, known_hosts (stale host key: Forget button), Netgear login, sudoers x4, Accessibility, Automation (watchdog only), Remote Login (incl. Ingest for the MacBook alias), ARD, Windows admin/execution policy, MacBook keychain, alert_webhook, editing code | lights (§B); §D never stored, masked everywhere incl. every data-dir copy (0600), re-attached on restore; by hand on each computer; web hook masked on Site; the one code made in the GUI session and shown only on loopback; lockout; command edits ask for the code again |
|
||||||
|
| workflow: deploy, rebuild/import pages, nav keys, offline tests, --once, new ATEM file, Companion one-time setup | §C Apply; no preview run: the key editor's WHAT THIS KEY WILL SAY line shows what a key will say and validate builds both pages in memory; ATEM upload / pick-newest with diff; Companion lights; Install the staged folder (one-shot launchd job, validated, rollback-able, survives the agent restart) |
|
||||||
|
|
||||||
|
**Lens 4: operator workflow and Companion-side state**
|
||||||
|
|
||||||
|
| Item | Handled where |
|
||||||
|
|---|---|
|
||||||
|
| edit config.json in the repo, validate, tests, --once | replaced by Draft -> Review & Apply (§C); behaviour tests on the frozen fixture, site tests per site (§F); the key editor's WHAT THIS KEY WILL SAY line shows the effect without saving |
|
||||||
|
| scp to staging, checklist-ingest alias, install.sh, --remove | the config no longer travels by scp; install.sh for code with pre-validation, prev/, the site-owned-files rule + History; Start/Stop/Restart the watchdog on Overview only, with the launchd state (guarded, STOP typed; Start allowed while live when the agent is down); Install the staged folder under Site setup > Site |
|
||||||
|
| data dir, LaunchAgent, logs, runtime state | §B, Logs, Where things are; state persisted across restarts in one `state.json` (identity-keyed) |
|
||||||
|
| make_page, push_page status/restart, import by hand, safe-target rule, tRPC sequence, export fetch, pages 22/97 | Apply does them in the data dir after the success test; marker-only rule incl. the jump keys; foreign keys refused by position; verified import; build hashes seeded from the live exports; refusal with by-hand steps; mechanics DNE |
|
||||||
|
| nav_keys and nav_locations.json | Site setup > Companion count + Colour them now + Undo; export walk; data dir, never overwritten by install.sh; per site |
|
||||||
|
| git commit/push, Gitea credential, README, "the artifact", verify live | Export site bundle + commit on the MacBook; no Pull button and no git credential on Ingest; README trimmed, the key guide is the living document; Apply verify (started.json) + Overview live deck |
|
||||||
|
| replace ATEM restore file; quiet Tascam module | Site setup > ATEM upload or pick-newest with cap, diff, tick, dated history, live refusal, part of the applied set, never overwritten by install.sh; module light, `companion-objects/`, by hand in Companion |
|
||||||
|
| custom variables by hand; sp_state/sp_hb/sp_cmd mechanics; flash_tick; stale rule | Site setup > Companion, by hand; mechanics DNE; `test`/`resurvey` verbs added; page requests go through the command variable like holds do; the command variable cleared at start |
|
||||||
|
| page numbers/names, nav positions/settings, nav_templates, export pins, HOLD_MS, expressions/actions/notes markers | Keys tab header and chips (only home) and the Lens 1 rows; page change consequences handled (§C); nav templates per version; markers kept (help warns not to edit notes in Companion) |
|
||||||
|
| companion host/port, HTTP endpoints, connection status fields, DB addresses, fast/slow variable lists | Site setup > Companion (Advanced); DNE; the HyperDeck `status` variable of rec_sync decks is on the fast list for the live signal |
|
||||||
|
| each connection's role (HyperDecks, tascam, Dante list, Shure/PSM, x32, lighting-midi, Propresenter-API, ableton/BibleTV, ATEM + library, OBS-Youtube, RESI, NovaStar, arena) | check cards with live, module-filtered pickers (Keys); the Companion list; the Gear filter |
|
||||||
|
| fix kinds acting on Companion, surfaces/grid, tablet URL, API press, single-page import rule | DNE; grid editable; one-way tablet link in the header; Run-now buttons through the watchdog with identities, only outside a live service |
|
||||||
|
| service_windows + ALARMS, auto_fix window/list, poller periods, SSH identity/options, hosts table, Mac/Windows external setup, Public profile, Vista autoload, keychain, Spotify Automation, MIDI sessions local/remote, Ingest services, ATEM Software Control pref, hidden tiles, groups, restart keys, fix catalogue, network, alert_webhook, colours, cooldown/hold age, project folder, variables not persisted, full change sequence | covered by §A-§D; one schedule on the page; the one alarms sentence and the Silence alarms until midnight button (not guarded); the full change sequence collapses to: change -> Review & Apply -> done (refused while live with the draft kept; Emergency override), with the repo snapshot optional |
|
||||||
|
| History / Undo reachable from a clean state; pending work visible; vendor noise; two edit homes; four meanings of "key"; two meanings of "Settings" | History from Overview and its own menu entry, list and bodies open on the LAN; no pending work exists (a change refused while live stays a saved draft, said on Overview and in Review); raw inventory list on the card and no Overview count; fixes edited from the key, the catalogue under the "Set up once" divider; one owner screen per value with read-only links; editing code / login key / page-number buttons / connection; the page is "Status Panel", the section is "Site setup" |
|
||||||
|
| too many places; admin actions hidden under Help; three status vocabularies; contradictions about alarms; thresholds on main screens; hidden jobs; confusing tray; two watchdog homes; mixed confirmations; walkthrough behind the code | eight entries, the two set-up-once screens under a divider and readable by anyone; Help read-only, actions under Site setup > Site (Install the staged folder, Export site bundle); the one status legend printed once under the top bar's `?`; one alarms sentence; numbers under Advanced with defaults; three job buttons on Overview + More jobs; the labelled tray; watchdog buttons on Overview only; one confirmation style; the walkthrough needs no code because reads are open (the Training copy is deferred) |
|
||||||
|
| the user's four named asks: a new MIDI link (incl. a brand-new peer, or a Mac that is off), a different Spotify playlist (name follows everywhere), HyperDecks 2 and 4 only (both set to "must have a disk", generic TCP connections not offered), a new campus (own windows incl. overnight, own wording, reachable page before the LAN port is named, optional Restart page, optional ATEM) | the Overview jobs (first three, one click each) and the "New campus from this one" job (started from a site bundle you pick, exported from the source campus's page and copied over with the folder, or from any sites/<x>/ folder in the copied repo; `install.sh --site <id>` uses sites/<id>/ when present, else sites/_template/; D12) incl. bootstrap, the template's self record with `networks: []` and `companion-objects/` in the data dir; every copied value flagged "copied from <site> - check it" until touched |
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# Status Panel settings page — the plan
|
||||||
|
|
||||||
|
One web page on the Ingest Mac that holds every setting the panel has, shows what runs on which computer, and works the same at every campus. Nobody edits `config.json` or a script by hand any more.
|
||||||
|
|
||||||
|
Companion files: [settings-plan-details.md](settings-plan-details.md) (every field, the data model, the apply pipeline, tests), [settings-plan-audit.md](settings-plan-audit.md) (how the plan was audited and what was cut), [settings-inventory.md](settings-inventory.md) (the 544 things the system has today, each with where it lives and how it is changed now).
|
||||||
|
|
||||||
|
## 1. What it is
|
||||||
|
|
||||||
|
The Status Panel is the Stream Deck page that is green when the room is fine and flashes red naming the problem. A watchdog program on the Ingest Mac runs it from one settings file. This plan adds a **settings page** on that same Mac, opened in any browser on the church network. Every setting is a labelled field with help: which computers exist and what runs on each, which keys are on the deck, what each key checks, what holding it does, when auto-fix may act, which Spotify playlist, switcher file, Dante devices, HyperDecks and MIDI links are in use. The page checks a change, shows in plain words what it will do, keeps a copy of every version, applies it safely, and the same page runs at every campus with that campus's own computers and gear.
|
||||||
|
|
||||||
|
Four words are used everywhere on it: a **key** (on the Stream Deck), a **computer** (anything we log in to, this Mac included), a **fix** (what holding a key runs) and a **connection** (Companion's link to one device: a HyperDeck, the Tascam, the switcher). The code you type to switch Editing on is the **editing code**. The file this Mac uses to log in to the other computers is the **login key**.
|
||||||
|
|
||||||
|
## 2. Where it runs and how you open it
|
||||||
|
|
||||||
|
- It runs on the Ingest Mac as a second background program beside the watchdog, installed by the same `install.sh`. Plain Python, nothing to install. It is a separate program so a bug in the page can never stop the panel, and the page keeps working while the watchdog restarts.
|
||||||
|
- Open **http://na-ingest.local:8020/** from any Mac, PC, iPad or phone on the church network. The address is printed on the cheat sheet and written into the ALL key's note in Companion, so anyone who finds the deck finds the page.
|
||||||
|
- **Anyone on the network can look** at everything: the deck, the computers, the logs, the history, the help. **Changing anything needs the editing code**: switch **Editing** on in the top bar, type the code once and your first name; the browser remembers both. The code is a few random words the page makes itself on first start; it is shown only on the Ingest Mac's own screen (at http://127.0.0.1:8020/edit-key), never over the network. It is not a password for anything else. Five wrong tries lock the browser out for ten minutes.
|
||||||
|
- It edits the live settings file the watchdog reads (in `~/Library/Application Support/status-panel/`). From then on the Ingest Mac's copy is the truth for that campus; the git repo keeps a snapshot you export from the page.
|
||||||
|
|
||||||
|
## 3. What you see
|
||||||
|
|
||||||
|
Eight entries, always visible and readable by anyone: **Overview, Keys, Computers, Logs, History, Help**, and under a divider "Set up once": **Site setup** and **Fixes**. Nothing is hidden; the editing code is what lets you change things. A search box at the top finds any key, fix, computer, device or setting by name. Every screen starts with two sentences saying what it is for. Every field has a `?` with plain-words help, its default, and "what you will see on the key" where that applies. Numbers nobody changes (thresholds, waits) sit under an **Advanced** fold with the default shown beside them. Buttons say what they do and to what: "Run this fix on the Lighting PC", "Check computers now", "Restart the watchdog".
|
||||||
|
|
||||||
|
```
|
||||||
|
+---------------------------------------------------------------------------------+
|
||||||
|
| STATUS PANEL - North Attleboro [search ________ ] |
|
||||||
|
| [!] 3 unsaved - Review & Apply [Editing: off] |
|
||||||
|
+------------------+--------------------------------------------------------------+
|
||||||
|
| Overview | |
|
||||||
|
| Keys | |
|
||||||
|
| Computers | (screen content) |
|
||||||
|
| Logs | |
|
||||||
|
| History | |
|
||||||
|
| Help | |
|
||||||
|
| -- set up once -| |
|
||||||
|
| Site setup | Site . Schedules . Companion . Network & Dante . |
|
||||||
|
| Fixes | Spotify . ATEM |
|
||||||
|
+------------------+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
The first screen answers "is the room OK, and what do I do". In this order: the **problems in the room** (one sentence each, mirroring the red keys, with the action: "MIDI: TRK LIVEBOX lost — hold the MIDI key, or [Run the MIDI fix on the Tracks Mac]"); a separate grey **Needs Chris** strip for housekeeping (pages out of sync, a failed apply that was rolled back); the **live deck** drawn exactly as the Stream Deck looks right now (real colours, real words, blinking; click a key to open it); the **computers**, one line each with one word (OK / Problem / Can't see from here); three **job buttons** for the changes you named: **Change the playlist**, **Add or remove a HyperDeck**, **Add a MIDI link** (and a small "More jobs" menu: Add a computer, Make a PC program startable). Below: the watchdog (running 3 days), Companion (5.0.7, pages OK), alarms, when auto-fix may act next, the last change (when, who, note). Buttons: **Restart the watchdog** (for a purple deck; Start and Stop sit under it, and Stop asks you to type a word) and **Silence alarms until midnight** (same as the ALARMS key). One status legend for the whole page, printed once under the top bar's `?`: OK / Problem (with the reason) / Can't see from here — ask at the computer / Not needed / Done by hand.
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
| 2 PROBLEMS IN THE ROOM |
|
||||||
|
| . MIDI: TRK LIVEBOX lost - hold the MIDI key, or [Run the MIDI fix on Tracks Mac]|
|
||||||
|
| . LED: UHDJr does not answer a ping - check its power and cable |
|
||||||
|
| Needs Chris: nothing |
|
||||||
|
+--------------------------------------------------+-------------------------------+
|
||||||
|
| LIVE DECK | COMPUTERS |
|
||||||
|
| [UP ][ALL ][REC ][AUD ][RF ][SHOW][VID ][STRM] | OK NA Ingest (this Mac) |
|
||||||
|
| [ 19][HD1 ][HD2 ][HD3 ][HD4 ][HD5 ][HD6 ][TASC] | OK ProPresenter Mac |
|
||||||
|
| [DN ][X32 ][DANT][MIDI][APPS][VIST][RST ][SPOT] | Problem Tracks Mac: Ableton |
|
||||||
|
| [ALM][ATEM][OBS ][RESI][NET ][ING ][LED ][AREN] | stopped since 09:12 |
|
||||||
|
| click a key to open it | OK Lighting PC |
|
||||||
|
| [Change the playlist] [Add or remove a HyperDeck]| OK LiveStream PC |
|
||||||
|
| [Add a MIDI link] [More jobs v] | OK Bible TV PC |
|
||||||
|
| | OK Studio B |
|
||||||
|
+--------------------------------------------------+-------------------------------+
|
||||||
|
| WATCHDOG OK, running 3 days [Restart the watchdog] [Silence alarms until midnight]
|
||||||
|
| COMPANION OK, 5.0.7, pages OK ALARMS ON AUTO-FIX next: Sunday 06:30-13:30 |
|
||||||
|
| LAST CHANGE 2026-10-13 14:02 by Chris "HD2 has a disk now" [History] |
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keys
|
||||||
|
|
||||||
|
Two tabs, **Status page** and **Restart page**, each drawn as the 8×4 deck people hold. Every key is a chip you can drag to move, drag off the deck to hide, or click to edit. Column 0 (UP / page number / DOWN) is locked. ALL and ALARMS can be moved but not deleted; NET can be removed at a campus that does not want it. Keys with no spot of their own (AXIENT 1, AXIENT 2, ULXD, PSM 1–3) sit in a tray labelled "Shown inside the RF group (no key of their own)". Each tab header holds its page number; the Restart tab's also holds the switch "this campus has no Restart page". A yellow note says "Layout changed: Apply will re-import the Status page" when it will.
|
||||||
|
|
||||||
|
Click a key and its editor opens beside the grid. Two lines at the top answer the only two things a person needs to know: **WHAT THIS KEY WILL SAY** (every word the checks can put on it) and **HOLDING 2 s WILL:** ("switch HyperDeck_2's connection off and on, also when green"). Then only the fields that matter: the label, the group members (for a group key), the **checks** as a short list, and **When held** (which fix, and "also when the key is green"). "Add a check" asks a question first, not a type: *Is a device answering? Is a program running? Is a MIDI link up? Is a recorder OK? Is a setting right? Is Spotify playing? Is Dante complete? Is the lighting console sending?* You pick the computer or device from a live list (Companion's connections, the Dante devices, that Mac's MIDI sessions and peers, its programs) and the page builds the check. The raw check types and ids live under Advanced.
|
||||||
|
|
||||||
|
```
|
||||||
|
STATUS PAGE (Page 19) | RESTART PAGE (Page 20) | KEY "HD2" row 1, column 2
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+ | WHAT THIS KEY WILL SAY: HD2 NO DISK,
|
||||||
|
| UP | ALL | REC |AUDIO| RF |SHOW |VIDEO|STRM | | HD2 LOW 12m, HD2 FULL 3m, HD2 NOT REC,
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+ | HD2 OFFLINE, HD2 NOT IN COMPANION
|
||||||
|
| 19 | HD1 |[HD2]| HD3 | HD4 | HD5 | HD6 |TASCM| | HOLDING 2 s WILL: switch HyperDeck_2's
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+ | connection off and on (also when green)
|
||||||
|
| DOWN| X32 |DANTE| MIDI| APPS|VISTA|RESTRT|SPOT| | Label [HD2 ]
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+ | CHECKS [+ Add a check v]
|
||||||
|
|ALARM| ATEM| OBS | RESI| NET |INGST| LED |ARENA| | 1. HyperDeck connection [HyperDeck_2 v]
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+ | [x] this deck may have no disk
|
||||||
|
Shown inside the RF group: [AXIENT 1][AXIENT 2][ULXD][PSM 1][PSM 2][PSM 3] | Advanced v [Remove]
|
||||||
|
[+ New key v] | WHEN HELD runs [Switch the deck's connection off and on v]
|
||||||
|
| [x] also when the key is green
|
||||||
|
| [Run this fix on HyperDeck_2 now] [Delete key]
|
||||||
|
```
|
||||||
|
|
||||||
|
"Which HyperDecks are in use" is simply which HD keys exist. The **Add or remove a HyperDeck** job on the Overview edits the HD keys, the REC group and each kept deck's "may have no disk" flag together.
|
||||||
|
|
||||||
|
### Fixes (set up once)
|
||||||
|
|
||||||
|
The list of everything a hold can run (27 today): key text, plain words, how it is built, which computer, used by which keys, and whether auto-fix may run it by itself. You never type a shell command: a fix is built from fields. "Start Vista on the Lighting PC if it is not running" is *Start a Windows program* + computer + program + wait. The builders cover every fix that exists today (start or restart a Mac or Windows program, start a Windows service, reopen Ableton with its last set, restart a helper on this Mac, restart DVS, play the playlist, reconnect a connection, switch a HyperDeck off and on, reconnect a MIDI peer, put the switcher back to the saved file, power-cycle a PoE port, Pro Tools solo mode). A raw "Custom command" exists under Advanced for the day something new appears; it shows in red in Review and asks for the editing code again. Most fixes have a **Run this fix now** button (the ones that only start or reconnect something); it goes through the watchdog exactly like a hold, so the same guards apply.
|
||||||
|
|
||||||
|
### Computers
|
||||||
|
|
||||||
|
Answers "what runs where" in one look. The **Map** has one row per computer: a status word, what it does for the panel, and **which keys turn red if it dies**. Click a row for the card: everything that runs there with a light each (the watchdog, Companion, helpers, scheduled tasks, services, the apps the panel watches, MIDI sessions), what it is used by, and a folded "Everything found on this computer" list from a live look. The **Gear** filter lists the devices that are not computers (switcher, HyperDecks, Tascam, LED processors, Shure units, Resi boxes, Dante switches) with address, kind, where it is in the building, which key watches it, and a light. The **Setup** tab is the one-time checklist per computer with a light per line (OK / Not done / Can't see from here — ask at the computer / Not needed / Done by hand), a [how] on every line, and the setup script for that computer generated with the right names in it, ready to copy.
|
||||||
|
|
||||||
|
```
|
||||||
|
COMPUTERS - MAP [Check computers now] looked 2 min ago
|
||||||
|
status computer what it does for the panel keys that turn red
|
||||||
|
Problem Tracks Mac Ableton, Dante sound card, MIDI "NA Tracks" APPS MIDI DANTE (Ableton stopped 09:12)
|
||||||
|
OK Lighting PC Vista, Resolume Arena, rtpMIDI, +2 VISTA ARENA MIDI
|
||||||
|
OK NA Ingest (this) watchdog, settings page, Companion, +9 everything
|
||||||
|
OK ProPresenter Mac ProPresenter, Dante sound card, MIDI "NA ProP" APPS MIDI DANTE
|
||||||
|
OK LiveStream PC OBS OBS
|
||||||
|
OK Bible TV PC Bible app APPS
|
||||||
|
OK Studio B Pro Tools, solo-mode helper, MIDI peer APPS MIDI DANTE
|
||||||
|
OK NA Translation Church Translate, Dante sound card APPS DANTE
|
||||||
|
[Gear] ATEM . HyperDeck 1-6 . Tascam . X32 . LED x2 . Shure x6 . Resi x2 . Netgear x2 . Studio B PoE x4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs, History, Help
|
||||||
|
|
||||||
|
**Logs:** the watchdog log with filters (Holds / Fixes / Auto-fix / Errors), the auto-fix daily logs, the apply log. This is where "check the log" happens. **History:** every version ever applied, who, when, the note, what was different, with **Go back to this** and **Undo the last change**. **Help:** opens on four cards (Read a red key; Purple means the watchdog is down; Which computer or device is that; What is a connection), then the generated **What each key does** (one entry per key: what it checks, what holding does, what words can appear, which computers and devices are involved), a glossary of every word a key can show grouped by what to do about it, a printable one-page cheat sheet, a first-day walkthrough, "Where things are" (every file, folder, log and port per computer, the one page allowed to show file names), and "Things only a person can do".
|
||||||
|
|
||||||
|
### Site setup (set up once)
|
||||||
|
|
||||||
|
Six sections, each opening on the few things a person changes:
|
||||||
|
|
||||||
|
- **Site:** site name, this Mac's name and addresses, contacts for the cheat sheet, the alert message hook. Advanced: page port, the login key (present / not, public half to copy), helper paths, make a new editing code, install the staged folder, export the site bundle.
|
||||||
|
- **Schedules:** one schedule, the **live services** (Sunday 06:30–13:30 today): during them changes wait, auto-fix may act, alarms are always on. The tick list of fixes auto-fix may run. Advanced: the timing numbers (a fault must last 3 s, flapping, the 60 s cooldown, the 2 s hold).
|
||||||
|
- **Companion:** the lights (variables exist, blink trigger, our pages exist, quiet Tascam module, ATEM module), the live list of connections with their module and which key watches each, "Add a key for it" for the ones nothing watches, the page-number buttons ("coloured on 96 of 97 pages" + Colour them now / Undo). Advanced: address, variable names, nav settings, grid, purple-after, export stamps.
|
||||||
|
- **Network & Dante:** the port and gateway the NET key watches, the Dante switches, and the **Dante device list** (the one place the DANTE key's devices are edited, with key text and per-device fix), "Check the Dante network now". Advanced: the blip rule, survey interval.
|
||||||
|
- **Spotify:** one field for the playlist (paste the link; name and song count fill in), the two waits, a light saying the watchdog may control Spotify.
|
||||||
|
- **ATEM:** the switcher address, the saved file (upload, or pick the newest from the Google Drive folder; you see what differs before it is applied), what to keep in line (audio, each camera setting), "Check the switcher now". A switch "this campus has no ATEM".
|
||||||
|
|
||||||
|
### Review & Apply
|
||||||
|
|
||||||
|
The yellow badge. A plain change list ("HD2: this deck may have no disk — yes → no"; "Spotify playlist: Christian EDM 2026 → Sunday Set 2 (SPOTIFY key, the play fix and the key guide)"; "LED moved from row 3 col 6 to row 2 col 5"), one sentence about what it will feel like ("the deck goes purple for about 5 seconds and comes back; the Status page is re-imported"), any warnings, a note for history, one button. Details show the exact commands for anything built. (What a key will say after the change is already on the key's editor, so there is no separate "preview run".)
|
||||||
|
|
||||||
|
## 4. Everything you can change
|
||||||
|
|
||||||
|
Grouped here; every single field is in [the details table](settings-plan-details.md#a-everything-you-can-change).
|
||||||
|
|
||||||
|
| Group | What | Where on the page |
|
||||||
|
|---|---|---|
|
||||||
|
| Keys | which keys exist, label, position or hidden, group members, hold "also when green", the page numbers, the Restart page's keys and jump positions, whether a Restart page exists | Keys |
|
||||||
|
| Checks | every check on every key, per type: connection, HyperDeck (may have no disk, follows the recording, full/low minutes), Dante device list, Shure/PSM channels and battery bars, Tascam, ping, program running (Mac or PC), helper loaded, web address answering, disk space, MIDI peer (session, peer, Mac, fallback address), macOS preference, Spotify playlist, Bonjour name, MIDI heard, lighting Art-Net, switcher audio/camera; per check: the fix, the short tag, wait before it shows | Keys → key editor |
|
||||||
|
| Fixes | every fix: what it is built from, computer, program, waits, "may close a running program", "never runs by itself", reconnect Companion afterwards; the raw command under Advanced | Fixes, and inline from a key's When held |
|
||||||
|
| Computers | each computer: display name, kind (Mac/Windows), log-in address, wake-on-LAN, does not answer pings, notes, what runs there (programs, tasks, services, helpers, MIDI sessions), network ports, done-by-hand ticks | Computers |
|
||||||
|
| Gear | where each device is, notes | Computers → Gear |
|
||||||
|
| Schedules | live-service windows (overnight allowed), the auto-fix list and numbers, the timing numbers, the daily log folder and how long logs are kept | Site setup → Schedules |
|
||||||
|
| Companion | address, variable names, page-number button settings, grid, purple-after, export stamps; the connection labels are read live | Site setup → Companion |
|
||||||
|
| Network & Dante | port, gateway, blip rule, switches, survey interval, Dante devices and key texts and per-device fixes | Site setup → Network & Dante |
|
||||||
|
| Spotify | playlist, name, waits | Site setup → Spotify |
|
||||||
|
| ATEM | address, saved file, what to compare, node/library patterns | Site setup → ATEM |
|
||||||
|
| Site | name, this Mac's name/addresses, contacts, alert hook, port, helper paths, editing code | Site setup → Site |
|
||||||
|
| Not editable on purpose | colours, the words on keys, the 3 s/1 s/0 s debounce defaults, poll rates, the hold/ack mechanics, Companion's API paths, the shell commands the page builds — these are how the panel *works*, the same at every campus (guideline 1), and are listed in Help › Glossary and Where things are | Help |
|
||||||
|
|
||||||
|
The four things you named, end to end: **A new MIDI link** = Add a MIDI link → pick the Mac → pick its session and peer from the live list (or type them if that Mac is off) → pick the key it goes on (MIDI if there is one, otherwise a new key) → it gains the check and the reconnect fix → Review & Apply. **A different playlist** = Change the playlist → paste the link → Apply (the key, the play fix and the key guide follow; only the watchdog restarts, and the key's note catches up at the next page re-import). **HyperDecks 2 and 4 only** = Add or remove a HyperDeck → untick 1, 3, 5, 6 → the HD keys and the REC group change (the emptied cells stay empty, or one click moves HD4 next to HD2), the page re-imports → Apply. **A new campus** = section 6.
|
||||||
|
|
||||||
|
## 5. Computers and what runs where
|
||||||
|
|
||||||
|
Each computer is a record the page owns: name, kind, how to log in, what runs there and whether the panel watches it. Today's eight are seeded from the inventory: NA Ingest (watchdog, settings page, Companion, Resi bridge, lobby captions and screen, ProdCom, ProdDash, Spotify, ATEM Software Control, DVS + helper, Google Drive for the switcher files, plus the other background tasks that are not the panel's), ProPresenter Mac, Tracks Mac, NA Translation, Studio B Mac Studio (the solo-mode helper, Accessibility), Lighting PC (Vista, Arena, rtpMIDI task + service, firewall, Public profile), LiveStream PC (OBS task with admin rights, wake-on-LAN), Bible TV PC (Bible app at logon, Wi-Fi power settings). The live look runs a fixed set of read-only commands on each computer (what launchd has loaded, which tasks and services exist, which programs run, is the login key accepted, is the helper installed), never during a fix, never waking a PC, with every name quoted so a typed name can never run as a command. The devices that are not computers are listed from the checks that watch them, with a note per device for where it lives.
|
||||||
|
|
||||||
|
The Setup tab generates each computer's one-time script (the Mac authorise script, the DVS helper, the Windows setup, the Studio B solo-mode agent) with that campus's key and addresses filled in. You still run them yourself (section 12); the page shows which lines are done.
|
||||||
|
|
||||||
|
## 6. The other campuses
|
||||||
|
|
||||||
|
One settings file per campus = one site block plus that campus's computers, keys, checks, fixes, windows and gear. The code never contains a site name, an address, a label, a playlist, a path or a day name; everything site-shaped is data. Differences between campuses are plain differences in that data: fewer HD keys, no Studio B, another playlist, other Dante names, an extra PC, other page numbers, a Saturday evening service that runs past midnight.
|
||||||
|
|
||||||
|
Setting up a new campus: install Companion and the command-line tools on its Ingest Mac, copy the folder over, run `install.sh --site <name>` once (it uses the repo's `sites/<name>/` if there is one, otherwise the empty template), read the editing code off that screen, do the Companion one-time steps (section 12). Then on the page: **New campus from this one** starts from a site bundle (export NA's from its page and copy it over with the folder, or pick any `sites/` folder in the copied repo) and walks you through it — keep, rename or drop each computer (dropping one drops every key, check and fix that used it and lists them), map the connection labels, Dante devices and MIDI sessions from that Companion's live lists, set the playlist, switcher, network and schedule, Review & Apply. Everything copied from NA is flagged "copied from North Attleboro — check it" until touched. The repo keeps one folder per site (`sites/na/`, `sites/<new>/`); the page's **Export site bundle** gives you the zip to drop in.
|
||||||
|
|
||||||
|
## 7. Saving a change
|
||||||
|
|
||||||
|
1. **Draft.** Every edit is saved on the page's server as you go, so a closed tab loses nothing, and two people see the same draft. If someone else changes the same field, the page says so and shows both.
|
||||||
|
2. **Check.** On every change and again at Apply: the rules (ids, positions, windows, addresses, every fix's computer exists…), the watchdog's own loader on a temporary copy, a build of both pages in memory, and live warnings (a connection label not in Companion, a computer unreachable, a private playlist).
|
||||||
|
3. **Review.** The plain change list, warnings, anything built as a command in red, what Apply will do.
|
||||||
|
4. **Apply**, each step only after the previous one worked: keep a copy of the current version in History → write the new file in one go (and the new switcher file with it, if there is one) → wait for any fix that is running to finish → restart the watchdog (a change to key notes alone skips this) → **prove it came back** (its start stamp with the new file's fingerprint, no error, still alive after 10 s) → only then re-import the pages if the layout changed (key notes alone never trigger a re-import; the page refuses to overwrite a key someone added by hand in Companion and names its position) → recolour the page-number buttons if pages changed → rebuild "What each key does". If the watchdog does not come back, the previous version (and switcher file) is restored automatically, once, and Overview shows a red note until the next good Apply.
|
||||||
|
5. **During a live service** (the schedule, or any HyperDeck that follows the recording is recording) Apply is refused with the reason and when it ends; the draft stays saved. The same goes for Run this fix now, Restart the watchdog, re-imports, the switcher check and program installs. An **Emergency** button states the consequence, asks you to type LIVE and a reason, and is logged. Starting a watchdog that is down is always allowed. Silencing alarms is never blocked.
|
||||||
|
6. **Going back** = History → Go back to this (or Undo the last change) through the same steps.
|
||||||
|
|
||||||
|
## 8. Safety
|
||||||
|
|
||||||
|
- The page answers only on the church network; everything is readable, nothing changes without the editing code; every change is logged with name and address.
|
||||||
|
- No passwords are stored or typed: the login key stays a file on the Ingest Mac; the Netgear login stays in the keychain (the page shows the one line to type in Terminal); the alert hook is masked everywhere; the editing code is made by the page and shown only on the Ingest screen.
|
||||||
|
- The page never runs a command before Apply. Fixes and checks are built from fields and run only by the watchdog, which applies its usual guards (cooldown, a recording deck is left alone, one MIDI fix at a time). The only raw command field is under Advanced, shown in red, and asks for the code again.
|
||||||
|
- Never automated: the setup scripts on other computers, firewall or system changes, BIOS, macOS permission prompts, creating Companion's custom variables (unless a safe way is proven first), rebooting a device, power-cycling a PoE port or pushing a switcher file from a Run button, restarting the Resi bridge by itself, anything during a live service without the Emergency button.
|
||||||
|
- Deleting a computer, key, fix or check shows everything that refers to it and asks. One confirmation style everywhere: a dialog that states the consequence; a typed word only for Emergency and for Stop the watchdog.
|
||||||
|
- A watchdog restart never cuts a fix in half (it finishes the fix first; a HyperDeck switched off is always switched back on) and never loses acknowledged alarms (they are remembered across the restart).
|
||||||
|
- The panel guidelines hold by construction: no per-key colour options exist (the one legacy "only during a service" flag sits under Advanced with a warning, and has no effect while alarms are armed all day); the schedule only governs auto-fix and the change guard, never alarms; a key with nothing to hold shows "pick a fix" in its editor; restart keys always have a fix; the gentle-fix rule for auto-fix is enforced.
|
||||||
|
|
||||||
|
## 9. Teaching other people
|
||||||
|
|
||||||
|
Help › four cards, then **What each key does** (regenerated on every Apply, so it is never stale), the glossary grouped by action (*hold the key fixes it* / *check the device or cable* / *ask Chris*), the printable cheat sheet (both grids, the colour legend, press = acknowledge / hold 2 s = fix, purple = restart the watchdog on Overview, the computer map, the page address, your contacts), and a ten-minute **first-day walkthrough** that needs no code: see the live deck, click a red key and read what it checks and what holding does, find the hold in Logs, the Restart page, ALARMS and auto-fix, which computer a key talks to, then watch a Review & Apply and History. Wording rule, enforced by a test: no file names, variable names, flags or Companion internals outside Advanced; the same four words everywhere. A newcomer explaining Overview, Computers and Help back in their own words in 20 minutes is the exit test of the first build step.
|
||||||
|
|
||||||
|
## 10. How it is built (short)
|
||||||
|
|
||||||
|
- `schema.py`: the registry of every setting (type, default, range, help, example, which screen owns it, advanced or not). Tests keep it honest: every key the watchdog reads must be declared; every default in code must equal the schema's; every word a key can show must be in the glossary; today's config must still build today's pages exactly (apart from the key notes, which gain the page address and the markers); every one of today's 27 fixes must regenerate from its builder exactly.
|
||||||
|
- `settings/`: the server (routes for the draft, validate, apply, history, live lists, logs, probes), the probes, the apply pipeline, the script generator, the key-guide generator, and one HTML/JS page with no framework.
|
||||||
|
- Watchdog changes are additive: a start stamp, a restart that finishes the running fix, remembered acknowledgements, a "run this fix" verb, new settings read with today's values as defaults, Windows program checks that actually work (cmd.exe needs `&& echo` pairs, not `%ERRORLEVEL%`).
|
||||||
|
- `install.sh` validates the settings with the new code before touching anything, keeps the previous code for rollback, installs both programs, and never overwrites the campus's own files once the page exists.
|
||||||
|
- Full detail: [settings-plan-details.md](settings-plan-details.md).
|
||||||
|
|
||||||
|
## 11. Build order
|
||||||
|
|
||||||
|
Each step is deployed on its own, outside a service, with tests green, a live check on the deck, README/Help updated, committed. About three weeks of work in all.
|
||||||
|
|
||||||
|
| Step | What you get | What you can try | Size |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 Groundwork | The registry and its tests; the watchdog's start stamp, safe restart, remembered acks, "run this fix" verb; install.sh changes. No page yet. | Nothing changes on the deck. Hold SPOTIFY still works. | 1.5 days |
|
||||||
|
| 1 The page, read-only | Overview with the live deck, Keys viewer, Computers Map/cards/Gear with live looks, Logs, History list, Help with the key guide and cheat sheet. The editing code exists. Also the small Companion experiments (can it create variables safely, which OBS variable says "streaming"). | Open it from your phone; see every computer and what runs on it; print the cheat sheet; a newcomer reads it back to you. | 3 days |
|
||||||
|
| 2 Editing + Apply | Drafts, Review, Apply with rollback and the live-service guard; Site setup editors; key editor with check and fix builders; Run this fix now. Moving keys is refused until step 3. | Change HD2 "may have no disk" and Apply; paste a new playlist link; add a Dante device; Go back to yesterday; try an Apply during a pretend service and see it refused. | 4 days |
|
||||||
|
| 3 Layout | Drag keys, add/delete keys, the Restart page, page numbers; Apply re-imports pages and recolours page-number buttons. | Move LED, add HD7, delete HD5, Apply, watch the deck change. | 2 days |
|
||||||
|
| 4 Jobs | Change the playlist, Add or remove a HyperDeck, Add a MIDI link, Add a computer, Make a PC program startable; "Add a key for it" on unwatched connections. | Untick HD1/3/5/6 and run on 2 and 4; add a MIDI link to a Mac that is off. | 2.5 days |
|
||||||
|
| 5 Setup + teaching | The Setup tab with lights and generated scripts, Where things are, Things only a person can do, Install the staged folder from the page. | Add a new PC, generate its script, run it, paste the result, watch the lights go green. | 2 days |
|
||||||
|
| 6 Other campuses | Site folders, the template, the bundle export, New campus from this one. | Build a demo campus on the MacBook with a Saturday night service and no OBS. | 1.5 days |
|
||||||
|
|
||||||
|
Later, if wanted: a Training copy of the page where nothing is real; an "OBS is streaming" live signal (if the experiment in step 1 finds the variable).
|
||||||
|
|
||||||
|
## 12. What you still do by hand
|
||||||
|
|
||||||
|
- **A new Ingest Mac:** install Companion, `xcode-select --install`, Remote Login on, copy the folder over, run `install.sh --site <name>` once, read the editing code off its screen.
|
||||||
|
- **Companion, per campus:** create the custom variables `sp_state`, `sp_hb`, `sp_cmd`, `flash_tick` and the blink trigger (the page gives you the file to import, or a button if step 1 proves it safe); add pages up to the Status and Restart numbers; install the ATEM module; import the quiet Tascam module where there is a DA-6400; name the Stream Decks. Why: Companion only allows these in its own UI; a full import would wipe the variables.
|
||||||
|
- **Every other Mac:** Remote Login on; paste the generated authorise script (and the DVS helper where DVS runs) via ARD "Send UNIX Command as root"; make the Audio MIDI Setup sessions the page names; on Studio B run the generated solo-mode install and allow Accessibility on that screen. Why: root rights and permission prompts must come from a person at that Mac.
|
||||||
|
- **Every Windows PC:** run the generated setup script as administrator, paste its RESULT block into the page; BIOS wake-on-LAN; keep the Lighting PC on Public; Vista autoload; the Bible TV Wi-Fi power settings (or Ethernet). Why: system and firewall changes need your OK; BIOS has no remote path.
|
||||||
|
- **Ingest Mac:** type the Netgear login into the keychain yourself (the page shows the line); click Allow once on the Spotify prompt; keep Google Drive signed in for the switcher files.
|
||||||
|
- **ATEM saved file:** save it from ATEM Software Control when the show changes, then "Pick the newest file" on the ATEM section, outside a service.
|
||||||
|
- **Code updates (you):** `scp` the folder to the Ingest Mac as today, then Site setup → Install the staged folder. Drop the exported site bundle into `sites/<name>/` when the repo should hold the latest snapshot. The repo stays private.
|
||||||
|
|
||||||
|
## 13. Questions for you (none of these block steps 0–2)
|
||||||
|
|
||||||
|
1. Who gets the editing code besides you?
|
||||||
|
2. Live services at NA: only Sunday 6:30–13:30, or also midweek services or rehearsals that changes should wait for?
|
||||||
|
3. The other campuses: names, and does each have a Mac that runs Companion? (The page and the watchdog live on that Mac.)
|
||||||
|
4. The Companion connections nothing watches today (7 Govee lights, DiGiCo_OSC, m32, AbleSet, tcp-HyperDeck-1..6): leave them unwatched?
|
||||||
|
|
||||||
|
## 14. How this plan was audited
|
||||||
|
|
||||||
|
Four readers inventoried everything configurable (544 items, one of them checking the Ingest Mac live, read-only). Three designs were written from different angles, scored by three judges, and merged. Four critics (completeness, simplicity, safety, feasibility) then audited the merged plan three times, finding 47, 59 and 51 gaps; the gap count never fell because each revision added machinery to satisfy the previous round. The result was a correct but heavy design (two access codes, a queue of pending changes, partial reloads, ~25 screens). This plan keeps its strong core (the live deck, the key editor's two preview lines, the fix builders, the computer map, the apply-then-prove pipeline, the live-service guard, the schema registry with sync tests, the multi-site model) and cuts the rest in favour of "simple and obvious": always restart on Apply instead of partial reloads; refuse during a service instead of queueing; one editing code; no training copy for now. Every finding and what became of it is in [settings-plan-audit.md](settings-plan-audit.md); every inventory item and where the plan handles it is in [the coverage table](settings-plan-details.md#g-coverage-checklist).
|
||||||
Reference in New Issue
Block a user