feat: sites and authors integration into existing screens (& formated all frontend files kinda accidentally)

This commit is contained in:
Florian Sylvain committed 2026-10-15 05:25:44 +02:00
1 parent 3d81d2c717
commit 6b8990c55e
239 files changed
+9194 -1354

No files matched your search

+8
View File
@@ -218,6 +218,7 @@ Feature: Authors backend, public bylines that articles credit in order
Then the status is 201
And the author name is "<name>" and the slug is "<slug>"
And the author has user_id of the caller and is_linked true
And slug_adjusted is false
Examples:
| display | username | name | slug |
@@ -239,6 +240,7 @@ Feature: Authors backend, public bylines that articles credit in order
When "Marta Alves" sends POST /v1/author/me
Then the status is 201
And the slug is "marta-alves-3"
And slug_adjusted is true
And no error is returned
Scenario: The suffix still fits in the slug limit
@@ -246,6 +248,12 @@ Feature: Authors backend, public bylines that articles credit in order
When the caller sends POST /v1/author/me
Then the status is 201
And the slug is at most 100 characters and ends with "-2"
And slug_adjusted is true
Scenario: Only the own-author creation carries slug_adjusted
When the owner sends POST /v1/author/ with {"name": "Camille Roche"}
Then the 201 body has no slug_adjusted field
And GET /v1/author/ items and GET /v1/auth/me author have no slug_adjusted field
Scenario: A caller who already has an author is refused
Given the caller is linked to an author
+528
View File
@@ -0,0 +1,528 @@
Feature: Sites and authors in the Editor, Articles and Profile screens
As a RenewCMS user who can edit articles
I want to choose an article's site and bylines in the editor, see them in the articles list, and create my own author profile
So that every article belongs to the right frontend and credits the right people
Background:
Given the screens are EditorView (/article/:id/edit), ArticlesView (/article) and ProfileView (/profile),
inside DashboardShell, per docs/design/routes.md
And the visual spec of record is docs/design/screens/editor.md (group "Editor: site and authors"),
articles.md (group "Articles: sites and authors") and profile.md (group "Profile: author profile"),
with the error copy in docs/design/states.md. Exact copy is read from those files and is not repeated here
And this feature supersedes the lines of features/sites_authors_screens.feature that said
POST /v1/author/me is not exposed in the UI and that Editor, Articles and Profile are not modified
And POST /v1/author/me answers 201 with the author and slug_adjusted, true when the stored slug differs from the plain slug of the name
And the SPA calls, with relative URLs through useApi only:
GET /v1/site/, GET /v1/author/ (page, size in 5, 25, 50 or 100, and q), POST /v1/author/, POST /v1/author/me,
GET /v1/auth/me, GET /v1/article/{id}, PATCH /v1/article/{id} with site_id and author_ids,
and GET /v1/article/?site={key}
And an article carries site_id and authors [{id, name, slug, ...}] in list order, and the first author is the primary byline
And GET /v1/auth/me carries "author" (the linked author, or null) and the SPA keeps it in the session store
And the sites fixture is Renew (id 1, renew.example, default), Journal (id 2, journal.renew.example), Projects (id 3) and Archive (id 4)
And the authors fixture is Inès Duarte (id 2), Jonas Pereira, Camille Roche and the Marta Alves of the design
And an author is drawn as AuthorAvatar (initials fallback, square), several as AuthorStack
And the domain defines only the roles "owner" and "admin" (internal/domain/user, RoleOwner and RoleAdmin)
And "author rights" means owner and admin: they may create authors and edit any author
And "verified" below names a verified account without the owner or admin role, which has no author rights
And "several sites" means GET /v1/site/ returned more than one site
And every visible string goes through t(...) with the English text as the key and a French entry in web/src/i18n/messages.ts
And a scenario tagged @not-in-design uses copy or behaviour that docs/design does not define: it takes the
proposal of this feature, has a French entry, and is listed in the .qa.md to be fed back to the design
And the screens use Nuxt UI components and design tokens only, with no raw colors
And the board frames of docs/design/source/AppFrame.dc.html and DirectoryFrame.dc.html are the layout tiebreaker
Rule: Editor: the "Site and authors" section (MetaSection)
Scenario: Collapsed by default with one site and an author
Given the install has one site and article 12 has the author "Inès Duarte"
And nothing is remembered for this browser
When the user opens /article/12/edit on desktop
Then the details sidebar shows a collapsed "Site and authors" section with the one-line summary "Inès Duarte"
And no Site select is rendered
And the section sits in the same place in every editor state that has the details sidebar
Scenario: Loading and not found
When GET /v1/article/12 is pending
Then the section header is shown with a skeleton summary
When GET /v1/article/12 answers 404
Then the "Not found" state shows no section
Scenario Outline: The section opens by itself when it needs attention
Given nothing is remembered for this browser
And the install has <sites> and the article has <authors>
When the user opens the editor on desktop
Then the section is <state>
Examples:
| sites | authors | state |
| one site | an author | collapsed |
| several sites | an author | open |
| one site | no authors | open |
| several sites | no authors | open |
Scenario: The choice is remembered per browser and wins over auto-open
Given the section is collapsed by default
When the user opens it and later opens another article in the same browser
Then the section is open there too
When the user collapses it and reloads
Then it is collapsed even if the install has several sites or the article has no authors
And the choice is kept in localStorage and nothing is sent to the API
Scenario: Mobile: inside the Details sheet, closed by default
Given the viewport is 390 px wide
When the user opens the editor
Then the section is not visible until the Details sheet is opened
And inside the sheet it is closed unless a choice is remembered
When the user opens it, closes the sheet and opens it again
Then the section keeps the choice made in the sheet
And the sheet and the desktop sidebar share the same storage key
@not-in-design
Scenario Outline: Collapsed summary line variants
Given the section is collapsed
And the install has <sites> and the article has <authors>
Then the summary line reads <summary>
Examples:
| sites | authors | summary |
| one site | Inès Duarte | "Inès Duarte" |
| one site | Inès Duarte, Jonas Pereira, Camille Roche | "Inès Duarte +2" |
| one site | no authors | "No authors" |
| several sites | Inès Duarte on Journal | "Journal · Inès Duarte" |
| several sites | no authors on Journal | "Journal · No authors" |
Rule: Site select
Scenario: One site: no Site select
Given the install has one site
When the section is open
Then there is no Site select and the "Authors" list is the first control
Scenario: Several sites: Site select above the authors
Given the install has several sites and article 12 is on "Journal"
When the section is open
Then a "Site" select above the authors shows "Journal" with "journal.renew.example"
And the helper text reads "Canonical host: journal.renew.example"
And the select is available to every role that can edit the article
Scenario: Site select options
When the user opens the Site select
Then each option shows the site name and its host, and the default site is first
Scenario: Changing the site is an edit
Given the editor shows "Saved" and the article is on "Renew"
When the user chooses "Journal"
Then the status becomes "Unsaved changes" and Save is enabled
And no request is sent yet
When the user activates Save
Then exactly one PATCH /v1/article/12 is sent containing site_id 2
And on 200 the status is "Saved" and the helper text shows the new host
Rule: Author list
Scenario: Authors are an ordered list; the first is the primary byline
Given article 12 has four authors
When the section is open
Then the first row shows the label "Primary" (@not-in-design)
And each row has a drag handle and a remove button
And the hint "Drag to reorder. The first author is the primary byline." is shown
And a long name is truncated with an ellipsis and carries the full name in title and aria-label
Scenario: Reorder with the keyboard
Given an author list of three with "Jonas Pereira" second
When the user focuses the handle of "Jonas Pereira" and presses Space or Enter
Then a live region announces the grabbed-row message of the design (position 2 of 3 and the key instructions)
When the user presses ArrowUp
Then the row moves to position 1 and the new position is announced
When the user presses Space
Then the row is dropped there and the status becomes "Unsaved changes"
When the user grabs a row, moves it and presses Escape
Then the row returns to its original position and the status does not change
@not-in-design
Scenario: Dropped and cancelled announcements
Given a row was grabbed
When the user drops it with Space
Then the live region announces the dropped message with the final position
When the user cancels with Escape
Then the live region announces the cancelled message and the original position
Scenario: Reorder with the pointer
When the user drags a row
Then the row lifts and a line shows the drop position
When the user drops it
Then the list takes the new order and the status becomes "Unsaved changes"
Scenario: Remove an author
Given the list has "Inès Duarte" and "Jonas Pereira"
When the user activates the remove button of "Inès Duarte"
Then "Jonas Pereira" becomes "Primary" and the status becomes "Unsaved changes"
When the last author is removed
Then the list shows "No authors. This article will have no byline."
Scenario: Saving authors
Given the author list was reordered to [Jonas Pereira, Inès Duarte]
When the user activates Save
Then exactly one PATCH /v1/article/12 is sent whose author_ids follow the list order
And a list emptied by the user is sent as author_ids []
And fields that did not change are not sent
Rule: Add author
Scenario: The combobox
When the user activates "Add an author"
Then a combobox opens with a "Search authors" field and GET /v1/author/ is requested with size 100
And authors already on the article are checked and dimmed
When the user activates a checked author
Then nothing happens and the author is not added twice
When the user picks "Camille Roche"
Then she is appended to the list and the status becomes "Unsaved changes"
Scenario: Searching beyond the first 100 authors
Given the install has more than 100 authors
When the user types "Zoe" in "Search authors"
Then GET /v1/author/ is requested with q "Zoe" and the list shows the matches
Scenario Outline: "Create author…" only for roles with author rights
Given the session role is "<role>"
When the combobox is open
Then the "Create author…" row is <visible>
Examples:
| role | visible |
| owner | shown |
| admin | shown |
| verified | absent |
Scenario Outline: No match
Given the session role is "<role>"
When the user types "Zoe Lambert" and nobody matches
Then the message "No author matches "Zoe Lambert"." is shown
And <create>
Examples:
| role | create |
| owner | the row Create author "Zoe Lambert"… is shown |
| verified | no create row is shown |
Scenario Outline: Authors could not be loaded
When GET /v1/author/ fails with <failure> as the combobox opens
Then the combobox shows "Couldn't load authors", "Check your connection and try again." and "Try again"
And the article's authors in the list are untouched
When the user activates "Try again"
Then GET /v1/author/ is requested again
Examples:
| failure |
| status 500 |
| a network error |
Rule: Create author dialog (owner and admin)
Scenario: Open the dialog from the combobox
Given the session role is "owner"
When the user activates Create author "Zoe Lambert"…
Then the dialog "Create author" opens over the editor, with Name prefilled "Zoe Lambert", Slug "zoe-lambert" (Auto, same SlugField as Authors), "Cancel" and "Create author"
And the unsaved text of the article is untouched
Scenario: Create succeeds
Given the Create author dialog is filled with name "Zoe Lambert" and slug "zoe-lambert"
When the user activates "Create author"
Then exactly one POST /v1/author/ is sent with name and slug
And the dialog closes, "Zoe Lambert" is appended to the author list and the status becomes "Unsaved changes"
And the article's authors are not sent to the API until Save
Scenario: "This is me" only when the user has no author profile
Given the session role is "owner" and GET /v1/auth/me has author null
When the Create author dialog is open
Then the checkbox "This is me" shows "Links the author to marta@renew.example."
And it is unchecked until the user checks it
When it is checked and the user creates the author
Then POST /v1/author/ is sent with user_id of the current user
And the session store now holds that author, so the hint and the Profile card show the profile
Scenario: No "This is me" when the user already has an author
Given GET /v1/auth/me carries an author
When the Create author dialog is open
Then there is no "This is me" checkbox and no user_id is ever sent
Scenario: Slug taken
When POST /v1/author/ answers 409 author_slug_taken
Then the dialog shows "The slug "zoe-lambert" is already used by another author." and the action "Use zoe-lambert-2"
And activating the action sets the slug and keeps the name
Scenario Outline: Create author failed
When POST /v1/author/ fails with <failure>
Then the dialog stays open with "Couldn't create the author" and "The article is unchanged. Try again in a moment."
And the values are kept and the article is unchanged
Examples:
| failure |
| status 500 |
| a network error |
Scenario: Role changed while the dialog was open
When POST /v1/author/ answers 403
Then the error toast of states.md "Only an owner or admin can change authors" shows and the dialog closes
Rule: "Create my author profile" hint (every role)
Scenario Outline: The hint appears
Given the session role is "<role>", GET /v1/auth/me has author null, the article has no authors and the hint was never dismissed
When the section is open
Then the dismissible hint "You don't have an author profile yet" with "Create my author profile" is shown
And the list shows "No authors. This article will have no byline."
Examples:
| role |
| owner |
| admin |
| verified |
Scenario Outline: The hint stays away
Given <condition>
Then the hint is not shown
Examples:
| condition |
| the user already has an author profile |
| the article has an author |
| the hint was dismissed by this user |
Scenario: Create my author profile
Given the hint is shown
When the user activates "Create my author profile"
Then exactly one POST /v1/author/me is sent with no body and no dialog opens
And on 201 the new author is added to the list as the primary author
And the status becomes "Unsaved changes" and the hint disappears
And the session store holds the author, so /profile shows the "Created" card
@not-in-design
Scenario: The hint-success toast
When POST /v1/author/me answers 201
Then a success toast shows "Author profile created" and "Marta Alves is now your byline." (the Profile copy)
Scenario Outline: Create my author profile fails
When POST /v1/author/me fails with <failure>
Then the hint stays, an error toast shows "Couldn't create your author profile" and "Try again in a moment."
And the article is unchanged
Examples:
| failure |
| status 500 |
| a network error |
Scenario: The profile already exists (409)
When POST /v1/author/me answers 409 user_already_linked
Then GET /v1/auth/me is requested again, the hint disappears and the existing author is offered in the combobox
Scenario: Dismissing the hint
When the user dismisses the hint
Then it disappears and does not come back for this user in this browser, on later visits to any article
And the dismissal is kept in localStorage keyed by user id
And dismissing sends no request
Rule: Editor failures and keyboard
Scenario: The sites could not be loaded
Given GET /v1/site/ fails
When the user opens the editor
Then the editor still loads, no Site select is rendered and no notice is shown
Scenario Outline: Save is refused for a site or author that no longer exists
Given the user changed the <what>
When PATCH /v1/article/12 answers 404 <code>
Then the existing "Save failed" state is shown with the changes kept
And an info toast "<toast>" shows and the lists are reloaded
Examples:
| what | code | toast |
| site | site_not_found | That site no longer exists |
| author | author_not_found | That author no longer exists |
Scenario: Existing save behaviour is kept
Given the user changed only the site or the authors
Then "Save and publish", the leave-confirmation dialog, Ctrl/Cmd S and the draft kept in sessionStorage after a session expiry treat the change like any other edit
And the restored draft shows "Unsaved changes" with the chosen site and author order
Rule: Articles list: Authors column, Site column and Site filter
Scenario Outline: Authors column on every frame
Given an article with <authors>
When the list is shown
Then the Authors cell shows <cell>
Examples:
| authors | cell |
| no authors | "—" |
| one author | one avatar |
| three | three avatars, no "+N" |
| five | three avatars and "+2" |
Scenario: Mobile Authors cell, and no site on the cards
Given the viewport is 390 px wide
Then the card shows the avatars with the first name of the first author, as drawn in the board
And the card shows no site, as in the design
Scenario: One site: no Site column and no filter
Given the install has one site
Then the list has an Authors column, no Site column and no Site filter
Scenario: Several sites: Site column and Site filter
Given the install has several sites
Then the list has a Site column showing the site name as read-only text for every role
And the toolbar has a Site select with "All sites" and every site name
Scenario: Choosing a site
Given the list is on page 3
When the user chooses "Journal" in the Site filter
Then the URL becomes /article?site=journal with the page omitted
And GET /v1/article/?site=journal is requested with the current q, status and sort
And the tab counts still show the full list
Scenario Outline: Other changes keep the site
Given the URL holds ?site=journal
When the user <action>
Then ?site=journal stays in the URL
Examples:
| action |
| searches for "plaster" |
| changes the tab |
| changes the sort |
| goes to page 2 |
| changes the page size |
Scenario: Choosing "All sites"
Given the URL holds ?site=journal
When the user chooses "All sites"
Then site leaves the URL, the page resets to 1 and the full list is requested
Scenario: Opening a URL with a site
When the user opens /article?site=journal&page=2
Then the Site filter shows "Journal" and GET /v1/article/?site=journal&page=2 is requested
Scenario Outline: A site key that cannot be used is dropped silently
Given the URL holds <query>
Then the key is removed from the URL with no notice and the unfiltered list is requested
Examples:
| query |
| ?site=nope |
| ?site=renew on a one-site install |
Scenario: No articles on a site
Given ?site=archive and the response is empty with no search and the tab "All"
Then the state shows "No articles on "Archive"", "Move articles here from the editor, or choose another site." and "Show all sites"
When the user activates "Show all sites"
Then site leaves the URL and the full list is requested
Scenario: Zero results on a site with a search or another tab
Given ?site=archive and a search or a tab other than "All" is active
Then the existing "No results" or "No drafts" state is shown
Scenario: Existing states keep working with a site
Then Loading, Empty, row actions, delete and pagination states behave as in docs/design/screens/articles.md with the site filter applied
Scenario: The sites could not be loaded
Given GET /v1/site/ fails
Then the list renders as a single-site install, with no Site column and no Site filter
Rule: Profile: the Author profile card
Scenario: Position and roles
Then in every Profile frame the "Author profile" card sits between "Account" and "Password"
And it is shown to every role
Scenario: Loading
When GET /v1/auth/me is pending
Then the card shows a skeleton
Scenario: No author profile
Given GET /v1/auth/me has author null
Then the card shows the copy of state 1, a "Create my author profile" button and the line "Uses your display name, Marta Alves. You can change it afterwards."
@not-in-design
Scenario: No author profile and no display name
Given GET /v1/auth/me has author null and no display name
Then the line reads "Uses your username, marta. You can change it afterwards."
Scenario: Create succeeds
When the user activates "Create my author profile"
Then exactly one POST /v1/author/me is sent
And on 201 the card shows "Author profile created", "Marta Alves is now your byline." and "Edit author" with "Linked to your account. New articles start with this author."
And the session store holds the author
Scenario: Slug adjusted
Given the 201 answer has slug_adjusted true and the slug "marta-alves-2"
Then no error is shown and the card also shows "Your slug is marta-alves-2" with "marta-alves was already used by another author. You can change it in Edit author."
Scenario Outline: The notice follows slug_adjusted, never the slug itself
Given the author's name is "<name>"
And the 201 answer has the slug "<returned>" and slug_adjusted <flag>
Then the card <notice> the "Your slug is" notice
Examples:
| name | returned | flag | notice |
| Marta Alves | marta-alves | false | does not show |
| Marta Alves | marta-alves-2 | true | shows |
| Inès Duarte | ines-duarte-2 | true | shows |
| Jean-Luc O'Neil | jean-luc-o-neil | false | does not show |
| Zoë Müller | zoe-muller | false | does not show |
| Agent 007 | agent-007 | false | does not show |
| Agent 007 | agent-007-2 | true | shows |
Scenario Outline: Create fails
When POST /v1/author/me fails with <failure>
Then the card shows "Couldn't create your author profile" and "Try again in a moment." and the button stays available
Examples:
| failure |
| status 500 |
| a network error |
Scenario: Profile already exists (409)
When POST /v1/author/me answers 409 user_already_linked
Then GET /v1/auth/me is requested again and the card shows the existing author
Scenario: Existing author on load
Given GET /v1/auth/me carries an author
Then the card shows the author's name, "Edit author" and "Linked to your account. New articles start with this author." without the "Created" banner
Scenario: Edit own author without author rights
Given the session role has no author rights and has a linked author
When the user activates "Edit author"
Then the author form opens titled "Edit Marta Alves" with Linked user "marta@renew.example" locked, "This is your author profile. Only an owner or admin can change the link." and "Save changes"
And there is no "Unlink" and no delete action on the form or on the screen
When the user saves a change
Then exactly one PATCH /v1/author/{id} is sent with only the changed fields
@not-in-design
Scenario: Edit author as owner or admin
Given the session role is "owner" or "admin" with a linked author
When the user activates "Edit author" on /profile
Then the existing AuthorFormPanel opens in place, on /profile, with "Unlink" allowed
Scenario: Language and session
Given the language is "fr"
Then every new string on the three screens is the French message
And a 401 on any new call lands on /login?redirect=<path>&reason=expired and a 403 from an unverified account lands on /register/pending
Rule: Catalogue and copy left over from the Sites and Authors screens
Scenario: Authors routes in the API catalogue
Then web/src/data/apiCatalogue.ts has an "Authors" group and an "Authors · owner and admin" group covering every /v1/author route, including POST /v1/author/me as a session-only route
And web/src/data/__tests__/apiCatalogue.spec.ts covers them
Scenario: French strings for the catalogue groups
Given the language is "fr"
Then the catalogue group titles "Sites", "Sites · owner only", "Authors" and "Authors · owner and admin" and their descriptions have French entries
Scenario: Session-only rows
Then the session-only routes of Sites and Authors appear in SESSION_GROUPS with SESSION_REQUIRED_MESSAGE
Scenario: The owner_only copy of the design board
Then the 403 page copy "You don't have access to Sites" with "Only the owner can manage sites. Ask the owner of this site if you need a change."
And the action toast "Only the owner can manage sites" with "Ask the owner of this site."
exist in the SPA and have French entries
+64
View File
@@ -0,0 +1,64 @@
# QA: Sites and authors in Editor, Articles and Profile
SPA slice only. No Go change: the API already exists (commits "Sites feature backend" and "Authors feature backend"). A wrong contract is reported as a backend defect.
Roles: the domain defines only `owner` and `admin` (`internal/domain/user`). There is no "member" constant. "verified" in the feature and below is a verified account without the owner or admin role, which has no author rights. The earlier `sites_authors_screens` files still say "member" for the same account; that wording is left as is.
## Automated SPA (Vitest + Vue Test Utils, `task test:web`)
Mock `useApi`'s `request` per case for GET /v1/site/, GET /v1/author/, POST /v1/author/, POST /v1/author/me, GET /v1/auth/me, GET and PATCH /v1/article/{id} and GET /v1/article/. Mount EditorView, ArticlesView and ProfileView inside DashboardShell with a resolved session and a test router. Roles: "owner" (Marta Alves, no author), "admin", "verified". Mock matchMedia for desktop and mobile. Use localStorage fakes for the remembered section state and the dismissed hint. Do not freeze the system date. Report the web tests as run or NOT RUN, never as passing when not run.
Regression: every existing Editor, Articles, ArticlesPagination, ArticlesPageSizeMemory and Profile spec now sees the new calls. Give them default mocks (one site, one author, author null where relevant) rather than loosening unmocked-call checks, and show that they still pass.
1. MetaSection: collapsed summary "Inès Duarte"; open/closed Examples (sites x authors); remembered choice wins over auto-open; same key in the desktop sidebar and the mobile sheet; mobile closed in the Details sheet; loading skeleton; no section on 404; summary variants.
2. SiteSelect: absent with one site; present with helper "Canonical host: ..."; options with name and host and the default first; choosing marks "Unsaved changes"; the PATCH contains `site_id` and only the changed fields; GET /v1/site/ failure renders no select and no notice.
3. AuthorList: Primary label, handle, remove, truncation with title and aria-label; keyboard reorder (grab, ArrowUp/Down, drop, Escape, live region text including dropped and cancelled); pointer reorder (use the sortable test helper; assert order and "Unsaved changes"); removing the last author shows the no-byline text; PATCH `author_ids` order, `[]` when emptied, absent when unchanged.
4. AuthorSelect: opens with GET /v1/author/ size 100; only sizes 5, 25, 50 or 100 are ever sent (other sizes answer 400); `q` search beyond the first 100; checked and dimmed items do nothing when activated; append on pick; "Create author…" per role (owner, admin, verified); no-match message with and without the create row; error state with "Try again".
5. Create author dialog: prefilled name and auto slug; POST body; appended to the list and "Unsaved changes"; the article's authors are not sent before Save; "This is me" only when author is null (default unchecked) and sends `user_id`; session store updated; slug taken and the "Use zoe-lambert-2" action; failure Examples; 403 toast.
6. CreateAuthorHint: shown by role Examples; hidden for author present, article with authors, dismissed; POST /v1/author/me with no body and no dialog; list and toast; session updated; failure Examples; 409 refetch; dismissal stored in localStorage by user id and never re-shown; no request on dismiss.
7. Editor save: a site-only or author-only edit enables Save, "Save and publish", leave-confirmation and the sessionStorage draft restore; 404 `site_not_found` / `author_not_found` handling with the "Save failed" state and the info toast.
8. ArticlesView: Authors cell Examples (0, 1, 3, 5 authors); mobile cell and no site on the cards; Site column and filter only with several sites; `?site=` read, written, combined with q/tab/sort/size/page and page reset; "All sites"; unknown key and single-site key dropped silently; "No articles on "Archive"" and "Show all sites"; "No results"/"No drafts" with a site and a search or tab; GET /v1/site/ failure falls back to single-site; tab counts unchanged.
9. ProfileView AuthorProfileCard: position between Account and Password for all roles; skeleton while loading; states 1 to 5 (no profile, error, created, created with adjusted slug, edit own author with locked link and no Unlink or delete); owner/admin Edit author reuses AuthorFormPanel with Unlink allowed; POST /v1/author/me count; 409 refetch; author present on load.
10. Slug adjusted comes from the server: POST /v1/author/me returns `slug_adjusted` and the SPA shows the "Your slug is" notice from that flag only. Vitest cases feed mocked responses (accented names, apostrophes, collision `marta-alves-2`, `agent-007` with the flag false) and check `baseSlugOf` strips only the one collision suffix. The slug rule itself is covered in Go (`features/authors_backend.feature`, `test/authors_backend_http_test.go`).
11. i18n: every new `t()` key has a French entry (extend the i18n spec); one French walk through the three screens.
12. Catalogue group (final scenario group): the Authors routes and groups in `apiCatalogue.ts` and its spec, French titles and descriptions for the Sites and Authors groups, Sites and Authors rows in `SESSION_GROUPS` with `SESSION_REQUIRED_MESSAGE`, and the owner_only 403 page and toast copy with French entries.
Handoff note for the coder: a grep of the SPA after the earlier commit found the Sites catalogue groups ("Sites", "Sites · owner only") but no `/v1/author` group, no French entry for "Sites · owner only", and no `owner_only` copy in `web/src`; `SESSION_REQUIRED_MESSAGE` and `SESSION_GROUPS` exist. Treat item 12 as work to do, and verify each part before adding it.
## Visual (`task visual:check -- <screen>`, desktop and mobile, light and dark)
Setups go in `web/visual/states.ts` (one setup per state, mocked /v1, the board's own sample data, no `skip` unless the state depends on the date). Hard gate: every capture within 10 %, with no per-state exception. Reference frames: `AppFrame.dc.html` (section, Articles columns, Profile card) and `DirectoryFrame.dc.html` (section content).
- Editor, all existing frames that show the sidebar: the section collapsed with the summary "Inès Duarte" (one site, one author). Their existing setups need default mocks for /v1/site/ and the article's authors. Not found has none. Loading shows the skeleton summary.
- Editor: site and authors (16): open one site, several sites, Site select open, no authors with hint, many authors, add open, no match, add open (no create), no match (no create), add error, reorder keyboard, reorder pointer, create dialog, for me, slug taken, failed. The "(no create)" frames use a verified session.
- Articles (existing frames + pagination frames): the Authors column in every frame. Articles: sites and authors (3): several sites, Site filter open, no articles on a site.
- Profile (existing frames): the Author profile card in its default state in every frame. Profile: author profile (5): no profile, error, created, slug adjusted, edit own author.
- Re-run Settings, Users and Home too: report any capture that moves above 10 %. Any failure needs the user's decision (an `approvedBy` exception or a design update).
## Exit conditions
- All existing Editor, Articles and Profile frames pass `task visual:check`, with no per-state exception.
- The new frames pass.
- Settings, Users and Home are re-run for regressions.
- Vitest specs are reported honestly, run or NOT RUN.
## Copy and behaviour not in the design (feed back to docs/design later)
Each has a French entry in `web/src/i18n/messages.ts`.
1. Editor hint-success toast: "Author profile created" / "Marta Alves is now your byline." (reuses the Profile copy).
2. The row label "Primary".
3. The "dropped" and "cancelled" live-region announcements (only "grabbed" is in the design).
4. Collapsed-summary variants: "Inès Duarte +2", "No authors", "Journal · Inès Duarte", "Journal · No authors".
5. Profile line without a display name: "Uses your username, marta. You can change it afterwards."
6. Profile "Edit author" for owner and admin: AuthorFormPanel in place on /profile with Unlink allowed.
## Known gap (no change in this slice)
`GET /media/{id}/usage` returns articles only. An image used as an author avatar or a site social image can therefore look unused in the Media detail panel and in the delete confirmation. This needs a Go change and is not part of this slice.
## Manual checks screenshots cannot cover
- Keyboard: tab order through the section, the Site select, handles, remove buttons, "Add an author", the dialog and the hint close button; focus returns to the opener after the dialog; the live region reads the grabbed, moved, dropped and cancelled messages in a screen reader.
- Pointer: drag feel, the drop line, scroll while dragging on mobile.
- Real `task dev`: an owner creates an author inline with "This is me", then opens /profile; a verified account creates a profile from the hint, saves, and the Authors column shows the avatar; filter by site and share the URL.
+3 -3
View File
@@ -11,7 +11,7 @@ Feature: Sites and Authors screens
DirectoryList, DirectoryPanel (the slide-over), ReassignDialog, SlugField and MediaField
And the screens call, with relative URLs through useApi only, GET, POST, PATCH and DELETE on
/v1/site/ and /v1/author/, PATCH /v1/site/{id}/default and GET /v1/user/
And POST /v1/author/me is not exposed in the UI by this slice
And POST /v1/author/me and the changes to the Editor, Articles and Profile screens are specified in features/sites_authors_integration.feature
And a site has the fields id, name, key, base_url, default_social_image_id, default_robots
("index" or "noindex"), locale, is_default and article_count
And an author has the fields id, name, slug, bio, avatar_id, website, social_links, user_id,
@@ -630,5 +630,5 @@ Feature: Sites and Authors screens
When the user opens each state of both screens
Then every visible string is the French message and none falls back to English
Scenario: Existing screens are unchanged
Then the Editor, Articles and Profile views and their specs are not modified by this slice
Scenario: Editor, Articles and Profile
Then their changes are specified in features/sites_authors_integration.feature
+2 -2
View File
@@ -32,12 +32,12 @@ Authors (AuthorsView):
19. Row menu: guest has Edit, Link user, Delete…; linked has Edit, Delete…; mobile sheet.
20. Read-only list and detail for member (no button, no menu, no bottom button, search works, no email); `?edit=` opens the read-only detail.
21. Form: Add empty copy; slug from the name; add succeeds with the exact POST body; slug collision and suggestions; validation Examples; server codes Examples (bio_too_long, invalid_url, user_already_linked); social links (types, placeholders, limit of 5, bad link); linked user options computed from GET /v1/user/ minus user_ids paged from GET /v1/author/?size=100, and "Every user already has an author."; edit linked; unlink (PATCH user_id null, toast); re-link; avatar picker; save failed Examples; 403 author_rights toast; deep link and unknown slug toast.
22. Own author by deep link only: member opens `/authors?edit=<own slug>` (own author from GET /v1/auth/me), form with locked link and no delete, PATCH on save; another slug opens the read-only detail. No test calls POST /v1/author/me, because the UI does not expose it in this slice.
22. Own author by deep link only: member opens `/authors?edit=<own slug>` (own author from GET /v1/auth/me), form with locked link and no delete, PATCH on save; another slug opens the read-only detail. POST /v1/author/me is covered by features/sites_authors_integration.qa.md.
23. Delete: reassign (copy, linked line, first other author preselected, one DELETE ?reassign_to=2, "Reassigning…", toast); choose who (no deleted author, "Nobody" last); nobody sends `reassign_to=none`; failed Examples; 409 fallback dialog and "Reassign articles"; no articles; 404.
24. 401 and 403 app-wide redirects on both routes.
25. French: `setLocale('fr')`, walk the list, form, delete and 403 page of each screen, no English source string remains.
26. Messages: every new `t()` key has a French entry (extend the i18n spec). Router: /settings/sites and /authors are registered and verified.
27. Regression: the existing Editor, Articles, Profile specs pass unchanged.
27. Editor, Articles and Profile changes and their regression specs are covered by features/sites_authors_integration.qa.md.
## Visual (`task visual:check -- <screen>`, desktop and mobile, light and dark)