diff --git a/docs/superpowers/evidence/i3blocks-tutor-minutes-2026-10-12.json b/docs/superpowers/evidence/i3blocks-tutor-minutes-2026-10-12.json new file mode 100644 index 00000000..3f0ce9ba --- /dev/null +++ b/docs/superpowers/evidence/i3blocks-tutor-minutes-2026-10-12.json @@ -0,0 +1,29 @@ +{ + "intent": "The i3 tutor block shows today's credited tutor minutes against the 60-minute daily cap; it used to count ledger rows against 4 and hid after 4 credited minutes since earned_time 0.8.0 made each row a minute count.", + "scope": [ + "linux_configuration/i3blocks/tutor.sh, linux_configuration/tests/test_i3blocks_tutor.sh", + "Non-goal: no HMAC check (earned_time stays the authority)" + ], + "changes": [ + "Sum detail.minutes of today's credit rows (legacy rows without it count 15, as earned_time does); TARGET 4 -> 60", + "Test fixture takes day:kind[:minutes]; new cases for summing, legacy rows and the 60/60 hidden state" + ], + "verification": [ + { + "command": "bash linux_configuration/tests/test_i3blocks_tutor.sh", + "result": "pass", + "evidence": "tutor block: all checks passed" + }, + { + "command": "bash linux_configuration/i3blocks/tutor.sh", + "result": "pass", + "evidence": "⚙ tutor 0/60 on the live ledger; same jq on 2026-10-11 gives 60, matching earned_time's 60 units that day" + } + ], + "risks": [ + "A ledger row with non-numeric minutes would make jq fail and show the orange 'ledger unreadable' state" + ], + "rollback": [ + "git revert the commit; the block goes back to counting rows / 4" + ] +} diff --git a/linux_configuration/i3blocks/tutor.sh b/linux_configuration/i3blocks/tutor.sh index 5178ad3b..e87a7c3a 100755 --- a/linux_configuration/i3blocks/tutor.sh +++ b/linux_configuration/i3blocks/tutor.sh @@ -1,13 +1,14 @@ #!/bin/bash # ============================================================================ -# Tutor status for i3blocks: today's credited 15-minute blocks. +# Tutor status for i3blocks: today's credited tutor minutes. # # A click runs ~/src/tutor/run.sh, which starts the tutor if it is down and -# opens it in the browser -- nobody has to remember the port. Hidden once all -# four blocks are in: nothing left to earn, nothing to click. +# opens it in the browser -- nobody has to remember the port. Hidden once the +# daily cap is in: nothing left to earn, nothing to click. # -# Counts the tutor ledger's credit rows dated today. It does not check their +# Sums detail.minutes of the ledger's credit rows dated today (legacy rows +# without it count 15, as earned_time does). It does not check their # HMAC (that needs the key and Python); earned_time does, so this is a # display, never the authority on what was paid. # ============================================================================ @@ -17,7 +18,7 @@ set -euo pipefail readonly LEDGER="${TUTOR_LEDGER:-$HOME/.local/share/tutor/ledger.json}" # i3blocks' PATH has no ~/src: absolute path. readonly RUN="${TUTOR_RUN:-$HOME/src/tutor/run.sh}" -readonly TARGET=4 +readonly TARGET=60 # earned_time daily cap, minutes readonly YELLOW='#F1FA8C' readonly ORANGE='#FFB86C' @@ -37,19 +38,19 @@ if ! command -v jq >/dev/null 2>&1; then fi printf -v today '%(%Y-%m-%d)T' -1 -done_blocks=0 -# No ledger yet is a real zero: the tutor creates it on the first block. +done_minutes=0 +# No ledger yet is a real zero: the tutor creates it on the first credit. if [[ -e $LEDGER ]]; then - if ! done_blocks=$(jq --arg d "$today" \ - '[.entries[] | select(.kind == "credit" and .day == $d)] | length' \ + if ! done_minutes=$(jq --arg d "$today" \ + '[.entries[] | select(.kind == "credit" and .day == $d) | (.detail.minutes // 15)] | add // 0 | floor' \ "$LEDGER" 2>/dev/null); then emit '⚙ tutor: ledger unreadable' '⚙ ?' "$ORANGE" exit 0 fi fi -if ((done_blocks >= TARGET)); then +if ((done_minutes >= TARGET)); then emit '' '' "$YELLOW" exit 0 fi -emit "⚙ tutor ${done_blocks}/${TARGET}" "⚙ ${done_blocks}/${TARGET}" "$YELLOW" +emit "⚙ tutor ${done_minutes}/${TARGET}" "⚙ ${done_minutes}/${TARGET}" "$YELLOW" diff --git a/linux_configuration/tests/test_i3blocks_tutor.sh b/linux_configuration/tests/test_i3blocks_tutor.sh index 666f7acf..8bc18b74 100755 --- a/linux_configuration/tests/test_i3blocks_tutor.sh +++ b/linux_configuration/tests/test_i3blocks_tutor.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Behaviour tests for the i3blocks tutor block: today's credit -# count, the hidden 4/4 state, the unreadable-ledger warning and the click. +# Behaviour tests for the i3blocks tutor block: today's credited +# minutes, the hidden 60/60 state, the unreadable-ledger warning and the click. # The ledger is a fixture and run.sh is a stub, so nothing reads the real # ledger and no tutor or browser is ever started. set -euo pipefail @@ -45,13 +45,15 @@ LEDGER="$TMP_DIR/ledger.json" TODAY=$(date +%Y-%m-%d) YESTERDAY=$(date -d yesterday +%Y-%m-%d) -# ledger ... (writes {"entries":[...]}) +# ledger ... (writes {"entries":[...]}; no minutes = legacy row) ledger() { local rows=() local spec for spec in "$@"; do - rows+=("$(jq -n --arg day "${spec%%:*}" --arg kind "${spec##*:}" \ - '{day: $day, kind: $kind}')") + local day kind minutes + IFS=: read -r day kind minutes <<<"$spec" + rows+=("$(jq -n --arg day "$day" --arg kind "$kind" --arg m "$minutes" \ + '{day: $day, kind: $kind} + (if $m == "" then {} else {detail: {minutes: ($m | tonumber)}} end)')") done printf '%s\n' "${rows[@]}" | jq -s '{entries: .}' >"$LEDGER" } @@ -69,22 +71,25 @@ printf 'Checking tutor block states...\n' rm -f "$LEDGER" out=$(run_block) -assert_eq '⚙ tutor 0/4' "$(line 1 "$out")" 'no ledger is 0/4' -assert_eq '⚙ 0/4' "$(line 2 "$out")" 'no ledger short text' +assert_eq '⚙ tutor 0/60' "$(line 1 "$out")" 'no ledger is 0/60' +assert_eq '⚙ 0/60' "$(line 2 "$out")" 'no ledger short text' assert_eq '#F1FA8C' "$(line 3 "$out")" 'progress is yellow' -ledger "$TODAY:credit" "$TODAY:credit" "$YESTERDAY:credit" +ledger "$TODAY:credit:3" "$TODAY:credit:1" "$YESTERDAY:credit:50" out=$(run_block) -assert_eq '⚙ tutor 2/4' "$(line 1 "$out")" 'counts only today' -assert_eq '⚙ 2/4' "$(line 2 "$out")" 'short text' +assert_eq '⚙ tutor 4/60' "$(line 1 "$out")" 'sums only today' +assert_eq '⚙ 4/60' "$(line 2 "$out")" 'short text' -ledger "$TODAY:credit" "$TODAY:debit" -assert_eq '⚙ tutor 1/4' "$(line 1 "$(run_block)")" 'only credit rows count' +ledger "$TODAY:credit" "$TODAY:credit:2" +assert_eq '⚙ tutor 17/60' "$(line 1 "$(run_block)")" 'legacy row counts 15' -ledger "$TODAY:credit" "$TODAY:credit" "$TODAY:credit" "$TODAY:credit" +ledger "$TODAY:credit:5" "$TODAY:debit:9" +assert_eq '⚙ tutor 5/60' "$(line 1 "$(run_block)")" 'only credit rows count' + +ledger "$TODAY:credit:59" "$TODAY:credit:1" out=$(run_block) -assert_eq '' "$(line 1 "$out")" '4/4 hides the block' -assert_eq '' "$(line 2 "$out")" '4/4 short text hidden' +assert_eq '' "$(line 1 "$out")" '60/60 hides the block' +assert_eq '' "$(line 2 "$out")" '60/60 short text hidden' printf 'Checking failure states are never blank or green...\n'