Skip to content

feat: collection permalinks, step 3 — Studio settings, MCP tool, approver scopes - #74

Merged
Madraka merged 2 commits into
mainfrom
feat/collection-permalink-ui
Oct 22, 2026
Merged

Madraka merged 2 commits into
mainfrom
feat/collection-permalink-ui

Conversation

@Madraka

@Madraka Madraka commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

RFC-0038 / ADR-0040, step 3 of 3. Closes #50.

Studio

  • A Collections section in Site settings (CollectionSettings.tsx, logic in lib/collection-permalinks.ts), gated on settings.write.
  • Each row is a collection: the keys of site.collections plus the collections its entries use. The InputGroup holds the prefix before /{slug}, and the preview shows a real entry's address (and its old address once it changes, with a note that it redirects).
  • Saving goes through PUT /api/sites/:id. Empty, or the collection's own name, means the default.
  • Validated in the browser like the schema; a path_conflict / reserved_path 409 shows on that row.
  • ConflictBanner moved to its own component (SiteSettingsPage 881 → 859 lines).
  • English strings, plus drafts for the other 18 languages through .i18n + i18n:import. These are not marked reviewed.

MCP: set_collection_permalink { siteId, collection, permalink | null, dryRun? }

  • Scope: the agent needs site:write.
  • Approval: requiresApproval + classify: person, so it waits for a person in every mode, bypass included. dryRun: true is classified read and returns the plan only.
  • Precheck before anyone is asked: the pattern, a no-op, and the store's checks via the new SiteGraphStore.updateSite(site, { dryRun: true }).
  • The prompt lists the old and new template, the entry count, up to 3 entries with their old and new URLs, the redirect added and existing redirects repointed or dropped, and the locales.
  • get_site documents collections; the server instructions and the README list the tool.

Who may approve (added in review)

New optional ToolPolicy.approverScopes (@cloudhal/agent-runtime), checked in the API's canDecide. This tool sets ["site:settings"]: the person approving must be able to change site settings, as in Studio, so an Editor cannot approve an agent's URL move. Person-only scopes can't be a tool's own scopes (they never satisfy a tool), which is why this is a separate field.

Other changes

  • Menus: a collection's menu link goes to a published page at its permalink prefix first, then to the page that lists it.
  • Starter: the README mentions collection permalinks.

Checks (one package at a time)

Package Tests
api 379/379, incl. the new shared testing-permalinks.ts on sqlite + D1, and approver-scopes.test.ts
mcp-server 145/145 (e2e tool count 101 → 102)
Studio 1144/1144 (design rules, i18n)
core-ts 654/654
astro-renderer 257/257
agent-runtime 72/72

Also passing: typecheck, size ratchet (baseline tightened), design lint (340 files), i18n import checks.

Studio was checked through the API on a local server: /blog/{slug} gave rev 6 plus the 301, and /about/{slug} gave 409. It was not checked visually, because the local Studio needs a signed-in account.

Open questions (follow-ups)

  1. A moved collection cannot have a page at its prefix (step 2), so its menu link falls back to the listing page. Wait for indexPath, or allow a page at the prefix itself?
  2. The row error shows the catalog's generic conflict text, not the server's message naming the page or archive.

🤖 Generated with Claude Code

Madraka and others added 2 commits October 22, 2026 11:26
…over scopes

RFC-0038 / ADR-0040, step 3:

- Studio: a Collections section in Site settings (CollectionSettings.tsx,
  lib/collection-permalinks.ts): one row per collection with the prefix
  before /{slug}, a real entry's address, client-side validation, and
  path conflicts shown on the row. ConflictBanner moved out of
  SiteSettingsPage. English strings plus drafts for the other 18 languages.
- MCP `set_collection_permalink` (site:write): always waits for a person
  (classify person), dryRun returns the plan, precheck runs the store's
  checks without writing (`updateSite(site, { dryRun: true })`), and the
  prompt lists old/new URLs and the redirects.
- `ToolPolicy.approverScopes`: the approver of a URL move must hold
  site:settings (as in Studio), so an Editor cannot approve it; checked in
  `canDecide`.
- API suite on sqlite and D1: /blog/{slug} writes the redirect; overlaps
  are 409.
- Menus link a collection to a published page at its prefix first.

Closes #50

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
@Madraka
Madraka merged commit a31d78c into main Oct 22, 2026
5 checks passed
@Madraka
Madraka deleted the feat/collection-permalink-ui branch October 22, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-collection URL base path

1 participant