feat: add local pomodoro focus timer
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m34s

Frontend-only 25m focus / 5m break timer with pause-resume-reset, deadline-based absolute timing, versioned localStorage persistence, cross-tab conflict protection, midnight-completion attribution, accessible stage controls, and reduced-motion support. Adds 专注 entry to desktop sidebar; mobile bottom nav unchanged; no notifications, task association, history, or cross-device sync. Also fixes mobile E2E task isolation and the stale calendar 390px overflow assertion.
This commit is contained in:
2026-09-22 09:10:05 +08:00
parent 12e4753aeb
commit a535f69cae
14 changed files with 513 additions and 25 deletions
+33
View File
@@ -0,0 +1,33 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
describe('pomodoro integration', () => {
const app = readFileSync('src/App.vue', 'utf8')
const css = readFileSync('src/style.css', 'utf8')
const panel = readFileSync('src/PomodoroPanel.vue', 'utf8')
it('adds focus to desktop navigation and keeps the five-item mobile navigation unchanged', () => {
const primary = app.slice(app.indexOf('<nav class="primary-nav">'), app.indexOf('</nav>', app.indexOf('<nav class="primary-nav">')))
const bottom = app.slice(app.indexOf('<nav class="bottom"'), app.indexOf('</nav>', app.indexOf('<nav class="bottom"')))
expect(primary).toContain("switchView('focus')")
expect(primary.indexOf("switchView('focus')")).toBeGreaterThan(primary.indexOf("switchView('habits')"))
expect(bottom).not.toContain("switchView('focus')")
expect(bottom.match(/aria-current=/g)).toHaveLength(5)
})
it('renders the focus panel without the shared FAB and includes accessible stage controls', () => {
expect(app).toContain("<PomodoroPanel v-else-if=\"activeView==='focus'\"")
expect(app).not.toContain("'focus'].includes(activeView)")
expect(css).toContain('.pomodoro-controls button{min-height:44px')
expect(css).toContain('@media(prefers-reduced-motion:reduce)')
})
it('coordinates writes across tabs with unique writers and a localStorage conflict check', () => {
expect(panel).toContain('const WRITER_ID = crypto.randomUUID()')
expect(panel).toContain('const stored = loadPomodoroState(window.localStorage, STORAGE_KEY)')
expect(panel).toContain('shouldAcceptPomodoroState(stored, next) ? stored : next')
expect(panel).toContain('Date.now(), WRITER_ID')
expect(panel).toContain('savePomodoroState(window.localStorage, STORAGE_KEY, state.value)')
})
})