Skip to content

Logging redesign Phase 5: unified LogScreen(date), period as a state of the day - #182

Merged
mapgie merged 14 commits into
mainfrom
claude/logging-redesign-phase-5
Aug 26, 2026
Merged

Logging redesign Phase 5: unified LogScreen(date), period as a state of the day#182
mapgie merged 14 commits into
mainfrom
claude/logging-redesign-phase-5

Conversation

@mapgie

@mapgie mapgie commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Phase 5: unified LogScreen(date)

One screen logs a day; a running period is a state of that day, not a separate destination. Fully additive: the LogPeriod and LogCategory routes stay registered, every existing entry point still uses them, and the only way into the new screen is an opt-in "Try the new day log (preview)" row in the calendar day sheet. Removal is Phase 8.

What's here

  • ui/screens/log/LogScreen.kt + LogViewModel.kt (new): the unified day surface behind route log_day?date={date} (Screen.LogDay), composed from the Phase 3/4 primitives (SectionHeader, ListCard/ListRow, ToneHero, ChipRow, MetricInput, Timeline via TimedIncrementTimeline, PrimarySaveBar).
  • ui/screens/log/PeriodDaySync.kt (new): the flow slider mapping, flow/symptom fan-out, pinned-value rules, and day-number helper extracted from LogPeriodViewModel's private members. LogPeriodViewModel now delegates to it (behaviour unchanged), and LogViewModel calls the same code, so the two surfaces cannot drift.
  • Two states, one layout: off-period the first tracked category leads as a ToneHero (its own role colour; the DB default role is secondary) and the footer is a quiet "Period started today" hairline row; on-period the Flow group and period-dates card slot in at the top, pinned categories render in the flow context, and the footer becomes a filled primaryContainer status row ("Period ongoing", "Since <date>", End/Undo). Everything between is the same components in the same order.
  • Grouped tracked-metric cards (Phase 2 data model, via TrackingRepository group methods): a group of 2+ categories renders as one ListCard of rows, tapping a row opens its input below the card; a group of one (and lone ungrouped categories) renders as its own open section; 2+ ungrouped categories share one "Tracking" card.
  • Switch sheet, organised by group and tinted by role: opened from the screen title it switches day or jumps to a category; opened from a metric's header (the category name is a button) it re-files the entered value under the picked category, preserving the value (serialised through the same label rules a save would use, then hydrated into the target's input shape).
  • Routing: Screen.LogDay + MainActivity registration; DayLogSheet gains an optional onOpenDayLog param (default null; existing callers unaffected) wired only from HomeScreen.

§3 Logging preservation checklist (each item: works on the old screens AND on the new one)

  • Chip multi-select (default) — old untouched; new via MetricInput/ChipRow per category
  • Stepped slider (numeric_slider) — old untouched; new via MetricInput (StepScale for whole ratings up to 10 steps, parity slider otherwise, per Phase 4)
  • Free numeric with unit (numeric_free) — old untouched; new via MetricInput, empty input skips that category (the single-entry screen's "empty blocks save" translated per entry)
  • Count stepper (increment) — old untouched; new via MetricInput, count of 0 skips (pinned-on-period keeps the period screen's save-including-0 rule)
  • Timed increment, per-tap timestamp + timeline — old untouched; new renders the same internal TimedIncrementTimeline with per-tap saves and per-entry delete
  • Yes/No and Time (Phase 4 types) — old untouched; new via MetricInput, stored as the same value-label strings
  • "Previously recorded (removed from options)" chips — both
  • Notes per log, 500-char cap — old untouched; new has per-entry notes ("Add note") saved with each log, plus the episode Notes field on-period
  • Edit an existing log — both (new loads the day's entry per category and updates in place)
  • Delete a log — both (per-entry "Delete entry" with confirmation; timed entries per row)
  • Date selection — both (day switch from the Date row or the title sheet, reloading the selected day's stored values; guarded when unsaved changes exist)
  • Period day: start/end dates, ongoing (null end) — both ("Started/Ended" rows, clear-end, open-ended explainer with the tolerance count)
  • Flow slider or chips with the 1 Spotting / 2 Light / 4 Heavy / else Medium mapping — both (shared PeriodDaySync.flowLabelForSliderValue)
  • Symptoms chips + inline Add via AddSymptomDialog writing to the value catalog — both (same dialog, same addValueToCategory path)
  • Pinned showInLogPeriod categories in the flow context — both (new: rendered after Flow while on-period, in their normal group otherwise; saves keep computePinnedValues semantics incl. slider-min fallback and count 0)
  • Episode/continuation: gap tolerance from prefs, episode day number, range vs open-ended — both (PeriodRepository.periodForDate, logPeriodDay/logPeriodRange/updateEpisode, day-past-stored-end extension rule preserved)
  • "Remove this day" — both (confirmation copy preserved; tracking logs for the day kept)
  • "Delete Entire Period" — both (episode + per-day tracking logs)
  • "Disable period logging" — both (overflow menu)
  • Unsaved-changes discard/save guard — both (same dialog; new also guards day switching)
  • Save fan-out to the tracking system (syncFlowToTrackingLog, syncSymptomsToTrackingLog, pinned logs) — both, through the shared PeriodDaySync
  • Widget refresh (GoFloWidget.updateAllWidgets) + reminder refresh (ReminderScheduler.refreshPredictionReminders) on period-affecting saves — both

Phase guide acceptance criteria

  • Off-period day: hero + symptoms + tracked metrics + notes log and persist; "Period started today" starts one — needs device pass
  • On-period day: flow leads, lead compresses, footer shows status + End; rest identical — needs device pass
  • Period data appears in Stats / Flow / Symptoms / widget exactly as the old screen produced — same repository calls by inspection; needs device pass
  • Header switcher re-files without losing the entered value (LogViewModel.refileEntry)
  • Grouped multi-metric card: 2+ categories one card of rows; 1 renders the single-metric section
  • TalkBack traversal top to bottom; StepScale reads as one control — layout order and StepScale's single-control semantics are as specified; needs device pass
  • a11y_check.py green (run locally)
  • wcag_check.py green (run locally; no colour values changed)
  • Old LogPeriod / LogCategory routes still registered and reachable; nothing deleted
  • CI build/tests — CI is the build check for this environment

Deviations (also recorded in PLAN.md §7)

  1. Re-file trigger: the guide's "title-as-button" sheet exists, but on a whole-day surface the title sheet is day-switch + jump; the re-file action lives on each metric's own header (its name is the button), where "file THIS entry elsewhere" is unambiguous. Both open the same sheet, organised by group and tinted by role.
  2. Day-level Notes bind to episode notes, so they render only while the day is on-period (there is no day-notes store off-period); per-log notes are editable per metric instead.
  3. Untouched entries are skipped on off-period saves: no fabricated logs for ignored categories, and no rewriting of stored entries (which would re-stamp or clear loggedAt). Pinned categories keep the period screen's always-save fan-out while on-period.
  4. allowMultiple (non-timed) categories always start a fresh entry on the day screen (matching LogCategoryViewModel's new-entry rule); editing one specific log among several same-day logs remains a LogCategory flow via the day sheet.
  5. Entry-point flip (guide sub-PR 5d) deferred: calendar tap, FAB, and quick log keep opening the old screens; the preview row in DayLogSheet is the only new entry, so a broken new screen can never block logging.

Notes for Phase 6

  • The switch sheet consumes groups via LogUiState.groups (TrackingRepository.getAllGroupsOnce) and renders group labels with Group.colorRole; category tints resolve through effectiveColorToken(groups), so "inherit" categories will follow group recolouring with no screen changes.
  • TrackingSections already implements the "group of 2+ = one card, group of 1 = own section" rule the What You Track home will mirror.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg


Generated by Claude Code

claude added 13 commits August 25, 2026 00:50
Adds the redesign's only genuinely new table: a category group that owns
a colour role and a default input type. Fully additive; no UI changes.

- Group entity (table `groups`) + GroupDao; TrackingCategory gains a
  nullable groupId (no FK: deleting a group unfiles members instead of
  cascading). MIGRATION_23_24 creates the table and adds the column;
  existing rows keep groupId NULL and their colorToken, so nothing
  changes visually after migrating.
- Group CRUD, reorder, delete-with-unfile, and assign/unassign methods
  on TrackingRepository (new nullable groupDao constructor param, wired
  in GoFloApplication).
- Colour inheritance per PLAN.md section 5: new "inherit" colorToken
  sentinel resolved by TrackingCategory.effectiveColorToken(groups) to
  the group's colorRole, or neutral surfaceVariant when groupless.
  Deliberately not a CategoryColor entry so the Phase 1 role picker
  does not offer it. Existing tokens are untouched (no grey wipe;
  confirmed with owner against the handover's neutral-by-default).
- Migration test (Migration23To24Test): runs the real MIGRATION_23_24
  against a real SQLite v23 schema via sqlite-jdbc (test-only dep),
  routing execSQL through a reflection proxy, and asserts the exact
  schema Room expects plus data preservation. MigrationTestHelper is
  unusable here (no instrumented tests, exportSchema = false).
- PLAN.md section 7 progress log updated; subsystem map 02 updated for
  v24; changelog fragment (minor); LESSONS.md entry on JVM-side Room
  migration testing.

Verified: migration SQL executed against a seeded v23 schema in SQLite
(PRAGMA output matches the test's expected schema exactly);
a11y_check.py and wcag_check.py both clean.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg

Co-authored-by: Claude <noreply@anthropic.com>
Adds the ~12 stateless, parameterised Compose primitives every later
redesign screen is assembled from, in ui/components/, previews only.
No existing screen is rewired; nothing is removed.

- SectionHeader: uppercase 11sp section label with right-aligned value
- ListCard / ListRow / HairlineDivider: hairline-outlined card of rows
- StepScale: discrete rising tap-steps; exposes to TalkBack as a single
  control ("Flow, Medium, 3 of 4") with per-step custom actions
- ChipToggle / ChipRow: tonal-fill + check selection chips (FilterChip)
- ToneHero: tonal hero container, Comfortaa word applied explicitly
- SegmentedToggle: single-choice segmented buttons, optional role tint
- RolePicker: 6 in-theme role pills + fixed-colour track, standalone
  (visuals mirrored from the Phase 1 inline picker; ManageCategories
  keeps its copy until a later phase rewires it)
- IconPicker: 48dp icon tile grid with radio semantics + display names
- SwitchRow: full-row toggle, Role.Switch + stateDescription, inner
  Switch has no click handler
- Timeline / TimelineEntry: timestamped multiple-per-day list with
  per-row overflow and an append row
- PrimarySaveBar: sticky 52dp pill with gradient fade
- MetricInput facade stub + MetricConfig/MetricValue value types
  (incl. YesNo/TimeOfDay variants ready for Phase 4)

Every primitive accents from a passed-in role Color (tonal container
fills derived via roleContainerTint, lerp toward surface); each carries
light, dark, and 200% font-scale previews through GoFloTheme so the
extended-roles CompositionLocal path is exercised. a11y_check.py and
wcag_check.py both clean. PLAN.md progress log updated; DB stays at 24.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg
Co-authored-by: Claude <noreply@anthropic.com>
Completes the MetricInput facade so it renders every category input type,
and refactors LogCategoryScreen to render through it with behaviour parity:

- CategoryType gains YES_NO ("yes_no") and TIME ("time"). Both store their
  readings as plain value-label strings ("Yes"/"No"; 24-hour "HH:mm") in
  tracking_log_values per the owner's resolution of PLAN.md §8 decision #3:
  no new columns, no migration (DB stays at v24).
- MetricInput: whole-step rating scales (up to 10 steps) render as StepScale;
  decimal or wider ranges keep the parity slider including whole-number
  stepped behaviour, large readout, scale labels, min/max labels, and the
  unset-value hint. numeric_free keeps unit label + placeholder; increment
  keeps the never-below-zero counter (decrement disabled at 0); yes_no
  renders a role-tinted SegmentedToggle; time renders the new TimeField
  primitive (Material time picker, 24h).
- LogCategoryScreen: the inline per-type when is gone; every non-timed input
  renders via MetricInput. Timed increment stays screen-driven (per-tap
  immediate saves, no Save button) and now renders the Timeline primitive.
  Notes 500-char cap, save/update, delete, date selection, edit-existing,
  empty-catalog text, and "previously recorded (removed)" chips unchanged.
- LogCategoryViewModel: additive setSelectedValues; yes_no/time flow through
  selectedValues, so the existing else-branch save path persists them and
  the numeric_free empty-blocks-save / increment <=0-blocks-save rules are
  untouched.
- LogPeriodScreen: PinnedCategoryInput gains additive yes_no/time branches
  delegating to MetricInput (pre-existing branches untouched); additive
  LogPeriodViewModel.setPinnedSingleValue feeds the existing selection-set
  save path.
- TrackingCategory.isNumeric now enumerates the numeric types explicitly so
  yes_no/time chart as label categories in Stats instead of being fed into
  numeric chart math.
- Create flow: the two new types appear automatically in the New Category
  type chips; the unit field is now limited to the genuinely numeric types.
- Docs: PLAN.md §7 row + §8 #3 resolution, subsystem map 01 drift note,
  LESSONS.md entry on negation-defined classifications, changelog fragment
  (minor).

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg
Co-authored-by: Claude <noreply@anthropic.com>
The flow slider mapping (1 Spotting / 2 Light / 4 Heavy / else Medium),
the flow and symptom fan-out into the tracking system, the pinned-category
value rules, and the episode day-number helper move from private members of
LogPeriodViewModel into an internal PeriodDaySync object, so the unified day
screen (next commit) shares the exact same code instead of a copy that could
drift. LogPeriodViewModel delegates to it; behaviour is unchanged.

Also widens LogCategoryScreen's metricConfigFor and TimedIncrementTimeline
from private to internal for the same reuse.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg
Co-authored-by: Claude <noreply@anthropic.com>
New LogScreen + LogViewModel compose the Phase 3/4 primitives into one
per-day surface behind the additive route log_day?date={date}:

- Off-period: the first tracked category leads as a ToneHero, flow is not
  rendered, and the footer is a quiet Period-started-today row (which flips
  the screen into its on-period arrangement before saving).
- On-period: the Flow group (StepScale, stepped slider, or chips per the
  category's mode, with the 1 Spotting / 2 Light / 4 Heavy / else Medium
  mapping) slots in at the top with the period dates card, pinned
  showInLogPeriod categories render in the flow context, and the footer is a
  filled status row with End/Undo.
- Between the two states everything is identical: symptoms chips with the
  inline Add dialog, tracked metrics organised by group (two or more members
  render as one ListCard of rows with the active row's input beneath; a group
  of one renders as its own open section), per-entry notes with the 500-char
  cap, previously-recorded chips, track-against-time, per-entry delete, and
  timed-increment timelines with per-tap saves.
- The title and every metric header open a switch sheet organised by group
  and tinted by role: from the title it switches day or jumps to a category;
  from a metric header it re-files the entered value under another category
  without losing it.
- Saving reuses the period screen's exact sequence via PeriodDaySync
  (episode continuation and boundary edits, episode meta, flow/symptom/pinned
  fan-out, widget and prediction-reminder refresh); off-period saves write
  only categories the user touched, using the category screen's per-type
  rules. Remove-day, delete-entire-period, disable-period-logging, and the
  unsaved-changes guard are all ported.

The LogPeriod and LogCategory routes stay registered and every existing
entry point still uses them; the only new entry is an opt-in preview row in
the calendar day sheet. Removal is Phase 8.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg
Co-authored-by: Claude <noreply@anthropic.com>
Marks Phase 5 done in PLAN.md with its deviations, records the new screen
and the PeriodDaySync extraction in subsystem map 01, adds the minor-bump
changelog fragment, and adds a lesson on translating single-entry save
blocking into per-entry skip rules on a batch save surface.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg
Co-authored-by: Claude <noreply@anthropic.com>
CI failed compileDebugKotlin on PR #180: role/selected/contentDescription/
customActions were assigned inside semantics{} blocks as this.role etc.
without importing the corresponding androidx.compose.ui.semantics extension
properties (importing the Role class does not cover the lowercase role
property). Adds the missing imports to IconPicker, ListCard, RolePicker,
StepScale, and Timeline, and extends the LESSONS.md shadowing entry with
the import requirement. Verified with a semantics-vs-imports sweep over
every main-source Kotlin file plus a11y_check.py.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg

Co-authored-by: Claude <noreply@anthropic.com>
…meField

Brings in the phase-3 fix for unresolved semantics extension properties and
applies the same fix to TimeField.kt (Phase 4 file, missing the lowercase
androidx.compose.ui.semantics.role import), found by sweeping every
main-source file with the same checker.

Claude-Session: https://claude.ai/code/session_01PZJLynVBkgLtehJFXffnfg

Co-authored-by: Claude <noreply@anthropic.com>
…gn-phase-4

# Conflicts:
#	app/src/main/java/com/mapgie/goflo/ui/components/MetricInput.kt
#	docs/design/logging-redesign/PLAN.md
Base automatically changed from claude/logging-redesign-phase-4 to main August 26, 2026 08:17
@mapgie
mapgie marked this pull request as ready for review August 26, 2026 08:18
…gn-phase-5

# Conflicts:
#	LESSONS.md
#	app/src/main/java/com/mapgie/goflo/ui/screens/log/LogCategoryScreen.kt
#	docs/design/logging-redesign/PLAN.md
#	docs/design/logging-redesign/subsystem-maps/01-logging-screens.md
@mapgie
mapgie merged commit b60cfbf into main Aug 26, 2026
4 checks passed
@mapgie
mapgie deleted the claude/logging-redesign-phase-5 branch August 26, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants