Commit Graph

14 Commits

Author SHA1 Message Date
徐翔宇
0d2d844538 feat(sound): swap sit/clockout tones for better character
- sit:      Morse → Submarine (1.5s sonar). Longer sustain + lower
            pitch pierces meeting audio and noise-cancelling
            headphones better than Morse's crisp staccato. 1Hz
            cadence over 15s still drives urgency.
- clockout: Glass → Hero (1.1s heroic fanfare). Glass was "
            achievement"; Hero is "🎺 victory" — better scale for
            the 下班 moment. Three plays with 0.6s gap.

Both still macOS-built-in (/System/Library/Sounds), zero external
deps. Single hard-coded path per sound — to swap again, edit the
URL line. Function name `playMorseSitAlert` kept for callsite
compat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:26:37 +08:00
徐翔宇
5e5540d9b1 fix(water): remove vertical scroll — fit toggles + hero in 580pt panel
WaterView added two toggle rows which pushed content past panel
height, so it got wrapped in a ScrollView. That introduced a
vertical scroll bar — visually noisy for a tab that should fit at a
glance.

Compaction strategy:
- VStack spacing 14 → 10 (saves ~24pt across 4 gaps)
- cupShape 130×160 → 100×130 (saves ~30pt + width breathing room)
- Big "X" number font 40 → unchanged (still readable in 100pt cup)
- toggleRows (vertical 2-stack of 32pt pills) → toggleRow
  (horizontal 30pt single line with short labels "🍅 +1" / "整点提醒")
- Drop tipText (saves ~30pt) — redundant with the badge text

Net height now fits inside the panel without scroll.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:16:57 +08:00
徐翔宇
dc0d22700c chore: bump v0.3.0 build 3 + holiday data + cross-day water dedupe fix
Three things squashed into one commit since they're all release-prep:

1. Bump CFBundleVersion 2 → 3, CFBundleShortVersionString 0.2.0 →
   0.3.0, WorkerPlugin.version & ExpandedView footer string to match.
   Aligns with the 5-feature v0.3.0 ship.

2. fix(water): lastWaterReminderHr was an Int hour (0-23) so a
   yesterday-fired 17:00 blocked today's 17:00 reminder. Now stores
   "yyyy-MM-dd HH" composite key so dedupe is scoped per-day.

3. Holidays.swift: 2026 entry table re-calibrated per typical
   State Council 调休 pattern. Notable changes:
   - 春节 starts on 除夕 02-16 (not 初一 02-17) per 2024 policy
   - 中秋 + 国庆 merged into 10-day 09-25 ~ 10-04 window (since
     2026 中秋 09-25 is 6 days before 国庆 10-01)
   - 劳动节 renamed from "五一" for clarity
   Disclaimer still in header: verify against 国务院 announcement
   when published, edit + re-release if 调休 differs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:12:40 +08:00
徐翔宇
51cc5bb0cc feat(weekend): show next China statutory holiday countdown
Adds HolidayDatabase with 7 entries for 2026 (元旦/春节/清明/五一/
端午/中秋·国庆 + 2027 元旦 roll-over). WorkerStore.nextHoliday
computed returns the upcoming or currently-ongoing holiday with
days-until. WeekendView gains a tomato-tinted card under the
weekend hero showing "下个法定假 国庆 · 放 8 天 · X 天后" — or
"假期中" + 🎉 when the user opens the panel during the window.

Dates are estimated from typical State Council patterns; the file
header carries a TODO to verify against the official announcement
each year.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:59:06 +08:00
徐翔宇
2aa68c2427 feat(pomodoro): focus quality score (idle gap tracking)
New cross-cutting metric: each focus phase accumulates SystemIdle
seconds > 30s, then on phase end computes quality = 10 × (1 -
idle/total). Today's average is published as pomodoroQualityTodayAvg
and rendered as a third stat card in PomodoroView.

Rationale: pomodoro phases counted but not graded conflate "actually
focused 25min" with "started the timer, opened YouTube, came back."
The idle accumulator catches Slack/IDE-switch/AFK distractions.

Threshold > 30s drops "I'm thinking about the algorithm" false
positives (most code-think pauses last <30s, the user still moves
the mouse / scrolls / glances).

Score color: ≥8 lime, ≥5 tomato, <5 alertRed. Em-dash when n=0.

Persisted as [String: [Double]] (date → list of quality scores) so
day rollover preserves history for future trend chart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:52:27 +08:00
徐翔宇
4b2ac2fc59 feat(water): pomodoro auto-+1 + hourly workday reminder
Two cross-tab integrations from the 2026-05-20 product superpower
review:

1. waterAutoFromPomodoro (default true) — each pomodoro focus phase
   end auto-logs 1 cup. Lazy users get streak data without taps,
   pomodoro break is the natural "go drink water" cue.

2. waterHourlyReminder (default true) — Mon–Fri 9..18 hourly nag if
   cups < goal. Deduped by last-fired-hour so plugin restart in the
   same hour won't double-fire.

UI: WaterView now wraps in ScrollView (toggles push it past 580pt),
adds two togglePill rows below the goalRow. Custom slim-track
indicator since SwiftUI's Toggle styling clashes with the plugin's
dark capsule aesthetic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:50:46 +08:00
徐翔宇
f0a116ccf7 feat(clockout): weekend "今天不上班" mode
Saturday/Sunday previously kept showing the workday countdown even
though no one's working — UX surfaced friction where there was
nothing to count. Now ClockoutView switches to a purple
weekend-hero card with "🌴 今天是周末 · 去做你想做的事" copy.

Implementation:
- WorkerStore.isWeekend computed (Sun=1, Sat=7)
- ClockoutView.body branches: weekendHero + weekendTipText for
  weekend, original countdown+progress+controls for weekday
- Clockout celebration logic still runs in the background — if
  someone does work Saturday and crosses their configured clockout
  hour, the next-weekday view will see the dedupe key updated;
  no spurious extra fire

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:49:17 +08:00
徐翔宇
d4fea96d17 feat(clockout): celebration on boundary cross
Prior: clockoutRemainingSec rolled forward to tomorrow the moment
target <= now, so the panel jumped 1s → 86399s with no observable
"zero" — the most expected moment of the day was silent.

Now:
- clockoutRemainingSec returns 0 when past today's clockout (sits
  at 0 until midnight rolls the calendar component)
- WorkerStore detects the >0 → 0 edge in tickFire, fires per-day-
  deduped celebration (notification + Glass × 3 + panel banner ~5s)
- SoundPlayer.playClockoutCelebration: Glass.aiff × 3 with 0.6s gap
  (joyful 1.8s shot, not the sit Morse's 15s nag)
- ClockoutView overlays a lime→tomato gradient banner driven by
  store.clockoutCelebrationActive
- tipText rewrite — old text said "不会响铃" which contradicts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:48:21 +08:00
徐翔宇
c1747dc1ce fix(sit): reset accumulator after threshold fire
Prior path used a lastFired-timestamp dedupe but never reset the
sitAccumActiveSec counter, so after the first 45min alert the
panel kept showing "已坐 90 分钟" / "135 分钟" — counter monotonic
across cycles. The UX intent is each ack starts a fresh cycle.

Now: on fire, alert + reset counter to 0. Next triggerSec of
sitting kicks a fresh alert. Reset is the simpler dedupe — there's
no physical way to re-fire without accumulating triggerSec from 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:29:28 +08:00
徐翔宇
19c5be91fa feat(sit): 15s Morse alert tone on sit threshold
The UN notification ding is one short beep — easy to miss in a
meeting. For sit alerts, repeat the system Morse tone (0.7s
staccato "S O S") at 1Hz for 15 ticks, layered on top of the
notification + dock bounce.

- new: SoundPlayer.swift — NSSound-based repeating tone with stop()
- WorkerStore.tickFire: trigger SoundPlayer.playMorseSitAlert(count: 15)
  alongside the existing notify() call
- WorkerStore.sitStop: silence in-flight alert when user stops monitoring

Uses /System/Library/Sounds/Morse.aiff — shipped on every macOS,
no resource bundling needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:25:48 +08:00
徐翔宇
3fb2c5c2a8 fix: pomodoro paused-in-break fraction + notif status wired to store
Two P0 issues from 2026-05-19 code review:

1. PomodoroView progress ring used pomodoroFocusMin*60 as denominator
   in paused state. If the user paused mid-break, ring rendered 80%
   done when the underlying break was actually 40% done. Now the
   store exposes pomodoroPhaseTotalSec which reads pausedPhase to
   pick the right denominator.

2. WorkerNotificationCenter set its own isAuthorized but never wrote
   WorkerStore.notificationsAuthorized — the top-bar notif status dot
   stayed dim even after the user authorized. Refactored into a single
   applyAuthorized helper that writes both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:54:28 +08:00
徐翔宇
8022abca76 docs: add 2026-05-19 product + code review
- product-review.md: 5 个 tab 拆评,"摸鱼侠"名字 vs 功能 mismatch
  与 P0 上架前必修项(下班瞬间无事件 / 喝水无提醒 / 版本号硬编码)
- code-review.md: 13 文件审视,标出 PomodoroView paused-in-break
  fraction 算错、notifAuthorized 未 wire 致状态 dot 永 dim 等 P0 项;
  亮点是 wallclock-based pomodoro + input-idle sit accumulator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:34:56 +08:00
徐翔宇
0cff83fc38 feat: v0.2.0 — sleep-aware timers + auto-loop pomodoro
Core change: both timers stop lying when the Mac sleeps or the user
walks away. Old code measured wallclock from a stored "start"
timestamp, so 4 hours of lid-closed showed up as 4 hours of "sitting"
and a 25-minute focus could quietly stretch across multiple hours
because Timer.scheduledTimer pauses during sleep.

What changed:

- Sit timer now reads CGEventSource HID idle to count only seconds
  with real input activity. New Sources/engine/SystemIdle.swift
  iterates the user-input event types and takes the min. Idle ≥ 5
  min resets the accumulator (counts as a real break). Tick-gap >
  30 s flags a sleep/wake and clears too — covers the case where
  the user unlocks and idle resets to ~0 instantly.

- Pomodoro switched from "decrement counter every second" to a
  wallclock end-time. Persisted to UserDefaults so quit/relaunch
  recovers correctly: still-running phases keep counting; phases
  whose end-time has already passed get caught up (one focus
  credited if missed) and the timer returns to idle.

- Auto-loop with classic 4-pack rhythm: focus → short break →
  focus → … on the 4th focus a long break (default 15 min) replaces
  the short break, then the cycle resets. Default ON; toggle pill
  in the settings row turns it off when the user wants manual
  restarts.

- New cycle-progress dot row (●●○○) next to the phase badge so
  the user can see where they are in the 4-pack at a glance.

- SitView tip text rewritten to reflect the new semantics: "只统计
  你真正在键盘前的时间。离开 5 分钟以上自动归零,午休/会议不会被
  算进去。"

User-facing wins:
- Closing the lid for lunch no longer fakes 90 minutes of sitting.
- A 25-min focus that survives a sleep cycle ends accurately.
- Start a focus once, the rhythm runs all morning.
2026-04-30 11:23:09 +08:00
徐翔宇
959580c55e v0.1.0: initial 摸鱼侠 — Worker Toolkit plugin for MioIsland
Office-survival toolkit. Five tabs in the notch:
- 🍅 番茄钟: 25/5 focus/break with circular ring timer + tally.
- 🪑 久坐: count-up since last break, dock-bounce alert at 45min.
- 💧 喝水: animated cup, daily goal tracking with progress dots.
- 🏃 下班: H:M:S countdown to clock-out hour with gradient bar.
- 🎉 周末: 天/时/分 to next Saturday + rotating 打工语录.

100% local. UNUserNotificationCenter for alerts, dock-bounce
fallback when authorization denied. UserDefaults persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:21:12 +08:00