Skip to content

Memories Feed

An authenticated user opens the app's home screen and sees a paginated feed of their captured memory sessions, each with a thumbnail, timestamp, type label, and processing status. Segments from the same recording session are automatically grouped into a single feed item.

Flow

  1. App loads home screen — The MemoriesScreen component (main/app/app/index.tsx) calls useMemoriesFeed({ limit: 20 }) via React Query. This hook calls the memories-feed Lambda.

  2. Authentication — The Lambda requires a valid Cognito JWT (require_auth_context=True). The user_id is extracted from the auth context.

  3. Database connection — The Lambda connects to PostgreSQL using credentials from SSM Parameter Store (or DATABASE_URL in local dev) and initializes SQLAlchemy.

  4. Cursor parsing — A numeric cursor (offset) and limit (capped at 50, default 20) are parsed from the request payload.

  5. Query — All WorldMMSegment records for the authenticated user are queried, ordered by start_time DESC then id DESC.

  6. Session grouping — Segments are grouped by their source_session_id. Segments without a source_session_id are each treated as their own session (keyed by segment ID).

  7. Sorting — Sessions are sorted by their latest segment's start_time (newest first) to ensure a natural chronological ordering.

  8. Pagination — Pagination is applied at the session level: OFFSET cursor LIMIT limit+1. This ensures that each feed item corresponds to exactly one session.

  9. Session aggregation — For each session, the feed derives:

  10. id: The session ID (either source_session_id or a single segment's ID if ungrouped)
  11. time: The earliest segment's start_time (session start)
  12. type: The richest segment type in the session (priority: visual > audio > text)
  13. thumbnail: The first visual segment's presigned S3 URL (TTL 1 hour), if any
  14. processing_status: The overall session status (pending if any segment pending, failed if any segment failed, complete otherwise)
  15. title: Not currently propagated by the feed lambda — the title field is absent from the response payload. MemoryFeedItem.title is declared optional (title?: string | null) in the TypeScript type to accommodate future inclusion.

  16. Next-cursor computation — If limit+1 sessions are returned, the next cursor is cursor + limit; otherwise there are no more pages.

  17. Response — Returns { "memories": [...], "next_cursor": "<offset-string>" | null }. Each memory item contains id, time, type, featured, processing_status, and optionally thumbnail. The title field is not yet included in the server response; the client TypeScript type declares it optional to anticipate future inclusion.

  18. UI rendering — The MemoryFeed component renders the list. After a session finishes recording (recordingState transitions from stopping to idle), the feed is automatically refetched to show newly ingested memories.

Entry Point

  • Lambda: main/server/api/memories/feed/app.pylambda_handler
  • HTTP method: GET /memories/feed (API Gateway, Cognito-authenticated)
  • Mobile: main/app/lib/api/memory/useMemoryApi.ts

Dependencies

  • PostgreSQL (WorldMMSegment table)
  • S3 (BUCKET_NAME) for presigned thumbnail URLs
  • Cognito JWT authorizer

MemoryCard Title Display

MemoryCard (main/app/components/memory/MemoryCard.tsx) renders a title overlay at the bottom of each thumbnail tile:

const titleDisplay = memory.title || "Untitled Memory";

When memory.title is null, undefined, or an empty string, the component falls back to the static string "Untitled Memory". The title is rendered in a semi-transparent black bar (rgba(0,0,0,0.7)) pinned to the bottom of the card using position: absolute. Because the feed lambda does not currently send title in the response, all cards currently display "Untitled Memory" until the feed propagation gap is resolved.

Audio Type Support Gap

The feed correctly returns sessions whose type is "audio" (when no visual segment exists). However, the MemoryViewerModal (docs/docs/memory-viewer-modal.md) has no AudioPlayer component and no audio branch in its renderItem logic. Audio memories fall through to the thumbnail/placeholder path — users can see the session in the feed but cannot play back the audio. Backend infrastructure for audio playback endpoints is tracked in docs/bugs/2026-05-08-audio-metadata-endpoint-403-forbidden.md.

Key Changes from Segment-Level Feed

Previously, the feed returned one item per segment, which meant a multi-media recording session (audio + video) would appear as multiple separate feed items. Now:

  • The feed groups segments by source_session_id and returns one item per session
  • The session's type reflects the richest content (visual takes priority over audio, which takes priority over text)
  • The session's thumbnail is drawn from the first visual segment if available
  • The session's processing_status reflects the overall completion state across all segments
  • Pagination operates at the session level, not the segment level, ensuring consistent and predictable page boundaries