Skip to content

Post-merge release-safety review: fix silent note-save failure + hygiene#29

Merged
BigAkins merged 2 commits into
mainfrom
audit/post-merge-fable5-review
Jul 12, 2026
Merged

Post-merge release-safety review: fix silent note-save failure + hygiene#29
BigAkins merged 2 commits into
mainfrom
audit/post-merge-fable5-review

Conversation

@BigAkins

@BigAkins BigAkins commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Multi-agent release-safety review of the merged security/stability work (#28): 7 review dimensions, every finding adversarially verified, then a two-round final verification gate on the resulting diff (all lenses APPROVE, zero bugs). No P0s existed. This PR fixes the two verified P1s plus small, verified hygiene items — no refactors, no UI redesign, no new features.

P1 fixes

  • Daily note saves can no longer fail silently. DailyNoteStore.save returns success, only updates in-memory content after the write is decided, and reports failures to Sentry with the standard classification tags. The note editor keeps the sheet (and the typed text) open with a retry alert on failure, and blocks swipe-to-dismiss while a save is in flight. Previously a failed save dismissed the sheet, showed the note as saved for the rest of the session, and the text vanished on next launch.
  • Store-reset coverage is now complete. StoreResetTests pins reset() for all six user-owned stores — DailyNoteStore and GroceryListStore were unpinned, which is the exact cross-user data-leak regression class Audit and fix AkFit security stability issues #28 fixed.

Verified hygiene (tiny)

  • Search post-log Undo surfaces delete failures (alert + Sentry) instead of silently leaving the entry logged.
  • Favorite star toggle reports errors to Sentry (was the only unobserved user-initiated Supabase write).
  • .gitignore adds *.xcarchive / *.xcresult.
  • claude.yml pins actions/checkout to the v4.3.1 release SHA (a secret is in scope in that job).
  • CI also runs on push to main, so direct pushes can't bypass the test suites.
  • config.toml: adds akfit://auth-callback (exact, for Google OAuth) and akfit://auth-callback** (recovery links carry ?flow=…&state=…, which exact entries don't match under GoTrue glob rules) to the redirect allow-list, and documents the dashboard-managed production auth posture.
  • Stale pre-Audit and fix AkFit security stability issues #28 reset comments corrected in four stores; README/release-checklist note that CLAUDE.md and docs/ui-reference/ are local-only.

Files / areas changed

15 files, +206/−28. App: DailyNoteStore, DashboardView (NoteEditorSheet), SearchView, FoodDetailView, comment-only edits in four stores. Tests: StoreResetTests (+2 tests). Repo: .gitignore, ci.yml, claude.yml, supabase/config.toml, README.md, docs/release-checklist.md.

Risk areas

  • Note editor behavior change (intentional): failed saves now keep the sheet open with an alert instead of optimistically showing unsaved text. Worst case on a network timeout, dismissal is blocked ~60s while the spinner shows — accepted tradeoff to protect typed text.
  • PR merges now trigger CI twice (pull_request + push) — intended, costs one extra run.
  • Everything else is additive error-reporting, comments, or config/docs.

Verification performed

  • 119 tests in 18 suites pass (verified three times, including an independent agent run on a fresh build); both new reset tests pass.
  • Fresh full build: zero Swift compiler warnings.
  • Edited TOML/YAML parse-checked; checkout SHA confirmed = actions/checkout v4.3.1 tag via GitHub API; git ls-files swept — no tracked artifacts/secrets.
  • Live production advisors checked via Supabase MCP: only pre-existing WARNs (leaked-password protection off, pg_trgm in public schema).

Follow-ups outside this repo (dashboard): enable leaked-password protection; confirm the production redirect allow-list contains both akfit://auth-callback and akfit://auth-callback**.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3aqVLXYVueZBeKd4U7imw

Greptile Summary

This PR fixes two verified bugs from a prior release-safety audit and adds matching hygiene: DailyNoteStore.save now returns Bool so the note editor can keep the sheet open with a retry alert on failure (previously a failed save silently dismissed the sheet and the typed text was lost on next launch), and StoreResetTests gains pinned tests for DailyNoteStore and GroceryListStore to close a cross-user data-leak coverage gap.

  • Silent note-save failure fixed: save() returns false on network/session failure, NoteEditorSheet blocks swipe-to-dismiss while in-flight (interactiveDismissDisabled), and shows a retry alert on failure; todayContent is only updated after a confirmed write.
  • Store reset coverage complete: Two new @Test cases exercise DailyNoteStore.reset() (async/guest path, verifies todayContent and isSaving cleared) and GroceryListStore.reset() (verifies all user-visible state), closing the regression class that Audit and fix AkFit security stability issues #28 introduced the centralized hook to prevent.
  • Error observability: Undo-delete in SearchView and favorite-toggle in FoodDetailView now catch errors, report to Sentry with the standard three-tag classification set, and surface alerts; claude.yml pins actions/checkout to a SHA because a secret is in scope.

Confidence Score: 5/5

Safe to merge — all changes are additive error-handling or corrections to previously silent failure paths, with no regressions identified.

Every behaviour change is a tightening of an existing contract: save returns false instead of silently succeeding, the editor keeps typed text instead of discarding it, and previously swallowed errors now reach Sentry and the user. The note-save flow is straightforward (local isSaving state mirrors the store, defer clears it, interactiveDismissDisabled guards the in-flight window). The two new test cases exercise the exact reset paths the prior audit flagged as uncovered. No new async hazards, no schema changes, no removed guards.

No files require special attention.

Important Files Changed

Filename Overview
AkFit/AkFit/Logging/DailyNoteStore.swift save() now returns Bool; todayContent updated only after confirmed write; identity-change guard after upsert returns true (write committed); OSLog + Sentry added on failure. Logic is sound.
AkFit/AkFit/Views/Dashboard/DashboardView.swift NoteEditorSheet: local isSaving drives spinner/buttons, interactiveDismissDisabled prevents swipe-to-dismiss mid-save, alert shown on failure — sheet stays open for retry. All state transitions are correct.
AkFit/AkFit/Views/Search/SearchView.swift undoLastLog() now catches delete errors, reports to Sentry, and surfaces an alert ('The entry is still logged') — previously failed silently.
AkFit/AkFit/Views/FoodDetail/FoodDetailView.swift Favorite-toggle error now caught and reported to Sentry; all three error tags (classification, postgrest_code, auth_code) present, consistent with DailyNoteStore.
AkFit/AkFitTests/StoreResetTests.swift Adds reset tests for DailyNoteStore (async/MainActor, guest path, verifies todayContent+isSaving cleared) and GroceryListStore (verifies items, isLoading, isAdding, isClearingChecked, actionErrorMessage, isItemBusy). Closes the coverage gap from the previous audit.
.github/workflows/claude.yml actions/checkout pinned to SHA 34e114876b0b11c390a56381ad16ebd13914f8d5 (v4.3.1) because CLAUDE_CODE_OAUTH_TOKEN is in scope; appropriate security hygiene for a secret-bearing job.
.github/workflows/ci.yml Adds push-to-main trigger so direct pushes can't bypass CI. No secrets in scope in this workflow; actions/checkout@v4 tag-float is low risk here.
supabase/config.toml Adds akfit://auth-callback (exact, for Google OAuth) and akfit://auth-callback** (wildcard, for recovery links with query params) to local redirect allow-list; well-documented with production sync reminder.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    actor User
    participant Sheet as NoteEditorSheet
    participant Store as DailyNoteStore
    participant Supabase

    User->>Sheet: tap Done
    Sheet->>Sheet: "isSaving = true"
    Sheet->>Store: save(content:userId:) async Bool
    Store->>Store: guard canApplyUserOwnedState
    alt Guest mode
        Store->>Store: saveDailyNote (UserDefaults)
        Store->>Store: "todayContent = content"
        Store-->>Sheet: return true
    else Authenticated
        Store->>Store: "isSaving = true"
        Store->>Supabase: requireAuthenticatedUserIDForWrite()
        Store->>Supabase: upsert daily_note
        alt Success
            Supabase-->>Store: 200 OK
            Store->>Store: "todayContent = content"
            Store-->>Sheet: return true
            Sheet->>Sheet: dismiss()
        else Failure
            Supabase-->>Store: error
            Store->>Store: log + Sentry.captureNonFatal
            Store-->>Sheet: return false
            Sheet->>Sheet: "showSaveError = true"
            Sheet-->>User: alert Couldnt save note
        end
        Store->>Store: "isSaving = false (defer)"
    end
    Sheet->>Sheet: "isSaving = false (defer)"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    actor User
    participant Sheet as NoteEditorSheet
    participant Store as DailyNoteStore
    participant Supabase

    User->>Sheet: tap Done
    Sheet->>Sheet: "isSaving = true"
    Sheet->>Store: save(content:userId:) async Bool
    Store->>Store: guard canApplyUserOwnedState
    alt Guest mode
        Store->>Store: saveDailyNote (UserDefaults)
        Store->>Store: "todayContent = content"
        Store-->>Sheet: return true
    else Authenticated
        Store->>Store: "isSaving = true"
        Store->>Supabase: requireAuthenticatedUserIDForWrite()
        Store->>Supabase: upsert daily_note
        alt Success
            Supabase-->>Store: 200 OK
            Store->>Store: "todayContent = content"
            Store-->>Sheet: return true
            Sheet->>Sheet: dismiss()
        else Failure
            Supabase-->>Store: error
            Store->>Store: log + Sentry.captureNonFatal
            Store-->>Sheet: return false
            Sheet->>Sheet: "showSaveError = true"
            Sheet-->>User: alert Couldnt save note
        end
        Store->>Store: "isSaving = false (defer)"
    end
    Sheet->>Sheet: "isSaving = false (defer)"
Loading

Reviews (2): Last reviewed commit: "Address PR review: clarify save() identi..." | Re-trigger Greptile

…ygiene

Multi-agent review (7 dimensions, adversarially verified) of the merged
security/stability work found no P0s and two P1s, both fixed here:

- DailyNoteStore.save no longer swallows persistence failures behind a
  false success signal: it returns Bool, mutates todayContent only after
  the write is decided, logs + captures failures to Sentry with standard
  classification tags, and NoteEditorSheet keeps the sheet open with a
  retry alert (swipe-dismiss blocked mid-save so typed text can't be
  destroyed by a failing in-flight save).
- StoreResetTests now pins reset() for all six user-owned stores
  (DailyNoteStore and GroceryListStore were unpinned — the exact
  cross-user data-leak regression class PR #28 fixed).

Tiny verified P2 hardening included:
- SearchView post-log Undo surfaces delete failures (alert + Sentry)
  instead of silently leaving the entry logged.
- FoodDetailView favorite toggle captures errors to Sentry (was the only
  unobserved user-initiated Supabase write).
- .gitignore adds *.xcarchive / *.xcresult.
- claude.yml pins actions/checkout to the v4.3.1 release SHA (secret in
  scope in that job).
- ci.yml also runs on push to main so direct pushes can't bypass tests.
- config.toml: akfit://auth-callback added to the redirect allow-list
  (exact entry for OAuth + ** wildcard for query-string recovery links,
  per GoTrue glob semantics) and documents the dashboard-managed
  production auth posture (email confirmations, leaked-password
  protection).
- Stale pre-PR#28 reset() comments in four stores corrected; README and
  release checklist note that CLAUDE.md / docs/ui-reference are
  local-only.

Verified: 119 tests in 18 suites pass on iPhone 17 Pro simulator; edited
TOML/YAML parse clean; production Supabase advisors checked live (only
pre-existing WARNs: pg_trgm in public, leaked-password protection off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3aqVLXYVueZBeKd4U7imw
Copilot AI review requested due to automatic review settings July 2, 2026 15:12
@supabase

supabase Bot commented Jul 2, 2026

Copy link
Copy Markdown

Updates to Preview Branch (audit/post-merge-fable5-review) ↗︎

Deployments Status Updated
Database Sun, 12 Jul 2026 23:10:13 UTC
Services Sun, 12 Jul 2026 23:10:13 UTC
APIs Sun, 12 Jul 2026 23:10:13 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Sun, 12 Jul 2026 23:10:12 UTC
Migrations Sun, 12 Jul 2026 23:10:12 UTC
Seeding Sun, 12 Jul 2026 23:10:12 UTC
Edge Functions Sun, 12 Jul 2026 23:10:12 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens release-safety and observability around user-owned data writes (notably daily notes), completes coverage for identity-transition store resets, and applies small CI/config/docs hygiene updates to reduce silent failures and improve production alignment.

Changes:

  • Make daily note saves non-silent by returning save success/failure, only updating in-memory content after persistence is decided, and surfacing failures in the UI (with Sentry logging).
  • Add missing store-reset tests for DailyNoteStore and GroceryListStore, and clarify reset-callsite documentation across stores.
  • Add error reporting/alerts for post-log Undo failures and favorite toggle failures; update CI triggers and repo ignore/docs/config notes.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
supabase/config.toml Adds iOS deep-link redirect allow-list entries + clarifying production notes for auth URL configuration.
README.md Documents that docs/ui-reference/ and CLAUDE.md are intentionally gitignored/local-only.
docs/release-checklist.md Clarifies CLAUDE.md is gitignored/local-only and not present in a fresh clone.
AkFit/AkFitTests/StoreResetTests.swift Adds reset contract tests for DailyNoteStore and GroceryListStore.
AkFit/AkFit/Views/Search/SearchView.swift Surfaces Undo-delete failures via alert + Sentry capture instead of silent failure.
AkFit/AkFit/Views/FoodDetail/FoodDetailView.swift Captures favorite-toggle errors to Sentry (store still handles optimistic revert).
AkFit/AkFit/Views/Dashboard/DashboardView.swift Keeps note editor open on save failure; blocks interactive dismiss while saving.
AkFit/AkFit/Logging/GroceryListStore.swift Updates reset documentation to reflect centralized identity-transition resets.
AkFit/AkFit/Logging/FoodLogStore.swift Updates reset documentation to reflect centralized identity-transition resets.
AkFit/AkFit/Logging/FavoriteFoodStore.swift Updates toggle behavior documentation to match optimistic revert + rethrow.
AkFit/AkFit/Logging/DailyNoteStore.swift Changes save to return Bool, adds logging/Sentry reporting, and avoids optimistic in-memory updates on failed save.
AkFit/AkFit/Logging/BodyweightStore.swift Updates reset documentation to reflect centralized identity-transition resets.
.gitignore Ignores Xcode archives and result bundles (*.xcarchive, *.xcresult).
.github/workflows/claude.yml Pins actions/checkout to a SHA due to secret scope in the job.
.github/workflows/ci.yml Runs CI on push to main in addition to PRs/workflow_dispatch to prevent bypass.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +136
guard canApplyUserOwnedState(for: userId) else { return true }
todayContent = content

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch on the doc imprecision — the code behavior is intentional (verified in review round 2: a post-upsert identity change means the write DID persist for the original account, so returning true is correct and RootView resets the store's local state), but the comment overstated the false case. Clarified in 7449a9b: identity change BEFORE the write → false; after a successful upsert → true with the local mutation skipped.

Comment thread AkFit/AkFit/Views/Search/SearchView.swift
- DailyNoteStore.save doc: identity change BEFORE the write returns false;
  identity change AFTER a successful upsert returns true (the write
  persisted for the original account; only the local mutation is skipped,
  RootView resets the store). Code behavior unchanged — the comment was
  imprecise (Copilot review).
- Add the auth_code Sentry tag to the quick_log_undo and favorite_toggle
  captures, matching the richer grocery/daily-note tag idiom — session
  expiry is exactly the failure class these paths can hit (Greptile P2).

119/119 tests pass locally after the change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3aqVLXYVueZBeKd4U7imw
@BigAkins
BigAkins merged commit ef70a2c into main Jul 12, 2026
8 checks passed
@BigAkins
BigAkins deleted the audit/post-merge-fable5-review branch July 12, 2026 23:44
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