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-275Done

5.2.3 Link-on-write — parse referenced blob URLs in description/explanation + 5.1 comment bodies; link/unlink attachment rows (`source: editor`)

Estimate: 26m · Depends on: 5.2.1, 5.1.2

The embeds-ARE-attachments rule, server-side. Editor uploads write unlinked rows (create modal: the issue does not exist yet), so linkage happens at BODY-WRITE time — the same parse-on-write pattern as 5.1's mentions.

Shared helper (lib/blob/referencedUrls.ts): extract our blob URLs from a Markdown body (image embeds + file links; match on the blob-host + the attachments/<workspaceId>/ pathname prefix so only OUR uploads qualify — a pasted foreign URL never links anything; workspace-scoped lookup via findManyByBlobUrls).

Wire into the write paths (inside each owning service's existing transaction):

  • workItemsService.createWorkItem / updateWorkItem — on a description/explanation write, diff referenced URLs against the issue's currently-linked editor-sourced rows: newly-referenced rows link (source: 'editor', + the revision attachment-added entry — uniform History, the fill of Jira's documented editor-add changelog gap); no-longer-referenced editor-sourced rows unlink (revision attachment-removed entry; the row goes GC-eligible — the Jira analogue: deleting the comment removes the attachment from the work item). Panel-sourced rows are NEVER touched by body diffs.
  • commentsService.addComment / editComment / deleteComment (5.1.2) — comment bodies get the same parse: add/edit links newly-referenced rows to the COMMENT's work item; a comment delete (incl. thread cascade) unlinks the rows its bodies referenced (unless another body on the same issue still references the URL — the diff is per-issue across the bodies that reference it; keep the check bounded: match against the issue's linked editor rows only).

Idempotent + bounded: re-saving an unchanged body is a no-op; the parse is per-write over ONE body + one bounded findManyByBlobUrls lookup — never a scan of the attachment table.

Acceptance criteria

  • Creating an issue whose description embeds an upload links that row (source: 'editor', workItemId set) in the create transaction; cancelling the modal leaves the row unlinked (GC-eligible, 5.2.7).
  • Editing a body links newly-referenced rows and unlinks de-referenced editor-sourced rows, each with the matching revision entry; panel-sourced rows are untouched; re-saving unchanged is a no-op.
  • Comment add/edit/delete (5.1.2) round-trips the same linkage incl. the still-referenced-elsewhere guard; a foreign/random URL in a body never links or unlinks anything (workspace + pathname-prefix scoped).
  • All linkage happens inside the owning service's existing transaction (one method = one tx); pnpm test:coverage ≥90% on the helper + touched paths.

Context refs

  • lib/blob/uploader.ts (2.3.7 — the pathname structure attachments/<workspaceId>/ the matcher keys on) + lib/blob/uploadClient.ts (what the editor inserts)
  • workItemsService create/update (the description write paths); commentsService (5.1.2) — the comment write paths
  • lib/mentions/parse.ts (5.1.2) — the sibling parse-on-write pattern to mirror
  • The Jira coupling contract (embeds are attachments; comment delete removes them) in the Story 5.2 description