Every screen a caregiver can reach in the redesigned surface, every happy path and every unhappy one, drawn straight from the built code. The phone on the left is the screen; the card on the right is what is happening and why.
Eight chapters, 73 screens. Chapters 1 to 6 are the shipping build, traced to code. Chapters 7 and 8 are the designed-but-not-yet-built warmth and graduation layers, drawn from the spec and marked throughout.
Every door into the caregiver app and every way it can catch you: the chooser, sign-in and its warm error copy, Sign in with Apple, joining by code,.
The 'Between You' channel is the whole home. Not tabs, not a dashboard: one warm surface that shows exactly one thing per open. A1 caught-up sends.
Manage is a bottom pull-up handle, never a tab bar. It opens to a 2x2 launcher, then rises to a working hub with a segmented control. Inside: Today.
The money surface, held inside the sensory envelope: no '$0' ever renders, the en dash carries the quiet week. The weekly hero, the pay/give.
The Care door is the one surface that is only for the caregiver. The same calming tools the subject uses (modeling is the point), a direct line to.
Settings, consolidated. Who you are, who you support, per-person theming, the circle roster and its removes, generating and sharing invites, joining.
Phase 2, designed but not built. The warmth that flows back from them: the felt-feedback card, the earning milestone, the load-relief hatch, and the.
Phase 2, designed but not built. Graduation as a display layer: the home reframes into their growth and your success, and admins get a gentle.
Every door into the caregiver app and every way it can catch you: the chooser, sign-in and its warm error copy, Sign in with Apple, joining by code, the Face ID gate, auto-lock, and the loading shells. This is the threshold, and it is built to never leave a caregiver stranded on a red screen.
Caregiver-first taps 'I'm new here'; invited taps 'I have an invite code'; returning taps 'Sign in'.
onPick sets chooserPath so AuthScreen renders with that initialMode; the logo breathe loop is skipped under Reduce Motion.
The three-door entry is pre-auth and uses the Bubblegum welcome palette, not the caregiver Dream default. The one place the doc shows the un-toned brand gradient.
ChooserScreen.js:25-162
Types email and password and taps Sign in; or Apple; or Forgot password.
handleLogin → signInWithPassword; on success saveCredentials() arms biometric; loading swaps the CTA label for a white spinner at opacity 0.7.
The default sign-in plus its in-flight state. Loading is a trivial micro-variant folded in here, not a screen of its own.
AuthScreen.js:426-786 · handleLogin 183
Fixes the field, re-enters the password, or routes to Forgot / create account.
setError with the mapped literal; it never leaks stack text. The generic is the last resort.
Every distinct auth failure copy survives as one verbatim line; only the shared error-box chrome is merged. No red flash beyond the calm box.
AuthScreen.js:184-190 · authError.js:13-77
Retries Apple or falls back to email.
setLoading(false) + setError literal; cancel is an early return with no setError; the no-session case clears auth artifacts first.
Apple's three distinct outcomes are separate branches with separate handling, kept as one mockup because they share the form and differ only in one line.
AuthScreen.js:133-178
Continues with Apple, or sets email and password and taps Join circle. The Apple button (device-gated by appleAuthAvailable) redeems the invite inline via handleAppleSignIn('join').
Email path: handleJoinSignup writes pending_invite + pending_role, then signUp; redeem is deferred to OnboardingFlow.finish(); banner copy resolves via roleCopy().
The invited-caregiver happy path: the green banner with the fully-resolved they/them acceptBody the caregiver actually reads.
AuthScreen.js:427-590 · revenue.js:150
Corrects the code or asks the inviter to re-share.
setError with the mapped reason string; the flow stays on the code step.
Five reachable copies: wrong-length, then the four validate_invite reasons (not_found, used, expired, already_member). "No longer valid" is the || catch-all default, unreachable today since the RPC only emits those four, all mapped. It is a safety net for a future reason (revoke, group-full) or a malformed response.
AuthScreen.js:270-288 · validate_invite RPC
Taps 'Open with Face ID →', or 'Sign out' as the deliberate escape hatch.
promptBiometric(); ok → onCleared() unlocks. A failed or cancelled prompt does NOT sign out, which prevents the sign-in → Face ID → sign-out loop.
The one biometric event per session, plus its no-op failure state. On failure nothing changes on screen, so the two states merge.
App.js:2464-2493
None. The caregiver lands on the home with no gate.
The lock gate is auto-bypassed and never shown, a reachable permission branch the pending-gate / fail / lock trio doesn't cover.
The no-gate path is a real device state, notably Expo Go and QA, worth documenting so the absence of a lock is understood as intended, not a bug.
useSession.js:144 · biometrics.js:20
Taps Unlock.
On tap, if biometric is available promptBiometric(); on ok setCaregiverLocked(false) → dashboard. In Expo Go it unlocks directly. Distinct from the app-open gate.
The 10-minute re-lock is a separate mechanism from the app-open gate and has its own Dream-toned surface.
App.js:2925-2947
Waits; after 5s can sign out if wedged.
Spins until loggedInCaregiver populates; loadingStuck flips true at 5000ms to surface the escape hatch.
Two loading shells share one spinner plus status-line pattern and the same 5s escape; merged as one loading mockup per the consolidation rule.
App.js:2875-2922 · loadingStuck 617
Admin: recognize, note, add / edit / delete tasks, full Settings. Non-admin: everything but task mutation and Settings config.
isAdmin gates the two task-mutation modals and the edit affordances, progressive enhancement inline, never a separate admin window.
The shell chrome is identical admin vs non-admin; the difference is capability not text, so both dashboard states collapse to one framed mockup.
CaregiverDashboard.js:92,970
Hands the phone to the caregiver, or taps Close; a solo subject can sign out here.
onLock closes; onSignOut signs out. PIN auth was retired in Session 3, so there is no PIN picker.
The wrong-person-in-the-seat recovery; a real dead-end that needs its own warm framing.
CaregiverView.js:52-83
The 'Between You' channel is the whole home. Not tabs, not a dashboard: one warm surface that shows exactly one thing per open. A1 caught-up sends you away, A2 recognize is the only job, A3 look-closer is where you settle one thing, A4 is the one composer. Everything else lives behind the Manage door.
Reads their state and the week's tally; the home sends you away. Optionally taps 'Send Maddy a note'.
stateOpt from STATE_OPTIONS; earnedWeekly > 0 renders the tabular earned line plus recognizedCount; the whole card is the reliability contract that lets them close the app.
The centerpiece calm open, authored verbatim from ChannelHome: their state read-only, the contract, the earned row, and the one offer.
ChannelHome.js:298 · earned row tappable to Rewards.
Reads the gentle not-yet cue; nothing to do.
stateOpt null so the title becomes 'With Maddy today' and the well caption reads "no check-in yet, and that's okay"; earned falls to the no-zero 'Nothing to settle up'.
The 'hasn't checked in' variant the matrix promises to preserve, distinct from A1-caughtup which assumes a state, carrying the no-verdict framing for a quiet start.
ChannelHome.js:308 · no-zero earned branch ChannelHome.js:348.
Taps a seam row and routes into Today to act on the friction signal.
Each row is a quiet cue that survives P1 restyled; the full B1/F5 cards are Phase 2. Never alarming, never red.
The friction signals that keep a visible cue on the calm home; each is unhappy because it needs the caregiver, restyled as the warmest possible rows.
ChannelHome.js:140 · reached-out on stateData.reached_out_at.
Nothing here beyond the CirclePill and heart chrome; the invite lives in Settings.
The !subjectId branch of ChannelHome renders a single card; seamRows null, no composer offered.
The empty-channel first-run, the home before anyone is in it, kept plain so the one next step (invite) is obvious.
ChannelHome.js:219 · !subjectId branch.
Taps one chip to look closer, or one batch 'Recognize · $X' to approve everything at once, or the off-ramp to quiet things down.
cluster is the first 3 of the queue, oldest first; countless and capped, recognizing reloads and refills so items 4+ get their turn with no count ever shown; pendingTotal sums partials at half.
The centerpiece A2: the queue is the job, the hue dot is their mark on their work, one batch wave of warmth.
ChannelHome.js:227 · hue dot in hueDot, not the caregiver accent.
Taps the one chip to look closer, or 'Recognize · $X'.
pending.length === 1 flips the title to the singular 'Something to recognize'.
The singular-queue copy branch is a separate reachable state from the plural cluster, one distinct verbatim string worth its own frame.
ChannelHome.js:232 · pending.length === 1.
Recognizes the one submission, or asks for a change (warm redo / needs proof).
Routes through the shipped atomic idempotent approve path; 'Ask for a change' writes needs_redo/needs_proof back to the task rather than approving.
The look-closer surface is the third of the channel's A1–A4 spine, where a caregiver settles one thing rather than batch-approving, and the warm-redo path lives.
ChannelHome.js:242 chip → onOpenApproval · ApprovalModal.
Types a note, optionally taps emoji quick-picks appended at the cursor, taps Send; on error re-taps Send to retry.
send() guards message && !sending && subjectId; insertCaregiverNote writes to caregiver_notes (bidirectional inner-ring); success runs successComplete() + onClose; catch sets the error line and re-enables. No read-receipt, no sent log either direction. Field resets fresh every open.
The centerpiece A4, authored verbatim: compose, the 'Sending…' in-flight, and the send-error, all one composer since they differ only by button label and one error line.
NoteComposer.js:26 · insertCaregiverNote().
Manage is a bottom pull-up handle, never a tab bar. It opens to a 2x2 launcher, then rises to a working hub with a segmented control. Inside: Today (the state line, progress, the approval queue, what they told you, their messages, the note composer) and Things (the budget hero and the task list, admin and read-only).
Taps the pill to open Manage at the mid launcher detent.
On press it increments cg_manage_handle_opens then calls onPress(); taught defaults true to avoid a flash for a settled caregiver.
The door is identical no matter what's behind it. The two handle states differ only by whether the teaching caption shows, so they merge into one quiet-chrome frame.
Never a badge, count, dot, or preview. HANDLE_TEACH_OPENS=4.
Taps one of four tiles, taps Done, or taps the scrim to dismiss.
Tile tap: haptic + onSelectSection(key); the sheet rises to the tall detent with that section (tap and rise are one motion). Done or scrim returns to the calm home.
The launcher is the map behind the door; subtitles are non-numeric by canon.
No counts live here. The one allowed soft shadow sits over a flat scrim.
Works inside the section; switches sections via the segmented control; drags the header down to return to launcher or close.
Header pan governs detents (fling up to tall, down to next detent, past mid + 0.5 to close). renderSection mounts each tab.
The working hub is the shared frame every Manage section renders inside; the segmented-control chrome is documented once.
Sub-sections use this control, TodayDetailTab and friends, never bottom tabs.
Same taps; the transitions are fades and snaps instead of springs.
Required accessibility rendering per spec §10 under useReducedMotion: every new-surface animation freezes.
A required accessibility state of the sheet that the two detent frames don't capture. Motion is a first-class part of this design system.
Glances at whether and how the subject checked in today.
Branches: no stateData shows hasn't-checked-in; entry_state 'shutdown' shows 'Checked in this morning'; else time · emoji · shortLabel.
Three distinct branches on one line, and the shutdown-masking one is sensitive, so they stay together but each verbatim branch is preserved.
stateLine protects a hard morning; the section title drops the name when none is set.
Sees where the day stands per category, no verdict.
groupEarned counts full completions at value, partials at half; each group carries a 4px progress bar and a 'X / week' caption.
The per-category read; the middle-dot no-zero is a specific design token worth showing.
A group earning 0 shows a centered middle dot '·', never '$0', distinct from the Rewards en dash.
Caught-up: moves on. No-circle: needs to invite a subject.
The empty branch's ternary falls to the no-name string; the no-circle case is a first-run and permission dead-end.
Manage · Today's approval queue is a different screen from the channel home; its two empty variants both survive, merged on shared chrome.
No count suffix and no Select action when empty.
Taps a row to open one day's approval, or Select then 'Recognize N' to approve many at once.
Row tap: selectMode ? onToggleSelect : onOpenApproval; batch fires onApproveSelected() + successComplete().
The full Manage-side approval queue with its multi-select batch, the detailed counterpart to the channel's A2 chips.
The section title carries a count; the batch bar appears only with selectedIds.size > 0.
Admin taps Edit or Remove to act, or Keep as is to dismiss; non-admin sees the ask with no buttons.
onRequestEdit / onRequestRemove / onRequestDismiss; isAdmin gates the action row.
A friction signal with an open decision, the subject's voice reaching into task config (JER-571).
The action row is admin-only; the ask itself is visible to all.
Reads how a task felt; for rough, admin taps 'Adjust this task'; dismisses durably via X or Clear all.
handleDismissFeedback is durable; 'Adjust this task' fires onSetEditingTask + dismiss + haptic; unseen auto-marked seen after 2.5s.
The shipped P1 The After surface; 'felt rough' is the actionable friction that routes to a fix, the seam of the P2 B1 card.
Inner-ring RLS: the outer ring never sees this.
Reads each; taps X to clear one, or 'Mark all as read'.
Single X: markMaddyMessageSeen(id) + local filter (no haptic); mark all: markMaddyMessagesSeen(subjectId).
Inbound messages from them, framed through their companion, a distinct Today surface from The After.
The section title uses the companion frame when one exists, else 'Maddy says'.
Picks a preset or types, taps Send note; or taps Voice to record; an empty circle blocks it.
Send: insertCaregiverNote then setNoteSent(true) + haptic; Voice: onStartRecording / onStopAndSendRecording; recordingError surfaces calmly in red text, not a flash.
The Manage-side composer, distinct from the A4 sheet, with its voice-memo path and empty state.
The many transient sub-states (recording, uploading, sent) merge into this one composer per the consolidation rule.
Glances at what's left and what's recently done, read-only.
Recent merges dated ledger approvals + direct completions, newest first, deduped; empty is a no-verdict line honoring daily reset.
The bottom of Today, what's left and what landed; the empty completions line carries the daily-reset canon.
Outstanding shows values in tone.dim (muted); recent uses formatRewardDelta.
Watches the weekly total move; taps a task to edit, holds and drags to reorder, swipes to move-group/edit/delete, taps Add a thing.
total = weeklyPossible(tasks); split = weeklyBreakdown; SortableTaskList wires the edit and reorder handlers.
The admin task-editing surface with the live budget hero, the same weekly number that becomes the Rewards goal denominator.
Grouped by non-empty group; gesture legend as the section footer.
No-subject: must generate an invite in Settings first. No-tasks: taps Add a thing.
Add onPress is undefined when !hasSubject, guarding the orphan insertTask(subjectId=null) bug; the empty-list branch keeps Add enabled.
The no-subject case is a real dead-end for a fresh caregiver; the no-tasks case is a neutral onboarding empty, both distinct from the populated list.
Blocked 'Add a thing' dims to 0.5 with no chevron.
Taps an unfinished task to mark it done on the subject's behalf.
Row onPress: !completed && markComplete(id); completed rows dim and strike, non-tappable; a zero-active-task day is a bare-footer empty gap.
The progressive-enhancement stripped view for non-admin caregivers, including the uncovered zero-active-tasks empty day.
No hero, no gesture legend, no Add, just a checkable list.
The money surface, held inside the sensory envelope: no '$0' ever renders, the en dash carries the quiet week. The weekly hero, the pay/give affordances with an undo window, the recognized tally, and the reward-style config where an abstract reward must bind to something real before it saves.
Passive read of week-to-date against the weekly possible. The bar fills to the earned percentage.
The quiet-week hero suppresses the bar and percent so no '$0' propagates to the subject through tone.
The earned, quiet, and goal-quote branches share one hero and merge cleanly; the quote is the goal backing made visible.
pct = min(earnedWeekly/possible,1) · RewardsTab.js:191-227
Taps to settle the full balance, 'part of this', or opens history.
isCurrency flips every Pay to Give and Payout to Rewards. PayoutButton records the amount with an idempotency token.
Money is one form, not the only one; the 'Give' flip is a full copy re-skin worth showing side by side.
recordPayout(balanceOwed) · RewardsTab.js:231-282
Settled: no Pay button; history stays so a mistaken payout can be voided.
No-subject: the label swaps and the history and recognized sections are suppressed.
No-zero is locked: settled weeks render the en dash '–', never '$0'. The label carries the meaning.
!subjectId hard guard blocks recordPayout(..., null) · RewardsTab.js:251-266
Waits; optionally taps Undo within 8s; on error re-taps the CTA.
Error keeps the idempotency token so a same-amount retry dedupes; a changed amount mints a fresh token.
The 8s undo and the not-red error live inside the sensory envelope: no startling flash, ever.
voidPayout() · justPaid clears at 8000ms · RewardsTab.js:287-313
Types an amount, taps Pay; or Cancel.
The amount is clamped to the balance so the running balance never goes negative; record_payout clamps server-side too.
The collapsed link and expanded form are one affordance, with live-remaining math.
parsedPartial = min(parseFloat, balanceOwed) · RewardsTab.js:600-739
Reads the loose tally (never WHEN); taps 'whole day' to drill into the dated ledger.
The tally is filtered to active-today, completed or partial; partials count at half.
No-zero uses the en dash '–', never '$0'; the rest-day variant is a valid state that names itself rather than reading as failure.
tally.filter(...) · RewardsTab.js:335-377
Taps Money or Screen Time (self-backing, applies inline) or Custom (opens the gated modal); optionally names a goal and backing.
Money and time write reward_type inline; Custom always commits through the modal. Non-admins never see this section.
Money is one form, not the only one; the self-backing goal fields are co-equal.
updateSubject(...) · RewardsTab.js:392-512
Taps to open the gated modal, or picks a real backing from the prompt; 'Maybe later' defers for the session.
The nudge and prompt route toward a real backing before save, and never block earning or payout.
An empty backing is a terminal abstract reward, forbidden by canon decision 1; binding it to something real satisfies the rule.
RewardBackingPrompt · RewardsTab.js:513-550
The Care door is the one surface that is only for the caregiver. The same calming tools the subject uses (modeling is the point), a direct line to Jeremy, ambient sound, and the short honest education cards, including what graduation really means: the app is built to need you less.
Scrolls; taps any tile to route to a tool, Reach Jeremy for the note modal, an education card to expand.
Static render; the Sounds row reactively reflects the global sound player; no writes on view. The soft note uses p.obj() so it reads "them" for they/them.
The one caregiver-only surface's lead, warm, no self-report ask, the caregiver's corner. No badge, no count, no "did you self-care?" prompt, a door never a prompt.
CareTab.js:101-199
Taps a row, haptic, routes via the matching onOpen* handler.
Each onPress fires selectPrimary() then the guarded handler; an undefined handler is a no-op.
The co-regulation toolkit, a caregiver using the same tools they'd hand the subject; each tile's exact label and subtitle preserved.
CareTab.js:121-133
Taps Reach Jeremy opens CareNoteModal; taps Sounds opens onOpenSounds.
Reach Jeremy opens a second doorway to the same founder_notes channel as Settings; Sounds subscribes to the global soundPlayer store.
The direct line to Jeremy and the ambient-sound banner. Idle (headset, "Something soft while you work") and playing ("Rain is playing") are one reactive banner.
CareTab.js:138-160
Default eduOpen null (collapsed); tapping a card sets eduOpen to its index.
Single-open accordion; card two is the honest-graduation copy, the app built to need the caregiver less, framed as the whole point not loss.
The three education cards, expanded, especially graduation, the emotional core of the whole product's arc, in the caregiver's own words.
CareTab.js:92-192
Types, taps Send; on error re-taps Send; success auto-dismisses. Sending swaps the send icon for a spinner; success shows a checkmark and "Sent. Thank you 💛" (auto-closes at 2200ms).
sendFounderNote writes founder_notes then notes-watch.js iMessage; the error is inline text, not a startling flash (sensory canon).
The one Care surface that writes to Supabase and its failure branch, the caregiver's outreach failing to land is a real unhappy path. The error keeps the composer and retains the text to retry.
CareNoteModal.js:30-83 · FounderNoteComposer.js
Settings, consolidated. Who you are, who you support, per-person theming, the circle roster and its removes, generating and sharing invites, joining another circle, the OTP password change, and the account edges: delete, lock, sign out. Every destructive path keeps its confirm.
Scrolls; taps any row to edit or expand.
circleMembers loads via loadCircleWithProfiles plus a realtime group_members subscription; subject copy renders live through the pronoun engine.
The Settings map and the admin/non-admin difference in one frame; the two full-scroll states differ only by which sections render.
Admin-only: Who you support, Invite, Tools, Account. Non-admin simply doesn't render them, and per-member Remove disappears.
Types a name, picks an emoji, sets pronouns.
Name writes onUpdateCaregiver({name}) per keystroke; emoji and pronouns persist on select via saveMyPronouns.
The caregiver's own identity fields; the three micro-editors merge into one You mockup per the consolidation rule.
Emoji taps expand an EmojiPicker grid inline; Pronouns taps expand a PronounPicker inline.
Switches active primary, or edits the subject's name, emoji, and pronouns.
onSwitchSubject reloads the dashboard against the chosen subject; pre-signup name persists via saveGroupPendingSubjectName; the pronoun picker is guarded by subjectId.
The multi-subject switcher and the pre-signup placeholder are distinct admin states of the same section, merged on shared editor rows.
Reads the roster; admin may tap Remove.
Live via the realtime group_members channel; a member with no name yet renders the literal "Joining…" (deliberately not "Someone").
The roster with role labels and the transient "Joining…" placeholder; a real in-progress state distinct from a resolved member.
Admin sees a red Remove on removable rows (non-self, non-subject).
Taps Remove to confirm, or Cancel; retries on error.
removeGroupMember(user_id, groupId) then reload; catch sets removeError.
A destructive, irreversible-access action; its confirm, loading, and error are one destructive path with distinct states.
In flight, Remove swaps to "Removing…" with both disabled; on failure a red line sits above the buttons and the card stays open.
Taps a theme.
setTheme(t) plus saveProfile({theme: t.key}) writes the caregiver's own theme; per-person theming means each person carries their own hue inside a role-fixed shape.
Rendered in Bubblegum specifically so the doc demonstrates that the same shape re-hues per person.
Each swatch keeps the theme's own hue, the one exemption from the Things-room flatten; the active theme shows a checkmark.
Taps to open confirm, then Reset or Cancel; retries on error.
resetSubjectCompanionRpc(subject.id); catch sets companionResetError; the card stays open.
Rendered in Night Owl so the doc proves the dark theme is a real sensory environment, not just an inverted palette.
Generates a code, shares it via the native sheet, regenerates or revokes.
generateInviteCode(groupId, role); Share.share(buildInviteMessage); revoke/regenerate reset the code; catch sets inviteError.
The full invite lifecycle merged into one flow; the generic error also covers the over-cap trip.
Loading swaps labels to "Generating…"; failure shows a red line.
Types a code, Continues, confirms Join circle; on the subject-conflict block taps "Got it".
validateInviteRpc then redeemInviteRpc; a subject can belong to only one circle, so a second subject code is hard-blocked.
Joining another circle with its full error family and the one-circle-per-subject hard block; each distinct verbatim rejection preserved.
Previews the subject's view, restores deleted starters (additive), or resets today's completion state (destructive).
Restore upserts STARTER_TASKs; Reset is guarded on subjectId and UPDATEs completed/pending/redo flags off.
Reset-today is genuinely destructive (wipes today's state) and restore is safely additive; the two confirms differ in button colour by intent.
Taps Send code, enters or pastes the 6-digit code, sets and confirms a new password.
resetPasswordForEmail → verifyOtp(recovery) → updateUser({password}); friendlyAuthError maps server errors; validation guards empty/length/mismatch in order.
The whole OTP password change consolidated to one flow mockup, rendered in the AuthFrame palette.
The code input auto-submits at 6 digits; a "Resend in 30s" countdown gates the resend.
Opens the quick guide, or sends Jeremy a founder note.
GuideModal at the bottom of the tree; FounderNoteSection → founder_notes → notes-watch.js.
The always-present help and the Settings-side founder note channel, a second doorway to the same pipe as Care.
On success a checkmark and "Sent. Thank you" row auto-clears at 4s.
Deletes the account (permanent), locks the caregiver view, or signs out (confirmed).
Delete → delete-account edge function + signOut; lock → onLock re-gates biometric; sign out → onSignOut after confirm.
Delete is permanent data destruction; sign-out is confirmed to avoid accidental session loss; each keeps its confirm.
Delete's confirm card is red-bordered; a failure appends a red line.
Phase 2, designed but not built. The warmth that flows back from them: the felt-feedback card, the earning milestone, the load-relief hatch, and the Group F signals they initiate, all governed by an arbiter that surfaces exactly one warm thing per open, or calm silence.
"Soften this task" writes task config only, fully reversible; the lean fix auto-applies with an undo. "Or just send them a note" routes to A4. Admin "Adjust this task" opens TaskEditModal.
Reads task_feedback under inner-ring RLS, dedupes by task|valence, auto-marks-seen after 2.5s.
The dedicated in-their-words card is never folded into the composer, and softening never touches earned or future money. Their quote keeps their own hue.
feedbackCadence.js · not built. No B1 surface renders in src/.
Fires when the subject crosses a monotonic earning milestone (7 / 30 / 100 / 365), shown once per milestone and dismissable.
Tapping "Close" writes the warm_acks ack so it never resurfaces.
A descriptive figure only, never a tier, never a preview of the next threshold, and no streak count. It renders inside the arbiter's single warm slot.
caregiverMilestones.js exists; the medallion and arbiter wiring are not built.
Reached by tapping the load off-ramp "Running low? Quiet things down" from A2. "Take a breath" sends the subject a warm holding note, then routes the caregiver to a breath. Zero self-report is asked.
On hold, defaults carry and the subject is never stranded. There is no "how are you feeling?" prompt anywhere.
NEW running-low hold + holding-note pipe, not built.
Fires when the subject sends the caregiver a gratitude message; arbiter precedence rank 2, just below F5. "Say something back" routes to A4 to reply, and tapping acks the F1 item.
Reads maddy_messages kind='gratitude' OR encouragement_notes.to_user_id, pushed content-free.
It renders as a quiet hero moment: never a stat, no confetti. The reply is optional.
NEW kind='gratitude' column + F1 card not built.
Fires when the subject reacts to a note the caregiver previously sent them; arbiter precedence rank 3. A passive view of their chosen reaction, acked on interaction or dismiss.
Reads caregiver_notes.reactions + NEW from_user_id for routing.
F2 never pushes, shows only their chosen reaction, never a "seen at 3:14" or a reaction count. A removed reaction is silent.
NEW from_user_id column + F2 card not built.
Fires when 3 or more distinct supporters sent warmth toward the subject in one subject-local day; consent-gated (default off); arbiter rank 5. A passive warmth view, acked on interaction or dismiss.
Reads milestone_shares / encouragement_notes + NEW consent column + F3-crossing evaluator.
Non-numeric framing spells the warmth ("Nana and two others"), never the raw count, never "who didn't show." At most one F3 per subject-local day.
NEW consent column + evaluator + card not built.
Fires when the subject sends the caregiver a laugh from their Laughs screen; arbiter rank 6, lowest. Tapping "Tap for the rest 👀" reveals the punchline (content-surprise); reacts; acked on interaction.
Reads NEW levity_sends table + private bucket.
Content-surprise only, never sensory. Send-cap 2/day 5/week silently coalesced, photos EXIF-stripped with short-TTL signed reads; the punchline stays out of the accessibility tree until revealed.
NEW levity_sends table + card + subject-side send flow not built.
Fires when the subject signals a hard day (writes daily_state.reached_out_at). "Be in their corner" routes to warmth, never asks "are you okay?"; the tap writes a subject-unreadable warm_acks ack.
Soft tint and heart, never red / banner / modal. It owns the whole screen with no cluster and no competing CTA, the one arbiter exception to pending-Recognize suppression. Silence still means okay.
reached_out_at may be shipped; the F5 surface + warm_acks ack are not built.
Runs once per home focus and returns exactly one unacked warm item or null (the common case). The one offer on the calm home is warmth, never a chore prompt: reach into Self-Care or reach them first.
One batched debounced read, LEFT JOIN to warm_acks.
Acked only on a real action or a completed VoiceOver read, never on bare render. Returns null while Recognize is pending (except F5); on read error it renders the plain calm home.
warmInbound.js verified absent, not built. The P1 A1 caught-up home is today's analog.
Phase 2, designed but not built. Graduation as a display layer: the home reframes into their growth and your success, and admins get a gentle step-down suggestion to offer them more room. The fade is framed as a win the caregiver built, never as loss.
Passive read; access to the condensed Self-Care rows in the YOU zone. No decision required.
Display layer; ships dormant (cannot compute a level at v1.0 and must not show a fabricated one); framed as THEIR growth + YOUR success, never emptiness, no downward metric.
The graduation reframe of the home. The them/you distinction is carried by LABEL + SIZE only, never by tint. It ships dormant and never activates in P1.
CAREGIVER-BUILD-SPEC.md §5 D1 · §12 (two-zone them / you layer)
Admin taps 'Offer it to Maddy', which sends the offer to the subject, who accepts or declines. The caregiver NEVER moves their level directly.
Admin-only gate (can_access_settings); asymmetric authority, suggest never decide; nothing changes unless they say yes; never guilt.
The designed graduation step-down suggestion. It closes the long-arc chapter on the subject's agency: the admin step-down surface doesn't exist yet.
CAREGIVER-BUILD-SPEC.md §5 D2 · can_access_settings