Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-273Done

5.2.1 Schema — `attachment.workItemId` link + `source` + indexes (SetNull lifecycle, relations both sides) + repo methods

Estimate: 18m

The linking layer 2.3.7 deliberately left out. Schema + migration + repository methods only — no service logic, no UI.

Attachment model extensions: workItemId (nullable — rows are born unlinked by the editor-at-create path and audit rows predate the link; onDelete: SetNull, NOT cascade: cascading would delete rows and strand their blobs invisibly — the nulled row is what the 5.2.7 GC sweeps) with the two-sided relation (WorkItem.attachments back-relation) per the CLAUDE.md FK rule; source ('editor' | 'panel' — string with a CHECK or enum, matching house style) defaulting editor for existing rows (every pre-5.2 row came from the editor path); index [workItemId, createdAt(sort: Desc)] for the paged panel read. NO commentId column — the editor-sourced block only needs source; the jump-to-exact-comment affordance is the documented extension ("no complexity for nothing").

Repository methods (single-op, writes require tx): listByWorkItem(workItemId, { cursor, take }) + countByWorkItem; findManyByBlobUrls(workspaceId, urls) (the link-on-write lookup — workspace-scoped so a foreign URL pasted into a body never links a foreign row); linkToWorkItem(ids, workItemId, source, tx) / unlinkFromWorkItem(ids, tx); delete(id, tx); listOrphans({ olderThan, cursor, take }) (unlinked rows for the GC).

Acceptance criteria

  • Attachment gains nullable workItemId (SetNull, relation modelled on BOTH sides) + source ('editor'|'panel', existing rows backfilled 'editor') + the [workItemId, createdAt desc] index; prisma migrate dev re-run reports no drift.
  • The 2.3.7 upload path is untouched and its tests stay green (rows still insert unlinked; uploadAttachment signature unchanged).
  • Repo methods listed above exist as single Prisma ops (writes require tx); findManyByBlobUrls is workspace-scoped; listOrphans is cursor-bounded.
  • Vitest (real Postgres): SetNull on issue delete verified (row survives unlinked); backfill verified; empty-input guards on every new repo method (the coverage gate).

Context refs

  • prisma/schema.prisma Attachment (the 2.3.7 model + its RLS migration) + WorkItem; motir-core/CLAUDE.md (FK-as-@relation rule, required-tx writes)
  • lib/repositories/attachmentRepository.ts (2.3.7) — extend, don't fork
  • Finding #52 — the reuse contract (rows deliberately unlinked until this story)
  • Story 5.2 description — the SetNull-not-cascade lifecycle decision