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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWXdzckyBH7fEbPywpr
This commit is contained in:
Krzysztof Rudnicki 2026-11-02 11:25:07 +01:00
parent 4c3a135faa
commit ce97db9e13
3 changed files with 41 additions and 15 deletions

View File

@ -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: `🎯 <name> <minutes>/<daily_minutes>`, 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"

View File

@ -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"

View File

@ -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'