JQ Day Planner (Family)
Status: v1 SHIPPED 2026-07-26 (migration 585). Placement + persistence decided by the owner: dashboard, per-member, applies to the day. Inherits FAMILY_INTENT.md.
What it is
A "🗓 Plan the day" button on each member's card at /dashboard/family. JQ takes
that person's context and proposes an ordered day the parent can drag to reorder,
tap ✕ to drop, then keep as the day's schedule.
Privacy — de-identified by construction
The payload sent to the LLM carries no name, no birthdate, no family name — only a
coarse role ("child"), an age band ("6-12"), and the day's activity lists. The member's
name never leaves the backend process (gatherPlannerContext returns it separately, for
the UI title only). Same posture as the ETHOS/Living-Voice walls.
Context gathered (family-day-planner.ts)
- Age band — from birthdate → band (
ageBandFromBirthdate), or "unknown". - Routines due today —
family_routines, filtered by cadence for the date. - Family calendar commitments —
fetchFamilyEventsfor the day (family-wide). - Growth goals — the member's enabled
growth_nudges(labels only). - Sleep — earliest wake alarm if set, else an age-appropriate sleep window.
- Weekend community events — opt-in
fetchEvents(family-friendly), weekends only.
Data model (migration 585)
family_day_plans — one row per member per day: blocks jsonb (ordered
{id,title,time_label,kind}; kind ∈ routine|meal|school|activity|event|rest|free),
plus dropped jsonb — the titles the parent deselected from JQ's proposal. RLS-on /
service-role-only, gated by parent_user_id.
Endpoints (routes/family.ts)
POST /api/family/members/:id/day-plan/generate→{ blocks, member_name, is_weekend }GET /api/family/members/:id/day-plan?date=→{ blocks }PUT /api/family/members/:id/day-plan{ blocks, dropped }→ saves the day's plan
The dropped[] signal → goal-formation nudge (SHIPPED 2026-07-26)
Requested alongside v1, now built. FamilyGoalNudge (mounted on /dashboard/family)
reads GET /api/family/goal-nudge (getRecentDeselections — last 7 days of
family_day_plans.dropped, grouped by member, most-dropped first). During a quiet
beat (~9s after landing, not on load) the JQ card rolls in from the bottom-right
and asks "You dropped N things from {member}'s day lately — like {titles}. Want to turn
one into a goal?" Picking a dropped item + a cadence seeds it as a routine (which
then gets its own freshness tracking). Deterministic — no LLM, no cost. Invitation
not nag: dismissible, capped once per signal (member + drop-count) per browser via
localStorage, so a new pattern can re-surface but the same one won't re-nag.
Design notes for that build:
- Signal store —
family_day_plans.droppedalready accumulates per day; a nudge reads recent rows (last N days) to find a pattern ("keeps dropping homework", "always drops the evening activity"). - Quiet-moment trigger — reuse the PresenceOrb (
components/layout/) idle detection; fire at most once per session, never mid-interaction. Inherits the growth-nudge ethics wall: invitation, never a nag; dismissible; frequency-capped. - The form — a tiny goal-capture (title + optional cadence) that can seed a
family_routinesrow, a growth goal, or a Downstream/Levers item. Keep it one question. - Additions too — when a parent adds a block not proposed, that's also signal (an unmet routine); capture symmetrically.
- Gate/telemetry as a cost-bearing LLM surface consistent with Ask JQ (Plus).
Other follow-ons
- Show the saved plan on the wall + collapsed card (the data's there via GET) so "apply to the day" is visible everywhere, not just in the planner.
- Free-text situations ("a rough sleep night", "sick day") feeding the same generator.