feat: three-level envelope tree, counter easter egg, and first-launch dashboard fix - #109
Open
MF-Rozi wants to merge 9 commits into
Open
feat: three-level envelope tree, counter easter egg, and first-launch dashboard fix#109MF-Rozi wants to merge 9 commits into
MF-Rozi wants to merge 9 commits into
Conversation
The default 1536M heap caused Java heap space failures in the JetifyTransform when assembling the development flavor.
EnvelopeTreeListView now renders level-3 envelopes inline below their sub-parent instead of requiring drill-down navigation. Gesture split on sub-parent rows: row tap opens the focused sub-envelopes page via onChildTap, the trailing chevron expands/collapses inline, and a new onChildEdit callback opens the form for sub-parents with children. Budget aggregation is depth-capped at three levels so malformed data containing a parent cycle cannot recurse unbounded. CategoryManagePage wires onChildEdit to the existing edit form. Adds five widget tests covering inline rendering, gesture split, and orphaned-cycle safety.
The leftover template counter becomes a hidden Easter egg instead of dead code. Unlock works like Android's developer options: 1. Tap the Settings version row 7 times to reveal a hint dialog. 2. Complete three steps (log a transaction, visit Stats, open Categories) — steps only count after the hint is seen, with live progress in the version subtitle. 3. Unlock flashes a celebration and adds a Counter entry to Settings. Hiding from the Counter screen (appbar action) re-locks everything, requiring the full ritual again. Progress persists via SharedPreferences across restarts and app reinstalls. Supporting changes: extracted the Stats placeholder into a page, converted CategoryManagePage to stateful for the trigger hook, added a /counter route, and lightly polished the counter page to match the app while keeping its template logic (and existing tests) intact. Adds 11 cubit tests covering the ritual, re-locking, idempotency, and persistence.
Code-review findings applied (7-reviewer wave on feat/three-level-envelope-tree): - FlashCubit registered as @lazySingleton so the app-shell listener and EasterEggCubit share one instance; unlock/re-lock flashes were previously emitted on a private factory instance no UI observed (P1, triple-reviewer agreement). - Self-heal on restore: a partial write of the unlocking transition (3/3 steps persisted, unlocked flag lost) previously bricked the ritual with no recovery path; restore now promotes it. - /counter route redirects to Settings while locked, closing the direct-navigation hole. - Editing an existing transaction no longer satisfies the "log a transaction" ritual step. - Budget aggregation unified into one depth-capped extension used by both the tree and the focused page; the page's uncapped duplicate is gone. - FAB hidden on level-3 focused pages so level-4 envelopes (invisible in the tree) can no longer be created. - totalSteps constant replaces the magic 3; _collapsed renamed to _collapsedPillars; stale chevron docs corrected; CounterPage gained a test seam. Tests: cubit suite moved to the conventional presentation/blocs path and extended with restore/mid-ritual/self-heal coverage; new SettingsPage widget tests (dialog-once, subtitle, tile gating); CounterPage hide-action test; tree aggregation and collapse-depth assertions. 128 tests pass.
…stence atomic Second-wave review findings applied (delta re-review of the fix commit): - The hierarchical picker sheet kept a third, uncapped _sumBudgetUnder copy that the "unified aggregation" fix missed; all surfaces now use the depth-capped CategoryTreeX.sumBudgetUnder. - Ritual persistence rewritten as one atomic JSON snapshot: separate keys allowed torn writes that the restore self-heal could not disambiguate (a torn deactivate looked like a torn unlock and resurrected the unlocked state after a re-lock). - FAB depth guard now reuses Category.getHierarchyChain (visited-set cycle protection) and treats an orphan parent as uncreatable; the level-3 focused page no longer promises nested envelopes it can never have. - Settings subtitle uses EasterEggProgress.totalSteps instead of a hardcoded 3. - Shared test helper (RecordingFlashCubit, buildEggCubit) replaces the triplicated private stubs; new tests pin the FlashCubit singleton DI identity and the /counter redirect (locked -> Settings, unlocked -> Counter). 131 tests pass.
…irst launch TransactionCard resolved its category via context.read, a one-shot lookup. On a cold start the categories Isar stream (which also waits for the seed) emits after the cards build, so every card rendered the "Uncategorized" fallback and never rebuilt when data arrived. Navigating away and back rebuilt the cards against the now-loaded state, which masked the bug. Switch to context.watch so cards rebuild whenever categories load or change. Regression-tested with a whenListen-driven widget test that proves the fallback renders first and resolves when the stream emits.
CI runs `very_good test --optimization`, which merges every test file into a single process — so GetIt.init runs once per suite on one shared instance, and the second configure call threw "Type CounterCubit is already registered inside GetIt" (exit 69). A GetIt.reset() before re-init was tried first but leaves the registry empty after reset, so every later getIt<>() call failed with "not registered". Instead, enable allowReassignment before each init: duplicate registrations now replace, every suite gets fresh canonical singletons, and app_test's mock override keeps working. Verified with the exact CI invocation (`very_good test --optimization --coverage --min-coverage 0 --report-on lib --show-uncovered`) locally: exit 0, 133/133 tests.
CI randomizes test order (--test-randomize-ordering-seed random), so "unlocked users reach the counter" could run before "redirects locked users" in the merged runner; the unlocked test drives the shared EasterEggCubit singleton to unlocked, leaving the locked test reading a mutated global state and landing on the Counter instead of Settings. Instead of reading global state, router() now accepts an optional isCounterUnlocked predicate (defaulting to the easter-egg cubit) and the tests inject it directly — deterministic under any ordering, and the prior review's "untestable redirect closure" concern is resolved too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
What:
onChildEditcallback). Budget aggregation is unified into one depth-capped, cycle-safe helper (CategoryTreeX.sumBudgetUnder) shared by the tree, the focused page, and the transaction-entry picker sheet.context.read, so on cold start (before the categories stream emitted) every card showed "Uncategorized" and never recovered. Now usescontext.watchso cards rebuild when categories arrive.How:
EnvelopeTreeListViewgained a second collapse set (_collapsedSubs), inline level-3 rendering reusing the stagger-line visual, a trailing action cluster (chevron + pencil), and a cycle-safe budget walk;CategoryManagePagewiresonChildEditand hides the FAB on level-3 focused pages (level-4 envelopes would be invisible).easter_eggfeature (entity/storage/cubit), ritual triggers wired fromTransactionEntryPage(create-only),StatsComingSoonPage, andCategoryManagePage;/counterroute redirects locked users;FlashCubitpromoted to@lazySingletonso app-shell flashes actually reach the UI.TransactionCardswitch fromcontext.readtocontext.watch, regression-tested with awhenListen-driven widget test proving fallback → resolution.Why:
Type of Change
Code Checklist
fvm flutter test),fvm flutter analyze --no-pubclean