feat(shell): the navigation host, four tabs and the live pill

`NavigationSuiteScaffold` gives the bar on a phone and the rail on a wide
layout from one declaration. Each tab keeps its own back stack; re-selecting
a tab returns to its root, and predictive back is wired only where back
actually goes somewhere.

The pill is the running-state surface from PLAN §9: it floats above the bar,
speaks for whichever subject is closest to needing attention, and pauses or
stops it from any tab. Its callbacks are gated on a live subject, so a second
tap during its exit animation cannot restart a timer the user just reset.

The notification permission is asked once, and the flag is written from the
request's result rather than before it — a process death mid-dialog leaves
the ask due again rather than recorded and never made. Three tabs are empty
shells until M5 through M8 fill them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wmy1BpCKi8KeSjaWhYuCPV
This commit is contained in:
2026-09-12 13:07:18 +02:00
parent 199005e795
commit 83c5e9fe3f
21 changed files with 1544 additions and 28 deletions
+34
View File
@@ -15,4 +15,38 @@
<!-- Crash reporting (floret-kit core-crash) -->
<string name="crash_report_issue_title">Crash report</string>
<string name="report_issue_url" translatable="false">https://codeberg.org/jlmakiola/clockula/issues/new</string>
<!-- M4: the app shell's four tabs -->
<string name="tab_alarms">Alarms</string>
<string name="tab_timers">Timers</string>
<string name="tab_stopwatch">Stopwatch</string>
<string name="tab_world_clock">World clock</string>
<!-- M4: the tabs' empty states -->
<string name="alarms_empty">No alarms yet</string>
<string name="timers_empty">No timers yet</string>
<string name="world_clocks_empty">No world clocks yet</string>
<!-- M4: the live pill -->
<string name="live_pill_pause">Pause</string>
<string name="live_pill_resume">Resume</string>
<string name="live_pill_stop">Stop</string>
<plurals name="live_pill_more_timers">
<item quantity="one">+%1$d more</item>
<item quantity="other">+%1$d more</item>
</plurals>
<string name="live_pill_timer_running">Timer %1$s, %2$s remaining</string>
<string name="live_pill_timer_paused">Timer %1$s paused, %2$s remaining</string>
<string name="live_pill_timer_expired">Timer %1$s finished</string>
<string name="live_pill_timer_untitled">Timer</string>
<string name="live_pill_stopwatch_running">Stopwatch running, %1$s</string>
<string name="live_pill_stopwatch_paused">Stopwatch paused at %1$s</string>
<!-- M4: the ring screen's dismiss challenge -->
<string name="ring_challenge_math_prompt">What is %1$s?</string>
<string name="ring_challenge_math_hint">Answer</string>
<string name="ring_challenge_wrong">Not quite \u2014 here is another</string>
<string name="ring_challenge_hold">Hold to dismiss</string>
<string name="ring_challenge_escape">Dismiss without solving</string>
<string name="ring_snooze_unavailable">No snoozes left</string>
</resources>