All widgets
Move Reminder
Comes with Chopstick
Reminds you to get up and move after 30 minutes of active laptop time. Resets when you're away 5+ minutes; stays quiet otherwise.
Already installed? It's in your widget library.




Type it in the notch's search
- Reset the move reminder
- Time until I should move
- Snooze the move reminder
What it runs
A widget is a folder of plain files. Here is all of its code.
widget.json
{
"id": "move-reminder",
"name": "Move Reminder",
"icon": "icon.png",
"description": "Reminds you to get up and move after 30 minutes of active laptop time. Resets when you're away 5+ minutes; stays quiet otherwise.",
"initial": "status",
"watch": [
{
"script": "tick.sh",
"every": 30,
"as": "sitting"
}
],
"triggers": [
{
"when": {
"sitting.due": true
},
"open": "remind"
}
],
"states": {
"status": {
"open": true,
"dismiss_after": 6,
"on_enter": [
{
"refresh": "sitting"
}
],
"compact": {
"leading": {
"symbol": "figure.walk"
},
"trailing": {
"text": "{{sitting.remaining | 30}}m"
}
},
"expanded": {
"size": "compact",
"header": {
"symbol": "figure.walk",
"title": "Move reminder"
},
"blocks": [
{
"text": "{{sitting.remaining | 30}} min until next reminder",
"style": "title"
},
{
"note": "{{sitting.minutes | 0}} min active so far"
}
]
},
"keys": [
{
"key": "esc",
"do": [
"dismiss"
]
}
]
},
"remind": {
"task": true,
"open": true,
"compact": {
"leading": {
"symbol": "figure.walk",
"tint": "green"
},
"trailing": {
"text": "Move"
}
},
"expanded": {
"size": "compact",
"header": {
"symbol": "figure.walk",
"title": "Time to move",
"tint": "green"
},
"blocks": [
{
"text": "You've been on your laptop for {{sitting.minutes | 30}} minutes. Stand up, stretch, walk around.",
"lines": 3
},
{
"row": [
{
"button": {
"label": "Done",
"style": "filled",
"tint": "green",
"do": [
{
"run": "reset.sh"
},
{
"refresh": "sitting"
},
"dismiss"
]
}
},
{
"button": {
"label": "Snooze 5 min",
"style": "plain",
"do": [
{
"run": "snooze.sh"
},
{
"refresh": "sitting"
},
"dismiss"
]
}
}
],
"spacing": "normal"
}
]
}
}
},
"commands": {
"reset": {
"phrase": "Reset the move reminder",
"description": "Restart the 30-minute timer.",
"do": [
{
"run": "reset.sh"
},
{
"refresh": "sitting"
},
{
"go": "status"
}
]
},
"status": {
"phrase": "Time until I should move", "preview": { "state": "status" },
"description": "Show how long until the next move reminder.",
"do": [
{
"refresh": "sitting"
},
{
"go": "status"
}
]
},
"snooze": {
"phrase": "Snooze the move reminder",
"description": "Delay the reminder by 5 minutes.",
"do": [
{
"run": "snooze.sh"
},
{
"refresh": "sitting"
},
"dismiss"
]
}
},
"preview": {
"states": [
"status",
"remind"
],
"data": {
"sitting": {
"minutes": 30,
"remaining": 12,
"due": false
}
}
}
}
reset.sh
#!/bin/sh
D="$HOME/.chopstick-move-reminder"; mkdir -p "$D"
echo "0 0 $(date +%s) 0" > "$D/state"
echo '{"minutes":0,"remaining":30,"due":false}'
snooze.sh
#!/bin/sh D="$HOME/.chopstick-move-reminder"; F="$D/state"; mkdir -p "$D" now=$(date +%s); active=0; away=0; last=$now; snooze=0 [ -f "$F" ] && read active away last snooze < "$F" echo "$active $away $now $((now + 300))" > "$F"
tick.sh
#!/bin/sh
# Tracks active laptop time. State file: active away last snooze_until (seconds / epoch).
D="$HOME/.chopstick-move-reminder"; F="$D/state"
mkdir -p "$D"
now=$(date +%s)
active=0; away=0; last=$now; snooze=0
[ -f "$F" ] && read active away last snooze < "$F"
dt=$((now - last)); [ "$dt" -lt 0 ] && dt=0
idle=$(ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {print int($NF/1000000000); exit}')
idle=${idle:-0}
locked=0
ioreg -n Root -d1 | grep -q 'CGSSessionScreenIsLocked.*Yes' && locked=1
if [ "$dt" -gt 120 ]; then
# gap: Mac was asleep or the widget was off, count it as time away
away=$((away + dt))
elif [ "$locked" = 1 ] || [ "$idle" -ge 300 ]; then
away=$((away + dt))
else
away=0; active=$((active + dt))
fi
if [ "$away" -ge 300 ]; then active=0; snooze=0; fi
echo "$active $away $now $snooze" > "$F"
mins=$((active / 60)); rem=$(( (1800 - active + 59) / 60 )); [ "$rem" -lt 0 ] && rem=0
due=false
[ "$active" -ge 1800 ] && [ "$now" -ge "$snooze" ] && due=true
echo "{\"minutes\":$mins,\"remaining\":$rem,\"due\":$due}"