Skip to content

Completed Monthly Dump Feature and Custom Grid#111

Merged
fortune710 merged 30 commits into
stagingfrom
codex/monthly-dump
May 29, 2026
Merged

Completed Monthly Dump Feature and Custom Grid#111
fortune710 merged 30 commits into
stagingfrom
codex/monthly-dump

Conversation

@fortune710

@fortune710 fortune710 commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Monthly dump experience: create photo grids (2x2/2x3) from entries, camera, or gallery; view multi-slide dumps (images, video, audio, grids); grid picker UI and image capture flow.
    • Recap UI: “Recap” chip and animated monthly dump banner on capture screen.
    • Gallery browsing: device gallery import for month-scoped photos.
  • Bug Fixes

    • Queries now exclude incomplete monthly dumps so only completed dumps are returned.
  • Documentation

    • Added frontend design guidelines and "Stop Slop" writing skill docs.
  • Tests

    • New tests covering monthly dump flows, notifications, and grid picker.

Review Change Stack

@fortune710 fortune710 self-assigned this May 29, 2026
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@fortune710 fortune710 marked this pull request as ready for review May 29, 2026 05:54
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds backend monthly-dump completion filtering, a month-batched notification enqueue flow, and frontend monthly-dumps UI: viewer with timed slides, recap banner, and a full photo grid picker (2x2/2x3) with capture, gallery/entries sources, caching, and optimistic grid creation; plus AI skill docs.

Changes

Monthly Dumps Feature Implementation

Layer / File(s) Summary
Database schema & constants
frontend/types/database.ts, frontend/supabase/migrations/*, frontend/constants/supabase.ts
Adds monthly_dump_next_run to profiles, new monthly_dumps and entry_reports tables, and MONTHLY_DUMPS table/bucket constants.
Backend notification pipeline
backend/controllers/monthly_dump_controller.py, backend/services/notification_enqueue_service.py, backend/services/queues/monthly_dump_queue_service.py, backend/.gitignore
get_dump filters completed dumps; new enqueue_monthly_dump_notifications sends monthly-dump-ready pushes; queue service batches user IDs by month and triggers enqueues.
Backend queue & notification tests
backend/tests/test_monthly_dump_queue_notifications.py
Adds async tests for missing-entries failure and successful processing that asserts notification batching per month.
Frontend MonthlyDumpService
frontend/services/monthly-dump-service.ts
Service to fetch/cached dumps, paginate entries, generate/upload grid images, queue optimistic grid creation, and process queued uploads.
Frontend data hooks
frontend/hooks/use-monthly-dump.ts, frontend/hooks/use-monthly-entries.ts, frontend/hooks/use-gallery-images.ts, frontend/hooks/phone-number/use-manage-phone-sheet.ts
Hooks for month enablement, fetching/merging cached slides, paginated entries/gallery loading with permissions, and phone-sheet visibility logic.
Slide components
frontend/components/monthly-dumps/*-slide.tsx
Image/video/audio/grid-prompt/status slide components with loading/error handling and prefetch.
Monthly dump banner & progress icons
frontend/components/monthly-dumps/{monthly-dump-banner,monthly-dump-grid-icons,monthly-dump-progress-bar-item}.tsx
Animated recap banner, 2x2/2x3 grid icons, and animated progress bar segment.
Photo grid picker
frontend/components/monthly-dumps/{photo-grid-picker,grid-image-picker*,grid-image-picker-*.tsx}
Full-featured grid editor supporting layout switching, source switching, camera capture, removal flows, ViewShot export, and subcomponents (cells, modal, canvas, trays, popover, actions).
Monthly dumps page & capture integration
frontend/app/monthly-dumps/[month].tsx, frontend/app/capture/index.tsx, frontend/components/capture/*, frontend/components/date-container.tsx, frontend/app/index.tsx
MonthlyDumpPage with timed slide navigation and grid creation mutation; Capture screen shows recap chip/banner and wires header/date container props; root screen prefetch adjustment.
Testing & Jest configuration
frontend/jest.setup.js, frontend/app/capture/__tests__/banner-visibility.test.tsx, frontend/hooks/__tests__/use-monthly-dump.test.tsx, frontend/components/monthly-dumps/__tests__/photo-grid-picker.test.tsx
Jest setup for reanimated and icon mocks; banner visibility, hook enablement, service caching, and photo grid picker interaction tests.
Frontend guidelines & config
frontend/AGENTS.md, frontend/package.json, .github/workflows/frontend-tests.yml, frontend/maestro/monthly-dumps/*.yaml
Require zod validation before Supabase/backend calls, add expo-media-library, and add frontend CI/test workflow and Maestro UI flows.

AI Skills Documentation

Layer / File(s) Summary
Frontend design skill
.agents/skills/frontend-design/SKILL.md
New skill describing design-thinking workflow, aesthetic guidelines (typography, color, motion, composition, backgrounds) and constraints against generic AI aesthetics.
Stop slop writing skill
.agents/skills/stop-slop/{CHANGELOG,LICENSE,README,SKILL,references/*}.md
New skill package with changelog, MIT license, README, core rules, scoring rubric, examples, and phrase/structure reference guides to remove formulaic AI writing.
Skills configuration
skills-lock.json
Updated lock to include frontend-design and stop-slop entries and refreshed hashes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

"I hopped through pixels, built a grid so neat,
Banners bloom like clover, memories to keep.
I stitched the month in color, snapped with tiny paws,
No slop, just tidy stories — applause, applause!" 🐇📸

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/monthly-dump

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (11)
frontend/maestro/monthly-dumps/create-grid-from-gallery-flow.yaml (1)

9-10: 💤 Low value

Misleading test ID naming.

The test ID monthly-dump-grid-open-entries-button is used in the gallery flow, which is confusing since this flow uses Gallery as the source, not entries. The button appears to be a generic "open picker" action but the name suggests it's specific to entries. Consider renaming to something like monthly-dump-grid-open-picker-button or monthly-dump-grid-expand-source-button.

Also applies to: 17-17, 21-21, 25-25, 29-29, 33-33

🤖 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 `@frontend/maestro/monthly-dumps/create-grid-from-gallery-flow.yaml` around
lines 9 - 10, Replace the misleading test ID
monthly-dump-grid-open-entries-button (and its repeated occurrences at the other
positions) with a name that reflects the Gallery source and generic picker
action, e.g. monthly-dump-grid-open-picker-button or
monthly-dump-grid-expand-source-button; update every instance of
monthly-dump-grid-open-entries-button used in this YAML flow (the tapOn keys at
lines where it appears) so the identifier consistently reflects "picker" or
"source" rather than "entries" to avoid confusion.
frontend/maestro/monthly-dumps/create-grid-from-entries-flow.yaml (2)

4-4: ⚡ Quick win

Hardcoded date will become stale.

The flow uses 2026-05 which will eventually become invalid as time passes. Consider parameterizing the date or using a dynamic value that references "current month" or "previous month".

🤖 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 `@frontend/maestro/monthly-dumps/create-grid-from-entries-flow.yaml` at line 4,
The openLink value currently hardcodes "keepsafe://monthly-dumps/2026-05" which
will become stale; change the openLink in create-grid-from-entries-flow.yaml to
use a parameter or runtime expression (e.g., a flow variable like MONTH or an
expression that computes current/previous month) instead of a literal date,
update any callers or defaults to provide that variable, and ensure downstream
steps that parse the path expect the same variable format.

37-37: ⚡ Quick win

Verify grid creation success beyond UI disappearance.

The flow only asserts that "Create Your Dump" is no longer visible, but doesn't verify the grid was actually created successfully. Consider adding assertions for:

  • Grid image visibility
  • Success toast/message
  • Navigation to viewing state
🤖 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 `@frontend/maestro/monthly-dumps/create-grid-from-entries-flow.yaml` at line
37, The current step only uses assertNotVisible "Create Your Dump" which only
verifies the modal closed; update the flow to also assert the grid was created
by adding checks after the creation step: verify the grid image is visible
(e.g., an assertGridImageVisible check targeting the new grid thumbnail
selector), assert a success toast/message is shown (e.g., assertToastVisible
with the expected success text), and assert navigation to the grid viewing state
(e.g., assertNavigationToViewingState or an assertion that the URL or page title
corresponds to the newly created grid). Ensure these additional assertions run
immediately after the existing assertNotVisible step so the flow confirms
creation, UI feedback, and navigation.
.agents/skills/frontend-design/SKILL.md (2)

42-42: ⚡ Quick win

Replace clichéd phrase with more distinctive language.

The phrase "thinking outside the box" is flagged as a cliché. This is particularly noteworthy given that this PR also introduces a writing quality skill (stop-slop) that prohibits predictable AI patterns. Consider more specific, evocative language that models the creativity you're asking the agent to demonstrate.

✍️ Suggested alternatives
-Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
+Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when breaking conventions and committing fully to a distinctive vision.

Or:

-Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
+Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when challenging design norms and committing fully to a distinctive vision.
🤖 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 @.agents/skills/frontend-design/SKILL.md at line 42, Replace the clichéd
phrase "thinking outside the box" in the SKILL.md sentence that begins
"Remember: Claude is capable..." with a more specific, evocative alternative;
edit that line to use language such as "forge unexpected connections,"
"reimagine boundaries," or "make bold, original leaps" (or another distinctive
phrase of similar tone) so the guidance models the creative, non-predictable
style the stop-slop skill enforces.

32-32: ⚡ Quick win

Specify which Motion library to use.

The instruction "Use Motion library for React when available" is ambiguous. Multiple popular React animation libraries exist (Framer Motion, React Spring, React Motion). Specify which library to use for consistency.

📝 Suggested clarification
-- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
+- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Framer Motion for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
🤖 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 @.agents/skills/frontend-design/SKILL.md at line 32, Update the ambiguous
"Use Motion library for React when available" line to explicitly name the chosen
library (e.g., "Use Framer Motion for React when available") so guidance is
consistent; locate the phrase "Use Motion library for React when available" in
SKILL.md and replace it with the chosen library name (reference symbol: the
sentence starting with "Use Motion library for React when available"), and keep
the rest of the guidance about CSS-first, staggered reveals, scroll-triggering,
and hover states unchanged.
frontend/components/monthly-dumps/monthly-dump-grid-icons.tsx (1)

26-41: ⚡ Quick win

Remove or utilize the unused mutedColor prop.

The mutedColor prop is destructured but never used in the rendering logic. Either remove it from the prop interface or incorporate it into the icon rendering.

♻️ Remove unused prop
 type GridIconProps = {
   size?: number;
   color?: string;
-  mutedColor?: string;
 };

 export function MonthlyDumpGrid2x2Icon({
   size = 22,
   color = '`#F8FAFC`',
-  mutedColor = 'rgba(248,250,252,0.28)',
 }: GridIconProps) {

Apply the same change to MonthlyDumpGrid2x3Icon (lines 43-46).

🤖 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 `@frontend/components/monthly-dumps/monthly-dump-grid-icons.tsx` around lines
26 - 41, The mutedColor prop is destructured but unused; either remove it from
the component signature and the GridIconProps type or apply it to some GridCell
fills (e.g., pass mutedColor to one or more GridCell components to render muted
cells). Update MonthlyDumpGrid2x2Icon and MonthlyDumpGrid2x3Icon accordingly,
ensuring any change to the prop list is mirrored in GridIconProps and that
GridCell uses the correct color prop when you choose to utilize mutedColor.
frontend/components/monthly-dumps/monthly-dump-video-slide.tsx (1)

35-54: ⚡ Quick win

Refactor conditional to check error condition first.

The component checks !hasPlaybackError before rendering the video, but the coding guideline requires evaluating negative conditions first with early returns.

As per coding guidelines: Always evaluate negative conditions first (e.g., use early returns with if (!condition) return; instead of if (condition) { doSomething(); }).

♻️ Refactor to check error condition first
- if (!hasPlaybackError) {
+ if (hasPlaybackError) {
+   return (
+     <View style={styles.fallbackContainer}>
+       <Text style={styles.fallbackTitle}>Video cannot be played</Text>
+       <Text style={styles.fallbackSubtitle}>This clip is unavailable on this device.</Text>
+     </View>
+   );
+ }
+ 
+ return (
    <View style={styles.videoContainer}>
      <VideoView player={player} style={styles.media} contentFit="cover" />
      {isLoading ? (
        <BlurView intensity={48} tint="dark" style={styles.loadingOverlay}>
          <ActivityIndicator size="large" color="white" />
        </BlurView>
      ) : null}
    </View>
- );
- }
- 
- return (
-   <View style={styles.fallbackContainer}>
-     <Text style={styles.fallbackTitle}>Video cannot be played</Text>
-     <Text style={styles.fallbackSubtitle}>This clip is unavailable on this device.</Text>
-   </View>
  );
🤖 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 `@frontend/components/monthly-dumps/monthly-dump-video-slide.tsx` around lines
35 - 54, The render logic currently checks !hasPlaybackError and nests the main
video UI, but per guidelines invert the condition to early-return the
error/fallback UI first; update the component (monthly-dump-video-slide) to if
(hasPlaybackError) return the <View> with styles.fallbackContainer and the two
<Text> elements, then proceed to render the <View style={styles.videoContainer}>
with VideoView (player), the isLoading conditional showing BlurView and
ActivityIndicator, ensuring references to hasPlaybackError, VideoView,
isLoading, BlurView, ActivityIndicator, and styles remain unchanged.
frontend/components/monthly-dumps/grid-image-picker-bottom-tray.tsx (1)

18-34: ⚡ Quick win

Add accessibility labels to icon-only buttons.

These actions are icon-only with no accessibilityLabel/accessibilityRole, so screen-reader users can't identify them. testID is for tests, not assistive tech.

♿ Proposed fix
       <TouchableOpacity
         testID="monthly-dump-grid-open-entries-button"
+        accessibilityRole="button"
+        accessibilityLabel="Add photo from entries"
         activeOpacity={0.85}
         onPress={onOpenEntries}
         style={styles.trayActionButton}
       >
         <ImagePlus size={22} color="`#F8FAFC`" strokeWidth={2.2} />
       </TouchableOpacity>

       <TouchableOpacity
         testID="monthly-dump-grid-open-camera-button"
+        accessibilityRole="button"
+        accessibilityLabel="Capture photo with camera"
         activeOpacity={0.85}
         onPress={onOpenCamera}
         style={styles.trayActionButton}
       >
         <Camera size={20} color="`#F8FAFC`" strokeWidth={2.2} />
       </TouchableOpacity>
🤖 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 `@frontend/components/monthly-dumps/grid-image-picker-bottom-tray.tsx` around
lines 18 - 34, The two icon-only TouchableOpacity buttons (the ones rendering
ImagePlus and Camera and using testIDs "monthly-dump-grid-open-entries-button"
and "monthly-dump-grid-open-camera-button" with handlers onOpenEntries and
onOpenCamera) need accessibility props so screen readers can identify them; add
accessibilityLabel (e.g., "Open entries" and "Open camera"), set
accessibilityRole="button", and optionally add accessibilityHint for extra
context, while keeping existing styles.trayActionButton and handlers intact.
frontend/app/monthly-dumps/[month].tsx (1)

102-103: ⚡ Quick win

Remove debug log from the slide effect.

logger.info("current slide", { currentSlide }) runs on every slide transition and emits the full slide payload (including URLs). This looks like a leftover debug artifact and adds noise on the hot navigation path.

♻️ Proposed change
     const currentSlide = allSlides[currentIndex];
-    logger.info("current slide", { currentSlide })
     if (!currentSlide || currentSlide.type === 'grid_prompt') {
🤖 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 `@frontend/app/monthly-dumps/`[month].tsx around lines 102 - 103, Remove the
noisy debug log in the slide effect: delete or disable the logger.info("current
slide", { currentSlide }) call inside the component effect that computes
currentSlide (referenced as currentSlide and currentIndex). If you still need
telemetry, replace it with a privacy-safe, low-frequency log (e.g., logger.debug
with only index or a hash) or wrap the log behind a development-only guard so it
does not run on every slide transition.
frontend/app/capture/__tests__/banner-visibility.test.tsx (1)

152-178: 💤 Low value

Test asserts nothing after the press.

After pressing banner-trigger-button, there is no assertion verifying the toggle effect — the test only confirms the screen renders and the press doesn't crash. Consider asserting an observable consequence (e.g., a toggled testID/accessibilityState, or pointerEvents change) so the test name reflects what it verifies. As-is it's a smoke test, not a toggle test.

🤖 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 `@frontend/app/capture/__tests__/banner-visibility.test.tsx` around lines 152 -
178, The test currently presses the banner trigger but asserts nothing about
visibility; update the test for CaptureScreen to verify an observable
consequence after pressing 'banner-trigger-button' — e.g., after the
act/fireEvent.press, assert that the banner's testID 'monthly-dump-banner' has
changed (presence/absence via queryByTestId), or check a toggled prop such as
accessibilityState.expanded or the banner element's pointerEvents/style to
reflect the open state; use getByTestId/queryByTestId on 'monthly-dump-banner'
(or add a distinct testID like 'monthly-dump-banner-open' if needed) and assert
the expected value so the test actually verifies the toggle behavior.
frontend/jest.setup.js (1)

45-54: ⚡ Quick win

Make the lucide-react-native mock resilient to any icon.

This mock only exports Sparkles, Play, UserPlus, and X. Any component under test that imports a different lucide icon will receive undefined and crash with an "Element type is invalid" error. A Proxy returning a stub View for every key avoids this brittleness as icon usage grows.

♻️ Proposed Proxy-based mock
-jest.mock('lucide-react-native', () => {
-    const React = require('react');
-    const { View } = require('react-native');
-    return {
-        Sparkles: () => <View />,
-        Play: () => <View />,
-        UserPlus: () => <View />,
-        X: () => <View />,
-    };
-});
+jest.mock('lucide-react-native', () => {
+    const React = require('react');
+    const { View } = require('react-native');
+    const Icon = () => <View />;
+    return new Proxy(
+        { __esModule: true },
+        { get: (target, prop) => (prop in target ? target[prop] : Icon) }
+    );
+});
🤖 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 `@frontend/jest.setup.js` around lines 45 - 54, The current jest.mock for
'lucide-react-native' only defines a few named icon components and breaks when
tests import other icons; replace the static object with a Proxy that returns a
stub React component (rendering react-native's View) for any accessed property
so any named export works. In the mock implementation (inside
jest.mock('lucide-react-native', ...)), require('react') and
require('react-native') as before, create a generic component that returns <View
/> and then return a Proxy that maps any property access to that component (also
expose the Proxy as the default export / __esModule if needed) so all icon
imports resolve to the stub without throwing.
🤖 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 @.agents/skills/frontend-design/SKILL.md:
- Around line 1-5: The skills-lock.json entry for
skills.frontend-design.skillPath is pointing to
plugins/frontend-design/skills/frontend-design/SKILL.md but the actual skill
file is .agents/skills/frontend-design/SKILL.md; update the mismatch by either
changing the skills-lock.json value for skills.frontend-design.skillPath to
".agents/skills/frontend-design/SKILL.md" or move the SKILL.md file into the
plugins/frontend-design/skills/frontend-design/ directory so the path in
skills-lock.json and the repository file location match; ensure you update any
other references to this skillPath to the chosen canonical path.
- Line 4: Frontmatter in .agents/skills/frontend-design/SKILL.md references a
non-existent LICENSE.txt and skills-lock.json contains wrong paths and some
ambiguous/awkward copy: update the frontmatter license field to point to an
existing license entry or add a LICENSE.txt to the repo; fix skills-lock.json
entries so "frontend-design" points to .agents/skills/frontend-design/SKILL.md
and "stop-slop" points to .agents/skills/stop-slop/SKILL.md; in the SKILL.md
content replace the ambiguous “Use Motion library…” with a specific library name
(e.g., Framer Motion or the intended lib) and reword the typography guideline to
remove the semicolon-separated awkward phrasing; also remove or rephrase the
concluding “thinking outside the box” line to avoid encouraging clichés.

In @.agents/skills/stop-slop/README.md:
- Around line 13-22: Update the fenced code block that shows the directory tree
(the block starting with ```) to include a language identifier by changing the
opening fence to ```text so markdownlint recognizes it as a plain-text code
block; specifically edit the README.md's directory-structure code fence that
contains "stop-slop/ ├── SKILL.md ..." to start with ```text instead of just
```.

In `@backend/services/notification_enqueue_service.py`:
- Around line 296-305: Replace the two early-return logger.info calls that embed
month_name in the message with structured logging using the extra kwarg: for the
check on filtered_recipients (referencing filtered_recipients and month_name)
and the check on push_tokens (referencing push_tokens and month_name) change the
calls to something like logger.info("No recipients with push_notifications
enabled for dump batch", extra={"month_name": month_name}) and logger.info("No
push tokens found for dump batch", extra={"month_name": month_name})
respectively, leaving the surrounding logic and return True intact so dynamic
context is passed via extra instead of interpolated into the message.

In `@backend/tests/test_monthly_dump_queue_notifications.py`:
- Around line 9-16: The test defines an unused mock_supabase fixture and
instantiates MonthlyDumpQueueService() which calls get_supabase_client() and
will fail if SUPABASE_* settings are empty; either remove the unused
mock_supabase fixture or, preferably, patch
services.queues.monthly_dump_queue_service.get_supabase_client to return a
MagicMock before creating MonthlyDumpQueueService so __init__ does not require
real env secrets (use the existing mock_supabase or a monkeypatch/patcher to
replace get_supabase_client during the test).

In `@frontend/components/monthly-dumps/monthly-dump-grid-prompt-slide.tsx`:
- Line 4: The file monthly-dump-grid-prompt-slide.tsx imports Sparkles from
'lucide-react-native' but never uses it; remove the unused Sparkles symbol from
the import statement (keep ArrowRight) in the import line so the module only
imports what is used and eliminates the unused-import lint warning.

In `@frontend/components/monthly-dumps/photo-grid-picker.tsx`:
- Around line 279-291: applyLayoutChange currently passes slots straight to
resizeSlots which truncates by index and can drop non-contiguous filled photos;
before calling resizeSlots (inside applyLayoutChange) compact the filled slots:
take the filtered prev array, extract only non-null slot entries in their
existing order, then pad with nulls up to filtered.length (or up to
nextConfig.requiredPhotos if needed) and pass that compacted array into
resizeSlots so filled photos are preserved when shrinking layouts; reference
functions/vars: applyLayoutChange, resizeSlots, GRID_LAYOUTS, setGridSlots,
nextConfig, removePhotoIds.

In `@frontend/hooks/phone-number/use-manage-phone-sheet.ts`:
- Around line 12-45: The effect currently queries TABLES.PHONE_NUMBER_UPDATES
inside useEffect via checkShouldShowPhonePrompt and uses manual cancelled/.catch
lifecycle handling; move that Supabase read into a useQuery (e.g., create a
query hook that selects id from TABLES.PHONE_NUMBER_UPDATES filtered by user.id)
and derive the showPhoneSheet state from the query result plus
getPhonePromptState, removing the internal async fetch and cancelled flag in
useEffect; also validate user.id with zod before invoking the query and handle
the pendingRecord?.id logic and setShowPhoneSheet updates from the
query/getPhonePromptState results instead of doing the supabase call inside
checkShouldShowPhonePrompt.
- Around line 1-10: Remove the unused import useCallback, add a short docstring
above the useManagePhoneSheet function describing its purpose/returns, and
consolidate the two calls to useAuthContext() into a single destructure (e.g.,
const { user, profile } = useAuthContext()) so both user and profile are
obtained from one call; update imports accordingly to drop useCallback and
ensure the docstring is present for useManagePhoneSheet.

In `@frontend/hooks/use-monthly-dump.ts`:
- Around line 36-41: The fallback bucket in buildSupabasePublicUrl currently
uses STORAGE_BUCKETS.MEDIA causing mismatch with
MonthlyDumpService.resolveStorageTarget; change the default bucket assignment so
that when hasBucketPrefix is false the bucket is STORAGE_BUCKETS.MONTHLY_DUMPS
(i.e., update the bucket variable assignment where possibleBucket/remaining are
computed) so prefix-less paths resolve to MONTHLY_DUMPS instead of MEDIA.
- Around line 57-58: Add a docstring to the custom hook function useMonthlyDump
describing what the hook does, its parameter and return shape; specifically
document the optional requestedMonth parameter (string | null) and the returned
UseMonthlyDumpResult, mirroring the style used in useMonthlyEntries and
useGalleryImages (purpose, args, returns). Place the docstring immediately above
the function declaration for useMonthlyDump so it satisfies the "ALL custom
hooks must have docstrings" guideline.

In `@frontend/services/monthly-dump-service.ts`:
- Around line 286-301: The strict monthlyDumpSlideSchema requiring
duration_seconds >= 1 causes setCachedMonthlyDump (which parses payload.slides)
to throw and make useMonthlyDump fall back when remote slides have missing or
zero duration_seconds; relax the schema to accept missing/zero values by
providing a default or coercion for duration_seconds in monthlyDumpSlideSchema
(or alternatively validate/ensure the backend sets duration_seconds for all
slide types) so that setCachedMonthlyDump no longer rejects freshly fetched
dumps due to missing duration_seconds.
- Around line 264-271: apiFetch is fine as-is (it doesn't double-prefix
BACKEND_URL); instead fix three issues: relax or handle strict slide validation
in setCachedMonthlyDump/monthlyDumpSlideSchema by allowing duration_seconds to
be 0 or optional (or validate per-slide and drop/repair invalid slides before
calling setCachedMonthlyDump) so a single bad slide won't make useMonthlyDump
fall back to cache; unify bucket defaults between buildSupabasePublicUrl and
resolveStorageTarget (make both default to STORAGE_BUCKETS.MONTHLY_DUMPS or
centralize the default constant) so URL resolution is consistent; and ensure
getMonthlyDump always hydrates slide.url via resolveStorageTarget (or populate
url before returning) so hooks that rely on buildSupabasePublicUrl never receive
slides lacking a url.

---

Nitpick comments:
In @.agents/skills/frontend-design/SKILL.md:
- Line 42: Replace the clichéd phrase "thinking outside the box" in the SKILL.md
sentence that begins "Remember: Claude is capable..." with a more specific,
evocative alternative; edit that line to use language such as "forge unexpected
connections," "reimagine boundaries," or "make bold, original leaps" (or another
distinctive phrase of similar tone) so the guidance models the creative,
non-predictable style the stop-slop skill enforces.
- Line 32: Update the ambiguous "Use Motion library for React when available"
line to explicitly name the chosen library (e.g., "Use Framer Motion for React
when available") so guidance is consistent; locate the phrase "Use Motion
library for React when available" in SKILL.md and replace it with the chosen
library name (reference symbol: the sentence starting with "Use Motion library
for React when available"), and keep the rest of the guidance about CSS-first,
staggered reveals, scroll-triggering, and hover states unchanged.

In `@frontend/app/capture/__tests__/banner-visibility.test.tsx`:
- Around line 152-178: The test currently presses the banner trigger but asserts
nothing about visibility; update the test for CaptureScreen to verify an
observable consequence after pressing 'banner-trigger-button' — e.g., after the
act/fireEvent.press, assert that the banner's testID 'monthly-dump-banner' has
changed (presence/absence via queryByTestId), or check a toggled prop such as
accessibilityState.expanded or the banner element's pointerEvents/style to
reflect the open state; use getByTestId/queryByTestId on 'monthly-dump-banner'
(or add a distinct testID like 'monthly-dump-banner-open' if needed) and assert
the expected value so the test actually verifies the toggle behavior.

In `@frontend/app/monthly-dumps/`[month].tsx:
- Around line 102-103: Remove the noisy debug log in the slide effect: delete or
disable the logger.info("current slide", { currentSlide }) call inside the
component effect that computes currentSlide (referenced as currentSlide and
currentIndex). If you still need telemetry, replace it with a privacy-safe,
low-frequency log (e.g., logger.debug with only index or a hash) or wrap the log
behind a development-only guard so it does not run on every slide transition.

In `@frontend/components/monthly-dumps/grid-image-picker-bottom-tray.tsx`:
- Around line 18-34: The two icon-only TouchableOpacity buttons (the ones
rendering ImagePlus and Camera and using testIDs
"monthly-dump-grid-open-entries-button" and
"monthly-dump-grid-open-camera-button" with handlers onOpenEntries and
onOpenCamera) need accessibility props so screen readers can identify them; add
accessibilityLabel (e.g., "Open entries" and "Open camera"), set
accessibilityRole="button", and optionally add accessibilityHint for extra
context, while keeping existing styles.trayActionButton and handlers intact.

In `@frontend/components/monthly-dumps/monthly-dump-grid-icons.tsx`:
- Around line 26-41: The mutedColor prop is destructured but unused; either
remove it from the component signature and the GridIconProps type or apply it to
some GridCell fills (e.g., pass mutedColor to one or more GridCell components to
render muted cells). Update MonthlyDumpGrid2x2Icon and MonthlyDumpGrid2x3Icon
accordingly, ensuring any change to the prop list is mirrored in GridIconProps
and that GridCell uses the correct color prop when you choose to utilize
mutedColor.

In `@frontend/components/monthly-dumps/monthly-dump-video-slide.tsx`:
- Around line 35-54: The render logic currently checks !hasPlaybackError and
nests the main video UI, but per guidelines invert the condition to early-return
the error/fallback UI first; update the component (monthly-dump-video-slide) to
if (hasPlaybackError) return the <View> with styles.fallbackContainer and the
two <Text> elements, then proceed to render the <View
style={styles.videoContainer}> with VideoView (player), the isLoading
conditional showing BlurView and ActivityIndicator, ensuring references to
hasPlaybackError, VideoView, isLoading, BlurView, ActivityIndicator, and styles
remain unchanged.

In `@frontend/jest.setup.js`:
- Around line 45-54: The current jest.mock for 'lucide-react-native' only
defines a few named icon components and breaks when tests import other icons;
replace the static object with a Proxy that returns a stub React component
(rendering react-native's View) for any accessed property so any named export
works. In the mock implementation (inside jest.mock('lucide-react-native',
...)), require('react') and require('react-native') as before, create a generic
component that returns <View /> and then return a Proxy that maps any property
access to that component (also expose the Proxy as the default export /
__esModule if needed) so all icon imports resolve to the stub without throwing.

In `@frontend/maestro/monthly-dumps/create-grid-from-entries-flow.yaml`:
- Line 4: The openLink value currently hardcodes
"keepsafe://monthly-dumps/2026-05" which will become stale; change the openLink
in create-grid-from-entries-flow.yaml to use a parameter or runtime expression
(e.g., a flow variable like MONTH or an expression that computes
current/previous month) instead of a literal date, update any callers or
defaults to provide that variable, and ensure downstream steps that parse the
path expect the same variable format.
- Line 37: The current step only uses assertNotVisible "Create Your Dump" which
only verifies the modal closed; update the flow to also assert the grid was
created by adding checks after the creation step: verify the grid image is
visible (e.g., an assertGridImageVisible check targeting the new grid thumbnail
selector), assert a success toast/message is shown (e.g., assertToastVisible
with the expected success text), and assert navigation to the grid viewing state
(e.g., assertNavigationToViewingState or an assertion that the URL or page title
corresponds to the newly created grid). Ensure these additional assertions run
immediately after the existing assertNotVisible step so the flow confirms
creation, UI feedback, and navigation.

In `@frontend/maestro/monthly-dumps/create-grid-from-gallery-flow.yaml`:
- Around line 9-10: Replace the misleading test ID
monthly-dump-grid-open-entries-button (and its repeated occurrences at the other
positions) with a name that reflects the Gallery source and generic picker
action, e.g. monthly-dump-grid-open-picker-button or
monthly-dump-grid-expand-source-button; update every instance of
monthly-dump-grid-open-entries-button used in this YAML flow (the tapOn keys at
lines where it appears) so the identifier consistently reflects "picker" or
"source" rather than "entries" to avoid confusion.
🪄 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: CHILL

Plan: Pro

Run ID: c5c7ac46-109f-414f-9c9d-09bb73767647

📥 Commits

Reviewing files that changed from the base of the PR and between 0634744 and 00e6c0c.

⛔ Files ignored due to path filters (8)
  • frontend/bun.lock is excluded by !**/*.lock
  • supabase/.temp/cli-latest is excluded by !**/.temp/**
  • supabase/.temp/gotrue-version is excluded by !**/.temp/**
  • supabase/.temp/pooler-url is excluded by !**/.temp/**
  • supabase/.temp/postgres-version is excluded by !**/.temp/**
  • supabase/.temp/project-ref is excluded by !**/.temp/**
  • supabase/.temp/rest-version is excluded by !**/.temp/**
  • supabase/.temp/storage-version is excluded by !**/.temp/**
📒 Files selected for processing (54)
  • .agents/skills/frontend-design/SKILL.md
  • .agents/skills/stop-slop/CHANGELOG.md
  • .agents/skills/stop-slop/LICENSE
  • .agents/skills/stop-slop/README.md
  • .agents/skills/stop-slop/SKILL.md
  • .agents/skills/stop-slop/references/examples.md
  • .agents/skills/stop-slop/references/phrases.md
  • .agents/skills/stop-slop/references/structures.md
  • backend/.gitignore
  • backend/controllers/monthly_dump_controller.py
  • backend/services/notification_enqueue_service.py
  • backend/services/queues/monthly_dump_queue_service.py
  • backend/tests/test_monthly_dump_queue_notifications.py
  • frontend/AGENTS.md
  • frontend/app/capture/__tests__/banner-visibility.test.tsx
  • frontend/app/capture/index.tsx
  • frontend/app/index.tsx
  • frontend/app/monthly-dumps/[month].tsx
  • frontend/components/capture/capture-header.tsx
  • frontend/components/date-container.tsx
  • frontend/components/monthly-dumps/__tests__/photo-grid-picker.test.tsx
  • frontend/components/monthly-dumps/grid-image-picker-bottom-tray.tsx
  • frontend/components/monthly-dumps/grid-image-picker-camera-modal.tsx
  • frontend/components/monthly-dumps/grid-image-picker-capture-canvas.tsx
  • frontend/components/monthly-dumps/grid-image-picker-cell.tsx
  • frontend/components/monthly-dumps/grid-image-picker-empty-state.tsx
  • frontend/components/monthly-dumps/grid-image-picker-layout-popover.tsx
  • frontend/components/monthly-dumps/grid-image-picker-right-actions.tsx
  • frontend/components/monthly-dumps/grid-image-picker-selection-pill.tsx
  • frontend/components/monthly-dumps/grid-image-picker.tsx
  • frontend/components/monthly-dumps/monthly-dump-audio-slide.tsx
  • frontend/components/monthly-dumps/monthly-dump-banner.tsx
  • frontend/components/monthly-dumps/monthly-dump-grid-icons.tsx
  • frontend/components/monthly-dumps/monthly-dump-grid-prompt-slide.tsx
  • frontend/components/monthly-dumps/monthly-dump-image-slide.tsx
  • frontend/components/monthly-dumps/monthly-dump-progress-bar-item.tsx
  • frontend/components/monthly-dumps/monthly-dump-status-screen.tsx
  • frontend/components/monthly-dumps/monthly-dump-video-slide.tsx
  • frontend/components/monthly-dumps/photo-grid-empty-state.tsx
  • frontend/components/monthly-dumps/photo-grid-picker.tsx
  • frontend/constants/supabase.ts
  • frontend/hooks/__tests__/use-monthly-dump.test.tsx
  • frontend/hooks/phone-number/use-manage-phone-sheet.ts
  • frontend/hooks/use-gallery-images.ts
  • frontend/hooks/use-monthly-dump.ts
  • frontend/hooks/use-monthly-entries.ts
  • frontend/jest.setup.js
  • frontend/maestro/monthly-dumps/create-grid-from-entries-flow.yaml
  • frontend/maestro/monthly-dumps/create-grid-from-gallery-flow.yaml
  • frontend/package.json
  • frontend/services/monthly-dump-service.ts
  • frontend/supabase/migrations/20260420043600_init_monthly_dump_next_run.sql
  • frontend/types/database.ts
  • skills-lock.json

Comment thread .agents/skills/frontend-design/SKILL.md
Comment thread .agents/skills/frontend-design/SKILL.md
Comment thread .agents/skills/stop-slop/README.md
Comment thread backend/services/notification_enqueue_service.py
Comment thread backend/tests/test_monthly_dump_queue_notifications.py Outdated
Comment on lines +12 to +45
useEffect(() => {
let cancelled = false;

const checkShouldShowPhonePrompt = async () => {
if (!user?.id) return;
if (profile?.phone_number) {
if (!cancelled) setShowPhoneSheet(false);
return;
}

// If the user already has a pending OTP record, always show the sheet.
const { data: pendingRecord } = await supabase
.from(TABLES.PHONE_NUMBER_UPDATES)
.select('id')
.eq('user_id', user.id)
.maybeSingle() as { data: { id: string } | null };

if (pendingRecord?.id) {
if (!cancelled) setShowPhoneSheet(true);
return;
}

const state = await getPhonePromptState(user.id);
const now = Date.now();
const shouldShow = !state.dontAskAgain && (!state.nextPromptAtMs || now >= state.nextPromptAtMs);

if (!cancelled) setShowPhoneSheet(shouldShow);
};

checkShouldShowPhonePrompt().catch(() => { });
return () => {
cancelled = true;
};
}, [profile?.phone_number, user?.id]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Fetch the pending OTP record with useQuery instead of inside useEffect.

This effect reads from TABLES.PHONE_NUMBER_UPDATES directly inside useEffect (no Realtime subscription involved), which the project guidelines prohibit. Move the Supabase read into a useQuery and derive showPhoneSheet from the query result; this also removes the manual cancelled/.catch(() => {}) lifecycle handling. Add zod validation for user.id before the call as well.

As per coding guidelines: "DO NOT call supabase tables inside of useEffect, ALWAYS use useQuery for fetching data from supabase, UNLESS you are using Supabase Realtime" and "Before making any calls to supabase, perform input validation with zod".

🤖 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 `@frontend/hooks/phone-number/use-manage-phone-sheet.ts` around lines 12 - 45,
The effect currently queries TABLES.PHONE_NUMBER_UPDATES inside useEffect via
checkShouldShowPhonePrompt and uses manual cancelled/.catch lifecycle handling;
move that Supabase read into a useQuery (e.g., create a query hook that selects
id from TABLES.PHONE_NUMBER_UPDATES filtered by user.id) and derive the
showPhoneSheet state from the query result plus getPhonePromptState, removing
the internal async fetch and cancelled flag in useEffect; also validate user.id
with zod before invoking the query and handle the pendingRecord?.id logic and
setShowPhoneSheet updates from the query/getPhonePromptState results instead of
doing the supabase call inside checkShouldShowPhonePrompt.

Comment thread frontend/hooks/use-monthly-dump.ts
Comment thread frontend/hooks/use-monthly-dump.ts
Comment thread frontend/services/monthly-dump-service.ts
Comment thread frontend/services/monthly-dump-service.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 @.github/workflows/frontend-tests.yml:
- Around line 18-21: Update the workflow to pin third-party actions to immutable
commit SHAs instead of tag refs and disable checkout credential persistence:
replace the tag-based refs for actions/checkout and actions/setup-node with
their corresponding commit SHAs, and add persist-credentials: false to the
actions/checkout step so credentials are not retained by subsequent steps;
ensure the commit SHAs are the official ones from the actions repositories and
preserve any existing step configuration while making these changes.
🪄 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: CHILL

Plan: Pro

Run ID: b7dd4218-2451-41bc-a2a3-69e55000cfda

📥 Commits

Reviewing files that changed from the base of the PR and between 00e6c0c and eed56eb.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • .github/workflows/frontend-tests.yml
  • backend/services/notification_enqueue_service.py
  • backend/tests/test_monthly_dump_queue_notifications.py
  • frontend/hooks/use-monthly-dump.ts
  • frontend/services/__tests__/monthly-dump-service.test.ts
  • frontend/services/monthly-dump-service.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/hooks/use-monthly-dump.ts
  • frontend/services/monthly-dump-service.ts
  • backend/services/notification_enqueue_service.py

Comment on lines +18 to +21
- uses: actions/checkout@v5

- name: Set up Node
uses: actions/setup-node@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=".github/workflows/frontend-tests.yml"

echo "Checking unpinned GitHub Actions refs..."
rg -n 'uses:\s*actions/(checkout|setup-node)`@v`[0-9]+' "$file" || true

echo "Checking checkout credential persistence flag..."
rg -n 'uses:\s*actions/checkout@|persist-credentials:\s*false' "$file"

Repository: fortune710/keepsafe

Length of output: 260


Pin GitHub Actions by commit SHA and disable checkout credential persistence.

  • .github/workflows/frontend-tests.yml uses tag-based action refs (actions/checkout@v5, actions/setup-node@v4) instead of immutable SHAs.
  • actions/checkout is not configured with persist-credentials: false, so credentials may be persisted into later steps.
Suggested hardening patch
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@<full_commit_sha>
+      with:
+        persist-credentials: false

-    - name: Set up Node
-      uses: actions/setup-node@v4
+    - name: Set up Node
+      uses: actions/setup-node@<full_commit_sha>
       with:
         node-version: 20
         cache: npm
         cache-dependency-path: frontend/package-lock.json
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 18-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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 @.github/workflows/frontend-tests.yml around lines 18 - 21, Update the
workflow to pin third-party actions to immutable commit SHAs instead of tag refs
and disable checkout credential persistence: replace the tag-based refs for
actions/checkout and actions/setup-node with their corresponding commit SHAs,
and add persist-credentials: false to the actions/checkout step so credentials
are not retained by subsequent steps; ensure the commit SHAs are the official
ones from the actions repositories and preserve any existing step configuration
while making these changes.

@fortune710 fortune710 merged commit 75823b2 into staging May 29, 2026
2 of 3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request May 29, 2026
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