From ce97db9e13368876eb8a4290085c8103fe3608f9 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 2 Nov 2026 11:25:07 +0100 Subject: [PATCH] focus block: click opens the tutor and work site Left: focus_project.open; middle: tutor only; right: daily-limits. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WzxdWXdzckyBH7fEbPywpr --- .../i3blocks-focus-project-2026-11-02.json | 13 +++++++--- linux_configuration/i3blocks/focus_project.sh | 18 ++++++++++--- .../tests/test_i3blocks_focus_project.sh | 25 +++++++++++++------ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/docs/superpowers/evidence/i3blocks-focus-project-2026-11-02.json b/docs/superpowers/evidence/i3blocks-focus-project-2026-11-02.json index 71b40965..8f505333 100644 --- a/docs/superpowers/evidence/i3blocks-focus-project-2026-11-02.json +++ b/docs/superpowers/evidence/i3blocks-focus-project-2026-11-02.json @@ -1,12 +1,13 @@ { - "intent": "kuhy sees the active focus project in i3bar: a focus_project block renders the focus field daily-limits now writes to its cache.", + "intent": "kuhy sees the active focus project in i3bar: a focus_project block renders the focus field daily-limits now writes to its cache. A click takes him to the work: left opens the tutor focus screen plus the work URL (focus_project.open), middle only the tutor, right daily-limits --gui.", "scope": [ "linux_configuration/i3blocks/focus_project.sh, the [focus_project] stanza in linux_configuration/i3blocks/config, linux_configuration/tests/test_i3blocks_focus_project.sh", "Non-goal: no focus logic here (focus_project.status() decides); daily-limits owns the cache" ], "changes": [ "New block: `🎯 /`, green at the daily target, hidden with no project or a missing/stale/garbage cache; jq re-parse only on cache mtime change", - "Stanza has signal=11 so daily-limits can refresh it with pkill -RTMIN+11 -x i3blocks" + "Stanza has signal=11 so daily-limits can refresh it with pkill -RTMIN+11 -x i3blocks", + "Clicks: left `python3 -m focus_project.open`, middle `--no-site`, right `daily-limits --gui`" ], "verification": [ { @@ -18,10 +19,16 @@ "command": "bash ~/.config/i3blocks/focus_project.sh (live cache after daily-limits.service)", "result": "pass", "evidence": "🎯 HackerRank prep 0/120, #F1FA8C" + }, + { + "command": "bash linux_configuration/tests/test_i3blocks_focus_project.sh", + "result": "pass", + "evidence": "clicks section: all checks passed (red first: left click ran '--gui')" } ], "risks": [ - "The running i3bar loads the stanza only after its next restart; until then the poke is harmless (i3blocks 1.5 survived an unconfigured RTMIN+11)" + "The running i3bar loads the stanza only after its next restart; until then the poke is harmless (i3blocks 1.5 survived an unconfigured RTMIN+11)", + "A click needs focus_project >= 0.5.0 in the system python user site" ], "rollback": [ "git revert the commit and remove the symlink ~/.config/i3blocks/focus_project.sh" diff --git a/linux_configuration/i3blocks/focus_project.sh b/linux_configuration/i3blocks/focus_project.sh index b8050270..08d88ebd 100755 --- a/linux_configuration/i3blocks/focus_project.sh +++ b/linux_configuration/i3blocks/focus_project.sh @@ -16,7 +16,13 @@ # # A missing, garbage or >180 s old cache hides the block: the neighbouring # gaming_budget block already says "⏱?" for the same cache, and old minutes -# must never be shown. Left-click opens `daily-limits --gui`. +# must never be shown. +# +# Clicks (the block says what to do, so a click goes where the work is): +# left `python3 -m focus_project.open`: the tutor focus screen (starts the +# focus timer if it is not running) and the project's work URL +# middle the same with --no-site (the focus screen only) +# right `daily-limits --gui`, the day's detail # ============================================================================ set -euo pipefail @@ -36,9 +42,13 @@ emit() { # i3blocks' PATH has no ~/.local/bin, where daily-limits' install.sh puts it. readonly DAILY_LIMITS_BIN=${DAILY_LIMITS_BIN:-$HOME/.local/bin/daily-limits} -if [[ ${BLOCK_BUTTON:-0} -eq 1 && -x $DAILY_LIMITS_BIN ]]; then - setsid -f "$DAILY_LIMITS_BIN" --gui >/dev/null 2>&1 -fi +# The system python: focus_project is installed in its user site (install.sh). +readonly FOCUS_OPEN_PYTHON=${FOCUS_OPEN_PYTHON:-/usr/bin/python3} +case ${BLOCK_BUTTON:-0} in +1) setsid -f "$FOCUS_OPEN_PYTHON" -m focus_project.open >/dev/null 2>&1 ;; +2) setsid -f "$FOCUS_OPEN_PYTHON" -m focus_project.open --no-site >/dev/null 2>&1 ;; +3) [[ -x $DAILY_LIMITS_BIN ]] && setsid -f "$DAILY_LIMITS_BIN" --gui >/dev/null 2>&1 ;; +esac hide() { emit '' '' "$YELLOW" diff --git a/linux_configuration/tests/test_i3blocks_focus_project.sh b/linux_configuration/tests/test_i3blocks_focus_project.sh index af80a287..080f0ca6 100755 --- a/linux_configuration/tests/test_i3blocks_focus_project.sh +++ b/linux_configuration/tests/test_i3blocks_focus_project.sh @@ -93,14 +93,23 @@ printf '%s\n' "$NOW" '🎯 CACHED 1/1' '🎯 1/1' '#50FA7B' >"$RENDER" touch -d '+1 second' "$RENDER" assert_eq '🎯 CACHED 1/1' "$(line 1 "$(run_block)")" 'unchanged cache is not re-parsed' -printf 'Checking a click opens the GUI...\n' +printf 'Checking the clicks...\n' +# Stubs record their argv: the block must never open anything for real. printf '#!/bin/bash\necho "$*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/dl" -chmod +x "$TMP_DIR/dl" -BLOCK_BUTTON=1 run_block >/dev/null -for _ in 1 2 3 4 5 6 7 8 9 10; do - [[ -s "$TMP_DIR/clicks" ]] && break - sleep 0.1 -done -assert_eq '--gui' "$(cat "$TMP_DIR/clicks" 2>/dev/null)" 'click runs daily-limits --gui' +printf '#!/bin/bash\necho "py $*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/py" +chmod +x "$TMP_DIR/dl" "$TMP_DIR/py" +click() { # BUTTON -> what the stubs recorded (empty if nothing ran) + rm -f "$TMP_DIR/clicks" + BLOCK_BUTTON="$1" FOCUS_OPEN_PYTHON="$TMP_DIR/py" run_block >/dev/null + for _ in 1 2 3 4 5 6 7 8 9 10; do + [[ -s "$TMP_DIR/clicks" ]] && break + sleep 0.1 + done + cat "$TMP_DIR/clicks" 2>/dev/null || true +} +assert_eq 'py -m focus_project.open' "$(click 1)" 'left click opens the tutor and the work site' +assert_eq 'py -m focus_project.open --no-site' "$(click 2)" 'middle click opens only the tutor' +assert_eq '--gui' "$(click 3)" 'right click opens daily-limits --gui' +assert_eq '' "$(click 0)" 'no click opens nothing' printf 'focus_project block: all checks passed\n'