Skip to content

Commit e8fff04

Browse files
apiadclaude
andcommitted
refactor(examples): 04_pwa — single toggle button instead of Start/Pause pair
The label flips reactively (Start ↔ Pause) via a new `toggle_label` field on PomodoroState, so the current state is always visible in the button itself. Reset stays as a separate secondary button. Smoke + e2e tests updated to assert the toggle's bound label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ac027b1 commit e8fff04

3 files changed

Lines changed: 39 additions & 20 deletions

File tree

examples/04_pwa.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class PomodoroState:
5252
running: bool = False
5353
sessions: int = 0
5454
time_display: str = "25:00"
55+
toggle_label: str = "Start"
5556

5657

5758
# ---------------------------------------------------------------------------
@@ -110,6 +111,7 @@ async def tick():
110111
PomodoroState.mode = next_mode
111112
PomodoroState.seconds_left = durations[next_mode]
112113
PomodoroState.running = False
114+
PomodoroState.toggle_label = "Start"
113115
await render_time()
114116
await save_state()
115117
return
@@ -119,24 +121,27 @@ async def tick():
119121

120122

121123
@app.client.callback
122-
async def start(event: Event):
124+
async def toggle(event: Event):
125+
# Single Start/Pause button. The label flips so the user can always see
126+
# whether the timer is running. When starting, await tick() — the loop
127+
# owns the running coroutine until it sees running=False (set here on
128+
# the next click) or seconds_left hits zero.
123129
if bool(PomodoroState.running):
130+
PomodoroState.running = False
131+
PomodoroState.toggle_label = "Start"
132+
await save_state()
124133
return
125134
PomodoroState.running = True
135+
PomodoroState.toggle_label = "Pause"
126136
await tick()
127137

128138

129-
@app.client.callback
130-
async def pause(event: Event):
131-
PomodoroState.running = False
132-
await save_state()
133-
134-
135139
@app.client.callback
136140
async def reset(event: Event):
137141
# Reset to the full duration of the current mode. Stops the timer.
138142
durations = {"work": 1500, "short": 300, "long": 900}
139143
PomodoroState.running = False
144+
PomodoroState.toggle_label = "Start"
140145
PomodoroState.seconds_left = durations[str(PomodoroState.mode)]
141146
await render_time()
142147
await save_state()
@@ -151,6 +156,7 @@ async def switch_mode(event: Event):
151156
if new_mode not in durations:
152157
return
153158
PomodoroState.running = False
159+
PomodoroState.toggle_label = "Start"
154160
PomodoroState.mode = new_mode
155161
PomodoroState.seconds_left = durations[new_mode]
156162
await render_time()
@@ -170,6 +176,7 @@ async def restore():
170176
PomodoroState.sessions = int(saved.sessions)
171177
# Always restore paused — user clicks start to resume.
172178
PomodoroState.running = False
179+
PomodoroState.toggle_label = "Start"
173180
await render_time()
174181

175182

@@ -288,14 +295,11 @@ def index():
288295
# Current mode label below the time.
289296
page.div(classes="mode-label").text(PomodoroState.mode)
290297

291-
# Primary controls.
298+
# Primary controls — toggle (Start/Pause flips reactively) + Reset.
292299
with page.div(classes="controls") as controls:
293-
controls.button(text="Start", classes="control-primary").attrs(
294-
type="button"
295-
).on("click", start)
296-
controls.button(text="Pause", classes="control-secondary").attrs(
297-
type="button"
298-
).on("click", pause)
300+
controls.button(classes="control-primary").attrs(type="button").text(
301+
PomodoroState.toggle_label
302+
).on("click", toggle)
299303
controls.button(text="Reset", classes="control-secondary").attrs(
300304
type="button"
301305
).on("click", reset)

tests/test_examples_canonical.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,9 @@ def test_04_pwa_manifest_serviceworker_and_bundle():
176176
assert 'data-bind-text="PomodoroState.time_display"' in html
177177
assert 'data-bind-text="PomodoroState.mode"' in html
178178
assert 'data-bind-text="PomodoroState.sessions"' in html
179-
assert 'data-on-click="start"' in html
180-
assert 'data-on-click="pause"' in html
179+
# The toggle button label flips between Start and Pause reactively.
180+
assert 'data-bind-text="PomodoroState.toggle_label"' in html
181+
assert 'data-on-click="toggle"' in html
181182
assert 'data-on-click="reset"' in html
182183
assert 'data-on-click="switch_mode"' in html
183184
assert 'data-mode="work"' in html

tests/test_examples_e2e.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,18 @@ def test_04_pwa_tick_loop_decrements_seconds(example_server, page):
172172
time_el = page.locator('[data-bind-text="PomodoroState.time_display"]')
173173
assert time_el.inner_text().strip() == "25:00"
174174

175-
# Click Start. The tick loop runs in the background while we wait.
176-
page.locator('button[data-on-click="start"]').click()
175+
# The toggle button starts labeled "Start" and flips to "Pause" once running.
176+
toggle_btn = page.locator('button[data-on-click="toggle"]')
177+
assert toggle_btn.inner_text().strip() == "Start"
178+
179+
# Click to start. The tick loop runs in the background while we wait.
180+
toggle_btn.click()
181+
182+
# The button text should flip to "Pause" reactively once running=True.
183+
page.wait_for_function(
184+
"() => document.querySelector('button[data-on-click=\"toggle\"]').textContent.trim() === 'Pause'",
185+
timeout=3_000,
186+
)
177187

178188
# Within 3 seconds the time should have decremented by at least 1s
179189
# (i.e. shown anything other than "25:00"). Tolerant of timing jitter
@@ -183,8 +193,12 @@ def test_04_pwa_tick_loop_decrements_seconds(example_server, page):
183193
timeout=3_000,
184194
)
185195

186-
# Pause so the loop stops and we don't churn for the rest of the suite.
187-
page.locator('button[data-on-click="pause"]').click()
196+
# Click again to pause — the loop stops and the label flips back.
197+
toggle_btn.click()
198+
page.wait_for_function(
199+
"() => document.querySelector('button[data-on-click=\"toggle\"]').textContent.trim() === 'Start'",
200+
timeout=3_000,
201+
)
188202

189203
assert errors == [], "Browser errors during pomodoro tick:\n " + "\n ".join(
190204
errors

0 commit comments

Comments
 (0)