- 05 Sep, 2026 40 commits
-
-
PLN: "ensure i can close midi mon atm it forces on when i want the gui/tray to allow run and or close? its too sticky atm ahah" -- the second report of the same complaint, because the first fix was committed and never made live: the shared checkout still runs 5468507b, so tonight's midiviz has neither the X nor the latch. The code was right and the deploy was missing, which from where he sits is indistinguishable from not being fixed. Adds the other half he asked for: launchers.stop(key), and a "Close ▸" entry under Rig. Closability is OPT-IN per launcher, keyed on declaring a systemd unit. A generic close-anything row is one mis-click from stopping Ardour mid-set, and this menu's whole value is being safe to touch while playing. Only the MIDI lens opts in today; a test asserts ardour/pulsar/supercollider never quietly acquire it. Close ▸ is a separate submenu rather than a Raise/Close pair on each row. The rows have one job mid-set -- one click raises the tool you need -- and putting a Close next to that is the mis-click itself. So closing lives in one place, holds only what is running AND closable, and is empty and disabled otherwise. The latch is written BEFORE anything is stopped. ensure() runs from gig-up and the Bridge watcher, a converge can land in the same second, and a latch written afterwards races it -- the window returns and the close looks broken, which is the exact complaint. A test pins the ORDER, not just the outcome. stop() prefers `systemctl --user stop` where a unit owns the process: killing it directly leaves systemd's view wrong, and for a unit with Restart= systemd is what decides next. Where there is no unit it SIGTERMs, using a /proc walk rather than pgrep -f -- the Bash wrapper embeds our own command line, so pgrep -f matching would find this session and the kill would take down the tool doing the killing. Same identity rule as is_running(), so "it says running" and "this is what I would kill" cannot disagree. And it confirms instead of assuming: it polls for up to 2s and returns "still-running" if the process outlives the request. Reporting a clean close while the window is still on screen is the same class of lie as this morning's delete that reported 0/14 for fourteen successful deletions.
PLN (Algolia) authored -
PLN (Algolia) authored
-
Two sessions checked each other and each found what the other missed. I measured that the tree was not waiting on a commit; the peer re-derived that independently, then added the two checks my recommendation lacked -- that HEAD is a true ancestor (so it is a real fast-forward, not a merge in disguise) and that the branch touches none of PLN's five modified .tidal files. That second one is the one that mattered. Recommending a checkout across somebody's live set prep without measuring the direction is how a repair becomes a clobber, and I had asserted the switch was clean on the strength of the two tools files alone. Both verified here before recording them. The caution that remains is real and unchanged: the fast-forward swaps code under a running rig.
PLN (Algolia) authored -
The first version told a cold reader to wait for the peer session to commit tools/check-mix.py and tools/gig-log.py before the main tree could be fast-forwarded. That precondition does not exist, and left as written it would have parked the tree waiting on a commit that was never coming. The peer flagged it; verified independently before amending. For both files the working-tree blob equals origin/master and equals f56775ae, and only HEAD differs. They show as 'M' because the shared tree's HEAD (5468507b) predates master's shared-ladder refactor (27f23515, which changed both to import tools/ardour_session.py) — the tree already holds the post-refactor content. Not in-flight work: the tree is simply behind its own HEAD's successor. Also verified, since 'one command' is a promise: HEAD is a true ancestor of f56775ae, and the branch touches NONE of PLN's five modified .tidal files, so his set prep survives the fast-forward untouched. The caution that remains is a different one, and it is real: the live rig runs from that tree via ~/.local/bin symlinks, so the fast-forward swaps code under a running system and wants three units restarted afterwards. That is PLN's call with eyes on the rig, not a housekeeping step to slip in overnight.
PLN (Algolia) authored -
The peer session's 043-open-decisions.md tells a cold reader the main working tree can be fast-forwarded once check-mix.py and gig-log.py are committed. They are not anyone's in-flight edits: worktree, master and f56775ae all hold the identical blob for both files. They read as modified only because the tree's HEAD predates 27f2531's shared-ladder refactor, which changed both files to import tools/ardour_session.py -- the tree already has the post-refactor content. Left as a precondition, that sentence blocks the fast-forward on a commit nobody can make. Recorded with the hashes and the two-command fix, and deliberately not run: PLN has folded, the live rig runs from that tree, and restarting his audio services unwatched is not a call to make for him.
PLN (Algolia) authored -
PLN folded tired and asked for the open questions written down rather than answered. Each of these would change how the rig sounds or what audio exists, so none of them is mine to guess: 1. Tidal 10 sits 5.5 dB under baseline — restore, or capture as the new intent? 2. something_about_drums' grid migration carries an overflow whose remedy is to comment out d3's live lines. Musical change, not a renumbering. 3. 24 dead Take101 sources are confirmed unrecoverable and ready to drop; the apply needs Ardour quit, by the tool's own design. 4. The Freebox mirror is 7 days stale and today's Take102 exists only on the laptop. Given the Freebox is the declared SSOT for audio, that is the real risk on the list, and the reason the space-reclaim thread stayed untouched. Also records the deliberate branch/worktree split so a cold reader does not 'fix' it: origin/claude/rig-streamline is 779cf17d (merged), the shared main tree is still 5468507b because it holds a peer session's uncommitted check-mix.py and gig-log.py edits, and the live rig runs from that main tree. The merge lives in ../Tidal-wt-merge where the suite is green.PLN (Algolia) authored -
A peer session consolidated branches onto master (6678acd1) and landed work in the same two files this branch was changing, so it flagged the collision early. Merged now, while both sides' intent is still known, rather than later from the diff alone. Both conflicts were purely additive — each side had inserted different code at the same seam — so both sides are kept: - launchers.py: my spawn guard (pidfile + identity re-check + per-decision logging, 3c00a9cb) sits alongside their `_clear_latch()`. These compose correctly and are about opposite problems: mine refuses to start a SECOND instance, theirs clears the "the human closed this" latch when a start is deliberate. Their `_clear_latch(spec)` call inside `launch()` merged cleanly into my rewritten version of that function. - midiviz.py: their close-latch block (`latch_close_path`, `_latch_close`) plus MY `build_widget` signature, which carries the `pinned_port`/`watch` parameters the replug-resilience work added (e9d16314). Their side's copy of that def line was the older two-arg form and was dropped in favour of mine. Verified in an isolated worktree, not in the shared checkout — that tree holds the peer's uncommitted edits to check-mix.py and gig-log.py, and merging there would have demanded I stash another session's in-flight work. Two untracked files of theirs were moved aside to let git proceed and restored byte-identical afterwards. pytest tools/tests tools/bridge/tests armada/tide-table -> 476 passed midiviz --selftest -> PASS (parsed=68 ingested=253 paints=172, 163 colours) Both of the features that had to survive this merge do: the surviving grep shows the spawn guard, the pending sweep, the latch clear, the close X, the pin toggle, the reconnect timer and the hardware-vs-phantom port discrimination all present.
PLN (Algolia) authored -
The tool worked but could not answer its own question. Proving a missing source is gone meant `rglob(name)` per file across the Freebox — 24 full traversals of a 25 G network mount for one session. It ran 8m15s without finishing, and on the 600s budget it would have returned "not found" purely because it ran out of time. An unfinished search reported as an absence is how you drop the last reference to a take that was actually recoverable. The fix is not a faster search, it is not searching. The Freebox is a converged mirror of $HOME (fbk), so an archived file's location is COMPUTABLE: mirror root + the path relative to $HOME. One stat per file instead of a walk. Runtime went from "8m15s, unfinished" to 0.40s, and the answer is now exact rather than best-effort. It still checks the session's own dead/ and audiofiles dirs, since a file can be moved aside rather than removed, and it refuses outright if no mirror is mounted — "gone" is not a claim you get to make about audio when the SSOT is offline. It also now reports HOW CURRENT the mirror is, because that changes what the absence means. Here: the mirror last changed 2026-08-29 22:41, so it holds up to Take100. Take101 is therefore absent locally AND absent from the mirror, but was also never eligible for that backup — so the honest statement is "it is not anywhere I can reach", not "it was archived and then lost". The tool now says that distinction out loud rather than letting the operator read absence as proof. Verdict for Tidal Live: 294 sources, 24 missing, all Take101, 12 orbits x L/R. Would drop 24 sources and 24 regions. That take's .mid files survive; only the audio is gone. Two bugs of my own, fixed here and worth the note: the mirror loop bound `root`, shadowing the XML tree and killing the region sweep with "'PosixPath' object has no attribute 'iter'" — a local rebinding eating an outer name, which is this repo's recurring self-inflicted wound. And an ElementTree truth test that Python 3.14 deprecates. Verified the write refusal fires: with Ardour running it stops at "Refusing to write: Ardour is running (pid [2708769])" and the session file's mtime is unchanged. Applying it needs Ardour closed, by design.
PLN (Algolia) authored -
PLN authorised the deletes, the CosmicFest tone and the gig page, so the board needed to stop describing a world where those were pending. Every row in the new block was measured today rather than recalled, which mattered: the standing 'catalog is STALE at 73 tracks / 23 gigs' warning is itself now false (81 / 25, as_of 2026-09-05), and the deletes that the board was waiting on a decision for have already run and been confirmed. The three remaining items all need PLN's screen or ears and nothing else, so they are listed as such instead of as work. Includes the verified prep for switching the shared checkout off rig-streamline, since that is the reason the running midiviz is still the old code.
PLN (Algolia) authored -
Audited what could have consumed the phantom SOUNDCHECK entry positionally. Answer: nothing did. The two real consumers of setlist.entries() are safe -- take-segments.py builds a dict keyed by path stem (a phantom adds one unused key) and migrate-columns.py calls tracks(), not entries(). No shipped artifact was off by one. But the audit found a different positional coupling, live and undocumented. opal_title_map() joins tracks.json's 1..N performance order against segments_v4.json's perf_track. OPAL was played as 15 tracks; the release cut Desire, and segments_v4 renumbered around the hole -- its perf_track keys are 1..13 and 15, with no 14. So the join lines up only while Desire still holds performance slot 14. Measured both ways rather than argued: as-performed yields 14 mappings including REVOLUTION; drop Desire to make the page match the release and it falls to 13. REVOLUTION's title is LOST -- silently, with nothing mislabelled, which is what would have made it hard to notice. PLN chose as-performed today as a documentary call about the page; this join quietly depended on the same answer, and nobody knew the two were connected. So: the assumption is written down where the join lives, and three tests hold it -- every released track gets a title, the encore by name, Desire maps to nothing rather than borrowing a neighbour's, and the hole at perf_track 14 is still really there. That last one matters: if segments_v4 is ever renumbered densely the reasoning stops holding, and the right outcome is a red test rather than a quietly missing track.
PLN (Algolia) authored -
PLN: "needs basic menu or at least top right X to close, and its too sticky atm if i close i wanna close it i guess maybe X and a [x] where x goes and comes and is the 'stick above' feature, but it anyway is all desks always." No menu -- two glyph targets in the header's right edge, in the same micro font as everything else: "[x]" toggles always-on-top (the x IS the state) and "X" closes. Dim by default, brighter under the pointer, which is the only affordance a frameless window can offer. T toggles the pin from the keyboard; Q and Escape already closed it and still do. Controls win over the drag. The whole surface is a drag handle, so without an explicit hit-test first the X would only ever have moved the window. Targets are laid out from the right edge with the glyphs centred inside them, not sized to fit the glyphs. Sizing to the glyph gave an 8px-wide X -- measured -- which is fine to look at and unhittable mid-set, and at 0.6 scale the two targets then had to either overlap or shrink below usable. Deciding targets first makes "disjoint and at least 18px" true by construction at every zoom, asserted across four scales and three widths. All-desktops is deliberately untouched: it is a KWin rule keyed on the app id, it is unconditional as PLN said, and toggling the pin must not disturb it. THE STICKINESS WAS NOT THE WINDOW. A clean exit is not a failure so systemd never restarted it -- but rig_units.ensure() starts every non-manual unit that is not active, and both gig-up and the Bridge watcher converge, so the lens came straight back with nothing in the output admitting why. Marking the unit "manual" would have answered the complaint by deleting the feature he asked for last week. So a deliberate close is recorded in $XDG_RUNTIME_DIR, ensure() honours the latch and says so, and anything that starts the lens on purpose clears it. That directory is wiped at logout, which is exactly the right lifetime: "always open" is about how a session starts, "closed means closed" is about what happens after he acts, and the two only looked contradictory. Closed for this session, back at next login, no state to remember to undo. Only a close the human asked for latches -- a compositor teardown is not an opinion. The latch path is a contract between three files that deliberately do not import each other, so a test asserts they agree rather than trusting three copies of an f-string. That test immediately earned its keep: rig_units.py used Path without importing it, on a line only reached once a latch existed. Also fixes a shadow of my own making: the new painter was called _paint_chrome, which is already a method on this widget, so it silently replaced the window-chrome painter and the selftest died on the signature. Renamed _paint_controls.
PLN (Algolia) authored -
Two unrelated failing tests, investigated in order. test_agreement_distribution_within_bounds asserted tracks_total == 73, a snapshot from before yesterday's catalog rebuild (73 -> 81 tracks). Bumping it to 81 would just re-freeze the same brittleness. Replaced it with test_tracks_total_matches_the_generated_catalog: assert the live view's tracks_total equals the CHECKED-IN catalog.generated.json's n_tracks. That file is a 1:1 derivation of this same view, so the two can only disagree on a real regression (corpus changed but catalog not regenerated, or the pipeline dropped/duplicated a row) — never on honest growth. Verified it still fails by corrupting n_tracks and re-running. test_real_backlog_coverage_does_not_regress: opal-festival-2024's recovered tracks fell from 13/14 to 3. The obvious suspect was e8f7066a (yesterday's tools/setlist.py soundcheck filter) — ruled out by inspection (e8f7066a touches only tools/setlist.py + its test, nothing under armada/tide-table) and by bisection: replaying backlog_setlists.py's ANCHORS against every historical backlog.md since the anchors were authored (5acf72f7, 2026-06-06) shows n_resolved holding at 14 across ten intervening commits and only breaking at 49e1b78e ("update: post cosmic", 2026-08-23). The real bug: ANCHORS keyed gigs by a raw 1-based LINE NUMBER pinned once against a single backlog.md snapshot. backlog.md is PLN's running journal, not a stable document, so every one of those ten commits that inserted lines earlier in the file silently shifted every anchor below it. Opal 2024's anchor drifted through blank lines and a lucky near-miss for months (the collected span still happened to contain the same real content), then 49e1b78e pushed the drift past the header entirely into the tail of the PREVIOUS gig's block, collecting 3 unrelated lines instead of the setlist. Fix: anchor by the header's exact TEXT, resolved fresh against the live file on every build() (resolve_anchors()), instead of a line number frozen at authoring time. Self-healing against drift elsewhere in the file; raises loudly if a heading is ever actually renamed or duplicated, rather than silently mis-anchoring. The three anchors that carried an explicit end-of-block bound (opal-2025/Latin-Heritage bleed, mephisteuf's commented block, the 38c3-toilet cross-check) now store that bound as a line-count SPAN measured from the anchor, since that span lives inside the block and doesn't move when the anchor does. tools/setlist.py's soundcheck filter (e8f7066a) is untouched and still yields 15 entries for OPAL 2026 — never the culprit here, just an unrelated same-day change that made a plausible but wrong prime suspect. Full suite: 468 passed, 2 skipped (465 baseline + 2 fixed + 1 new regression test), up from 2 failing.
PLN (Algolia) authored -
Captain's log for the loose-thread sweep, plus the new tool it produced. tools/ardour-drop-missing-sources.py: Ardour's "Missing File" modal appears once per unreadable source, no flag suppresses it, and "skip all missing" does not fix it — Ardour substitutes silent stubs so the session loads, but the dead names stay in the session file, so the modal returns on every launch. PLN hit it twice in one evening. This drops the <Source> and every <Region> naming it, in <Regions> and in all 14 playlists. It refuses to act on a guess, twice over: - it will not write while Ardour is running, because Ardour holds the session in memory and would save over the edit on quit, silently undoing everything; - it will not drop a reference until it has PROVEN the file is gone from both the session tree and the Freebox, the SSOT for audio here. A reference is the last breadcrumb pointing at a lost take. And an unfinished search reports as unfinished rather than as "not found" — only one of those justifies dropping the breadcrumb. Its first version rglobbed the Freebox once per missing file: 24 full traversals over the network, and it never finished. Now one indexed os.walk per root against a set of names, with the deadline checked per directory so a fruitless search still stops on time. 041 is marked resolved in place: the blocker's premise was wrong.PLN (Algolia) authored -
🌊 Un-parks the rewire that was blocked behind the Ardour double-launch (log 041). The patch saved with that log no longer applied — perf-tray.py moved under it in 90afa239 — so this is reimplemented against the current file rather than forced; `git apply --reject` had left the file half-patched and was reset before starting. PLN, 2026-09-05: "click launch gig started pulsar, not ardour??", then "is our launch gig now autodoing?". The old button opened Pulsar and nothing else. That was deliberate — #116 says the hot path must not be able to start the sound by accident — but the name promised a gig, so it read as broken rather than as careful, and the rig's real launcher (the Bridge's RIG UP, which converges every unit and then opens the apps) had not been pressed in 6.7 days. A safety rule that makes the safe path invisible protects nothing; it just moves the launch to a hand-typed command. So the tray and the Bridge now drive the SAME reconciler, and #116 is honoured by ARMING instead of by crippling — the idiom already proven two menu entries below on Restart SuperDirt: - SuperDirt already up → the press only converges and focuses. One click. - SuperDirt down → the press would start the sound. First click arms and says so in a tray notification; a second click, which means reopening the menu, commits. Arming lapses after 10 s. - No confirmation dialog, ever: a modal stealing focus mid-set is its own hazard (#136). "Would this start the sound?" is answered from SuperDirt's unit state, not by asking the Bridge — the cheap local truth, and it keeps an HTTP round trip out of the front of a button press. The label carries all three states so the dangerous one announces itself before it is pressed. The POST runs in a worker thread and is never waited on. A cold converge takes ~100 s, and the reason PLN launched Ardour by hand mid-evening — believing RIG UP had failed — was a face that showed a spinner and no window. The Bridge answers immediately and runs the job in the background; the Bridge page opens right away. Nothing in the worker touches Qt. Verified end-to-end, with Ardour already running and SuperDirt up: POST /api/rig {"launch_apps":true} → {"ok":true,"status":"started"} journal: pulsar: SPAWNED pid 2779892 (it was down) ardour: running → focus midimon: running → focus ArdourGUI process count: 1 before, 1 after. That "ardour: running → focus" is the line this whole thread existed to produce: the last time this path ran it said "launched" while Ardour was up, and the two instances killed each other over the session lock. Tray after restart: active, NRestarts=0. Incidentally closes the suspected perf-tray memory leak — systemd's own accounting reports a 59.6M memory peak over 2h15m wall clock, so the "2.6 GB RSS" in the hand-off was a misread of virtual size, not a leak. Nothing to fix.
PLN (Algolia) authored -
gig-up's "surface grid intact" blocker was NOT the CosmicFest false positive again — that was the first thing to rule out, because check-drift.sh once reported "drift" that was really PLN's own in-progress set prep and proposed `git checkout` on 13 files as the remedy. Checked: check-drift.sh passes, and it clears all five currently-modified .tidal files individually. None of PLN's open edits are implicated. The failure is gig-up's stricter composite (gig-up.sh:343), which also demands `migrate-columns.py --plan` report zero moves. It reported 10, across four files that are all COMMITTED and all untouched by b5ad8b6c ("finish the #94 column remap", 2026-08-23) — `git show --stat` has no match for any of them. Three were last edited 2026-08-21, before that commit; the remap-finish simply missed them. So the direction is forward onto the authored grid, not a reversion of anyone's work — a standing migration debt, not a clobber. Two of the three are SWAPS (^52<->^32 in perfect, ^89<->^57 in vague_de_crime), which is exactly where a naive rewriter eats itself: renumber ^52->^32 first and the following ^32->^52 sees the value it just wrote, collapsing both onto one control. Checked the tool before letting it near the tracks — rewrite() maps each line in a single `CC_REF.sub` pass, so every match is resolved against the ORIGINAL text and a swap is atomic. Confirmed in the diff: both directions flipped, all four occurrences in vague_de_crime, nothing collapsed. Verified after: pvlint 3 tracks, 0 errors; silent-eval --seeded OK (every declared orbit still emits events); migrate-columns --plan now reports 0 moves for these three. Deliberately NOT applied: something_about_drums.tidal. Its five moves come with an overflow (d3's ^44 has no slot in the new grid), and the tool's remedy for an overflow is to COMMENT OUT every live line of that orbit and head it with a FIXME. That silences part of d3 until it is rewired by hand — a musical consequence, not a mechanical renumbering, so it is PLN's call and gig-up will keep flagging the grid until he makes it. Also noted, not touched: perfect.tidal:61 carries a pre-existing PV010 warning (d5 gates on ^89, momentary row F, wants ^57 on the latching row E), and the commented-out alternative at the_revolution_will_be_sampled.tidal:47 still names ^52 — the rewriter deliberately never edits comments, since a commented ^NN is an alternative PLN may re-enable.
PLN (Algolia) authored -
Three of gig-up's four standing NO-GO blockers were one file and one buffering bug. Cleared them, verified each by planting the failure back. **something_about_drums.tidal — two typos, both silent by design of the tooling** :13 `(<| "~ <s s <s!3 ~> <~!7 [~ s]>>")d1` — a stray `d1` pasted straight after the closing paren, no operator, no newline. GHC reported it as "Variable not in scope: d1" at 13:56, and because a parse error takes the whole do-block with it, the reported line belonged to the GROUP, not to the break. The d2 block never ran. :81 `d8 $ gF1 $ gM1` — d8 copy-pasted from d1's block without updating the family macro. The authored map puts d8 in gM2 with d2 and d3, so d8 was wired to the wrong mute family: pressing d8's mute would have taken d1 with it, live, and nothing about the code looked wrong. after: `silent-eval --seeded` OK (every declared orbit emits events), `fix-mute-roles --check` WOULD REWRITE: 0 lines, `pvlint (setlist)` ok. **check-boot-blocks.py — the check was the third defect** `tools/check-boot.sh` run alone passed clean, every line green, while gig-up called the same check FAIL. The difference was not the rig. Under gig-up the checker crashed: ValueError: invalid literal for int() with base 10: '6C1o' `'6C1o'` is two output lines interleaved character-wise. The cause is in the capture: stdout and stderr are deliberately ONE merged pipe (separate capture would put every marker before every error and attribute each error to the last block — the mis-attribution this tool exists to prevent), but GHC block-buffers stdout while writing errors to stderr unbuffered. Two writers, one pipe, so a marker could be flushed into a chunk a stderr write had already cut into. Rare enough to look like a phantom, and it presented as "your boot helpers are broken" — the rig's most alarming failure — when nothing was wrong. - `hSetBuffering stdout LineBuffering` is now the script's first statement, so each marker is one atomic write, in order. - Markers are self-delimiting (`@@PVBLOCK i start @@`) and matched by regex, so a torn one CANNOT parse as intact. - A torn marker, or a parse error that cannot be placed, is now INCONCLUSIVE (rc=2, "rerun") rather than a crash — and rather than a silent skip, which would have hidden a real error behind a green verdict. Verified: 3 consecutive clean runs, then a planted unbalanced paren appended to the block starting at :581 was caught and blamed on exactly that block (rc=1, "block 16 (starts near bt.hs:581) is NOT one statement"). A checker that cannot catch a planted bug is worth nothing, so that test is the one that counts. gig-up: 4 blockers -> 2 (surface-grid migration debt, fader baseline).PLN (Algolia) authored -
The bug: midiviz resolved its ALSA source port ONCE at startup and shelled out to `aseqdump -p <port>`. Unplugging the LCXL removes the ALSA client it was subscribed to, `aseqdump`'s stdout hits EOF, the reader thread's for-loop just returns, and nothing else was keeping `app.exec()` alive — the window closed with exit 0. Because that is a CLEAN exit, `Restart=on-failure` never fired, so PLN's "always open" glyph-rain lens was gone for the rest of the session the moment he unplugged the board. Reproduced from tonight's own journal: `midiviz.service` ran 6m57s and exited status=0/SUCCESS the instant the surface came off USB — textbook rig failure mode #1, "a binding resolved once, killed by a replug, never re-resolved", except this time the binding was the whole window's reason to exist rather than just a port variable. The fix, mirroring the resolve/rebind pattern already proven in `tools/lcxl-leds.py` (`find_seq_port` + `invalidate_ports`, cached and dropped on failure): - `Reader` now tracks `.alive()` (child process poll) and marks itself `error = "closed"` when its `aseqdump` child exits on its own (vs. an intentional `.close()`), so the difference between "I quit" and "I died" is visible to the widget. - `MidiViz` gained a second QTimer (`RECONNECT_MS = 2000`, matching `midi-autoconnect.sh`'s own reconcile cadence) that re-resolves the source every tick and swaps the `Reader` in place if it moved, died, or vanished. Losing the source is never fatal any more: the window drops to its existing idle/dim-pulse paint state (already built for "no events recently") and keeps ticking, painting, and listening for the surface's return — no `sys.exit`, no fatal path added anywhere. - Liveness is gated by `_hardware_present()`, a `type=kernel` + name-match scan of `aconnect -l` reused from `midi-autoconnect.sh`'s `DIRECT_LEG_AWK` (`hw = ($0 ~ /type=kernel/ && $0 ~ /Launch Control XL|LCXL/)`). This matters because `lcxl3-driver.service` publishes a VIRTUAL port literally named 'ParVagues LCXL3' — the translated, corpus-numbered stream `resolve_watch_port()` deliberately prefers, since that is the CC numbering the grid and every `.tidal` file actually speak. That virtual client can outlive a physical unplug for a beat if the driver hasn't noticed yet, so a bare name match would report "still connected" against a ghost carrying nothing. The rebind tick distrusts a match ONLY when the matched label is itself LCXL-named and no real hardware backs it; a "Midi Through" catch-all match needs no hardware and is trusted as before. Content still comes from the preferred (possibly virtual) port — only the "is it actually there" judgement moved to hardware. - A user-pinned `-p` port keeps working, checked instead against `aseqdump -l`'s live listing (a pin surviving a client renumbering across replug is not guaranteed, same as any other resolved-by-address binding). - `tools/midiviz.service`: `Restart=on-failure` → `Restart=always` + `StartLimitIntervalSec=0` (moved to `[Unit]`, where it belongs — the first install attempt logged "Unknown key 'StartLimitIntervalSec' in section [Service], ignoring", caught before it shipped) as belt-and-braces under the in-process fix, since the unit holds no audio ports and a restart loop costs nothing real. Verified: - `--selftest`: `parsed=68 ingested=250 frames=159 paints=170 grabs=83 distinct_sampled_colours=165 platform=offscreen -> PASS`. - Reinstalled the unit (`install -m644` + `daemon-reload`); no more "Unknown key" warning in the journal on the next start. - Live restart: `ActiveState=active`, `MainPID=2728202`, `NRestarts=0`, bound to the "Midi Through" fallback (no LCXL physically plugged in tonight, confirming the hardware-gated fallback still works with zero surface present). - Port-loss simulation (couldn't unplug hardware; killed the reader's `aseqdump` child directly — the same failure shape as the source disappearing under it): child pid 2728206 -> `<defunct>`; within the next 2s rebind tick a fresh `aseqdump -p 14:0` (pid 2730846) appeared as midiviz's child. Main PID stayed 2728202 throughout, `NRestarts` stayed 0, `ActiveState` stayed `active` — recovered entirely IN-PROCESS, no systemd restart needed. CPU time kept accumulating (1.237s over 38s wall) proving the paint timer never stopped ticking.
PLN (Algolia) authored -
The parked blocker was wrong about its own cause, which turned out to be the more useful finding. RIG UP answered one press with "ardour: launched — launched Ardour" while Ardour was already up. The two instances collided over the session lock and BOTH exited, taking all twelve orbit links with them mid-evening. The obvious suspect was is_running()'s `exe` regex, so the GIG UP rewire was parked behind "fix the detection first". Measured cold, the detection is fine. With Ardour running, is_running() returns True: argv0 is `ardour-9.7.0` (the wrapper /usr/bin/ardour9 is a shell script that exec's /usr/lib/ardour9/ardour-9.7.0) and `[Aa]rdour[-\d.]*$` matches it. Two presses in a row now correctly answer "running -> focus". The false negative is not reproducible, and -- the actual defect -- nothing anywhere recorded the decision, so the cause cannot be recovered. The incident had to be reconstructed from a chat transcript. So stop repairing a probe that measures correctly, and remove the probe's veto over an irreversible action: - A spawn guard keyed on a pidfile in $XDG_RUNTIME_DIR, consulted only after is_running() says no. It blocks while the pid we spawned is alive, plus a 20s window covering fork->exec->/proc visibility -- the one interval in which is_running() is legitimately blind. A pidfile rather than a module global because the two faces are two processes: the web Bridge and the perf-tray both call launch(), and an in-memory note in one is invisible to the other. - The guard re-checks process IDENTITY, not just liveness, so a recycled pid cannot jam it shut forever. A guard that fails closed permanently is its own outage. - _log() writes every launch decision to stderr -> the journal. - Ardour additionally sweeps a SUPERSEDED .pending aside before launching. No flag suppresses the "recover from crash?" modal -- only the file's absence does -- and that modal is exactly what the hot path must not contain (#136). It moves (never deletes, into the session's own dead/) and only when the session was SAVED AFTER the pending was written, i.e. a later save already superseded it. A .pending newer than the save holds unsaved captures; that one deserves a human, so its dialog is left to appear. Verified live, in this order: 1. cold launch -> SPAWNED pid 2708769, session path passed, 1 ArdourGUI 2. immediate 2nd press -> "running -> focus", still 1 ArdourGUI 3. is_running monkeypatched to lie False (the incident's exact condition) -> "NOT spawning -- we started pid 2708769 14.9s ago", still 1 ArdourGUI 4. same, 52s later (past SPAWN_GRACE) -> still refuses, on pid liveness 5. dead pid + old timestamp -> guard stands down, launching is possible again Also: the session chooser is gone from every entry point. Stock ardour9.desktop runs `ardour9` with no argument, so every launch from the menu asked "Tidal Live or Tidal Multi?" -- a modal in the hot path, and a chance to open the ARCHIVE by mistake (that same mixup produced a confidently wrong fader report on 2026-07-28). A user-local override sends the default click straight into Tidal Live and keeps the chooser as a right-click action. Stale comment corrected: the real binary is 9.7.0, not the 9.2.0 recorded there.PLN (Algolia) authored -
Eight commits of rig work that had accumulated on the branch while the shared checkout sat on it. Reviewed as a diff, not taken on faith: perf.sh stop DWIM-launching Pulsar into /usr/local/sbin -- 183 SIGABRTs between 08-19 and 09-05 because BASH_SOURCE resolved to the deploy path, and the perf watcher re-entered every 30s. rig_units.py neither LCXL painter may be auto-started blind; gig-up.sh's generation-aware chooser owns that decision. midiviz its own Wayland app id (desktopFileName, not applicationName), so focus and KWin rules can name this window and no other; plus the CC readout PLN asked for while wiring. logs 040/041 the phantom port, and the GIG UP rewire parked behind the Ardour double-launch bug. The four other branches were already upstream by patch-id. # Conflicts: # TASKS_DUMP.mdPLN (Algolia) authored -
PLN ran the root install at 20:06. Verified rather than assumed: the deployed /usr/local/sbin/perf-audio md5 matches the repo copy, the bug's fingerprints (nohup pulsar, SCRIPT_DIR) are absent from the deployed script, and prioritization is retained. Behavioural confirmation is the part worth keeping: the Bridge's perf watcher still reasserts every cycle, but now only prioritizes -- no launch attempts in the journal -- and the last Pulsar SIGABRT in coredumpctl is 15:58:44, before the deploy. The counter that ran 183 deep since 2026-08-19 has stopped.
PLN (Algolia) authored -
Structured archive entry. The keeper: two bugs hid each other for a month. One file's module-level sys.exit aborted pytest collection for the whole directory, so the obvious command ran zero tests while 273 passed file-by-file — and that invisibility concealed the file's own month-old red check, whose NAME was a complete bug report about a soundcheck being counted as OPAL's first track. A test nobody can see failing is worse than one that does not exist, because its name reads as reassurance. Fixing the harness surfaced a real correctness bug at no extra cost.
PLN (Algolia) authored -
Refreshes the resume state. The R1-R5 block from the first pre-compact is now partly stale — R2 closed (PLN raised the faders and saved; check-mix reports all 12 orbits reaching master), both R5 test items fixed, and the suffix question answered by evidence rather than taste. Three things landed after those resume points that a cold reader needs: pytest tools/tests/ had been running ZERO tests because one file's module-level sys.exit aborts collection, and behind that invisibility the OPAL setlist's first track was a soundcheck, so every tool reading it believed the set had 16 tracks opening on Quand on décolle. Both fixed; suite is 274 green in one command. Separated what is genuinely PLN's from what is merely undone, because that distinction is the whole value of this block. Four decisions block everything (--go on the 14, /desire specifically, CosmicFest tone, gig page as-performed vs as-released) plus one ears question about nine faders moving where four were broken. Four next moves need nobody, and the first is a 15-minute audit of whether anything consumed the phantom setlist positionally.
PLN (Algolia) authored -
Two bugs that were hiding each other, which is why both survived a month. test_setlist.py ended in a module-level sys.exit(), pytest raises that during collection, so 'pytest tools/tests/' aborted the whole directory and ran NOTHING while 273 tests passed file-by-file. And what that invisibility concealed was test_setlist's own failure, on a check whose name states the bug: OPAL's backlog opens with '## SOUNDCHECK / -- Quand on decolle', entries() took every item under the gig heading, and so every tool reading the setlist believed the set had 16 tracks opening on Quand on décolle. The recording has 15, opening on Ceci n'est pas Une Bombe. A test nobody can see failing is worse than a test that does not exist, because its name is a correct bug report that everyone reads as reassurance. Whole suite now runs in one command: 274 passed, 0 failed.
PLN (Algolia) authored -
Two bugs that were hiding each other, which is why both survived a month. THE SUITE RAN ZERO TESTS. test_setlist.py was a plain script ending in a bare module-level sys.exit(), and pytest raises that during COLLECTION — so 'pytest tools/tests/' aborted the whole directory with an INTERNALERROR and ran nothing, while 273 tests passed when invoked file-by-file. Every test in this repo was invisible to the obvious command. Guarded the exit under __main__ (the script still works run directly) and added one assertion so pytest actually sees the checks. Whole suite in one command now: 274 passed, 2 skipped, 0 failed. AND THE THING IT WAS HIDING: test_setlist had been failing all along, on a check whose name says exactly what is wrong — 'a deliberation note is not a track (the quand_on_decolle phantom)'. OPAL 2026's backlog section opens with ## SOUNDCHECK -- Quand on decolle and entries() takes every list item under the gig heading, so every one of the tools reading this setlist believed the set had 16 tracks opening on Quand on décolle. The recorded set has 15 and opens on Ceci n'est pas Une Bombe (segments_v3/v4 track 1). The parser now yields 15, in the right order. The fix is deliberately NOT the section whitelist entries() refuses to build — that one tried to enumerate which of PLN's prose headings are part of the set and died on '## Livecoding Techno DNB Nu-jazz'. This is the inverse and far narrower: naming the one phase that is definitionally not a performance. Three patterns, and skips print to stderr, because a dropped track and a skipped soundcheck must never look alike. Fixed the parser, never backlog.md — the backlog is PLN's own file and it was right. A soundcheck IS in his set list; it just isn't in his set. Verified by breaking it: neutered _NOT_THE_SET_RE, watched test_backlog_setlist_checks_all_pass fail naming the quand_on_decolle phantom, restored, 274 green. the first track was never the first track. sixteen tools agreed with each other and none of them agreed with the recording.PLN (Algolia) authored -
Both were pre-existing, unrelated to today's check-mix.py/gig-log.py work (spotted while running the suite for that change). test_orphan_orbits.py: 9 of 41 tests failed (16-row setlist vs a frozen "13", and 6 HAND_MEASURED orbit sets vs the parser). Root cause confirmed by git log + grep, not a parser bug: the setlist grew from 13 to 16 tracks since the fixture was written, and commit 2376e431 (2026-08-01, "d10 is the riser") added a d10 safe-riser idiom to 5 of the hand-measured tracks (bombe_dj, wap, you_my_sunshine, mafia_sans_serif, desire) after the 2026-07-28 hand measurement date; piment_bresilien went the other way (d10->d9, PLN's own request, commit 71bb9bc2/5e5a37ad). Fix: the setlist-length assertions are now derived from an independent sed-based row count of the setlist file (same pipeline check-tracks.sh uses) instead of a literal that rots every time PLN adds a track; the HAND_MEASURED dict and the vague_de_crime->bombe_dj orphan assertion are re-measured by hand (grep on each file's dN lines, not by copying the parser's own output) against the corpus as it stands today. No assertion loosened — 41/41 pass, same invariants, current facts. test_lcxl3_display.py: pytest collected 0 items. Cause: this was written as a plain script (module-level `check()` calls, run via `python3 tools/tests/test_lcxl3_display.py`), like test_setlist.py, with no `test_*` function pytest could find — a file that cannot fail under pytest, i.e. silent coverage loss. Converted to 30 ordinary pytest test_* functions (one assert group per logical check), same assertions, nothing dropped or loosened. Verified live: broke rec_line's hour rollover (divmod 3600->3601) and watched test_rec_line_formats_hmmss_past_an_hour fail with the exact wrong value (REC 1:02:02 vs 1:02:03), then reverted. Suite: 234 passed/9 failed/2 skipped -> 273 passed/2 skipped (ignoring test_setlist.py, a separate plain-script file, out of scope here — it already breaks whole-directory pytest collection via a module-level sys.exit and predates this fix). test_gig_log.py untouched: 169 passed before and after.
PLN (Algolia) authored -
Ran the new generation lens proactively against Cosmic26_master, before that release goes anywhere: 12 OK, zero stale, zero strays. The exact opposite of OPAL, for two reasons that are lessons rather than luck — only one render generation exists on disk, and every permalink is gig-scoped via --permalink-suffix, so no CosmicFest upload could collide with the OPAL cut of the same score. Four of these titles exist at both gigs. That answers an open board decision with evidence instead of taste. It asked whether to keep OPAL unsuffixed for consistency; the suffixed release has 0 permalink pathologies and the unsuffixed one has 14. Consistency with a broken scheme is not a virtue, so new OPAL uploads get suffixed. The 6 correct unsuffixed tracks stay — they are right, and renaming spends a permalink for nothing. Found in passing: CosmicFest is missing exactly 2 tracks, and they are almost certainly the two already-known vanishers that logged '✓ live at', passed an API check, then 404'd. A plain idempotent re-run converges. Which is the standing lesson landing on its feet: verify the shelf AFTER a run, not only during it — 'N uploaded, 0 failed' was never evidence that N tracks exist, and it took a lens three weeks later to notice two were gone.
PLN (Algolia) authored -
check-mix reports OK, all 12 orbits reach master. Third recurrence of the saved--inf bug, closed by PLN's hands. Two things fell out of verifying it. check-mix resolves the saved session with Ardour CLOSED — it reads the file, not the process, so the fader check can run in a pre-gig script before anything is launched. And the diff is bigger than the fix: orbits 02-06 were in a coherent -8 to -10 dB trim set and are now all near unity, so nine faders moved where four were broken. Master is still -0.2 so nothing clips, but the balance between orbits shifted ~9 dB on five of them. Recorded as a QUESTION, not a finding. The LCXL3's faders are absolute-position controls and touching one snaps the DAW value to the pot — which also means the board's 'v3 retires pot-pickup' claim covers the endless ENCODERS only; the eight faders are still absolute and that whole class survives the upgrade. But PLN may simply have dragged them on purpose, and a cause nobody asked about is not a cause.
PLN (Algolia) authored -
Found while shutting down: unplugging the LCXL took midiviz's ALSA source with it, the process exited cleanly, and Restart=on-failure did not fire. The permanent lens is only permanent while the board stays plugged -- which is the same resolve-once stale-binding shape as the zombie driver this session was opened to fix. lcxl-leds.py already has the fix pattern (re-resolve per send, cache invalidated on failure).
PLN (Algolia) authored -
The rewire (tray Launch Gig -> POST /api/rig, arm-then-confirm) is written and loads clean, but testing it end-to-end killed Ardour: launchers.launch('ardour') reported 'launched' while Ardour was already running, a second instance collided with the first over the session lock, and both exited -- taking all 12 orbit links with them. A false negative in is_running() wired to a tray button is worse than the mislabeled button it replaces, so it does not ship until launch() fails closed. Parked as a patch beside the note rather than committed (unvalidated) or left loose in the worktree (this checkout is shared and another session commits into it by pathspec). Carries the rest of the session's open ends too: the perf.sh redeploy that is committed but not live, gig-up's four pre-existing NO-GO blockers, perf-tray's 2.6GB RSS, and why gig-down is blocked on the Freebox being down.PLN (Algolia) authored -
Captain's log for the gear-failure pass: one zombie lcxl3-driver holding a stale binding to an unplugged mk3 explained three of PLN's five symptoms, the fourth was a mislabeled tray button, and the fifth was tooling that had been running invisibly all along. Written for mining later — the sharp bit is that name-based identity is the actual bug, and it caught the investigation as well as the rig: a subagent grepped aconnect for LCXL3, hit the driver's own phantom virtual port, and reported the hardware was back.
PLN (Algolia) authored -
TASKS_DUMP has accreted amendments since 2026-08-16 and the oldest sections are now the least true — but a reader arrives at the top, where A3 still says /take-five-drops uploaded '24s short', that every other track 'matched to the second', that 8 of 15 are up, and that no artwork exists. All four are wrong, and the first two are the exact reasoning that let six stale uploads pass a duration check in August. So: a READ THIS FIRST banner naming what is superseded and sending the reader to the resume points at the end, plus an inline stop sign at A3's 'TWO DEFECTS' where someone reading that epic top-down actually hits it. The wrong diagnosis is kept rather than deleted, because the reasoning being auditable is the point — it just no longer gets to be the first thing anyone reads. A board whose front page is a year of stale confidence is worse than no board. Nobody rewrites 1700 lines; they act on line 90.
PLN (Algolia) authored -
lcxl3-driver was policy "login", so rig_units.ensure() started it whenever it was not already active -- and ensure() runs inside gig-up.sh --converge, which is what the Bridge's RIG UP calls. With the CLASSIC LCXL plugged that is actively destructive: the v3 driver has Conflicts=lcxl-leds-watch, so a converge would stop the painter that was correctly lighting the classic board, then fail to bind (there is no LCXL3 port) and crash-loop at RestartSec=5. That is exactly what happened between 11:26 and 14:36 on 2026-09-05 -- 86 restarts, the board dark the whole time -- and today's repair would have been undone by the next press of GIG UP. lcxl-leds-watch was already "manual" for the mirror-image reason, stated in the comment right below this one. The rule generalises: neither painter may be auto-started blind, because "which painter" is a question only the plugged hardware can answer. gig-up.sh's leds() is the generation-aware chooser -- it greps aconnect for LCXL3 and restarts the right unit -- and it now owns the decision outright. Verified: `rig_units.py --ensure` (dry) reports "nothing to do" with the driver inactive and the v2 painter running, instead of queueing a start that would have gone dark. Trade-off worth knowing: on a mk3 rig the driver no longer comes up at login, so faders reach Ardour only after gig-up runs. That is the price of not guessing the generation, and gig-up is the documented bring-up.
PLN (Algolia) authored -
Structured archive entry for the session, written as standalone learning rather than a board line. Fourteen findings, of which the four that will outlive the task: SoundCloud accumulates generations and never replaces (so a stale upload is permanent until deleted); the wrong cut wore the cover art, which is why PLN's ear beat every tool to it; the 'matches to the second' tolerance was the bug, because full_duration is millisecond-exact and a loose window made two different cuts identical; and roll call cannot find a stray, because a check that only looks where it expects a thing never discovers it elsewhere. Written for a reader months out with no memory of any of it.
PLN (Algolia) authored -
Captain's log 040 and the pre-compact resume state. The log's finding, in one line: OPAL-26 sounded broken on SoundCloud while every rendered file was correct to the millisecond, because SoundCloud will not replace audio behind a permalink and our uploader is idempotent — so every re-upload under a fresh slug ADDED a copy. The whole Aug-12 first cut was still up beside the six correct v4 ones. And the tell PLN was actually reacting to: eight of the fourteen stale uploads wear cover art and not one of the correct six does. The superseded audio looked finished. His ear was reading the artwork. Two lens lessons in it, both recurring. 'Matches to the second' was a tolerance, not a verdict — SoundCloud reproduces full_duration to the MILLISECOND, so at the ±1.5s a transcode seems to deserve, the Aug-12 and v4 cuts of /wap are the same track, which is how six stale uploads passed a duration check in August. And roll call cannot find a stray, because a check that only looks where it expects a thing can never find that thing somewhere else. Plus the git one, which cost four commits: this is a shared checkout, another session moved HEAD between two of my commits, and 'git push origin master' from a non-master HEAD is a silent no-op that exits 0. The PUSHED in my own log was something I wrote myself. The board now carries R1-R5 written for a reader with zero context: exact commands with paths, the artifacts already on disk so nothing is regenerated, the three decisions that are PLN's and not a machine's, and the shared-checkout hazard spelled out before anyone commits into it again.
PLN (Algolia) authored -
183 Pulsar SIGABRTs between 2026-08-19 and 2026-09-05, 80 of them inside one 70-minute window, all from this one block. perf.sh launched Pulsar in "the script's directory", $(dirname "${BASH_SOURCE[0]}") -- but perf.sh is deployed root-owned as /usr/local/sbin/perf-audio (perf-audio.sudoers, for scoped NOPASSWD), so BASH_SOURCE resolved to /usr/local/sbin and Pulsar got /usr/local/sbin as its project directory. Coredump argv: /opt/Pulsar/pulsar --executed-from=/home/pln --no-sandbox /usr/local/sbin The Bridge's perf watcher (bridge.py:41 -> perf.py:run_watcher -> reconcile -> set_mode) shells out here every ~30s, and every mode flag routes through set_priorities, so the retry was unbounded: crash -> pgrep finds nothing -> next tick tries again. No backoff, no lock, no exit-status check. And the "✓ Pulsar launched" line proved nothing: the verification was a 0.5s pgrep presence poll that caught the doomed Electron process alive during startup and returned before it aborted. Fixed by removing the launch, not by resolving the directory better. Setting priorities is this script's job; starting applications is not. Ardour, two branches up, has always been prioritize-if-present with no launch branch -- Pulsar is now symmetric with it. Both the tray and the Bridge have real Pulsar launchers that pass the right project directory and surface failures. #116 also wants the hot path unable to start things by accident, and a root script spawning a GUI editor through `sudo -u $USER env ...` every 30s is the opposite of that. NOT LIVE YET: /usr/local/sbin/perf-audio is a root-owned copy and still carries the bug. It needs sudo install -m755 -o root -g root perf.sh /usr/local/sbin/perf-audio which is outside the sudoers whitelist, so PLN has to run it. Dormant until then -- the block only fires when no Pulsar is running.PLN (Algolia) authored -
The argv-only bug is fixed by extracting the 3-rung ladder into tools/ardour_session.py, imported by both gig-log and check-mix. gig-log's 'keep check-mix in sync' comment is gone because there is nothing left to keep in sync — one parser per concept satisfied by construction rather than by somebody remembering. 169 gig-log tests pass unchanged. Then it ran for real and reported Tidal 07, 08, 10 and 12 at -inf dB in the saved session. Third recurrence of that bug (04-08+12, then 02+10, now these four). PLN's parked two-minute check went from unrunnable straight to failing, which is the whole argument for wiring a check into a caller: it gets run under conditions the unit test never sees. Also logged: two pre-existing test failures the suite run made visible (test_orphan_orbits 16 rows vs 13 expected; test_lcxl3_display collects zero tests, and a test file that cannot fail is not a passing test), and the uncommitted duplicates left sitting on claude/rig-streamline so whoever rebases it does not double-apply them. the fader was down in the file, not in the room. nobody could hear the difference because a closed fader and a silent instrument look identical on a post-fader meter.PLN (Algolia) authored -
PLN, after the first look at it: "the midi mon is beautiful! so lets streamline, now it should always oppen, with always on top, all desktops displayed when i switch from desk 1 to 2 3 4" -- and then "would help to see the chan num: 33 or 53 etc so as i press i can wire". Always-on-top needed no change: the WindowStaysOnTopHint at :293 is unconditional and KWin honours it. The other three did. IDENTITY. The Wayland app_id comes from desktopFileName, not applicationName, so KWin saw resourceClass "python3" -- verified by querying workspace.windowList(), not assumed. Two consequences: a window rule keyed on the app id would have matched EVERY python GUI on the box, and launchers.focus_window("midimon") could never match, so clicking the Bridge/tray button on an already-running lens silently did nothing. Set desktopFileName + ship a .desktop (which also silences the portal's "App info not found" warning), and point the launcher's focus at "midiviz". ALL DESKTOPS is a compositor property, not a Qt flag, so it lives in a KWin rule (~/.config/kwinrulesrc, wmclass=midiviz, desktops= empty with desktopsrule=2/Force). Declarative on purpose: it applies at map time, every time, with no post-launch retry racing the window's existence. Verified on the live window rather than inferred from the key names -- onAllDesktops=true keepAbove=true. CC NUMBERS, two layers. Per-cell dim decimals are the map you consult while wiring; they render in their own pass because f_micro is already current there, and a per-cell setFont would add 96 font switches a frame to a path this file keeps to a counted number of drawText calls. They are skipped when a cell is too narrow to hold them beside the value. And the header now prints the last-touched control as ^53=127 -- decimal, with the corpus's caret, so it is the literal token to type into a pattern instead of a hex value to convert mid-set. Autostart as a systemd --user unit bound to graphical-session.target, not default.target: it needs WAYLAND_DISPLAY/XDG_RUNTIME_DIR imported into the user manager first. Registered "login" in rig_units.py. Paint path exercised by the offscreen selftest: 116 paints, PASS.PLN (Algolia) authored -
check-mix.py resolved the live session from argv only, so any Ardour launched from the GUI's recent-sessions list (bare argv) went unresolved and check-mix silently fell back to a guessed path. gig-log.py's FilesLens already solved this with a 3-rung evidence ladder (argv > cwd > open fds, commit abad7515) and had a sync comment flagging check-mix as needing it. Extracted the ladder into tools/ardour_session.py — one resolver instead of two, per feedback_one_parser_per_concept — and made both gig-log.py and check-mix.py import it. gig-log's FilesLens keeps its instance-level caching/logging but delegates all evidence-gathering to the shared module; the now-unnecessary "keep in sync" comment is gone. Verified: gig-log's 169 pytest tests still pass unchanged (they exercise all 3 rungs against synthetic /proc fixtures). Live: Ardour was running ("Tidal Live" session, launched with argv), so check-mix.py resolved it for real and reported per-orbit gains — surfacing the same recurring bug this fix was meant to catch: Tidal 07/08/10/12 sit at -inf dB in the currently SAVED session. Rungs 2 (cwd) and 3 (open-fds) were proven against synthetic fixtures matching the live-rig gap, since the running process's argv already carried the session path.
PLN (Algolia) authored -
master sc delete --manifest exists: dry-run by default, identity verified in BOTH directions (a permalink that now resolves to a different sc_id is a refusal, not a warning), public tracks refused, a reason required per entry because a deletion without provenance is not allowed, batch capped before any API call, and a receipt that keeps every field plus the on-disk paths of the archived artwork and the source FLAC. The old test-namespace gate was not widened — a second gate was added beside it, and a test proves the old one is unreachable from the new path. Dry run: 14 deletable, 0 refused. All private, all uploaded in the same minute on 2026-08-12, and every one still has its FLAC in Opal26_master/tracks/, so deleting them loses a URL and not a recording. --go is NOT approved yet. Two caveats recorded loudly rather than buried. The delete TRANSPORT is unproven: no DELETE has ever been issued from this codebase against api-v2, and a clean dry run is not evidence the deletes will land. And the generator's first version lined stale uploads against the newest cut BY TRACK NUMBER — dropping Desire renumbered REVOLUTION from 15 to 14, so the manifest confidently compared Desire against REVOLUTION's duration. An unnamed reference, again. Also logged: four of my own commits landed on a concurrent session's branch because the shared checkout moved under me, and 'git push origin master' from a non-master HEAD is a silent no-op that exits 0. Recovered via worktree cherry-pick without touching their branch or the live tree. The worktree rule in CLAUDE.md has no orchestrator exemption, which is what I had assumed. two sessions, one working tree, and a push that said yes to a question it had never actually been asked.PLN (Algolia) authored -
The regen moved timecodes by up to 21s (Piment +21.0, REVOLUTION +19.0, Gimme Acid +16.7). That file is not just website furniture — Slopmotion cuts visuals to it, so the release videos were pointed a third of a minute off the music on a third of the record. The more interesting finding is a parser conflict the regen walked into. build_gig_tracksjson.py drafts NEW gig pages and never computes the styleDistribution that LiveEvent.js renders; gig-metadata.py regenerates existing ones and does. They also disagree outright on REVOLUTION's style (jazz vs nujazz). Two parsers, one concept — so the board's line that build_gig_tracksjson 'is now the way to make any gig page' is too strong and is corrected here rather than left to bite the next regen. Left explicitly to PLN: whether the OPAL page documents the set as PERFORMED (15 tracks, Desire included) or as RELEASED (14). Desire has no v4 boundary because it was cut, so its v3 timecode rides through unverified. A page can honestly be either thing; only he knows which one he wants a visitor to get.
PLN (Algolia) authored
-