Prototype Implementation Status
Maps every screen and sub-view in the
~/Desktop/Oterdesign prototypes to its implementation in the KMP app, per platform. Companion to Tokens and design-system-conventions.md. Follow-ups: tech-debt/design-system.md, tech-debt/migrations.md.
Two UI stacks (read this first)
| Stack | Platforms | Entry / navhost | Notes |
|---|---|---|---|
| Shared | Android · iOS · Web (WASM) | SharedAppNavHost (shared/.../navigation/AppNavHost.kt); hosts androidApp/.../MainActivity.kt, composeApp/iosMain/.../IOSApp.kt, composeApp/wasmJsMain/.../App.kt | One commonMain code path → Android, iOS and Web are identical. The prototype rebuilds live here. |
| Desktop | Desktop (JVM) | composeApp/desktopMain/.../ui/navigation/AppNavHost.kt + per-feature *Destinations | Reuses the shared Dashboard, Notifications, Work-Jobs-Hub, Statistics and Nutrition prototype rebuilds (the desktop-only older StatisticsScreen/RecipesScreen + destinations were removed). Command / Search / Focus / Onboarding routes are now registered, with first-launch onboarding gating wired into the desktop navhost (reads OnboardingPreferences, routes un-onboarded users into the wizard). The topbar bell now opens a dropdown-panel variant. |
Because the shared stack is one code path, the per-view tables below use a single Shared (A·iOS·Web) column plus a Desktop column. “Shared ✅” therefore means done on Android and iOS and Web.
Status legend: ✅ done (prototype-faithful, wired) · 🟡 partial · ◐ present but a different/older screen, or limited wiring · ⬜ not present.
Top-level matrix (all four platforms explicit)
| Prototype feature | Android | iOS | Web | Desktop |
|---|---|---|---|---|
| Login / Auth | ✅ | ✅ | ✅ | ✅ |
| Dashboard | ✅ | ✅ | ✅ | ✅ |
| Tasks | ✅ | ✅ | ✅ | ✅ |
| Habits | ✅ | ✅ | ✅ | ✅ |
| Journal | ✅ | ✅ | ✅ | ✅ |
| Books | ✅ | ✅ | ✅ | ✅ |
| Calendar | ✅ | ✅ | ✅ | ✅ |
| Finance home | ✅ | ✅ | ✅ | ✅ |
| Finance sub-screens (stats/category-mapper/product-prices) | ✅ | ✅ | ✅ | ✅ |
| Finance sub-screens (receipt/reconcile/importer/prediction) | ✅ | ✅ | 🟡 | ✅ |
| Workout (+ history) | ✅ | ✅ | ✅ | ✅ |
| Study | ✅ | ✅ | ✅ | ✅ |
| Settings | ✅ | ✅ | ✅ | ✅ |
| Projects | ✅ | ✅ | ✅ | ✅ |
| Tags · Timers · Pomodoros · Chat | ✅ | ✅ | ✅ | ✅ |
| Statistics | ✅ | ✅ | ✅ | ✅ |
| Notifications | ✅ | ✅ | ✅ | ✅ |
| Onboarding | ✅ | ✅ | ✅ | ✅ |
| Command palette | ✅ | ✅ | ✅ | ✅ |
| Search | 🟡 | 🟡 | 🟡 | 🟡 |
| Focus | ✅ | ✅ | ✅ | ✅ |
| Work | 🟡 | 🟡 | 🟡 | ✅ |
| Nutrition | ✅ | ✅ | ✅ | ✅ |
Per-platform tally — Android = iOS = Web: 19 ✅ · 2 🟡 (Work, Search) + the transaction-backed finance sub-screens still stubbed (ProductPrices/ReceiptScan/Reconcile/Importer/BalancePrediction). Desktop now wires Command / Search / Focus / Onboarding / WorkTimeLog / WorkStats + the Work role typed-routes (Jobs Hub stays panel-based, which is richer than the mobile drill-downs). Statistics & Nutrition render the shared prototype rebuilds on desktop; FinanceStatistics + CategoryKeywordMapper are now real on the shared stack (were “coming soon” stubs).
Update: TransactionViewModel was migrated to commonMain (shared/.../ui/viewmodels/TransactionViewModel.kt, converted from androidx ViewModel to the plain-class scope pattern, registered single{} in all four platform DI modules; desktop call sites now use koinInject). ProductPrices is now the real screen on the shared stack. ReceiptScan and Reconcile are now functional on iOS — the iOS picker actuals are implemented (UIImagePickerController for receipts, UIDocumentPickerViewController for bank statements; ReceiptImageBitmap.ios.kt now decodes via skiko). Android pickers were already real. Bank-statement picker now real on all four platforms — the desktop/JVM BankStatementFilePicker.jvm.kt actual (which previously returned emptyList(), breaking the Finance Importer on desktop) now uses a multi-select JFileChooser (csv/txt/tsv, read as text to match the Android actual). Android, iOS and Web (wasmJs) pickers were already implemented. FinanceImporter + BalancePrediction are now also wired on the shared navhost (Screen.FinanceImporter / Screen.FinanceBalancePrediction).
Per-view breakdown (done / not done, by platform)
Work — WorkScreen.kt + role screens + services/work/*
Desktop registers only Screen.Work (→ shared Jobs Hub); none of the role routes are in the desktop navhost.
| View / sub-screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Jobs Hub (cards by type) | ✅ | ✅ |
| New-job dialog | ✅ | ✅ |
| Time Log (day-grouped, filter, billable summary) | ✅ | ⬜ |
Log-session dialog (createSession) | ✅ | ⬜ |
| Time Stats (KPIs, billable split, by-activity) | ✅ | ⬜ |
| SE — Tickets kanban (6 columns) | ✅ | ⬜ |
| SE — Ticket detail + interactive checklist | ✅ | ⬜ |
| SE — Overview tab | ⬜ | ⬜ |
| SE — Team tab | ⬜ | ⬜ |
| CS — Clients & Projects | ✅ | ⬜ |
| CS — Project detail (milestones + billing breakdown) | ⬜ | ⬜ |
| Store — Overview (launch countdown · P&L summary · profile · areas) | ✅ | ⬜ |
| Store — Tasks tab | ✅ | ⬜ |
| Store — Inventory tab (on-hand/reorder + low-stock + KPIs) | ✅ | ⬜ |
| Store — Suppliers tab | ✅ | ⬜ |
| Store — Shifts tab | ✅ | ⬜ |
| Store — P&L line-item detail | ⬜ | ⬜ |
| Inventory stock-events sparkline | ⬜ | ⬜ |
| Dialogs: new ticket / client / project / milestone / supplier / product / stock-event / shift | ⬜ | ⬜ |
| Live running-timer (start/stop) | ⬜ | ⬜ |
Nutrition — NutritionScreen.kt + services/meals/NutritionViewModel.kt
Desktop now renders the shared NutritionScreen (the desktop navhost calls it directly under Screen.Meals.route), so all Nutrition views are at parity across all four platforms.
| View / sub-screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Today dashboard (calorie ring + protein/carbs/fat bars) | ✅ | ✅ |
| Meals grouped by tag + consume/undo | ✅ | ✅ |
Library tab (getAllRecipes + meal-tag filters + day badges) | ✅ | ✅ |
| Week tab (7-day plan, today highlighted) | ✅ | ✅ |
History tab (getRecipeTracksByDateRange 7/14/30d + vs-goal) | ✅ | ✅ |
Add-food / edit / delete recipe + skip-with-alternative + barcode-scan dialogs (AddEditRecipeDialog / SkipMealDialog / BarcodeLookupDialog; camera capture still manual-entry) | ✅ | ✅ |
| Per-user macro goals (vs prototype defaults) | ⬜ | ⬜ |
Statistics — StatisticsScreen.kt
Desktop now renders the shared StatisticsScreen (the desktop navhost calls it directly under Screen.Statistics.route with the shared StatisticsViewModel), at parity across all four platforms.
| View / sub-screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| KPI strip (+ week-over-week deltas: Tasks/Habit-rate/Workouts) | ✅ | ✅ |
| Weekly-activity stacked bars | ✅ | ✅ |
| Activity-by-area donut | ✅ | ✅ |
| Module completion bars · Top streaks | ✅ | ✅ |
Range switcher (Day / Week / Month / Year — StatRange + OterSegmentedControl → setRange, /statistics?range=) | ✅ | ✅ |
Real focus / work time aggregation (Time-spent bars over StudySessions + WorkSessions) | ✅ | ✅ |
Notifications — NotificationsScreen.kt + services/notifications/*
| View / sub-screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Inbox page (filter chips, day groups, typed rows) | ✅ | ✅ |
Mark-read / mark-all-read (POST /notifications/read-all) | ✅ | ✅ |
| Topbar bell + unread badge | ➖ (mobile nav) | ✅ |
| Topbar dropdown-panel variant (prototype) | ➖ (mobile nav) | ✅ |
Onboarding — OnboardingScreen.kt + preferences/OnboardingPreferences
| View / sub-screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| 5-step wizard (Welcome→Areas→Goals→Connect→Done) | ✅ | ✅ |
| First-launch gating (start destination) | ✅ | ✅ |
| Persistence (DataStore / localStorage) | ✅ | ✅ |
| Per-day prompt source | ⬜ | ⬜ |
Command / Search / Focus
| Screen | Shared (A·iOS·Web) | Desktop |
|---|---|---|
Command palette (CommandScreen.kt) | ✅ | ✅ |
Search shell (SearchScreen.kt, quick-access + live /search; results deep-link to task/habit/book/recipe/study/journal detail screens; multi-term AND matching + relevance ranking server-side; a true tsvector/GIN index is still a future optimization) | ✅ | ✅ |
Focus timer (FocusScreen.kt, ring + presets) | ✅ | ✅ |
Dashboard — DashboardScreen.kt (wired on all four platforms)
| Card / element | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Greeting + responsive Up-Next + KPI strips | ✅ | ✅ |
| Tasks · Habits · Books cards | ✅ | ✅ |
| Nutrition card (calorie ring + carbs/fat macro bars) | ✅ | ✅ |
| Workout card (+ next-session highlight) | ✅ | ✅ |
| Finance card (+ month-net delta line) | ✅ | ✅ |
| Journal card (+ per-entry mood dots) | ✅ | ✅ |
Tasks (mobile) — TasksScreen.kt + NewEditTaskMobileSheet.kt + TaskDetailScreen.kt
The shared (mobile) Tasks editor was name+priority only; it’s now at field parity with the desktop dialog. The desktop reuses its own richer NewEditTaskDialog (slot-injected) and is unchanged.
| View / capability | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Create / edit: name · priority | ✅ | ✅ |
| Note | ✅ | ✅ |
Due & Scheduled date+time (real CustomDatePicker/CustomTimePicker) | ✅ | ✅ |
| Subtasks (add / toggle / remove) | ✅ | ✅ |
| Reminders (offset presets) | ✅ | ✅ |
Tags (multi-select from TagsViewModel) | ✅ | ✅ |
Project (picker from ProjectsViewModel) | ✅ | ✅ |
| Detail screen (status · priority · dates · note · tags · subtasks toggle · reminders + complete/edit/delete) | ✅ (TaskDetailRoute) | ✅ (TaskDetailScreen) |
AI import · AI tag organize · view modes (incl. Eisenhower matrix) · showInListFrom | ✅ | ✅ |
Create path carries reminders + tags via the extended
TaskRepository/TaskService.addTask(+TasksViewModel.createTask).ProjectApiServiceis now registered in the Android/iOS DI modules (was desktop/web-only), which the project picker requires.
Habits (mobile) — HabitsScreen.kt (DefaultAddEditHabitDialog) + HabitDetailScreen.kt
The shared habit dialog was name+frequency only; now full. Desktop reuses its own NewEditHabitDialog (already had reminders + subtasks) and is unchanged — a habit tap still opens edit there (the new onOpenDetail is mobile-only).
| View / capability | Shared (A·iOS·Web) | Desktop |
|---|---|---|
| Create / edit: name · frequency | ✅ | ✅ |
| Note | ✅ | ✅ |
Time (real CustomTimePicker) | ✅ | ✅ |
| Reminders (offset presets) | ✅ | ✅ |
| Subtasks (add / toggle / remove) | ✅ | ✅ |
| Detail screen (frequency · streak · time · note · subtasks toggle · reminders + check-in/edit/delete) | ✅ (HabitDetailRoute) | ✅ (HabitDetailRoute → shared HabitDetailScreen via HabitDetailDestination) |
Per-day check-in history (30-day heatmap, GET /habits/{id}/history) | ✅ | ✅ |
| Background reminder service | ⬜ | ◐ (desktop reminder manager) |
Pre-existing features (reused, wired on both stacks)
These were migrated before this program; both stacks register their routes. Sub-views (detail screens, dialogs) are pre-existing and not re-audited here — except Tasks and Habits, whose mobile editors + detail screens were brought to desktop parity (see the two sections above).
| Feature | Shared routes | Desktop routes |
|---|---|---|
| Auth | auth | auth (own Login/SignUp) |
| Tasks (+ detail) | tasks + TaskDetailRoute (now on the shared stack too) | tasks + TaskDetailRoute |
| Habits (+ detail) | habits + HabitDetailRoute (new on shared) | habits |
| Journal | journal | journal |
| Books | books | books |
| Calendar | calendar | calendar |
| Finance | finance + importer/receipt/prices/reconcile/statistics/prediction | same + transaction/budget/scheduled detail routes |
| Workout | workout, workout_history | same |
| Study | study | study |
| Settings | settings | settings (own) |
| Projects | projects | projects |
| Tags · Timers · Pomodoros · Chat · CategoryKeywordMapper | all wired | all wired (+ TimerListDetailRoute) |
Remaining work (tracked follow-ups)
2026-06-21 — mobile (Android) parity hardening: sweep across foundations + Tasks/Habits/Finance Importer + Timers + Pomodoros + Books to close functional gaps. All four targets (:composeApp:compileAndroidMain, :composeApp:compileKotlinDesktop, :composeApp:compileKotlinWasmJs, :shared:compileKotlinIosArm64) green; :androidApp:assembleDebug produces a working APK.
- Foundations on Android — feature-gate composables (
FeatureGuard.kt,AppUnavailableScreen.kt,ConfigLoadingShimmer.kt) were desktop-only; moved toshared/src/commonMain/.../ui/components/so the shared navhost can guard routes the same way desktop does.SharedAppNavHostnow wraps every gated route in agated("KEY")helper that callsRequireFeatureScreenwith the same feature-key map desktop uses.androidApp/.../activities/MainActivity.ktnow collectsThemeViewModel.isDarkTheme+AppLocaleViewModel.languageTagand wraps the content inkey(languageTag) { OterTheme(darkTheme = …) }— Android can now honor dark/light + locale changes.AppViewModel+AppIntent+GuidanceViewModel+BalancePredictionViewModelwere desktop-only; moved toshared/commonMain(BalancePrediction swapped JVM-onlySystem.currentTimeMillis()/java.util.UUIDforkotlin.time.Clock+kotlin.uuid.Uuidunder@file:OptIn) and registered inAndroidAppModule.kt. - Finance Importer is no longer a stub —
FinanceImporterScreeninshared/.../ui/screens/SubScreens.ktwas a 5-line “coming soon” placeholder.TransactionImportScreen(the real CSV/Lines parse + account picker + preview + type overrides + skip-duplicates flow) moved fromcomposeApp/.../desktopMain/.../ui/screens/toshared/src/commonMain/.../ui/screens/(same FQN). Switched theAccountViewModelimport inside the screen from the desktopui.viewmodelspackage to the sharedcom.esteban.ruano.oter.viewmodelpackage; registered the sharedAccountViewModelon desktop too so the dual-namespace coexists. The stubSubScreens.ktwas deleted. - Timer notifications on mobile —
TimerListDetailMobileDestinationhardcodedlistNotifications = emptyList()andonGetTimerNotifications = { emptyList() }. The mobileTimersViewModelnow ownsnotifications(sourced fromwebSocketClient.timerNotifications, mirroring desktop),listNotifications(filtered by the open list id),updateListNotifications(listId), andgetTimerNotifications(timerId). The destination collects the flow + invokes the loader inLaunchedEffect(timerListId). - Pomodoros VM-backed —
TimersViewModelconstructor gainedpomodoroService: PomodoroService(Android, iOS, wasmJs DI all updated fromTimersViewModel(get(), get())→TimersViewModel(get(), get(), get())) pluspomodoros/pomodorosLoading/pomodorosErrorflows +loadPomodoros()/removePomodoro(id)/resetPomodorosError(). The Pomodoros route inSharedAppNavHostdropped its inlineLaunchedEffect { pomodoroService.getPomodoros() }and uses VM state instead. - Tasks + Habits behavior parity on mobile —
TaskDetailScreennow surfaces a time-tracking history section (uses the sharedTimeTrackingHistorySection+TimeTrackingService.getSessionsForTask, refreshes onDomainEvent.TimerSessionStopped) and a three-color due-date indicator (danger / warning / primary by days-until). The mobileTasksScreenoverload now wirescustomRangeStartDate/customRangeEndDate/onCustomRangeChange+ acustomRangePickerslot (using the sharedCustomDatePicker);onRescheduleopens the same date picker and preserves the existingHH:mmsuffix ondueDateTime;onToggleTaskSubtaskflips subtasks inline viatasksViewModel.updateTask. The mobileHabitsScreenoverload passesvirtualHabitRowsafter switchingHabitsViewModel.getHabits()togetByDateRangeWithMeta(the metadata-bearing endpoint desktop already used), wiresonToggleHabitSubtaskinline, and accepts anonOpenVirtualFeaturemap that routesVirtualFeatureKind.WORKOUT/BOOK/STUDY/JOURNALto the matching screens. BooksServiceon Android — was missing fromAndroidAppModule.kt;BookDetailScreen(reached from Books, Search, or the typedBookDetailRoute) calledkoinInject<BooksService>()and would have crashed withNoBeanDefFoundException. Now registered next toRecipesService/StudyService/WorkoutServicewith the samesingle { services.books.BooksService(get(), PlatformConfig.BASE_URL) }shape desktop uses.
Verdict: the shared (Android/iOS/Web) stack is now at functional parity with desktop for every route both platforms target. Deliberately desktop-only items — the 6 typed Finance CRUD routes (FinanceNewTransactionRoute, etc.) and the Work role-specific routes already on the matrix — remain by design; mobile users reach the same operations via FinanceScreen.kt’s inline showTransactionForm / showScheduledTransactionForm / showSavingsGoalForm / showAccountForm / showDebtForm dialogs and the Work feature drill-downs.
2026-06-11 — parity-closure round (real asymmetries found in a fresh code audit):
- Principles on Web (fixed) —
DashboardPrinciplesSectionis in commonMain (renders on every platform) butPrincipleService/PrinciplesViewModelwere only registered in the Android/iOS/Desktop DI modules; they’re now registered incomposeApp/src/wasmJsMain/.../di/WebModules.kttoo (was a wasmJs DI crash). - Mobile transaction detail (fixed) — tapping a transaction on mobile was a dead no-op (
FinanceScreen.ktcalled the no-oponOpenTransactionDetailand short-circuited the edit fallback). The shared navhost now wiresonOpenTransactionDetail→ a new sharedTransactionDetailRoute→ a new sharedTransactionDetailDestinationthat renders the existing commonMainTransactionDetailScreen(via the sharedTransactionViewModel) and hosts the edit form (TransactionForm) so Edit works without a dedicated route. Desktop unchanged. - Chat tier-gate on mobile (fixed) — mobile now gates
Screen.Chat.routeonAuthState.Authenticated.tier, matching desktop: FREE users see a “premium feature” upsell instead of the assistant. - Mobile timer list-detail (fixed, slot-based) —
TimerControls+TimerListDetailScreenmoved to commonMain; the add/edit-timer dialog is now a slot so desktop keeps its richTimersDialog(which depends on desktop-only duration utils — never ported) and mobile supplies a compactMobileTimerDialog. The screen is now responsive (stacks on narrow widths). New sharedTimerListDetailRoute+TimerListDetailMobileDestination(adapts the sharedTimersViewModel’s dashboard state into aTimerPlaybackState; live websocket notifications are desktop-only for now). The sharedTimersViewModelgainedtimerDetailList+ a publicloadTimerListByID.TimersScreen.onOpenTimerListis now wired on mobile.
Verdict: all four asymmetries from this audit are now closed and compile-verified (Principles-on-Web, mobile transaction detail, mobile timer list-detail, chat tier-gate). The shared & desktop stacks are at functional parity; remaining differences (More menu, feature-flag guards, walkthrough, live timer-notification panel) are intentional or platform-shell.
2026-06-11 — post-parity feature round (four prototype features):
- Nutrition skip-with-alternative —
NutritionViewModel.skipMeal()over the existingtrackRecipeSkippedWithAlternativeendpoint; a “Skip” action on today’s meal rows opensSkipMealDialog(pick a library recipe, or enter a manual name + macros viaAlternativeNutrients). - Statistics focus/work time —
ActivityBucketDTO/ActivityBucketgainedfocusMinutes/workMinutes; the serverStatisticsServicenow sumsdurationMinutesper bucket fromStudySessions(focus) andWorkSessions(work), rendered as a “Time spent” bar panel inStatisticsScreen. - Habit per-day history — new
GET /habits/{id}/history?startDate=&endDate=(HabitService.getCheckInHistoryoverHabitTracks→HabitHistoryDTO); clientHabitService.getHabitHistory+HabitsViewModel.loadHabitHistoryfeed a 30-day check-in heatmap onHabitDetailScreen(both stacks). - Search detail screens — new shared
Book/Recipe/StudyItem/JournalDetailScreens (read-only, fetch by id via the existing services; journal reuses the pre-existingGET /daily-journals/{id}with a new clientgetJournalById). Routed on both navhosts (*DetailRoute, desktop mirrors with local route copies);onOpenResultnow deep-links book/recipe/study/journal results, not just task/habit. - Search quality — server
SearchServicenow splits the query into terms and requires all to match (AND) across each type’s columns, then ranks results (exact title → prefix → contains → shorter); a true Postgrestsvector/GIN index remains a future optimization. - Nutrition barcode scan — server
OpenFoodFactsServiceproxies the free Open Food Facts API (GET /nutrition/barcode/{code}→ cleanBarcodeProductDTO, sidestepping client JSON-engine differences); clientRecipesService.lookupBarcode+NutritionViewModel.lookupBarcode+ a “Scan” action on the Library tab openBarcodeLookupDialog, then prefillAddEditRecipeDialog. (Camera capture is out of scope — the user types/pastes the barcode.) - Still open: onboarding per-day prompt (no prototype/code reference — needs a placement + content spec before building), camera-based barcode capture, and the
tsvector/GIN search index.
2026-06-11 — parity round closed the last mobile↔desktop asymmetries:
- Mobile Task power-features — the shared
TasksViewModelwas enriched (in-memoryviewMode/sort/overdue/undated flows +previewTaskImport/importTasksFromText/previewTaskTagAi/applyTaskTagAiwrappers over the commonMainTaskService). The sharedTasksScreenmobile overload now wires view modes (incl. the Eisenhower matrix), AI import (TaskImportSheet), and AI tag-organize (AiTagOrganizerContent), reached from aTasksMobileSettingsPanel. (showInListFromalready flowed viaNewEditTaskMobileSheet.) - Desktop Habit detail — new
HabitDetailDestination+composable<HabitDetailRoute>in the desktop navhost render the sharedHabitDetailScreen(backed by the sharedservices.habits.HabitsViewModel, registered as asinglein desktop Koin). Tapping a habit now opens the detail screen instead of a no-op self-nav. - Search deep-linking —
SearchScreengainedonOpenResult; both navhosts routetask/habitresults toTaskDetailRoute/HabitDetailRoute, other types fall back to the module via the now-publicrouteForType. - Nutrition recipe CRUD —
NutritionViewModelgainedcreateRecipe/updateRecipe/deleteRecipeover the existingRecipesServiceendpoints; the Library tab has an Add-recipe action and tappable cards openingAddEditRecipeDialog(name · meal-tag · macros · note · delete). Barcode/image “scan” still pending. - Desktop bank-statement picker —
BankStatementFilePicker.jvm.ktnow uses a realJFileChooser(see above). - Statistics range switcher — verified already fully wired (Day/Week/Month/Year); no change needed.
None block the core experience.
Desktop parity is now closed — Command / Search / Focus / Onboarding routes are registered (with first-launch onboarding gating), Statistics & Nutrition render the shared prototype rebuilds, and the Work role-routes (WorkTimeLog, WorkStats, WorkTickets/Clients/Store) are wired. iOS finance pickers and the desktop notifications topbar dropdown are done. The genuinely-remaining work, classified by where the gap actually is:
-
Work depth — largely done on the shared stack:
- New
GET /work/jobs/{jobId}/store-pnl/lines(WorkStoreService.getStorePnLLinesover the job’sTransactions) + clientWorkStorePnLLine/WorkService.getStorePnLLines(), surfaced as an expandable P&L line-item detail inWorkStoreScreen. - Inventory stock-events sparkline —
WorkService.getStoreStockEvents()+ a running-levelStockSparklineper product, lazy-loaded byWorkStoreViewModel.loadStockEvents. - Create dialogs (shared
WorkFormDialogsprimitives —WorkFormDialog/WorkTextField/WorkOptionRow): supplier, product, stock-event, shift (store; shift uses a people picker now thatWorkStoreViewModelloadsgetPeople); ticket + person (SE); client + project + milestone (CS). Each backed by new*ViewModel.create*methods over the existingWorkServiceroutes. - SE tabs:
WorkTicketsScreennow has Overview (KPI strip + by-status breakdown bars), Board (the kanban), and Team (people list + add-person) tabs. - CS project detail: project rows in
WorkClientsScreenexpand to show milestones (billing/delivery breakdown, lazy-loaded) with an add-milestone dialog. - Live running-timer (done):
WorkSessionTimerRepositoryis now registered as asinglein all four platform DI modules (TimerWebSocketClient+TimerService+TokenStoragewere already registered everywhere).WorkTimeLogViewModelgainedstartTimedSession/stopActiveTimer— it opens a session withactualStart, kicks the server timer via the repo (best-effort), runs a local stopwatch so the UI ticks even if the WebSocket is down, thencompleteSessionon stop.WorkTimeLogScreenshows a Start timer action in the session dialog and a running-timer banner (elapsedhh:mm:ss+ Stop).
Work depth is now complete on the shared stack.
- New
-
Statistics (done): new dedicated
GET /statistics?range=endpoint (StatisticsService+statisticsRouting, leaving/dashboarduntouched) with real per-bucket activity aggregation over Day/Week/Month/Year and prior-period task/workout deltas; streaks/net-cash/completion come from the dashboard slice. ClientStatisticsService+StatisticsViewModel+ a range segmented control inStatisticsScreen, registered in all four platform DI modules. -
Nutrition: per-user macro goals are now implemented — added
daily{Calorie,Protein,Carbs,Fat,Fiber,Sugar}Goalcolumns toUserSettings(exposed via the existing/settingsGET/PUT),NutritionViewModelreads them (prototype defaults as fallback) and an in-app Daily macro goals dialog persists edits. Remaining (frontend-feasible): recipe-CRUD / add-food / skip-with-alternative sheets (POST/PATCH/DELETE /nutrition/recipesalready exist). -
Search (done):
GET /searchaggregates across tasks/habits/journal/books/recipes/study (SearchService+searchRouting); clientSearchService+SearchViewModeldrive the live results inSearchScreen(debounced), registered in all four platform DI modules. Result deep-linking to the specific record is now implemented for all types (see the 2026-06-11 feature round above); only full-text/FTS ranking remains. -
Tasks & Habits mobile parity (done): the shared editors went from name+priority / name+frequency to full field parity with desktop — note, due/scheduled date+time (real
CustomDatePicker/CustomTimePicker), subtasks, reminders, and (tasks) tags + project pickers — plus dedicated Task/Habit detail screens (TaskDetailRoute/HabitDetailRoute) with complete/check-in, edit and delete.TaskService.addTaskwas extended to carry reminders+tags on create;ProjectApiServicewas registered in the Android/iOS DI modules (was desktop/web-only — the omission only surfaced once the mobile editor injectedProjectsViewModel). Remaining task-only desktop extras on mobile:showInListFrom, AI import / AI tag organize, Eisenhower-matrix view modes.
Verification
./gradlew :shared:compileKotlinJvm :composeApp:compileAndroidMain \
:composeApp:compileDevelopmentExecutableKotlinWasmJs \
:composeApp:compileKotlinDesktop :server:compileKotlin
bash scripts/check_design_tokens.sh --strict