feat: editor language menu, title field, header actions, publish dialogs, read-only rendering and draft recovery

This commit is contained in:
Florian Sylvain committed 2026-11-02 17:13:14 +01:00
1 parent a671b81f80
commit 339e646eb8
85 files changed
+6369 -624

No files matched your search

+13 -12
View File
@@ -4,16 +4,15 @@
`docs/design/components.md`'s `ErrorView` only documents two fixed copies (`kind: 'not-found'` →
"Page not found" / Home, and `kind: 'error'` → the generic retry copy). The editor's own 404-on-
load state needs different copy ("This article doesn't exist." / "All articles" → /article),
per `docs/design/states.md`'s HTTP-status-to-copy table. Either widen `ErrorView` with an optional
title/description/action override, or add a third `kind`; this is a same-layer prop widening, not
a new component, so it doesn't need its own slice.
load state shows the Version unavailable copy ("This version isn't available" / "Back to
Articles" → /article), per `features/localization_editor_navigation.feature`, which supersedes the
original "This article doesn't exist." copy; it is rendered by `EditorVersionGone`.
Known limitation, not a bug to silently fix: per `features/public_article_read.feature`,
`GET /v1/article/{id}` falls back to public online-only visibility on a malformed/expired jwt
(no 401). So if a session is already stale when the owner opens an *offline* article, the GET
answers 404 exactly like a truly deleted article — the editor cannot tell the two apart from this
response alone, and correctly shows the "This article doesn't exist." state.
response alone, and correctly shows the "This version isn't available" state.
## Automated (Vitest + Vue Test Utils, `task test:web`)
@@ -46,9 +45,9 @@ Fixture article (reused across cases unless a scenario overrides a field): id 12
6. **Relative save time after an hour** — after a 200 save at 11:02, advance the fake clock past
one hour with no further edit; assert SaveStatus switches to "Saved {n} min ago".
7. **Save failed, Examples-driven** — (a) `PATCH` resolves 500, (b) `PATCH` rejects (network);
in each, assert SaveStatus "Save failed" with a visible Retry button, and the editor still
in each, assert SaveStatus "Save failed" with the primary button "Try saving again", and the editor still
shows the typed body.
8. **Retry recovers** — from Save failed (500), click Retry; assert `PATCH` is called again with
8. **Retry recovers** — from Save failed (500), click "Try saving again"; assert `PATCH` is called again with
the same body; resolve 200; assert SaveStatus "Saved at {time}".
9. **404 on save shows a toast, not the error SaveStatus** — from Unsaved changes, save; resolve
`PATCH` 404; assert a toast "That article no longer exists." appears and SaveStatus never
@@ -74,15 +73,17 @@ Fixture article (reused across cases unless a scenario overrides a field): id 12
assert the router navigates to `/article` with no ConfirmDialog ever rendered.
17. **Session expiry mid-save mirrors and restores the draft** — from Unsaved changes with body
"Paragraph added just before the session expired.", save; resolve `PATCH` 401; assert
`sessionStorage` holds that body keyed by article id 12, and the router navigates to
`sessionStorage` holds that body keyed by user id and article id 12 (key
`renewcms:article-draft:{userId}:12`, a JSON object with title, body, search fields and the
mirror time), and the router navigates to
`/login?redirect=/article/12/edit&reason=expired`. Then, simulating the return trip, mount
EditorPage again at `/article/12/edit` with `GET /v1/article/12` resolving 200 with a body
that lacks that paragraph; assert the rendered body is the restored sessionStorage draft
(with the paragraph) and SaveStatus reads "Unsaved changes".
18. **Not found on load** — mock `GET /v1/article/9999` resolving 404; mount at
`/article/9999/edit`; assert only the error state renders (title "This article doesn't
exist.", action "All articles"), with no breadcrumb-title/SaveStatus/Save/PublishControl/
ArticleEditor; click "All articles"; assert the router navigates to `/article`.
`/article/9999/edit`; assert only the Version unavailable state renders (title "This version
isn't available", action "Back to Articles"), with no header/SaveStatus/Save/PublishControl/
ArticleEditor; click "Back to Articles"; assert the router navigates to `/article`.
## Visual (manual verification — desktop and mobile, light and dark)
@@ -98,7 +99,7 @@ reusing the sample data below and a fixed `now` of "2026-09-26T11:02:00Z" (updat
"N words · 4,318 characters", details panel (Created/Updated/ID/Images/Public API URL).
- [ ] Unsaved changes: "Unsaved changes" SaveStatus, enabled Save, "Save and publish".
- [ ] Saving: "Saving…" SaveStatus, disabled Save.
- [ ] Save failed: "Save failed" with Retry, per the SaveStatus error spec.
- [ ] Save failed: "Save failed" with the "Try saving again" primary, per the SaveStatus error spec.
- [ ] Very long article: count "19,845 words · 120,000 characters", neutral, Save/Publish enabled.
- [ ] Preview: read-only rendered title and body in the content scale, toolbar hidden.
- [ ] Offline: SaveStatus shows the wifi-off icon and reads "Waiting for connection", disabled