# REF-014 — Date-First Data Model · Shared Design

**Locked 2026-07-03 12:30 UTC. Operator-signed. Source of truth: `/workspace/.mavis/tracker/refinements.json` → `REF-014`.**

## Core principle

Date is the primary key. Project is a tag. Capture is silent. Cards expose
metadata on the back. Every artefact card has the FVS-canonical anatomy
(breadcrumb, sub-nav, sub-chips, widget, support-acts) — same shape across
all surfaces.

## Schema migration v15 → v16

Widen these four schemas from `project_id: string` to `project_id: string | null`:

- `src/data/models/Voice.ts` (line 5)
- `src/data/models/Media.ts` (line 7)
- `src/data/models/MapLayer.ts` (line 7)
- `src/data/models/Calendar.ts` (line 7)

**Migration safety:**

The `upgrade(db, oldVersion, newVersion, tx)` handler in `src/data/persistence/idb.ts`
must check oldVersion on upgrade and bump DB_VERSION = 16. Existing records have
non-null project_id, which is still valid against `string | null` — no data migration
needed. IDB allows widening a schema.

**Downstream callers (must handle null):**

- `src/voice/VoiceView.tsx` saveVoice() — line 222-256. Allow null. Voice: Voice.project_id: string → string | null. Loader (line 96-103) filters by project_id matching current or null.
- `src/media/MediaView.tsx` — mediaRepo.put already handles records. Detail drawer (line 424) accepts string | null for project_id.
- `src/map/MapView.tsx` — mapLayersRepo stores MapLayer records. Already tolerates null in many places via `?? 'default'`.
- `src/views/Calendar.tsx` — eventsRepo + add-event form line 100ish. project_id becomes optional in form.

**Important:** Tree, Survey, Note, Report models do NOT need schema changes — they
either have no project_id (Note, Report) or are inherently site-scoped (Tree via
siteId, Survey via siteId). They keep their required-site invariant.

**Important:** Channel, Message, StyleProfile, Task, Workflow models already have
`project_id?: string` (optional). No changes needed. They already work as the
operator expected.

## Silent audit trail (assignment_history)

New IDB store:

```typescript
interface AssignmentRecord {
  id: string;             // ULID
  recordId: string;       // the artefact id (e.g. voice_xxx)
  recordKind: string;     // 'voice' | 'media' | 'mapLayer' | 'calendarEvent' | etc.
  oldProjectId: string | null;
  newProjectId: string | null;
  at: number;             // timestamp
  by: 'auto' | 'user';    // auto = default, user = explicit bulk-assign
}
```

Writers fire automatically:
- On every save of an artefact, write a `{ oldProjectId, newProjectId, by: 'auto' }`.
- On every project change, write a delta entry.
- IDB: `assignment_history` store, schema v16.

**No UI at MVP.** This is purely a data capture. Future bulk-assign tools
will read from this store.

## ArtefactCard component

Path: `src/components/ArtefactCard.tsx`

**Shape:**

```typescript
interface ArtefactCardProps {
  // Identity + content
  kind: 'voice' | 'media' | 'mapLayer' | 'calendarEvent' | 'task' | 'note' | 'report';
  id: string;
  title: string;
  capturedAt: number;          // ms epoch
  projectId: string | null;    // null = unassigned
  tags?: string[];
  preview?: string;            // 1-line summary
  icon?: string;               // 🎙 📷 🗺 📅 ✓ 📝 📄

  // State
  state?: 'dormant' | 'active' | 'expanded' | 'peeked' | 'filtered' | 'grouped';
  defaultState?: 'dormant';    // default for non-gallery surfaces

  // Sub-systems (per FVS Card anatomy)
  subNav?: ChipDef[];          // multi-active activators (e.g. "Trees / Surveys / Notes")
  subChips?: ChipDef[];        // single-active switchers (e.g. "Overview | History | Related")
  supportActs?: ChipDef[];     // contextual actions (Pin / Share / Assign / Delete)

  // Regions (slot content per state)
  front?: ReactNode;
  back?: ReactNode;
  widget?: ReactNode;
  previewContent?: ReactNode;

  // Navigation context (for breadcrumb)
  entryPoint?: 'today' | 'projects' | 'media' | 'calendar' | 'tasks' | 'notes' | 'reports' | 'map';

  // Callbacks
  onClick?: () => void;
  onActivate?: (chipId: string) => void;
  onSubChipSelect?: (chipId: string) => void;
  onSupportAct?: (chipId: string) => void;
  onStateChange?: (state: CardState) => void;
}
```

**CSS / data-* attributes (per operator directive):**

```html
<article
  class="artefact-card artefact-card-kind-{kind} artefact-card-state-{state}"
  data-card-id="{id}"
  data-card-kind="{kind}"
  data-card-state="{state}"
  data-breadcrumb="root:{entryPoint},date:{ISO(capturedAt)},project:{projectId|'—'},kind:{kind}"
  data-sub-nav="{joined subNav ids}"
  data-active-tab="{active subChip id}"
  data-support-acts="{joined supportAct ids}"
  data-project-id="{projectId}"
  data-captured-at="{ms}"
  data-tags="{joined tags}"
>
```

**Render order (top → bottom):**

1. Front region — title + icon + capture date + preview line. Always visible.
2. Sub-nav chips — multi-active. Visible when subNav provided.
3. Sub-chip tabs — single-active switcher. Visible when subChips provided.
4. Tab content — reactive to active sub-chip.
5. Widget region — visible in 'active' or 'expanded' state.
6. Back region (above shows): breadcrumb strip + extended detail + support-acts. Visible in 'expanded' state.
7. State toggle (expand button) — bottom-right corner.

**Default behaviour:**
- Gallery surface: starts `dormant`. Click → `active`. Click expand button → `expanded`.
- Other surfaces (list views): starts `dormant` + back region hidden by default. Hover (desktop) or tap → reveals back chips.

## /today route

Path: `src/views/Today.tsx`. Registered in `src/App.tsx` at path `/today`.

**What it shows:**
- Header: "Today, [formatted long date]"
- Content: groups all artefacts from today by hour bucket (morning / afternoon / evening / specific hour). Each artefact rendered as an ArtefactCard.
- Project filter chip row: "All / [project1] / [project2] / Unassigned"
- Date navigation: ← / → arrows to flip days. Today highlighted.

**Query:**
- Single source of truth: query each store (voices, media, mapLayers, notes, tasks, reports) for records where `capturedAt >= today_start && capturedAt < tomorrow_start`.
- Sort by capturedAt descending.

## DateLink extension (piece #3)

`src/views/DateLink.tsx` already exists. Extend the set of surfaces that
render DateLink on artefact cards:

- Voice: ListView.tsx + DetailDrawer
- Media: MediaView.tsx + DetailDrawer
- Map layers: MapView.tsx (sidebar list)
- Tasks: Tasks.tsx
- Reports: Reports.tsx
- Notes: Notes.tsx (already has DateLink per Stage 1.1)

DateLink URL pattern: `/today?date=YYYY-MM-DD`. When clicked, navigates to
the /today view filtered to that day.

## Build order (sequence, not parallel)

1. `schema-migration` — bump to v16, widen 4 schemas, IDB handlers. Tests: existing IDB tests pass + new "null project_id" tests.
2. `artefact-card-component` — TypeScript component with full FVS anatomy. Tests: rendering all 6 states, breadcrumb clickability, data-* attributes correct.
3. `assignment-history-store` — IDB store + auto-writer. Tests: writers fire at save time, audit trail queryable.
4. `today-view` — uses ArtefactCard across all kinds. Tests: query covers today, project filter works, date navigation works.
5. `datelink-extension` — extend to 5 surfaces. Tests: each surface renders DateLink, click navigates to /today.
6. `bug-fixes` (parallel where possible, see below).
7. `integrate-audit` — Playwright run on all 22 routes, capture screenshots, log console errors. Same as `app/audit-screenshots.mjs`. Should pass with 0 errors.

## Bug fixes that ride along with REF-014

- **BUG-015** (Voice Save needs project): partially fixed by schema migration (allow null) + assignment_history auto-writer. Operator can save with no project selected.
- **BUG-016** (Media Upload silent fail): independent. To be triaged separately, likely a Mantine controlled-input mismatch.
- **BUG-012 + BUG-019** (Comms no-toast + missing page-doc): independent. Add `/comms` entry to `src/assistant/page-docs.ts`.

## What's NOT in REF-014

- Bulk-assign UI (`/assign` route)
- Whisper / local transcription model
- Social console (REF-012, separate)
- Map Polygon crash (BUG-014, separate)

## Reference files to read first

- `/workspace/.mavis/tracker/refinements.json` → `REF-014` — full plan
- `/workspace/.mavis/tracker/bugs.json` — full bug list
- `/workspace/recon/freshvibestudio/studio/modules/freshcards/src/Card.tsx` — canonical anatomy reference
- `/workspace/recon/freshvibestudio/studio/modules/freshcards/src/InnerCard.tsx` — InnerCard pattern
- `/workspace/recon/freshvibestudio/studio/src/workspaces/FreshCardsWorkspace.tsx` — mounting pattern
- `pact/plans/stage1-upgrade-plan.md` — context (why date-first exists)

## Test expectations

- IDB tests pass at v16.
- ArtefactCard renders correctly for each kind (visual snapshot via Playwright).
- `/today` view loads + shows today's artefacts.
- DateLink navigates to filtered /today view.
- Assignment_history is queryable; entries fire at save.
- BUG-014 (Map Polygon crash) — Page no longer goes blank. Playwright clicks Polygon, no body.innerText === ''.
- BUG-019 (/comms page-doc) — toast on /comms chat-icon click is Comms-specific (mentions channels, reactions, read receipts).
- All 22 routes load with 0 console errors (audit-screenshots.mjs final pass).
