# QA: Sites and Authors screens SPA slice only. The Go API already exists (commits "Sites feature backend" and "Authors feature backend"), so no new Go test is requested. If a contract below turns out wrong, report it as a backend defect. ## Automated SPA (Vitest + Vue Test Utils, `task test:web`) Mock `useApi`'s `request` per case for GET/POST/PATCH/DELETE on /v1/site/ and /v1/author/, PATCH /v1/site/{id}/default and GET /v1/user/. Mount SitesView and AuthorsView inside DashboardShell with a resolved session and a test router. Roles: "owner" (Marta Alves), "admin", and "member". Mock matchMedia for desktop and below-md. Do not freeze the system date. Use fake timers only for the 150 ms skeleton delay and the search debounce. Un-run web tests are NOT RUN, never passing. Shared pieces get their own specs, reused by both screens (`DirectoryList`, `DirectoryPanel`, `ReassignDialog`, `SlugField`, `MediaField`): 1. **DirectoryList** - rows, truncation, read-only chevron variant, below-md cards, loading (skeleton after 150 ms, aria-busy), error alert with "Try again". 2. **DirectoryPanel** - desktop slide-over and 92 % bottom sheet below md, focus trap and return, "Discard changes?" on a dirty close, Escape ignored while sending. 3. **SlugField** - auto from the name, "Auto" badge, pencil, "Reset to match the name", suggestion, locked variant with lock and "Used in API paths, so it can't change." 4. **MediaField** - opens the existing picker, square avatar variant, "Use image" returns the id. 5. **ReassignDialog** - select excludes the deleted item, default selection, count in the button, in-flight label, failed notice, 409 notice. Sites (SitesView), one test per scenario in the feature: 6. Default list: columns, badges "Default" and "Noindex", counts including "No articles", mobile cards and pinned "Add site". 7. Only the default site hint; no empty-state wall. 8. Loading: nothing before 150 ms, skeleton after. 9. Error Examples-driven (500, network) and "Try again". 10. Row menu and the bottom-sheet title; Default site: "Delete…" aria-disabled, focusable, tooltip text, no request on activate. 11. Set as default: dialog copy, one PATCH /v1/site/2/default, "Saving…", badge moves, toast; failures 500/404/403. 12. Hidden entry (admin) and the owner's entry: sidebar Publishing group, Sites entry, Settings "Publishing" card and "Manage sites" row. Extend the existing `SettingsView.spec.ts` and the shell navigation spec for role-based visibility. 13. 403 page for admin and member on /settings/sites and on ?edit=journal; "Back to Settings"; no redirect; the API-403 page load versus action toast. 14. Form: Add empty (every label, placeholder, help); key from the name and pencil; URL validation Examples (3 messages, no request); server invalid_site_url; loopback http accepted; duplicate key (blur against the loaded list and 409) with the suggestion; save failed Examples; invalid_locale; Edit default site (lock, switch on and locked); Edit other site, noindex; PATCH has no key; default switch sends the two calls in order; discard; deep link and unknown key toast. 15. Delete: with articles (copy, default destination, one DELETE with reassign_to=1, "Moving articles…", toast); destination options (no deleted site, name and host); no articles (no select, no reassign_to); failures: 500/network with title "Couldn't delete the site" and "Nothing was changed. Try again in a moment.", 409, 403 default_site, 404. Authors (AuthorsView): 16. Default list for owner and admin: columns, badges, "9 articles"/"1 article"/"No articles", first request `?page=1&size=25`; sidebar entry order and aria-current. 17. Loading, error Examples, empty (owner, admin with button; member without), no results and "Clear search". 18. Search and pagination: debounce, URL q/page/size, paginator above 25, invalid page/size replaced silently. 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 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. Editor, Articles and Profile changes and their regression specs are covered by features/sites_authors_integration.qa.md. ## Visual (`task visual:check -- `, desktop and mobile, light and dark) Reference: docs/design/screens/sites.md and authors.md, board frames in `docs/design/source/DirectoryFrame.dc.html` (list, panel, dialogs) and `AppFrame.dc.html` (sidebar, Settings card). Add each state to `web/visual/states.ts` as the groups `sites`, `sites-form`, `sites-delete`, `authors`, `authors-form`, `authors-delete`, with mocked /v1 calls and the board's own sample data. Hard gate: every capture within 10 %. No state is clock-sensitive, so none gets `skip`. Confirm in the frame script before skipping anything. Sample data: the sites fixture (268, 38, 6 and 0 articles), the authors fixture, Camille Roche (3 articles), Inès Duarte (ines.duarte@renew.example, 9 articles), Léa Fontaine, Marta Alves (marta@renew.example), Rui Santos (rui@renew.example), the 291-character bio for validation, "zoe" for no results. Read the host and row order from the frame. Sites (9): - [ ] Default - [ ] Only the default site - [ ] Loading (setup holds the request) - [ ] Error - [ ] Row menu - [ ] Default site: delete unavailable - [ ] Set as default: confirm - [ ] Hidden entry (admin) (path /settings, admin session) - [ ] Forbidden (403) Sites: form (7): - [ ] Add: empty - [ ] Add: key from the name - [ ] Add: URL not valid - [ ] Add: duplicate key - [ ] Add: save failed - [ ] Edit: default site - [ ] Edit: other site, noindex Sites: delete (3): - [ ] With articles - [ ] Choose destination - [ ] No articles Authors (8): - [ ] Default - [ ] Loading - [ ] Error - [ ] Empty, first run - [ ] No results - [ ] Row menu - [ ] Read-only list (other roles) - [ ] Read-only detail Authors: form (11): - [ ] Add: empty - [ ] Slug from the name - [ ] Slug collision - [ ] Validation - [ ] Social links - [ ] Linked user: choose - [ ] Edit: linked author - [ ] Avatar: picker - [ ] Avatar: saved image shows in the list, the detail, the reopened form and the preview (not only the initials) - [ ] Save failed - [ ] Unlink: confirm - [ ] Re-link Authors: delete (6): - [ ] Reassign (recommended) - [ ] Choose who - [ ] Nobody - [ ] Failed - [ ] Server fallback (409) - [ ] No articles Layout from the frame, not components.md: row height as in Users; slide-over width and the 92 % bottom sheet at 390 px; bottom sheet row menu titled with the site name; pinned mobile "Add site"/"Add author" button. Not drawn on the board, Vitest only: in-flight labels ("Saving…", "Moving articles…", "Reassigning…"), the 403 and 404 toasts, the 401 redirect, the pagination states of the Authors list, Set-as-default failures, `invalid_locale`, the loopback rule and the discard dialog. Risk: the sidebar gains "Authors" for every role and "Publishing > Sites" for the owner, and Settings gains a Publishing card for the owner. This can push existing captures of other screens over 10 %. Re-run `task visual:check` for the Settings, Users, Media and API keys screens after the setups pass. A failure needs the user's decision (an `approvedBy` exception or a design update). Manual checks screenshots cannot cover: - Keyboard: Tab order through "Add", search, rows and the ellipsis buttons; Enter opens the menu; focus is trapped in the panel and dialogs and returns to the opener; the disabled "Delete…" stays focusable; the select options are reachable by keyboard. - Hover and motion: truncated name tooltip, row hover, slide-over and sheet motion, skeleton pulse stopped under reduced motion. ## Manual verification (real `task dev`) - Owner: add a site with the loopback URL http://localhost:5173, set it default, delete a site with articles and check the articles moved. Admin: no Sites entry, a direct link shows the 403 page. - Authors: owner creates, links and unlinks an author; member sees the read-only list and detail; member with an author opens `/authors?edit=` and cannot delete. ## French strings for web/src/i18n/messages.ts Keyed by the English text, with the English as in the design. `coder` completes them for every string listed in the two screen files. Proposed French for the main ones: Sites = Sites; Add site = Ajouter un site; Add author = Ajouter un auteur; Authors = Auteurs; Default = Par défaut; Noindex = Noindex; Set as default = Définir par défaut; Delete… = Supprimer… ; Edit = Modifier; Link user = Lier un utilisateur; Cancel = Annuler; Save changes = Enregistrer les modifications; Saving… = Enregistrement…; Moving articles… = Déplacement des articles…; Reassigning… = Réattribution…; Guest = Invité; Linked = Lié; Try again = Réessayer; Couldn't load sites = Impossible de charger les sites; Couldn't load authors = Impossible de charger les auteurs; Couldn't delete the site = Impossible de supprimer le site; You don't have access to Sites = Vous n'avez pas accès aux sites; Back to Settings = Retour aux réglages; Nobody = Personne; Unlink user = Dissocier l'utilisateur.