Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions .ai/skills/decompose-component-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ description: Use in the Blinkly Kotlin Multiplatform repository when writing, up
Read `AGENTS.md` first for architecture and test conventions.

Blinkly component tests cover onboarding, home navigation, Store-backed tabs,
nested feature components, coroutine side effects, manager-derived state, fake
time/settings, output mapping, and Mokkery collaborator verification.
root-pushed feature components, coroutine side effects, manager-derived state,
fake time/settings, output mapping, and Mokkery collaborator verification.
`MainTabComponentTest` remains the reference for a Store-backed tab with
manager-derived state, watcher flows, error labels, and CTA output mapping.

Expand All @@ -31,7 +31,7 @@ Store-backed tab component tests:
- `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt`
- `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt`

Store-backed nested feature tests:
Store-backed root-pushed feature tests:
- `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/preferences/PreferencesComponentTest.kt`
- `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt`
- `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt`
Expand Down Expand Up @@ -69,6 +69,15 @@ For parent navigation components, verify:
- back navigation by child callback and by parent `onBack()`
- output translation from child to parent navigation

Current Blinkly navigation ownership:
- `RootComponentDefault` owns root-pushed feature screens: `Preferences`,
`Workout`, `Achievements`, `Garden`, and `AddNewReminder`.
- `HomeScreenComponentDefault` owns only the tab stack and handles
`Main.OpenProgressTab` locally.
- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and
`TrainingsTabComponentDefault` should be tested as Store-backed tabs that
emit outputs, not as parents with nested child stacks.

For Store-backed child components, verify:
- model state before actions
- model state after component callbacks
Expand Down
22 changes: 18 additions & 4 deletions .ai/skills/decompose/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ description: Use in the Blinkly Kotlin Multiplatform repository when adding, cha

Read `AGENTS.md` first for the project-level architecture.

Blinkly has baseline Decompose components across onboarding, home tabs, nested
feature routes, Compose bindings, previews, and common component tests.
Blinkly has baseline Decompose components across onboarding, home tabs,
root-pushed feature routes, Compose bindings, previews, and common component tests.
`MainTabComponent` remains the primary Store-backed tab reference, while
`progress`, `reminders`, and `trainings` show Store-backed tabs with nested
child navigation.
`progress`, `reminders`, and `trainings` show Store-backed tabs that emit
root-handled navigation outputs.

## Use these local references first

Expand Down Expand Up @@ -112,6 +112,16 @@ Config rules:

Keep navigation calls on the main thread.

Current Blinkly hierarchy:
- `RootComponentDefault` owns the app-level stack and pushes `Preferences`,
`Workout`, `Achievements`, `Garden`, and `AddNewReminder`.
- `HomeScreenComponentDefault` owns only the four-tab stack and forwards most
tab outputs upward to root.
- `OnboardingComponentDefault` owns the onboarding step stack.
- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and
`TrainingsTabComponentDefault` do not own nested child stacks; they emit
`ComponentOutput` values that root interprets.

## Output rules

Use `ComponentOutput` to communicate upward.
Expand Down Expand Up @@ -166,6 +176,7 @@ Use them to:
- create platform dispatchers
- build module factories from `shared/*/di`
- assemble `DomainModule`
- create `BeeperModule` and pass `BlinklyBeeper` to workout execution
- instantiate `RootComponentDefault`

Do not push DI container logic into feature components.
Expand All @@ -180,6 +191,9 @@ Keep feature components constructor-injected.
5. add navigation config and child mapping in the parent
6. add Compose content in `shared/compose`
7. add common tests for navigation or behaviour
8. update `AGENTS.md` in the same change when the new screen or flow changes
module layout, component hierarchy, navigation ownership, dependency
direction, DI modules, external interfaces, or root factory wiring

For existing feature modules, preserve their component/store/Compose/test
shape and extend the nearest parent navigation only when the route changes.
Expand Down
24 changes: 18 additions & 6 deletions .ai/skills/mvikotlin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ description: Use in the Blinkly Kotlin Multiplatform repository when adding, cha
Read `AGENTS.md` first for the project-level architecture.

Blinkly has Store-backed references across onboarding steps, home tabs, and
nested feature screens. Use `main` as the primary reference for a Store-backed
tab with manager-based business logic, real Compose UI, preview-only component
implementation, and common component tests.
root-pushed feature screens. Use `main` as the primary reference for a
Store-backed tab with manager-based business logic, real Compose UI,
preview-only component implementation, and common component tests.

## Use these local references first

Expand Down Expand Up @@ -138,13 +138,13 @@ Use these patterns:
- `withContext(ioContext)` for IO or heavier domain calls
- feature-local managers in component `domain` packages return `Result<T>` from suspend/business operations using `runCatching`
- handle manager results with `unwrap(result = ..., onSuccess = ..., onError = ...)` in the executor
- keep manager flow subscriptions as `Flow<T>` and protect them with `.catch { publish(Label.ErrorCaught(it)) }`
- `publish(Label.ErrorCaught(...))` for one-off failures that should not live in state
- keep manager flow subscriptions as `Flow<T>` and protect them with `.catch { publish(Label.ErrorCaught(it.asBlinklyError(...))) }`, using the matching `BlinklyError` subclass for that feature or operation
- `publish(Label.ErrorCaught(...))` for one-off failures that should not live in state; do not publish raw platform/domain exceptions when the error will be visible app-wide

`MainTabStoreProvider` is the reference for bootstrapper actions that subscribe
to multiple flows, run manager calculations on `ioContext`, and translate
stream updates into `Msg` values. `step5`, `progress`, `reminders`,
`trainings`, and nested feature components are valid references for narrower
`trainings`, and root-pushed feature screens are valid references for narrower
Store-backed flows.

## Component integration rules
Expand All @@ -167,6 +167,15 @@ Store `Intent` sealed interface empty as in `MainTabStore`. Component callbacks
can use current `model.value` to choose a `ComponentOutput` when the decision is
navigation-only, as `MainTabComponentDefault.onPrimaryCtaClick()` does.

Navigation ownership note:
- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and
`TrainingsTabComponentDefault` expose Store-backed tab state and emit outputs.
They do not own nested child stacks.
- `Preferences`, `Achievements`, `Garden`, `AddNewReminder`, and `Workout` are
Store-backed feature screens opened by `RootComponentDefault`.
- `WorkoutStoreProvider` receives `BlinklyBeeper` for exercise audio feedback;
`WorkoutComponentDefault` releases it in lifecycle cleanup.

## Error handling

Blinkly does not route every Store label to parent output.
Expand All @@ -181,6 +190,9 @@ it maps `MainTabStore.Label.ErrorCaught` to
`ComponentOutput.Common.ErrorCaught`. `OnboardingStep5ComponentDefault` is the
nested-flow reference for the same pattern.

App-wide user-visible errors use `BlinklyError` from `shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt`.
Wrap low-level failures at the point where the user-facing operation is known, keep the original throwable as `cause`, and route the wrapped error through `ComponentOutput.Common.ErrorCaught`.
`RootComponentDefault` exposes the resulting errors through `RootComponent.errors`; `RootContent` maps them to localized Compose resources and shows the top error snackbar.
Do not leak platform-specific exceptions into Compose.

## Testing expectations
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/AnalysisAndTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:

steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
lfs: true

- name: Set up JDK
uses: actions/setup-java@v4
Expand All @@ -28,3 +30,16 @@ jobs:

- name: Testing
run: ./gradlew testDebugUnitTest

- name: Screenshot testing
run: ./gradlew :shared:compose:verifyPaparazziDebug

- name: Upload Paparazzi report
if: failure()
uses: actions/upload-artifact@v4
with:
name: paparazzi-report
path: |
shared/compose/build/reports/paparazzi
shared/compose/build/paparazzi/failures
if-no-files-found: ignore
Loading
Loading