Sidebar paints a glyph and digit per section, the palette button a
magnifier and ⌃K; page titles, subtitles and the Actions heading are
spoken only. web/scripts/visible_words.mjs lists chrome words in the
built pages (146 -> 4, the command-button labels).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Partial textgate rework: job state pills, notices, empty and
loading states, copy button and timeline legend paint icons; the
old wording is spoken only. Guard test covers these pieces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raise the pylint hook to --fail-under=10.0 and make it lint the whole
package on every run (pass_filenames: false): over the staged subset a
9.99 tree passed at commit time.
Ports de2aea3 (9b9342ba-1) onto main, minus its web/ and requirements.txt
bumps and the iptables dedupe f1822f1 already landed; _hltb_confidence and
_hltb_search keep main's 429 handling. _search_batch splits out
_acquire_auth / _search_headers, and _stats.py is back to 250 lines.
Drops the min-similarity-lines = 8 floor (0 duplicate-code hits at the
default) and sets max-attributes = 15 for the flat persisted records, as
kuhy decided. Tests stay outside pylint, as at every site already at 10.
Todo: fda71ed1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set [tool.pylint.similarities] min-similarity-lines = 8 (as diet-guard)
and refactor the remaining R0801 hits: the store and total-block
iptables modules now share create_fresh_chain / add_drop_rule /
drop_chain in _store_tools. No duplicate-code disable or exclude.
Run pylint serially (pyproject jobs = 1, hook --jobs=1): parallel mode
pickles PyLinter with dill, which crashes on Python 3.15 (co_lnotab).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests isolate FOCUS_PROJECT_DATA so a live project never leaks in. Also
bumps the freshness gate's stale dev pins and websockets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYcmik6APnHfc9gEVy
Owned games Steam confirms have no achievements can never be assigned or
completed, so the enforcer no longer uninstalls, hides or kills them as
unauthorized. The gaming-time budget kill still applies to them.
Fail-closed: unknown status (no cache, other account, unreadable config
or snapshot, request error) keeps a game enforced. The set comes from
no_achievements_cache.json minus snapshot.json, so a game that gains
achievements is enforced again after the next scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWAkVShjD2Bo6hM88B
pnpm-lock.yaml had missed the previous bump (react-query 5.104.5,
typescript-eslint 8.72.0, vite 8.3.5); both lockfiles now match package.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYmCezqhUjUmMisF5YtM4F
The BG3 fast install of 2026-10-23 downloaded 160 GB, was refused by the
installer, and the web job still ended on a full bar, "Done!" and Succeeded
while Steam restarted the download from 0%.
- A failed run's reason is read from the installer's own run log (its ERROR
lines). A waiting job prints "Failed: fast install of <game>: <reason>",
which fails the job; `install` no longer says "Done!" after it.
- Daemon-started failures stay on the Dashboard's Fast install card (and in
ctl status, `error`) until a new run starts or the game is installed.
- install_game writes Steam's "needs downloading" manifest before the
installer runs: it only finishes a manifest it finds, so fresh installs
were refused for want of one.
- Command building moved to _fast_install_cmd (250-line cap).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYmCezqhUjUmMisF5YtM4F
pick and pick-manual no longer uninstall games that are already allowed:
the outgoing assignment is kept as a released pick (installed, no lock)
until 'pick --replace', done/check, or abandon-pick. The allowed set is
capped at max_manual_picks + 1; a pick over it is refused before the
candidate list / confirmation, naming the games. status and the web
Picks card list kept games with an Abandon button.
Fast install now tracks the installer log from the spawn offset: web
jobs drive the progress bar (per-depot %, phase) and echo a line every
30 s; daemon-started runs are exposed via ctl status / /api/daemon and
shown on the Dashboard.
Also: web deps bumped for dependency freshness (react-query 5.104.5,
typescript-eslint 8.72.0, vite 8.3.5, node 24.22.1 in CI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CUaX4zAa6H4sMu3jd6RFdb
When a now.json reason carries the start time it saw (leisure-watch
0.2.1 `start`, /proc/<pid>/stat field 22), only the live process with
that start time is signalled; a reused pid is spared. Absent (0.2.0) or
unusable, the old behaviour stands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
earned_time 0.16.0 made resolve(leisure=False) force pre-leisure pricing,
so the enforced call passing False would have stayed 5 h/8 h after
go-live. It now passes leisure_active(gaming day) explicitly (its own
redirectable LEISURE_FROM_PATH): the go-live gaming day is 7 h/10 h, and
00:00-05:59 on the go-live date stays pre-leisure, matching billing. The
shadow resolve keeps leisure=True.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
Its module-level rules_for runs before any autouse fixture, so a live
/etc/leisure-watch/leisure_from priced RULES. collection_isolated()
points every switch reader at a path that cannot exist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
One stale moment (resume from sleep, a watcher restart) while spent no
longer kills every browser at once. A trusted or paused tick, a clock
step back, or a gap between ticks over 10 s (a suspend) restarts the
window; untrust is tracked while budget remains too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
Defence in depth for a forged or buggy now.json while root: i3 and its
bar, X, systemd --user, dbus, audio, ssh/gpg agents, tmux/screen, every
terminal and shell, sudo, Claude (incl. its versioned native binary and
node), keepassxc, notification daemons, and Python running a locker,
leisure_watch or daily_limits are refused by exe/script. Each refusal is
logged once (journal + budget.jsonl leisure_kill_refused).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
A path naming the repo (git -C ~/src/wsg-grabber, pytest, an editor) no
longer counts as the reviewer: only the exe itself, or a Python
interpreter's script / -m module, and still never `scrape`. An updated
browser or player (exe link ' (deleted)') is still recognised, for the
sweep and the browser focus exemption.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
Dormant until earned_time.leisure_active(gaming day) -- the switch file
written by `leisure-watch go-live`. From that day:
- enforced `seconds` are billed by the shadow rule (lock pause, fail-closed,
once per wall clock); budget from plain resolve(); warnings and the cutoff
say "Leisure".
- at 0, beside the unchanged Steam path, _leisure_kill SIGTERMs (SIGKILL 30 s
later, per process) every focus/media pid in now.json except browsers,
every failclosed browser pid, and -- with now.json untrusted -- every
browser, media player and wsg-grabber reviewer. Root/system, lockers and
the enforcer's own chain are never signalled. Kills go to budget.jsonl.
- tests: an autouse fixture redirects both switch readers, so no test reads
/etc/leisure-watch/leisure_from; existing tests unchanged.
Re-pins earned-time to v0.15.0 (leisure_active).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
The once-per-window warning map kept every distinct error forever; an
error whose text varies per tick would grow the daemon without bound.
Entries older than the window are now dropped before each check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N