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 and the article has When the user opens the editor on desktop Then the section is 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 and the article has Then the summary line reads 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 "" When the combobox is open Then the "Create author…" row is Examples: | role | visible | | owner | shown | | admin | shown | | verified | absent | Scenario Outline: No match Given the session role is "" When the user types "Zoe Lambert" and nobody matches Then the message "No author matches "Zoe Lambert"." is shown And 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 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 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 "", 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 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 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 When PATCH /v1/article/12 answers 404 Then the existing "Save failed" state is shown with the changes kept And an info 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 When the list is shown Then the Authors cell shows 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 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 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 "" And the 201 answer has the slug "" and slug_adjusted Then the card 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 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=&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