Skip to content

Commit 8192c6f

Browse files
rezailmiReza Ilmi
andauthored
feat(history): mirror detail page + rich day cards + review hardening (#64)
* feat(history): mirror day-detail cards + routed detail page at /mirror/$id - DayDetailCard renders mirror reflections as rich rows: context badge, time, story-reframe headline, highlight-phrase pull quote, transcript snippet, plus a "Show more →" link for backend-backed entries - New MirrorDetailSheet at /mirror/$id shows the full mirror: Story reframe, Validation, Inferred meaning, Transcript, with Confirm / Forget actions for pending reviews; sidebar carries date, time, context, status, and mood tags - Route lives at /mirror/$id (not /history/mirror/$id) because TanStack's matcher picks /history/$tab over the more specific /history/mirror/$id when both sit under _app/history - Distinguish "Loading…" (captures slice not yet hydrated) from "couldn't find that mirror" (genuinely missing) - backend-snapshot: validation flows from MirrorEntryRow into the snapshot (optional on the type so incremental captures.patch writes are unaffected) * fix(history): rename day-card section "Mirrors" → "Reflections" "Mirror" is the agent name; "reflection" is the student-facing capture. The list heading is user-facing copy, so match the noun the user owns. * chore(demo): land demo reflections in the current week, hide redundant status line - Shift all seed timestamps forward 182 days so demo data lands in the 2026-04 to 2026-05 window (was 2025-07 to 2025-11). Demo-a's 8 reflections are then re-spread across May 18–30 so the calendar's default 'this week' view shows reflection markers on multiple days - Day detail card: drop the literal `status: confirmed` text — review state is already implicit (Confirm/Forget appear only when pending), and the detail page surfaces the status badge explicitly * feat(history): make day-card reflections clickable, drop per-card chrome - Each reflection card is now itself a link to /mirror/$id (when backend-backed); no separate "Show more" button - Drop the per-card "Reflection" label — the section heading already says "Reflections", repeating it on every row is noise - Drop Confirm/Forget buttons + status text from the card; reviews live on the detail page where there's room for the full context - Generic non-ask captures keep their text-only render with the kind label removed for the same reason * chore(history): drop dead review/sync code; calendar mood icon goes white when selected DayDetailCard: - Remove CaptureActions component, reviewCapture / retryCaptureSync handlers, patchReviewCapture, syncLine helper, and their state. All unused now that the day card is a passive list of clickable cards. CalendarPane: - Mood Smile icon flips to text-white when its day cell is selected, matching the reflection/event icons that already do. * fix(seed): scope per-student wipes by student_id explicitly resetSeedStudent ran unqualified DELETEs and relied on the RLS policy to scope them to the current student. The seed CLI typically runs as neondb_owner which has BYPASSRLS, so those DELETEs wiped every student's rows on each iteration — only the last student's seed survived. Same problem made the 'existing count' check see other students' rows and skip them on subsequent runs. Fix: filter every reset DELETE by studentId, and filter the existing count query the same way. Correct under both RLS-enforced and BYPASSRLS roles. * fix(history): darken disgust mood color #9CC36E → #5E9135 for stronger contrast * feat(history): use mood shape images instead of smile icons Replace the single lucide Smile icon (color-tinted per emotion) with the existing 3D-style shape art from `mood-shapes.ts`: - Calendar cell mood markers render the shape image (sphere / teardrop / octahedron / cube / torus / capsule / egg / halfcube / disk) - Day-detail card "Moods" list shows the shape next to the emotion name in place of the small colored dot Drop the local MOOD_HEX color maps in both files — the shape SVG owns its own palette via EMOTIONS, so duplicated hex constants were stale and prone to drift. --------- Co-authored-by: Reza Ilmi <reza.ilmi@gt.tech.gov.sg>
1 parent 28587bd commit 8192c6f

7 files changed

Lines changed: 664 additions & 320 deletions

File tree

src/components/student-space/sheets/CalendarPane.tsx

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Toggle } from '@base-ui-components/react/toggle'
33
import { ToggleGroup } from '@base-ui-components/react/toggle-group'
44
import { CalendarDays, Camera, ChevronLeft, ChevronRight, NotebookPen, Smile } from 'lucide-react'
55
import { useEffect, useMemo, useState } from 'react'
6+
import { EMOTION_BY_ID, shapeDataUri } from '~/lib/student-space/mood-shapes'
67
import { cn } from '~/lib/utils'
78

89
/**
@@ -15,18 +16,6 @@ import { cn } from '~/lib/utils'
1516
* Tailwind variant only, not a full re-render of the calendar (PR #33
1617
* invariant).
1718
*/
18-
const MOOD_HEX: Record<string, string> = {
19-
joy: '#FFD66B',
20-
sadness: '#7FB3D9',
21-
anger: '#E36A55',
22-
fear: '#B49AD6',
23-
disgust: '#9CC36E',
24-
anxiety: '#F1A04E',
25-
envy: '#6FC2B3',
26-
embarrassment: '#F0A6B5',
27-
ennui: '#A8A5BD',
28-
}
29-
3019
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
3120
const MONTH_NAMES = [
3221
'January',
@@ -271,15 +260,21 @@ export function CalendarPane({
271260
>
272261
<span className="text-xs font-medium tabular-nums">{cell.getDate()}</span>
273262
<div className="mt-auto flex min-h-2 flex-wrap items-center gap-0.5">
274-
{cellMoods.slice(0, 3).map((mood, i) => (
275-
<Smile
276-
// biome-ignore lint/suspicious/noArrayIndexKey: mood badges are positional within a day
277-
key={i}
278-
aria-hidden
279-
className="size-3"
280-
style={{ color: MOOD_HEX[mood.emotion ?? ''] ?? '#bbb' }}
281-
/>
282-
))}
263+
{cellMoods.slice(0, 3).map((mood, i) => {
264+
const emotion = EMOTION_BY_ID[mood.emotion ?? '']
265+
if (!emotion) return null
266+
return (
267+
<img
268+
// biome-ignore lint/suspicious/noArrayIndexKey: mood badges are positional within a day
269+
key={i}
270+
src={shapeDataUri(emotion)}
271+
alt=""
272+
aria-hidden
273+
className="size-3.5"
274+
draggable={false}
275+
/>
276+
)
277+
})}
283278
{cellCaps.length > 0
284279
? (() => {
285280
const hasPhoto = cellCaps.some((c) => c.kind === 'photo')

0 commit comments

Comments
 (0)