feat(mediaops): count library links in body prose and name referencing places #311

Merged
dominik.polakovics merged 1 commit from afk/310 into main 2026-10-16 18:34:15 +02:00

Closes #310

What changes

A library reference in body prose (a markdown link [x](/media/docs/a.pdf) or an inline <a href="/media/docs/a.pdf">, in a text body, a rich-text param or a front-matter string) now counts everywhere the media operations care.

  • Rename/move (file and folder) rewrite prose links in the same operation. The rewrite is linkrefs.RewritePage called with media pairs; there is no second rewriter in mediaops.
    • linkrefs.Pair gains an Exact form for single files, so /media/docs/a.pdf never claims a.pdf.bak. The trailing-slash prefix contract for page links is unchanged.
    • Each rewritten page goes back through the same detection. If a link survives, for example inside a nested front-matter value, the operation refuses with 422 media.pageRefUnrewritable before anything moves.
  • Delete and folder delete refuse or keep the file while a prose reference exists, with the same 409 media.referenced.
  • Publish: PageRefs and ReferencingPages see prose links, so the file is carried with the page, and the pre-flight refuses with publish.mediaMissing when the file is missing.
  • One detection: mediaops.pageLibraryRefs reads link positions through the new linkrefs.DestinationsIn, which gives each destination with the shortcode holding it. The whole-value rule (StoredLibraryRef over params and front matter) stays in mediaops.
  • Referencing places are links.
    • The 409 body, the folder plan's keep[] and GET /api/media/refs now carry places: [{path, lang, uid?}] instead of pages.
    • uid is the innermost element or items row holding the reference, decided by elemops.WalkRoles. Front matter and page-level prose name only the page.
    • GET /api/media/refs answers a library ref with mediaops.References, the same answer the refusal gives. Bundle refs keep the best-effort warning.
    • The editor's delete dialogs (DeleteMediaConfirm, DeleteFileDialog, the folder-delete dialog, the details panel) render each place as a link to elementEditRoute, or to the page when there is no uid.

Docs

  • docs/api.md: the place shape, and the new shapes of refs, delete, folder delete-plan and folder delete. The rename/move routes now say prose links are rewritten. The new 422 code is listed.
  • docs/architecture.md "Media gate".
  • scaffold/site/AGENTS.md "Library media publishes with the pages that use it".
  • The edit-content skill.
  • The mediaops, linkrefs and media package docs.

Verification

  • go test ./..., go vet ./internal/... ./cmd/... and gofmt are clean.
  • In web/: npm test (3324 tests) and npm run build pass, and tsc --noEmit is clean.
  • New tests:
    • mediaops: the acceptance cases. Rename, move, folder rename and folder move rewrite markdown, href and rich-text-param links. A plain-words mention and a .bak neighbour stay untouched. Delete and the folder plan refuse or keep with places. Place attribution is covered. The honest refusal is covered.
    • linkrefs: exact pairs and DestinationsIn.
    • server: media_prose_test.go covers places on an en/de site, refs agreeing with the refusal, and rename. Two publish tests cover a prose-only file being carried and the refusal when it is missing.
    • web: place parsing, and the links in each dialog.

🤖 Generated with Claude Code

Closes #310 ## What changes A library reference in body prose (a markdown link `[x](/media/docs/a.pdf)` or an inline `<a href="/media/docs/a.pdf">`, in a text body, a rich-text param or a front-matter string) now counts everywhere the media operations care. - **Rename/move** (file and folder) rewrite prose links in the same operation. The rewrite is `linkrefs.RewritePage` called with media pairs; there is no second rewriter in mediaops. - `linkrefs.Pair` gains an `Exact` form for single files, so `/media/docs/a.pdf` never claims `a.pdf.bak`. The trailing-slash prefix contract for page links is unchanged. - Each rewritten page goes back through the same detection. If a link survives, for example inside a nested front-matter value, the operation refuses with `422 media.pageRefUnrewritable` before anything moves. - **Delete and folder delete** refuse or keep the file while a prose reference exists, with the same `409 media.referenced`. - **Publish:** `PageRefs` and `ReferencingPages` see prose links, so the file is carried with the page, and the pre-flight refuses with `publish.mediaMissing` when the file is missing. - **One detection:** `mediaops.pageLibraryRefs` reads link positions through the new `linkrefs.DestinationsIn`, which gives each destination with the shortcode holding it. The whole-value rule (`StoredLibraryRef` over params and front matter) stays in mediaops. - **Referencing places are links.** - The 409 body, the folder plan's `keep[]` and `GET /api/media/refs` now carry `places: [{path, lang, uid?}]` instead of `pages`. - `uid` is the innermost element or items row holding the reference, decided by `elemops.WalkRoles`. Front matter and page-level prose name only the page. - `GET /api/media/refs` answers a library ref with `mediaops.References`, the same answer the refusal gives. Bundle refs keep the best-effort warning. - The editor's delete dialogs (`DeleteMediaConfirm`, `DeleteFileDialog`, the folder-delete dialog, the details panel) render each place as a link to `elementEditRoute`, or to the page when there is no uid. ## Docs - `docs/api.md`: the place shape, and the new shapes of refs, delete, folder delete-plan and folder delete. The rename/move routes now say prose links are rewritten. The new 422 code is listed. - `docs/architecture.md` "Media gate". - `scaffold/site/AGENTS.md` "Library media publishes with the pages that use it". - The edit-content skill. - The mediaops, linkrefs and media package docs. ## Verification - `go test ./...`, `go vet ./internal/... ./cmd/...` and `gofmt` are clean. - In `web/`: `npm test` (3324 tests) and `npm run build` pass, and `tsc --noEmit` is clean. - New tests: - mediaops: the acceptance cases. Rename, move, folder rename and folder move rewrite markdown, href and rich-text-param links. A plain-words mention and a `.bak` neighbour stay untouched. Delete and the folder plan refuse or keep with places. Place attribution is covered. The honest refusal is covered. - linkrefs: exact pairs and `DestinationsIn`. - server: `media_prose_test.go` covers places on an en/de site, refs agreeing with the refusal, and rename. Two publish tests cover a prose-only file being carried and the refusal when it is missing. - web: place parsing, and the links in each dialog. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(mediaops): count library links in body prose and name referencing places
All checks were successful
ci / build-and-test (pull_request) Successful in 4m37s
ci / release (pull_request) Has been skipped
cca6e22ae9
A markdown link or inline anchor to /media/<folder>/<name> in a text body,
a rich-text param or a front-matter string is now a library reference
everywhere the media operations care:

- rename/move of a file or folder rewrites it in the same operation,
  through linkrefs.RewritePage with media pairs (a new exact-file
  linkrefs.Pair form; the trailing-slash prefix contract for page links
  is unchanged), and refuses honestly (media.pageRefUnrewritable) when a
  link survives where the editor cannot rewrite it;
- delete and the folder-delete plan refuse/keep while one exists;
- publish (PageRefs, ReferencingPages) carries the file and its
  pre-flight refuses when it is missing.

One per-page detection in mediaops reads link positions through the new
linkrefs.DestinationsIn and keeps the whole-value notion its own. The
delete 409 body, the folder plan's keep entries and GET /api/media/refs
now carry places ({path, lang, uid?}); /api/media/refs answers a library
ref with the engine's verdict, so the dialog's pre-check list is exactly
what blocks the delete. The editor's delete dialogs render each place as
a link to the element's edit page, or to the page.

Docs: api.md, architecture.md "Media gate", AGENTS.md and the
edit-content skill name prose links among the reference shapes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

PASS — relied on CI aggregate success (ci / build-and-test, run 318: web npm ci/build/test, go vet ./..., go test ./... incl. docs/scaffold/element/route drift tests). Conventions: Conventional Commits title, Closes #310 present, single commit on main's tip (no conflict). Scope matches #310: one detection (mediaops.pageLibraryRefs over linkrefs.DestinationsIn), rewrites via linkrefs.RewritePage with new Pair.Exact (prefix contract unchanged for pages), honest residual refusal (media.pageRefUnrewritable), places {path, lang, uid?} in 409/plan/refs with refs answered by mediaops.References, delete dialogs link via elementEditRoute; api.md, architecture.md, AGENTS.md, edit-content skill and package docs updated; acceptance cases tested in mediaops, linkrefs, server and web.

[autoland] verdict: pass PASS — relied on CI aggregate `success` (ci / build-and-test, run 318: web npm ci/build/test, go vet ./..., go test ./... incl. docs/scaffold/element/route drift tests). Conventions: Conventional Commits title, `Closes #310` present, single commit on main's tip (no conflict). Scope matches #310: one detection (mediaops.pageLibraryRefs over linkrefs.DestinationsIn), rewrites via linkrefs.RewritePage with new Pair.Exact (prefix contract unchanged for pages), honest residual refusal (media.pageRefUnrewritable), places {path, lang, uid?} in 409/plan/refs with refs answered by mediaops.References, delete dialogs link via elementEditRoute; api.md, architecture.md, AGENTS.md, edit-content skill and package docs updated; acceptance cases tested in mediaops, linkrefs, server and web.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!311
No description provided.