mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
i3blocks gaming_budget: list every earner from .rules.earners (anki 🗂), sub-hour bonuses as minutes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e288b073b9
commit
856866e8b8
@ -1,103 +0,0 @@
|
||||
{
|
||||
"intent": "Extract the last over-cap unit into github.com/kuhyx/digital-wellbeing (cloned at ~/src/digital-wellbeing), then delete --warn from the directory-depth-cap hook so the cap enforces for real. This is the closing step of the restructure campaign.",
|
||||
"scope": [
|
||||
"REMOVED: linux_configuration/periodic_background/digital_wellbeing/ (80 tracked files)",
|
||||
"REMOVED: 6 test suites + 2 test libs from linux_configuration/tests/",
|
||||
"REWIRED: check_and_enable_services.sh, setup_periodic_system.sh, install_core_system.sh, fresh-install/main.sh, installers/install_ungoogled_chromium_ubuntu.sh, tests/test_security_hardening.sh, tests/lib/security_apps.sh, tests/lib/security_pacman.sh",
|
||||
"CONFIG: .pre-commit-config.yaml (--warn removed, 2 stale excludes dropped), .github/workflows/shell-tests.yml, meta/shell-coverage-allowlist.txt, meta/pyproject.toml, .gitignore, meta/scripts/check_directory_depth.sh",
|
||||
"LIVE: /usr/local/bin/periodic-system-maintenance.sh",
|
||||
"NON-GOAL: the unwrapped discord/signal-desktop binaries (pre-existing) and the workout_locker error (different repo)"
|
||||
],
|
||||
"changes": [
|
||||
"Filtered history across all three paths the unit has lived at (63 commits). The current path alone holds 2 -- the same trap as hosts-blocker, and the reason the count was checked before filtering rather than after.",
|
||||
"The six suites that test this code lived in linux_configuration/tests/, not beside it. They were filtered separately (24 commits) and merged as a second root, then their hardcoded periodic_background/digital_wellbeing/ prefixes rewritten for the new root-level layout. A path filter of the unit alone would have left them in the monorepo testing code that had left it.",
|
||||
"Vendored linux_configuration/lib/common.sh into vendor/ rather than trimming it: set_actual_user_vars calls get_actual_user and get_actual_user_home, so a 4-function trim breaks silently. It went to vendor/ and not lib/ because test_music_parallelism.sh copies the unit's lib/ wholesale into a scratch worktree while stubbing the shared lib -- with both in one directory the real common.sh overwrote the stub and the test measured the wrong library.",
|
||||
"Generalised that test's layout-drift guard from a required ../ prefix to (../)*; the script now sits at the repo root so the prefix is empty. The guard caught this move, which is what it was written for.",
|
||||
"Dropped the two .pre-commit-config.yaml excludes for setup_midnight_shutdown.sh instead of moving them: lsattr shows nothing in the unit is chattr +i any more, and the fixer hooks leave the file untouched when allowed to run.",
|
||||
"Removed --warn from the directory-depth-cap hook and reworded its comment from 'deliberately --warn for now' to state that re-adding it re-opens the hole."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "./meta/scripts/check_directory_depth.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "'all checked paths are within 2 directories', exit 0. 46 -> 41 (after hosts) -> 0. The cap now enforces without --warn."
|
||||
},
|
||||
{
|
||||
"command": "per-root commit counts before filtering",
|
||||
"result": "pass",
|
||||
"evidence": "current path 2, scripts/periodic_background/ 41, scripts/digital_wellbeing/ 23; 63 on main. The 94 an --all union reported included unreachable refs and one merge commit that filter-repo correctly prunes."
|
||||
},
|
||||
{
|
||||
"command": "cmp each of the 80 tracked files against the monorepo copy",
|
||||
"result": "pass",
|
||||
"evidence": "ALL 80 TRACKED FILES IDENTICAL. node_modules (340 files) and __pycache__ correctly excluded -- the repo has 80 tracked files, not the 423 on disk."
|
||||
},
|
||||
{
|
||||
"command": "~/src/digital-wellbeing/tests/run_all.sh",
|
||||
"result": "pass",
|
||||
"evidence": "All six regression suites plus the discovered lib/tests/run_all.sh pass: 'All digital-wellbeing suites passed.'"
|
||||
},
|
||||
{
|
||||
"command": "env -i PATH=/usr/bin:/bin HOME=/tmp bash ./tests/run_all.sh",
|
||||
"result": "pass",
|
||||
"evidence": "Passes under a minimal environment, and no suite needs root or invokes pacman -- so the new repo needs no arch-tests job even though five of these ran in the monorepo's Arch container."
|
||||
},
|
||||
{
|
||||
"command": "sudo check_and_enable_services.sh (first attempt)",
|
||||
"result": "fail",
|
||||
"evidence": "'line 61: DW_REPO: unbound variable' -- the definition sat below its first use. shellcheck and grep both passed the file; only running it caught this."
|
||||
},
|
||||
{
|
||||
"command": "sudo check_and_enable_services.sh (after moving the block)",
|
||||
"result": "pass",
|
||||
"evidence": "Runs to completion: pacman_wrapper ok, makepkg_wrapper ok, midnight_shutdown ok, startup_monitor ok, periodic_systems ok, hosts ok, guest_mode_removal ok. Applied 3 fix(es)."
|
||||
},
|
||||
{
|
||||
"command": "sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256",
|
||||
"result": "pass",
|
||||
"evidence": "Exit 0. The manifest embeds ABSOLUTE paths and had named deleted scripts/ paths since the flatten, so the drift check reported a permanent false 'stale or tampered'. It now names ~/src/digital-wellbeing and verifies -- with unchanged hashes, confirming the extracted bytes are identical."
|
||||
},
|
||||
{
|
||||
"command": "report_live_state.sh --check | grep -c linux_configuration/scripts/",
|
||||
"result": "pass",
|
||||
"evidence": "0, down from the 8 pre-existing stale references at session start. Removals only; none added at any point."
|
||||
},
|
||||
{
|
||||
"command": "grep the six moved test names in .github/workflows/shell-tests.yml",
|
||||
"result": "pass",
|
||||
"evidence": "0 matches, and all four names still listed (test_i3blocks_persist_common, test_setup_dns_blocker, test_i3blocks_efficiency, test_security_hardening) resolve to files that exist."
|
||||
},
|
||||
{
|
||||
"command": "test_security_hardening.sh, current tree vs a detached worktree at HEAD",
|
||||
"result": "pass",
|
||||
"evidence": "18 passed / 1 failed / 5 skipped in BOTH. The one failure is 'Compulsive block wrappers installed' -- discord and signal-desktop lack their /usr/bin wrapper. Live-system state, identical before and after."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck on all nine changed shell files",
|
||||
"result": "pass",
|
||||
"evidence": "ALL CLEAN, no severity filter."
|
||||
},
|
||||
{
|
||||
"command": "source vendor/common.sh in a subshell and inspect stderr (post-push audit)",
|
||||
"result": "fail",
|
||||
"evidence": "Three 'No such file or directory' errors: common.sh sources common_packages.sh, common_services.sh and common_datetime.sh unconditionally, and only common.sh had been vendored. Every suite still passed -- the four functions used are defined above that block, and a failed source of a sibling is non-fatal -- so nothing caught it. Fixed in digital-wellbeing c5eb028 by vendoring all four and adding tests/test_vendor_closure.sh, verified to fail when any sibling is removed."
|
||||
},
|
||||
{
|
||||
"command": "sudo grep -rl 'testsAndMisc/linux_configuration/(scripts|periodic_background)' /etc /usr/local /usr/bin",
|
||||
"result": "pass",
|
||||
"evidence": "Broader than report_live_state.sh, which greps only /usr/local/{bin,lib} plus units and symlinks. Three hits, none from this work: pw_guard.sh names a path that still exists, and /usr/local/bin/dwm-session carries two pre-existing single_use/ paths from the flatten. dwm is dormant (lightdm user-session=i3, no dwm process) and one of its targets no longer exists anywhere -- reported, not changed, since dwm was deliberately abandoned."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"vendor/common.sh duplicates 163 lines of the monorepo lib. The two copies may drift; that is accepted, and the README says so, so a future session does not 're-fix' it by re-coupling the repos.",
|
||||
"The pacman wrapper's drift manifest is regenerated by the checker's own fix path. A machine that clones digital-wellbeing to a non-default location must re-run install_pacman_wrapper.sh or the manifest will name the wrong paths.",
|
||||
"install_core_system.sh on a fresh machine now hard-stops at three modules until both extracted repos are cloned. Deliberate and loud; INSTALLER_FIX_TASK.md tracks fresh-machine installer defects.",
|
||||
"The depth cap is now enforcing, so any future path more than two directories deep is a hard commit failure rather than a warning.",
|
||||
"digital_wellbeing/data/ (2 untracked files) went with the rm -rf. Verified safe to lose: it was never tracked on any historical path, and no script in the unit -- blocker, seeders or libs -- references data/ at all."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit to restore the unit, the six suites, the two test libs and every rewired caller, and to put --warn back on the depth hook.",
|
||||
"Restore /var/tmp/periodic-system-maintenance.sh.pre-dw to /usr/local/bin/.",
|
||||
"After rollback validate: check_directory_depth.sh --all reports 41 violations again, and sudo check_and_enable_services.sh still completes without an unbound-variable error."
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
{
|
||||
"intent": "The i3 gaming-budget block lists every earned_time earner the enforcer reports (now including Anki), so a new earner needs only an icon here, and sub-hour bonuses read as minutes.",
|
||||
"scope": [
|
||||
"linux_configuration/i3blocks/gaming_budget.sh",
|
||||
"linux_configuration/tests/test_i3blocks_gaming_budget.sh",
|
||||
"Non-goal: no change to how the budget is computed; the block still only reads steam-backlog-enforcer's /api/budget"
|
||||
],
|
||||
"changes": [
|
||||
"Unearned bonuses come from .rules.earners ([{name, label, earned_seconds, bonus_seconds}], steam-backlog-enforcer 7ab65dd) with an icon map {workout 💪, leetcode 🧩, reading 📖, anki 🗂}; an unmapped earner shows its label",
|
||||
"A daemon without .rules.earners falls back to the old three fixed fields and the configured sizes",
|
||||
"bonus_hm prints '30m' for sizes under an hour instead of '0h30'"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash linux_configuration/i3blocks/gaming_budget.sh (live enforcer API)",
|
||||
"result": "pass",
|
||||
"evidence": "'🎮 2h10 left · 4h53/7h03 +📖1h +🗂30m' / '🎮 2h10' / #50FA7B; /api/budget lists workout and leetcode earned, reading 0/3600, anki 0/1800"
|
||||
},
|
||||
{
|
||||
"command": "user looked at the bar",
|
||||
"result": "pass",
|
||||
"evidence": "kuhy confirmed the '+🗂30m' bar 'looks ok' (2026-10-05)"
|
||||
},
|
||||
{
|
||||
"command": "bash linux_configuration/tests/test_i3blocks_gaming_budget.sh",
|
||||
"result": "pass",
|
||||
"evidence": "All gaming_budget checks passed, incl. the new .rules.earners case: registry order, earned omitted, daemon sizes win over config, label for an unmapped earner, 30m and 1h30 sizes; old-daemon fallback cases unchanged"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"An earner whose name is missing from the icon map shows its text label, which is wider in the bar."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert this commit; the block goes back to the three fixed bonuses and Anki disappears from the bar"
|
||||
]
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
# ============================================================================
|
||||
# Gaming time budget for i3blocks: time left today, used/total, and which
|
||||
# bonuses (workout, LeetCode, reading) are still there to be earned.
|
||||
# bonuses (workout, LeetCode, reading, Anki, ...) are still there to be earned.
|
||||
#
|
||||
# Reads steam-backlog-enforcer's /api/budget, which already folds in the
|
||||
# screen-locker, leetcode-guard and book-guard bonuses. Like screen_locker.sh
|
||||
@ -61,7 +61,7 @@ rendered=$(jq -r \
|
||||
--arg red "$RED" --arg orange "$ORANGE" --arg yellow "$YELLOW" --arg green "$GREEN" '
|
||||
def hm: (. / 60 | round) as $m
|
||||
| "\($m / 60 | floor)h\($m % 60 | tostring | if length < 2 then "0" + . else . end)";
|
||||
def bonus_hm: hm | sub("h00$"; "h");
|
||||
def bonus_hm: if . < 3600 then "\(. / 60 | round)m" else hm | sub("h00$"; "h") end;
|
||||
($cfg[0] // {}) as $c
|
||||
| if .state_status != "ok" then ["nocache", "🎮 state \(.state_status)", "🎮 !", $red]
|
||||
elif .ok != true then ["nocache", "🎮 budget error", "🎮 !", $red]
|
||||
@ -70,10 +70,18 @@ rendered=$(jq -r \
|
||||
| .rules.bonuses as $b
|
||||
| ([$t.seconds_remaining, 0] | max) as $left
|
||||
| "\($t.seconds_used | hm)/\($t.budget_seconds | hm)" as $ratio
|
||||
# Unearned bonuses: [earned, configured size (enforcer default), icon].
|
||||
| [ [$b.workout, ($c.workout_bonus_seconds // 7200), "💪"],
|
||||
[$b.leetcode, ($c.leetcode_bonus_seconds // 3600), "🧩"],
|
||||
[$b.reading, ($c.reading_bonus_seconds // 3600), "📖"] ]
|
||||
# Unearned bonuses: [earned, size, icon]. A daemon that lists the
|
||||
# registry (.rules.earners) is the source of truth, so a new earner
|
||||
# needs only an icon here; an older daemon falls back to the three
|
||||
# fixed fields and the configured sizes.
|
||||
| {workout: "💪", leetcode: "🧩", reading: "📖", anki: "🗂"} as $icon
|
||||
| (if .rules.earners then
|
||||
[.rules.earners[] | [.earned_seconds, .bonus_seconds, ($icon[.name] // .label)]]
|
||||
else
|
||||
[ [$b.workout, ($c.workout_bonus_seconds // 7200), "💪"],
|
||||
[$b.leetcode, ($c.leetcode_bonus_seconds // 3600), "🧩"],
|
||||
[$b.reading, ($c.reading_bonus_seconds // 3600), "📖"] ]
|
||||
end)
|
||||
| map(select((.[0] // 0) == 0 and .[1] > 0) | "+\(.[2])\(.[1] | bonus_hm)")
|
||||
| (if length > 0 then " " + join(" ") else "" end) as $todo
|
||||
| if $t.blocked or $left <= 0 then
|
||||
|
||||
@ -90,6 +90,21 @@ write_budget 0 18000 18000 false 0 0 0
|
||||
assert_eq '🎮 5h00 left · 0h00/5h00 +💪1h30 +📖1h' "$(line 1)" 'configured sizes, zero-size bonus omitted'
|
||||
rm -f "$CONFIG_FILE"
|
||||
|
||||
printf 'Checking a daemon that lists the registry (.rules.earners)...\n'
|
||||
# earner NAME LABEL EARNED SIZE -> one JSON object for .rules.earners.
|
||||
earner() {
|
||||
jq -nc --arg n "$1" --arg l "$2" --argjson e "$3" --argjson s "$4" \
|
||||
'{name: $n, label: $l, earned_seconds: $e, bonus_seconds: $s}'
|
||||
}
|
||||
jq -n --argjson earners "[$(earner workout workout 7200 7200),$(earner leetcode LeetCode 0 3600),$(earner reading reading 0 3600),$(earner anki Anki 0 1800),$(earner piano Piano 0 5400)]" \
|
||||
'{ok: true, state_status: "ok",
|
||||
today: {seconds_used: 0, budget_seconds: 14400, seconds_remaining: 14400, blocked: false},
|
||||
rules: {bonuses: {}, earners: $earners}}' >"$FIXTURE"
|
||||
printf '{"leetcode_bonus_seconds":0}\n' >"$CONFIG_FILE"
|
||||
assert_eq '🎮 4h00 left · 0h00/4h00 +🧩1h +📖1h +🗂30m +Piano1h30' "$(line 1)" \
|
||||
'registry order, earned omitted, sizes from the daemon not the config, label for an unmapped earner'
|
||||
rm -f "$CONFIG_FILE"
|
||||
|
||||
printf 'Checking a dead server shows the cached value as stale...\n'
|
||||
write_budget 21600 28800 7200 false 7200 3600 0
|
||||
run_block >/dev/null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user