Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
63fa6bd
Phase 2: group data model (migration 23 to 24) with colour inheritance
claude Aug 25, 2026
6265680
Phase 3: reusable component library for the logging redesign
claude Aug 25, 2026
cd074f3
Merge origin/main (Phase 2 merged as #179, release v0.54.0-beta.1)
claude Aug 25, 2026
d93476e
Phase 4: MetricInput facade + Yes/No and Time input types
claude Aug 25, 2026
ad12e98
Extract shared period-day logic into PeriodDaySync
claude Aug 25, 2026
ed027ad
Add unified LogScreen(date): one screen logs a day, period is a state
claude Aug 25, 2026
b2cf50d
Phase 5 bookkeeping: progress log, map drift note, changelog, lesson
claude Aug 25, 2026
94fb3f8
Fix unresolved semantics property references in the component library
claude Aug 25, 2026
9c4c6ee
Merge branch 'claude/logging-redesign-phase-3' into claude/logging-re…
claude Aug 25, 2026
e6353b0
Merge phase-3 semantics-import fix; fix the same missing import in Ti…
claude Aug 25, 2026
4113e39
Merge branch 'claude/logging-redesign-phase-4' into claude/logging-re…
claude Aug 25, 2026
e76e398
Phase 6: redesign What You Track home with first-class groups
claude Aug 25, 2026
007f11a
Merge remote-tracking branch 'origin/main' into claude/logging-redesi…
claude Aug 26, 2026
1121e0c
Merge branch 'claude/logging-redesign-phase-4' into claude/logging-re…
claude Aug 26, 2026
eede9e1
Merge branch 'claude/logging-redesign-phase-5' into claude/logging-re…
claude Aug 26, 2026
790830b
Merge remote-tracking branch 'origin/main' into claude/logging-redesi…
claude Aug 26, 2026
cf515a6
Merge branch 'claude/logging-redesign-phase-5' into claude/logging-re…
claude Aug 26, 2026
86e3233
Merge remote-tracking branch 'origin/main' into claude/logging-redesi…
claude Aug 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Entries within each section are ordered by risk to a new project if forgotten: b
**A `role: Color` parameter shadows the `role` semantics property — qualify with `this.role` inside `semantics {}`**
Components that take the category's colour as a `role: Color` parameter break the idiomatic `semantics { role = Role.Button }` assignment: inside the lambda, the enclosing function's `role` parameter shadows the `SemanticsPropertyReceiver.role` extension property, so the unqualified assignment tries to reassign the `val` parameter and fails to compile. Write `this.role = Role.Button` (and likewise `this.selected` / `this.contentDescription` when locals share those names) inside `semantics {}` and `clearAndSetSemantics {}` blocks. The qualified form still satisfies `a11y_check.py`'s pattern match. Crucially, `this.role` still needs the extension property imported: each semantics property is a top-level extension in `androidx.compose.ui.semantics` (`import androidx.compose.ui.semantics.role`, `.selected`, `.contentDescription`, `.customActions`, ...), and importing the `Role` class does NOT cover the lowercase `role` property — omitting them fails only at compile time ("Unresolved reference"), which a build-less environment won't catch. When writing semantics blocks without a compiler, check every property assigned in the block against the file's import list.

**An async completion callback that reads Compose state vars races with the reset that closes the dialog — snapshot into locals first**
A dialog's confirm handler often does two things: launch work whose completion callback reads UI state (`viewModel.addGroup(...) { id -> file(pendingCategoryId, adoptColor) }`) and immediately reset that same state to close the dialog (`pendingCategoryId = null; adoptColor = true`). Because the callback runs after the coroutine completes, it reads the already-reset values, silently dropping the user's choice with no error. Capture every state var the callback needs into an immutable local at the top of the handler and reference only the locals inside the callback. This applies to any `onCreated`/`onComplete`-style lambda passed into a ViewModel from a composable that also clears its own state.

**`SwipeToDismissBox`: use `confirmValueChange` returning `false` to intercept — not `LaunchedEffect` + `reset()`**
When a swipe should show a confirmation dialog before committing, the natural-looking approach is `confirmValueChange = { true }` (allow the state change) then call `state.reset()` in the dialog's Cancel handler. This causes two bugs: (1) if the composition survives navigation, the `LaunchedEffect` key hasn't changed on return so the dialog silently re-appears; (2) `reset()` takes one animation frame, leaving a brief window where swiping is disabled. The correct pattern is `confirmValueChange = { newValue -> if (newValue == EndToStart) { showConfirm = true; false } else true }`. Returning `false` rejects the transition entirely — the box springs back immediately, no `reset()` call is needed, and the dialog fully controls the outcome. Remove the `LaunchedEffect` and the coroutine scope from the composable.

Expand Down
32 changes: 19 additions & 13 deletions app/src/main/java/com/mapgie/goflo/ui/components/RolePicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ import com.mapgie.goflo.ui.util.toHexColorKey
*
* State is hoisted: [selectedToken] is a [CategoryColor] key or an 8-char hex
* key, and [onPick] fires with the tapped token.
*
* Set [showFixedSection] to false on surfaces that only offer in-theme roles
* (a group's colour role is never a raw hex).
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
Expand All @@ -61,6 +64,7 @@ fun RolePicker(
roles: List<CategoryColor> = CategoryColor.entries,
fixedColors: List<Int> = CATEGORY_COLOR_OPTIONS,
extraFixedSlot: (@Composable () -> Unit)? = null,
showFixedSection: Boolean = true,
) {
Column(
modifier = modifier,
Expand All @@ -79,20 +83,22 @@ fun RolePicker(
)
}
}
HairlineDivider()
SectionHeader(label = "Fixed colour", value = "Stays put on theme change")
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
fixedColors.forEach { argb ->
FixedSwatch(
argb = argb,
isSelected = argb.toHexColorKey() == selectedToken,
onPick = onPick,
)
if (showFixedSection) {
HairlineDivider()
SectionHeader(label = "Fixed colour", value = "Stays put on theme change")
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
fixedColors.forEach { argb ->
FixedSwatch(
argb = argb,
isSelected = argb.toHexColorKey() == selectedToken,
onPick = onPick,
)
}
extraFixedSlot?.invoke()
}
extraFixedSlot?.invoke()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ internal fun CategoriesHelpDialog(onDismiss: () -> Unit) {
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
HelpSection(
"Grouped and Ungrouped",
"The Grouped view shows one card per group, tinted with the group's colour. The Ungrouped view lists categories that are not in any group. Both views show the same categories; nothing is hidden by switching."
)
HelpSection(
"Groups",
"A group collects related categories and gives them a shared colour role. Use Add to group to file a category, and Edit on a group card to rename it, change its colour, reorder it, or delete it. Deleting a group keeps its categories: they just become ungrouped."
)
HelpSection(
"Category types",
"Default: choose from a list of named values you define. Slider and Numeric Input: record a number. Plus One: tap to add to a daily count."
Expand All @@ -32,7 +40,7 @@ internal fun CategoriesHelpDialog(onDismiss: () -> Unit) {
)
HelpSection(
"Reorder categories",
"Long-press the drag handle on the right side of a row to pick it up, then drag it to a new position."
"Tap the reorder button in the top bar to show every active category in one list, then long-press the drag handle on the right side of a row to pick it up and drag it to a new position."
)
HelpSection(
"Archive",
Expand Down
Loading
Loading