docs: consolidate design change logs and refresh stale docs

This commit is contained in:
Florian Sylvain committed 2026-10-30 20:40:24 +01:00
1 parent c050db78d2
commit c92d8740ed
40 files changed
+185 -604

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ Feature: Authors backend, public bylines that articles credit in order
So that readers see who wrote an article, whether or not the writer has an account
Background:
Given the spec of record is docs/design/screens/authors.md, docs/design/screens/editor.md (site and authors), docs/design/screens/profile.md, docs/design/states.md (author rows), docs/design/routes.md and docs/design/sites-authors-slices.md
Given the spec of record is docs/design/screens/authors.md, docs/design/screens/editor.md (site and authors), docs/design/screens/profile.md, docs/design/states.md (author rows), docs/design/routes.md and docs/design/decisions.md
And this slice is backend only: no SPA, no change to docs/design
And an Author has id (uint32), name, slug, bio, avatar_id (nullable media id), website (nullable), social_links, user_id (nullable and unique), is_linked and article_count, in snake_case JSON
And an article has zero or more authors in a stored order through an ordered article_authors join; the first is the primary byline
+1 -1
View File
@@ -6,7 +6,7 @@ Feature: Editor: search and sharing, suggestions and "What the frontend gets"
Background:
Given the screen is EditorView (/article/:id/edit) inside DashboardShell, per docs/design/routes.md
And the visual spec of record is docs/design/screens/editor.md (groups "Editor: search and sharing" with 24 states and "Editor: what the frontend gets" with 6 states), docs/design/seo-slices.md, docs/design/components.md (SearchSharingSection, SeoPanel, SoftCounter, SearchPreview, SocialCardPreview, SuggestionList, ApiPreviewDialog, SlugField, MediaField) and docs/design/states.md ("SEO round"). Exact copy is read from those files and is not repeated here unless a scenario needs it
And the visual spec of record is docs/design/screens/editor.md (groups "Editor: search and sharing" with 24 states and "Editor: what the frontend gets" with 6 states), docs/design/decisions.md, docs/design/components.md (SearchSharingSection, SeoPanel, SoftCounter, SearchPreview, SocialCardPreview, SuggestionList, ApiPreviewDialog, SlugField, MediaField) and docs/design/states.md ("SEO round"). Exact copy is read from those files and is not repeated here unless a scenario needs it
And the board frames of docs/design/source/RenewCMS Screens Back-office.dc.html, AppFrame.dc.html and DirectoryFrame.dc.html are the layout tiebreaker
And this slice is SPA only and reuses the API of features/seo_data_backend.feature and features/seo_output_backend.feature without change
And the SPA calls, with relative URLs through useApi only: GET /v1/article/{id} and PATCH /v1/article/{id} (SEO fields slug, excerpt, seo_title, seo_description, cover_image_id, cover_image_alt, social_title, social_description, social_image_id, canonical_url, noindex), POST /v1/article/{id}/seo/check, GET /v1/article/{id}/seo, GET /v1/site/ and the media routes of the shared picker
+1 -1
View File
@@ -7,7 +7,7 @@ Feature: Media field usage in the Media library
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
And API errors are plain text
And this slice changes only web/ (and web/visual); the backend of features/media_field_type.feature is done and not touched
And the design is docs/design/media-field-changes.md section 3.8 and section 4, with the board group "11 Media library: usage" (4 states) of "RenewCMS Screens Schema.dc.html", which draws MediaFrame.dc.html with entries counted as uses
And the design is docs/design/media-field.md section 3.8 and section 4, with the board group "11 Media library: usage" (4 states) of "RenewCMS Screens Schema.dc.html", which draws MediaFrame.dc.html with entries counted as uses
And GET /v1/media/ items carry "used_in", which counts articles, authors, sites and entries together, and no split
And GET /v1/media/{id}/usage answers an array ordered articles, authors, sites, then entries by content type name then entry id, with no total and no "first few" wrapper
And an article item is {"type":"article","id","title","published","fields"}
+2 -2
View File
@@ -7,7 +7,7 @@ Feature: Media field type in the SPA
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
And API errors are plain text
And this slice changes only web/ (and web/visual); the backend of features/media_field_type.feature is done
And the design is docs/design/media-field-changes.md sections 1, 2, 3.2 to 3.7, 4, 6 and 7, with the board groups 2 Schema builder, 3 Review and delete, 4 Field types, 5 Content and entry list, 6 Entry editor and 10 Media picker for fields of "RenewCMS Screens Schema.dc.html"
And the design is docs/design/media-field.md sections 1, 2, 3.2 to 3.7, 4 and 6, with the board groups 2 Schema builder, 3 Review and delete, 4 Field types, 5 Content and entry list, 6 Entry editor and 10 Media picker for fields of "RenewCMS Screens Schema.dc.html"
And the registry is lib/fieldTypes with lib/entryEditors, the single source for the builder, the list cells and the entry form; no screen branches on the type name "media" where a registry property can say it
And a media value is the id of a library image (a uint32 of at least 1) or null
And every entry answer (POST 201, PUT 200, GET one, each list item) carries a top-level "media" object keyed by media field key: {"id","filename","width","height","size","thumbnail_url"} for an existing image, or {"id","missing":true} when the image is gone; a null value has no key
@@ -476,7 +476,7 @@ Feature: Media field type in the SPA
Given the card or a title needs the text of a media cell
Then it reads "–" for none and never the numeric id
# ---------- Accessibility (media-field-changes.md section 6)
# ---------- Accessibility (media-field.md section 6)
Scenario: Accessibility of the control
Then the control is role="group" labelled by the field label
+1 -1
View File
@@ -27,7 +27,7 @@ Helper: decode into {items []map, total, page, size int, counts {all, unused, du
- `docs/api.md` lines 99-103: replace `cursor` and `limit` with `page` (default 1) and `size` (5, 25, 50, 100; default 25); state the envelope (`items`, `total`, `page`, `size`, `counts`), that `counts` stays library-wide, that invalid values answer 400 "The media query is invalid.", and a "Breaking change" note: `next_cursor` is gone, `cursor` and `limit` are ignored.
- `docs/api/*.md`: grep for `/v1/media/` and `cursor`; update the media listing section if one exists.
- `web/src/data/apiCatalogue.ts` `med-list`: add `page`, `size`, `q`, `filter`, `sort`, `variants`, `hash` query entries, the 400 error, and a paged example (`total`, `page`, `size`, no `next_cursor`). Remove the `next_cursor` rule (line ~129, with its French entry in `web/src/i18n/messages.ts` line ~1606) if it is only used by media. Integration specs asserting the catalogue are amended.
- Flag for the user (read-only to agents): `docs/design/screens/integration.md` and `docs/design/source/IntegrationPage.dc.html` (med-list with no params), `docs/design/screens/media.md`, `routes.md`, `change-list.md` item 13b, `README.md` line 92 and `screens/media-picker.md`: all still say "array body" and "X-Total-Count".
- Flag for the user (read-only to agents): `docs/design/screens/integration.md` and `docs/design/source/IntegrationPage.dc.html` (med-list with no params), `docs/design/screens/media.md`, `routes.md`, `README.md` line 92 and `screens/media-picker.md`: all still say "array body" and "X-Total-Count".
- A release note under `docs/releases/` is the maintainer's call: flag it.
## Manual verification
@@ -5,7 +5,7 @@ Feature: Relation picker "loading more" state
So that pressing "Load more" never loses focus or shows a blank list
Background:
Given the spec of record is docs/design/screens/schema-later.md group 7 states 2 and 3, and docs/design/change-log-roles-sync.md "Relation picker"
Given the spec of record is docs/design/screens/schema-later.md group 7 states 2 and 3
And the picker is RelationField.vue over useRelationSearch.ts, which already requests GET /v1/content-type/{key}/entry?page=N&size=25 and has loading, loadingMore, hasMore and total
And the sample is 25 venues loaded of 62 for "rue", footer "Showing 25 of 62" with "Load more"
And the design marks the group "Later" but the picker exists in the app; this file specifies the missing state only
+1 -1
View File
@@ -13,7 +13,7 @@ Do not copy the old `Role ==` checks into new code.
## Design notes for the user
- docs/design/screens/roles.md "Owner-only items" disables View roles and Manage roles for an admin. This slice follows the user's decision (Admin has 29 keys). The design needs a fix by the user.
- docs/design/change-list.md item 21 ("Sites: owner only, hidden for other roles") and its SEO "Permissions" paragraph ("only the owner edits site SEO defaults", 403 `owner_only`), plus the `owner_only` clause in routes.md "SEO", disagree with this slice. Admin has `settings.manage_sites`, so an admin can now edit sites and their SEO defaults.
- the removed change-list.md item 21 ("Sites: owner only, hidden for other roles") and its SEO "Permissions" paragraph ("only the owner edits site SEO defaults", 403 `owner_only`), plus the `owner_only` clause in routes.md "SEO", disagree with this slice. Admin has `settings.manage_sites`, so an admin can now edit sites and their SEO defaults.
- roles.md is inconsistent about Writer and Schema. The "Writer: Articles list" state hides Schema from the sidebar, while the catalogue, the built-in table and change-list ("Schema read-only for view-only roles") give Writer `schema.view`.
- change-list line 100 mentions a "restricted `user` role" that does not exist.
- The docs name `GET /v1/me`. The implemented route is `GET /v1/auth/me` (resolved in slice 1).
+1 -1
View File
@@ -5,7 +5,7 @@ Feature: Roles sync pass 6: final copy, neutral wording and role activity events
So that every message names what happened, says what was kept and says what to do next
Background:
Given the spec of record is docs/design/screens/roles.md groups 8, 12 and 13, the tables "Activity events: sentences", "Activity events: failed sentences", "Activity filter: category names" and "Final copy: toasts and errors, EN and FR", docs/design/states.md "Roles sync (pass 6)", docs/design/screens/activity.md "Roles sync (pass 6)" and docs/design/change-log-roles-sync.md section 1
Given the spec of record is docs/design/screens/roles.md groups 8, 12 and 13, the tables "Activity events: sentences", "Activity events: failed sentences", "Activity filter: category names" and "Final copy: toasts and errors, EN and FR", docs/design/states.md "Roles sync (pass 6)", docs/design/screens/activity.md "Roles sync (pass 6)"
And the stale-state toasts, banners and dialog alerts of group 15 are in features/roles_sync_conflicts.feature, not here
And the removal of a user (route, dialog, toast, user.remove event) is in features/roles_sync_remove_user.feature
And the audit actions are role.create, role.edit, role.delete, role.reassign, user.change_role and user.transfer_ownership (internal/domain/audit/auditDomain.go), the outcome is a field and a failed event keeps its name
@@ -5,7 +5,7 @@ Feature: Integration page: permission-gated routes and role-aware copy
So that a key's owner knows which permission a route needs
Background:
Given the spec of record is docs/design/screens/integration.md (Page copy, EndpointCard states, Error messages) and docs/design/change-log-roles-sync.md "Integration page"
Given the spec of record is docs/design/screens/integration.md (Page copy, EndpointCard states, Error messages)
And the catalogue is web/src/data/apiCatalogue.ts and its search index; routes are gated in api/router.go by container.require(...) with the permission keys of internal/domain/role
And the real gating is:
| route | permission label (lib/permissionNames.ts) |
@@ -5,7 +5,7 @@ Feature: Profile role card of the roles-sync design (group 9)
So that I can understand my access without reading who assigned it
Background:
Given the spec of record is docs/design/screens/roles.md group 9 and docs/design/change-log-roles-sync.md section 2
Given the spec of record is docs/design/screens/roles.md group 9
And features/roles_spa_users.feature "Profile, your role" is amended by this file: the summary line and "Assigned by" are removed
And GET /v1/auth/me answers access.role {id, name, built_in, locked} and access.permissions; the design says GET /v1/me, the real route is /v1/auth/me
And the card layout, top to bottom, inside the existing card with a gap of 12: the title, one row of badges, the link row with the list-checks icon, the muted key line
+1 -1
View File
@@ -6,7 +6,7 @@ Feature: Read = Own surfaces of the roles-sync design (group 14)
Background:
Given the backend contract of features/roles_permissions_content_scope.feature: a caller with content.read=own gets online articles plus their own drafts, and only the entries they created, with counts, total, page and size computed over the visible set
And the spec of record is docs/design/screens/roles.md group 14, its table "Read = Own: what each surface returns", and docs/design/change-log-roles-sync.md section 4
And the spec of record is docs/design/screens/roles.md group 14, its table "Read = Own: what each surface returns"
And the SPA never computes visibility: every count, total and pager value shown comes from the API (GET /v1/article counts and total, GET /v1/content-type/{key}/entry total)
And the SPA knows a person reads Own through scopeOf("content.read", type) of stores/permissions.ts
And Update or Delete set to Own keep the "View only" rows of slice 5; only Read = Own hides rows
+1 -1
View File
@@ -7,7 +7,7 @@ Feature: SEO data backend, article and site search fields, unique slugs and medi
So that a frontend can later resolve titles, descriptions, images and addresses from RenewCMS
Background:
Given the spec of record is docs/design/seo-slices.md (slice 1), docs/design/change-list.md ("SEO round"), docs/design/screens/editor.md, docs/design/screens/sites.md and docs/design/states.md
Given the spec of record is docs/design/decisions.md (Search and sharing), docs/design/screens/editor.md, docs/design/screens/sites.md and docs/design/states.md
And this slice is backend only: no SPA, no change to docs/design, and no resolver, check endpoint, sitemap or list seo filter
And an article gains these snake_case fields: slug, excerpt, seo_title, seo_description, cover_image_id, cover_image_alt, social_title, social_description, social_image_id, canonical_url, noindex, seo_dismissed
And in article JSON the text fields are "" when empty, cover_image_id and social_image_id are null when unset, noindex is false by default and seo_dismissed is [] by default
+1 -1
View File
@@ -12,7 +12,7 @@ Spec: `features/seo_data_backend.feature`. Go tests only, in `test/` (package `t
- **Site image and robots names:** the API uses `default_social_image_id` and `default_robots` (existing site contract). The design writes `social_image_id` for the site and, in places, `robots`. The SPA uses the code names.
- **Non-owner site writes:** the API answers 403 with the existing text "Only the owner can do this." (`apperr.MissingPermissionErrorMessage`). The design mentions a 403 `owner_only` token, which does not exist.
- **Empty-slug fallback:** one rule everywhere. A title with no letter or digit becomes `article`, then `article-2`, `article-3` on conflict, in the API and in the migration. `docs/design/seo-slices.md` (decision 5) says `article-<id>` for the migration; that wording is not followed.
- **Empty-slug fallback:** one rule everywhere. A title with no letter or digit becomes `article`, then `article-2`, `article-3` on conflict, in the API and in the migration. `docs/design/decisions.md` (decision 5) says `article-<id>` for the migration; that wording is not followed.
- **Slug conflict transport:** the conflict follows the authors slug conflict (plain text `slug_taken`, 409); the suggestion travels in the `X-Slug-Suggestion` response header, not in a JSON body. The `slug.suggestion` of the slice 2 check endpoint is separate.
- **Empty slug:** `slug: ""` regenerates the slug from the title (the SPA "Reset to match the title" sends `""`); 409 only if the regenerated slug belongs to another article on the site.
- **Status codes:** `invalid_url` is 422 (authors); `invalid_slug`, `invalid_title_template` and `image_not_found` are 400.
+1 -1
View File
@@ -7,7 +7,7 @@ Feature: SEO output backend, resolved SEO object, non-persisting check, list sta
So that a frontend can render meta tags and a sitemap without reimplementing the fallback chain
Background:
Given the spec of record is docs/design/seo-slices.md (slice 2), docs/design/change-list.md ("SEO round"), docs/design/screens/editor.md, docs/design/screens/articles.md and docs/design/screens/integration.md
Given the spec of record is docs/design/decisions.md (Search and sharing), docs/design/screens/editor.md, docs/design/screens/articles.md and docs/design/screens/integration.md
And this slice is backend only: no SPA screen and no change to docs/design
And it reuses the article and site SEO fields of features/seo_data_backend.feature and adds no column and no migration
And RenewCMS renders no page and injects no meta tag: it only returns data
@@ -8,7 +8,7 @@ Feature: SEO round, slice 4: Sites defaults, Articles search status, Integration
Background:
Given the screens are SitesView (/settings/sites), ArticleView (/article, also reached as /articles), IntegrationView (/integration) and the Editor SEO panel (/article/:id/edit), inside DashboardShell, per docs/design/routes.md
And the visual spec of record is docs/design/screens/sites.md ("Sites: SEO defaults", 5 states), articles.md ("Articles: search status", 3 states), integration.md ("Search and sharing"), editor.md (SuggestionList), docs/design/components.md (SeoStatusChip, SuggestionList), docs/design/seo-slices.md and docs/design/states.md ("SEO round"). Exact copy is read from those files and is not repeated here unless a scenario needs it
And the visual spec of record is docs/design/screens/sites.md ("Sites: SEO defaults", 5 states), articles.md ("Articles: search status", 3 states), integration.md ("Search and sharing"), editor.md (SuggestionList), docs/design/components.md (SeoStatusChip, SuggestionList), docs/design/decisions.md and docs/design/states.md ("SEO round"). Exact copy is read from those files and is not repeated here unless a scenario needs it
And the board frames of docs/design/source/DirectoryFrame.dc.html (Sites form, SuggestionList), AppFrame.dc.html (Articles rows, cards and toolbar) and IntegrationPage.dc.html (group and index) are the layout tiebreaker
And this slice is SPA only and uses the API of features/seo_data_backend.feature and features/seo_output_backend.feature, plus counts.suggestions added by features/articles_pagination_api.feature. No other Go file changes
And the SPA calls, with relative URLs through useApi only: GET /v1/site/, POST /v1/site/ and PATCH /v1/site/{id} (site_name, title_template, default_description), GET /v1/article/ with the optional seo=suggestions parameter, whose response also carries counts.suggestions, and PATCH /v1/article/{id} with seo_dismissed. The Integration page makes no request
+1 -1
View File
@@ -6,7 +6,7 @@ Feature: Sites backend, the owner manages sites and every article belongs to exa
So that a single back-office can hold the content of more than one website
Background:
Given the spec of record is docs/design/screens/sites.md, docs/design/states.md (site rows), docs/design/routes.md and docs/design/sites-authors-slices.md
Given the spec of record is docs/design/screens/sites.md, docs/design/states.md (site rows), docs/design/routes.md and docs/design/decisions.md
And this slice is backend only: no SPA, no change to docs/design
And a Site has id (uint32), name, key, base_url, default_social_image_id (nullable), default_robots ("index" or "noindex"), locale (nullable), is_default and article_count, in snake_case JSON
And exactly one site is the default at any time, enforced by the use case inside a transaction and not by a partial index