Skip to main content
This plan turns an external contributor’s mockup (seven iOS-style screens) into a delivery sequence for apps/mobile. It builds on the design-system stack that started with #6857:
  1. #6857: one sheet design and one loader;
  2. #6860: profile edit and timeline;
  3. #6861: Assistant settings IA;
  4. #6862: Mira Live redesign;
  5. #6863: drawers, toggles and dialogs.
Every phase below sits above that stack and must not rewrite it. Product decisions are recorded on the feature pages linked in Decisions. This page owns the plan and the inventory.

Decisions

Information architecture

The mockup proposes five tabs (Home, Apps, Mira, Inbox, You) plus a search action. Today the dock has Home, Apps and the centre Assistant (Mira) entry. Profile and Notifications are reachable from the shell.
  • Search. The dock’s trailing search action opens the existing shell search field. It does not add a second search field in the body.
  • Back. Back stays on the dock (decision 1). The task detail mockup’s top-left chevron is dropped. Its trailing check, share and overflow actions become shell chrome actions.
  • Workspace switcher. Chips are All, then pinned workspaces, then More. More opens a popover menu with:
    • the remaining workspaces (initial tile and name);
    • Find Workspace (opens the existing picker search);
    • Manage Workspaces;
    • New Workspace.
    With fewer than five workspaces, every workspace gets a chip and More holds only the three commands. Pins are stored per user (see Data and storage).
  • Tablet and desktop. These widths keep the leading ShellNavigationRail with the same five destinations. The dock and rail must not remount a visited root tab.
Promoting Inbox and You to tabs changes the root routes. That phase must update the route aliases in core/router/routes.dart, the shell chrome config titles, and the dock regression tests.

Component inventory

Each component either exists in core/ui or is new and owned by the listed phase. Rows marked “exists” must be reused. Shared rules for all phases:
  • Colours come from AppTokens.
  • No emojis and no hard-coded hues. The mockup’s green, orange and red swipe fills map to AppTone.success, warning and destructive.
  • Every row action has a semantics action.
  • Every interactive target is at least 44pt.

Appearance style system

Appearance already persists the mode, theme, accent, background, intensity, softness and card opacity on the device. This plan adds an Interface group. Its values are applied by the theme layer, not by per-widget platform forks. Design constraints:
  • One extension, read through AppTokens. Components read the active style from AppTokens.of(context). A widget never branches on Platform.isIOS. The only remaining platform checks are system-owned behaviours such as Android predictive Back and iOS edge-swipe interception.
  • Persisted per device. The settings use the same storage as the existing appearance payload: a versioned JSON field in AppearanceSettings, decoded field by field so unknown values fall back. They are not synced to the account.
  • Contrast. appearance_contrast_test.dart gains cases for every Style and Radius combination, in light and dark.
  • Preview. The Appearance page shows a live sample row: a task row with a swipe hint, a toggle and a sheet header.
  • Reset to default restores every option.

Pomodoro and Focus Time

Focus Time is time tracking, not a separate store. A Pomodoro focus interval is a normal time_tracking_sessions row, and Focus Time statistics are existing tracker statistics filtered to those rows.

Proposed feature set

  • Configurable focus, short-break and long-break lengths, plus the number of focus intervals before a long break. These are the existing PomodoroSettings fields and the control foundation’s config contract: minutes 1–180, cycle 1–24.
  • Auto-start breaks and auto-start focus (existing booleans).
  • Link to a task. Starting focus from a task row (the context menu’s “Start focus”, phase 6) or from task detail sets task_id on the session.
  • Notifications. At each phase end, the device schedules a local notification (flutter_local_notifications, already a dependency) and cancels it when the phase ends early. Cross-device delivery when every client is closed needs the server deadline described below.
  • Pause, resume and skip. Pause uses the existing break model (time_tracking_breaks). Skip ends the current phase.
  • Focus Time stats. Today, last week and the current month totals, a seven-day bar chart, the completed-interval count and a streak, on the You tab and in the Timer statistics.
  • Web parity. The web time tracker shows the same running phase and remaining time. This waits for shared control activation.

Data model (reuse first)

A migration is required before cross-device Pomodoro can ship. It must relax the mode, phase and deadline_at checks on private.time_tracker_controls, add a pause/resume/skip command contract under the existing advisory-lock order, and optionally add a tag filter to the stats RPCs. This plan does not write that migration. As the Timer page says, activation needs its own reviewed contract that normalizes every lifecycle writer.
Phase deadlines that must fire while all clients are closed belong to a Cloudflare scheduler (Durable Object alarm per actor and workspace), not Vercel Cron, pg_cron or Edge Functions. That work must have:
  • bounded retries;
  • idempotent phase transitions keyed by the control revision;
  • a tested durable stop fence when the control returns to off.
Until then, Pomodoro is device-driven: the client writes ordinary sessions, and local notifications fire on that device only.

Data and storage

  • Workspace pins. Pins are stored per user in user_configs under a mobile pins key: an ordered JSON array of workspace IDs. That is no schema change, but check the key against any user_configs id allowlist. Reads drop IDs the user can no longer access. Pins are not shared with other users.
  • Appearance. The new options are device-local (see above).
  • Swipe and menu actions reuse the board cubit mutations and add no storage.

Phased PR breakdown

Every phase is a native stack layer above #6863 (stack #6883). If a phase does not depend on the stack, it goes on its own branch from main. Each phase needs:
  • English and Vietnamese ARB strings and flutter gen-l10n;
  • widget tests at 320x568 with 2x text in light and dark;
  • Thanh flutter analyze and focused tests;
  • exact-commit CI.

Open questions

  • Should Inbox replace the shell notifications bell entirely once it is a tab, or keep the bell on Home?
  • Should Focus Time count only Pomodoro-tagged sessions, or all tracked time linked to tasks?
  • Should Style be offered per device only (planned), or also follow the account to a new device?
  • Should the Kanban card long-press also move to the context menu, matching List view?