Skip to content

fix(cleanup): address post-merge code review findings#12

Merged
notedwin-dev merged 3 commits into
stagingfrom
bugfix/code-review-cleanup
Jun 5, 2026
Merged

fix(cleanup): address post-merge code review findings#12
notedwin-dev merged 3 commits into
stagingfrom
bugfix/code-review-cleanup

Conversation

@notedwin-dev
Copy link
Copy Markdown
Owner

@notedwin-dev notedwin-dev commented Jun 5, 2026

Follow-up to PR #10 and PR #11. Addresses the high/medium findings from the post-merge code review without changing behavior.

Code

  • History.handleSwipeDelete: only clear swipe state on successful delete (was: clear on both success and failure)
  • useAppInit: .catch() handlers on getUSDToMYRRate and getCryptoPrices (unhandled-rejection log noise)
  • MainLayout: aria-pressed={maskMode} on desktop Mask Mode toggle (mobile already had it)
  • ProfilePage: recursive stripSensitive deny-list regex for export (handles nested objects; replaces 2-key destructure)
  • commands/balance.ts: Promise.all + await for the saveAccounts/savePots/savePockets trio
  • commands/chat-sessions.ts: await + save-before-set
  • commands/pockets.ts: clear toSavingPocketId on pocket delete (was: only savingPocketId)
  • commands/pots.ts, subscriptions.ts: save-before-set (persist first, then store; matches new convention)
  • commands/sync.ts: use activeProfile.syncChatToSheets at L536 (was: profile.syncChatToSheets which ignored the cloud-merged value)
  • domain/migration.ts: updatedAt parameter (pure function; defaults to new Date().toISOString())
  • domain/search.ts: extract matchesSearch to a pure domain module; useFilteredTransactions re-exports for back-compat

Tests

  • matchesSearch.test.ts: import from domain; transfer fixture now uses TRANSFER type
  • migration.test.ts: updatedAt test asserts on the provided timestamp

Docs

  • AGENTS.md: drop context/ reference, update layer-isolation rule, matchesSearch row points to src/lib/domain/search.ts
  • ADR-001: split ui.store.ts row into State / Actions columns
  • issues/003-004-005: update to 147 tests, mark completed subtasks
  • review-pr2.md: typo commmitcommit

Config

  • opencode.json: portable plugin path ./opencode-power-pack (was: absolute Windows path)
  • .opencode/prompts/{page,state}-migrator.txt: reflect current architecture (no DataProvider/vault)

Skipped (with reasons)

  • ADR 001/002 Status heading reorder — both ADRs consistently have Status at top; no convention violation
  • balance.engine.ts dateNormalizer param + historical skip in budget/savings — larger refactor with behavior change risk
  • accounts.ts save order — current order matches the migration test convention (entity before derived)
  • sync.ts drop profile / _forceUnlock from loadData_forceUnlock has the standard TS underscore prefix; profile is used
  • transactions.ts extract inline comments → helpers — 699-line refactor, deferred
  • migration.ts header comment — comment carries the ADR-002 removal criterion, worth keeping

Validation

  • npx tsc --noEmit — clean
  • npm test — 13 files, 147 tests pass

Closes the high/medium items from the post-merge code review of PR #10 / PR #11.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed transaction deletion to preserve UI state if operation fails, allowing users to retry without re-opening the transaction.
    • Corrected pocket references cleanup to handle all transaction fields when deleting pockets.
    • Fixed profile sync to respect updated chat-sync settings during sync operations.
  • New Features

    • Improved data export privacy with enhanced sensitive field removal logic.
    • Enhanced accessibility with better state indication for mask mode toggle.
  • Documentation

    • Updated architecture guidance and issue tracking to reflect completed refactoring progress.

Code:
- History.handleSwipeDelete: only clear swipe state on successful delete
- useAppInit: catch exchange/crypto fetch failures (no more unhandled rejections)
- MainLayout: add aria-pressed to desktop Mask Mode toggle
- ProfilePage: recursive stripSensitive (deny-list regex) for export
- commands/balance: Promise.all + await for save trio
- commands/chat-sessions: await saveChatSessions + save-before-set
- commands/pockets: clear toSavingPocketId on delete
- commands/pots, subscriptions: save-before-set pattern
- commands/sync: use activeProfile.syncChatToSheets (post-merge value)
- domain/migration: updatedAt parameter (pure function)
- domain/search: extract matchesSearch from useFilteredTransactions

Tests:
- matchesSearch.test: import from domain; transfer fixture uses TRANSFER type
- migration.test: updatedAt test asserts on provided timestamp

Docs:
- AGENTS.md: drop context/ ref, update layer-isolation rule, matchesSearch row
- ADR-001: split ui.store.ts row (State | Actions columns)
- issues/003-004-005: update to 147 tests, mark subtasks complete
- review-pr2: typo fix

Config:
- opencode.json: portable plugin path (./opencode-power-pack)
- .opencode/prompts/{page,state}-migrator: reflect current architecture

Skipped: ADR Status reorder (consistent across repo), balance.engine
dateNormalizer (larger refactor), accounts.ts save order (matches test
convention), transactions.ts comment extraction (699-line refactor),
migration.ts header comment (carries removal criterion).

147 tests pass, npx tsc --noEmit clean.
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
finance-tracker-app Ready Ready Preview, Comment Jun 5, 2026 6:14pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eb097ee3-f510-4151-8f59-43dbbb7c4e74

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/code-review-cleanup

Comment @coderabbitai help to get the list of available commands and usage tips.

…Page conflict

Conflict in pages/ProfilePage.tsx: HEAD used a deny-list regex
(11 patterns) for export sanitization; staging's PR #11 used an
allow-list for profile (11 fields) + deny-list of { details, isEncrypted }
for accounts. Resolved with the deny-list approach but extended the
regex to match the logger's SENSITIVE_KEYS set plus common identifier
patterns (apikey lowercase, vite_* env keys, totpSecret, encryptionKey,
biometricCred[Id|Ids], devices, cardNumber, cvv, expiry, holderName,
accountNumber, pin, ssn, taxId, iban, routingNumber, swift).

Why deny-list over allow-list:
- Export is user-initiated data backup; preserve as much as possible
- UserProfile type has 16+ fields; the 11-field allow-list silently
  dropped photoUrl, updatedAt, isSecurityEnabled, isVaultEnabled,
  totpEnabled, biometricEnabled — that's data loss for a backup feature
- Recursive redact() handles nested objects uniformly (transactions
  referencing accounts, goals, pockets) without per-entity allow-lists
- The OWASP 'default deny' rule applies to auth/authz on untrusted
  input, not to user data export

153 tests pass, npx tsc --noEmit clean.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/application/commands/pockets.ts (1)

23-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reverse the persistence order to match the save-before-set pattern.

saveSavingPocket updates the store before awaiting storage, which is inconsistent with the other command handlers in this PR and creates a window where the UI reflects unsaved state. If the save fails, the store will contain data that was never persisted.

🔄 Proposed fix
-  store.setPockets(updated);
   await StorageService.savePockets(updated);
+  store.setPockets(updated);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/application/commands/pockets.ts` around lines 23 - 24, The code calls
store.setPockets(updated) before awaiting persistence, which can leave the UI
showing unsaved state if saving fails; change the order so you first await
StorageService.savePockets(updated) and only after a successful save call
store.setPockets(updated) (modify the saveSavingPocket handler that currently
uses store.setPockets and StorageService.savePockets to reverse those calls and
preserve existing error handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/History.tsx`:
- Around line 167-170: Remove the inline comment inside the empty catch block
that follows swipe.setSwipedId(null); in the component so the catch remains
empty and self-explanatory; locate the try/catch that calls
swipe.setSwipedId(null) and delete the comment line between catch { and } to
comply with the guideline preferring self-documenting code.

In `@helpers/useAppInit.tsx`:
- Around line 40-47: The warning message is misleading because getUSDToMYRRate()
failures do not set a fallback of 1; update the catch block that calls
logger.warn to accurately state that the USD/MYR rate fetch failed and the store
will retain its current value (via useFinanceStore.getState().setUsdRate /
setExchangeRate not being called), or alternatively set an explicit fallback by
calling useFinanceStore.getState().setUsdRate(1) and setExchangeRate(...) if you
prefer a true default; modify the message accordingly so it references the
actual behavior.
- Around line 48-54: The warning message in the getCryptoPrices promise catch is
misleading — it says "using empty list" but the code leaves useFinanceStore's
initial cryptoPrices ({ BTC: 0, ETH: 0 }) in place; update the logger.warn call
in the getCryptoPrices().catch block to accurately describe the fallback (e.g.,
"Failed to fetch crypto prices; retaining initial prices" or include the actual
initial/default state) and keep the existing call to
useFinanceStore.getState().setCryptoPrices untouched unless you intend to
explicitly set an empty list.

In `@opencode.json`:
- Line 4: The opencode plugin entry
"opencode-power-pack@git+file:./opencode-power-pack" in opencode.json points to
a missing local directory; update the plugin reference or CI/setup to ensure the
plugin exists: either change the entry in opencode.json to the correct existing
location/name of the package, replace the git+file URI with a valid git URL or
registry version, or add steps to your setup/CI to fetch/create
./opencode-power-pack (ensure it is not gitignored) so the plugin referenced by
opencode.json can be resolved at runtime.

In `@pages/ProfilePage.tsx`:
- Around line 42-54: stripSensitive currently only filters top-level keys
(SENSITIVE_KEYS) but does not recurse into nested objects/arrays, so sensitive
fields like meta.apiKey remain; modify stripSensitive to recursively process
object property values and array elements: when handling an object (used in the
Object.entries/Object.fromEntries block for stripSensitive), after filtering out
keys matching SENSITIVE_KEYS, map each retained value through stripSensitive so
nested objects/arrays are cleaned too (similarly ensure Array.isArray branch
maps elements via stripSensitive); follow the same value-recursion approach used
by logger.redact to avoid leaving nested sensitive fields.

In `@src/lib/application/commands/pockets.ts`:
- Around line 40-48: The map callback that builds updatedTransactions
incorrectly early-returns after clearing one field so a transaction with both
savingPocketId and toSavingPocketId equal to id only has the first cleared;
update the map logic in the existingTransactions.map callback to always produce
a new transaction object and conditionally set both savingPocketId and
toSavingPocketId to null when they equal id (e.g., create let newT = { ...t };
if (newT.savingPocketId === id) newT.savingPocketId = null; if
(newT.toSavingPocketId === id) newT.toSavingPocketId = null; return newT) so
both references are removed.

In `@src/lib/domain/__tests__/migration.test.ts`:
- Around line 183-187: Add a new unit test in migration.test.ts that calls
migrateSchemaV1toV2([], baseProfile) without the third parameter and verifies
profile.updatedAt is set to a valid ISO timestamp: capture a `before` and
`after` ISO timestamp around the call, assert profile.updatedAt is defined and a
string, match it against an ISO timestamp regex, and assert it falls between
`before` and `after`; reference the migrateSchemaV1toV2 function and the
profile.updatedAt property in the assertions.

In `@src/lib/domain/migration.ts`:
- Line 58: The domain function's default parameter new Date().toISOString()
introduces a side effect; remove the default and make the timestamp an explicit
required parameter (change the updatedAt parameter in migrateSchemaV1toV2 in
src/lib/domain/migration.ts to a plain string without a default). Then update
all callers (e.g., where migrateSchemaV1toV2 is invoked in
src/lib/application/commands/migration.ts) to pass new Date().toISOString() at
call sites, ensuring the domain layer remains pure and all time dependencies are
injected by the caller.

---

Outside diff comments:
In `@src/lib/application/commands/pockets.ts`:
- Around line 23-24: The code calls store.setPockets(updated) before awaiting
persistence, which can leave the UI showing unsaved state if saving fails;
change the order so you first await StorageService.savePockets(updated) and only
after a successful save call store.setPockets(updated) (modify the
saveSavingPocket handler that currently uses store.setPockets and
StorageService.savePockets to reverse those calls and preserve existing error
handling).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6e856a8d-c7cf-448c-b7aa-99a94305f7a7

📥 Commits

Reviewing files that changed from the base of the PR and between f3edfb7 and cf8aace.

📒 Files selected for processing (24)
  • .opencode/prompts/page-migrator.txt
  • .opencode/prompts/state-migrator.txt
  • AGENTS.md
  • components/History.tsx
  • components/history/useFilteredTransactions.ts
  • docs/adrs/001-layered-architecture-refactor.md
  • docs/issues/003-privacy-store.md
  • docs/issues/004-commands-extraction.md
  • docs/issues/005-cleanup.md
  • docs/issues/review-pr2.md
  • helpers/useAppInit.tsx
  • layouts/MainLayout.tsx
  • opencode.json
  • pages/ProfilePage.tsx
  • src/lib/application/commands/balance.ts
  • src/lib/application/commands/chat-sessions.ts
  • src/lib/application/commands/pockets.ts
  • src/lib/application/commands/pots.ts
  • src/lib/application/commands/subscriptions.ts
  • src/lib/application/commands/sync.ts
  • src/lib/domain/__tests__/matchesSearch.test.ts
  • src/lib/domain/__tests__/migration.test.ts
  • src/lib/domain/migration.ts
  • src/lib/domain/search.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid comments — prefer self-documenting code with clear naming and structure.

Files:

  • src/lib/application/commands/chat-sessions.ts
  • src/lib/domain/migration.ts
  • src/lib/application/commands/sync.ts
  • src/lib/domain/__tests__/migration.test.ts
  • src/lib/application/commands/balance.ts
  • src/lib/domain/search.ts
  • layouts/MainLayout.tsx
  • src/lib/application/commands/subscriptions.ts
  • pages/ProfilePage.tsx
  • src/lib/domain/__tests__/matchesSearch.test.ts
  • src/lib/application/commands/pockets.ts
  • helpers/useAppInit.tsx
  • components/History.tsx
  • src/lib/application/commands/pots.ts
  • components/history/useFilteredTransactions.ts
src/lib/domain/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Domain layer functions must be pure functions only — no React imports, no side effects, no imports from services/ or stores/. All dependencies must be explicit parameters.

Use TransactionType enum from types for type discrimination in domain functions.

Keep domain modules organized as one module per file with co-located __tests__/ directories in src/lib/domain/.

Files:

  • src/lib/domain/migration.ts
  • src/lib/domain/__tests__/migration.test.ts
  • src/lib/domain/search.ts
  • src/lib/domain/__tests__/matchesSearch.test.ts
src/**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate unit tests in __tests__/ directories adjacent to source files, using Vitest.

Files:

  • src/lib/domain/__tests__/migration.test.ts
  • src/lib/domain/__tests__/matchesSearch.test.ts
{components,pages,layouts}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS utility-first styling for all React components.

Files:

  • layouts/MainLayout.tsx
  • pages/ProfilePage.tsx
  • components/History.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Domain functions use Vitest for pure unit tests without jsdom. Zustand stores use Vitest + jsdom for store action tests. React components use Vitest + React Testing Library + jsdom for component tests.
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Create new Git branches from `main` with prefixes: `feature/xxx` for new features, `bugfix/xxx` for bug fixes, `refactor/xxx` for major refactoring or UI revamp.
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Push feature branches and open PRs to the `staging` branch for review before merging to `main` for production deployment.
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Document significant architectural decisions in `docs/adrs/` using the ADR template (Context → Decision → Consequences → Status), numbered sequentially.
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Read the latest ADR in `docs/adrs/` before making architectural changes to understand current decisions and constraints.
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Organize page-level components in `pages/` directory and feature-specific React components in feature folders within `components/` (e.g., `components/history/`).
Learnt from: CR
Repo: notedwin-dev/finance-tracker-app

Timestamp: 2026-06-05T17:53:15.998Z
Learning: Use `VITE_BACKEND_API_URL`, `VITE_GEMINI_API_KEY`, and `VITE_GOOGLE_API_KEY` environment variables for external service configuration in `.env` files.
🔇 Additional comments (23)
src/lib/domain/search.ts (1)

3-31: LGTM!

components/history/useFilteredTransactions.ts (1)

10-12: LGTM!

src/lib/domain/__tests__/matchesSearch.test.ts (2)

2-2: LGTM!


92-99: LGTM!

layouts/MainLayout.tsx (1)

246-248: LGTM!

.opencode/prompts/page-migrator.txt (1)

1-12: LGTM!

.opencode/prompts/state-migrator.txt (1)

1-12: LGTM!

AGENTS.md (3)

5-28: LGTM!


77-82: LGTM!


89-97: LGTM!

docs/adrs/001-layered-architecture-refactor.md (1)

108-114: LGTM!

docs/issues/003-privacy-store.md (1)

1-26: LGTM!

docs/issues/004-commands-extraction.md (1)

23-30: LGTM!

docs/issues/005-cleanup.md (1)

15-27: LGTM!

docs/issues/review-pr2.md (1)

1-6: LGTM!

src/lib/application/commands/balance.ts (1)

73-80: LGTM!

src/lib/application/commands/chat-sessions.ts (2)

17-18: LGTM!


30-31: LGTM!

src/lib/application/commands/pots.ts (2)

25-26: LGTM!


38-39: LGTM!

src/lib/application/commands/subscriptions.ts (2)

21-22: LGTM!


34-35: LGTM!

src/lib/application/commands/sync.ts (1)

547-547: LGTM!

Comment thread components/History.tsx Outdated
Comment thread helpers/useAppInit.tsx
Comment thread helpers/useAppInit.tsx
Comment thread opencode.json
Comment thread pages/ProfilePage.tsx
Comment thread src/lib/application/commands/pockets.ts
Comment thread src/lib/domain/__tests__/migration.test.ts
Comment thread src/lib/domain/migration.ts Outdated
Code:
- History.handleSwipeDelete: drop inline comment from empty catch
  block (project rule: no comments)
- useAppInit: accurate warning messages — store retains initial
  usdRate (0) / exchangeRate (null) / cryptoPrices ({ BTC: 0, ETH: 0 })
  on fetch failure, not '1' or 'empty list' as previously stated
- ProfilePage.stripSensitive: recurse into retained values so nested
  sensitive fields (meta.apiKey, accounts[].geminiApiKey) are caught
- commands/pockets.saveSavingPocket: persist before setPockets to match
  the deleteSavingPocket pattern (UI never shows unsaved state)
- commands/pockets.deleteSavingPocket: always build new tx object so
  both savingPocketId and toSavingPocketId are cleared when a pocket
  is deleted (was: early return after first match)
- domain/migration: drop default for updatedAt param (was a side effect
  in a pure function); callers in commands/migration now pass
  new Date().toISOString() explicitly
- domain/__tests__/migration: update existing tests to pass FIXED_TS as
  required third argument

Skipped:
- opencode.json plugin path: ./opencode-power-pack directory exists in
  the repo (verified) — finding invalid

153 tests pass, npx tsc --noEmit clean.
@notedwin-dev notedwin-dev merged commit 3f81a18 into staging Jun 5, 2026
3 checks passed
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.

1 participant