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

+26 -19
View File
@@ -15,8 +15,10 @@ Feature: Editor screen — load, edit and save an article's body
/article (mobile top bar title "Edit article" instead), an ArticleStatusBadge size "md"
reflecting article.is_online ("Draft" or "Online"), a SaveStatus span, a Save button,
and a PublishControl button, per docs/design/components.md
And the article's title is never editable after creation, per
features/article_title_rules.feature; only the body is edited here
And the title is an editable field labelled "Title" ("Title (French)" when the site has more
than one enabled language), per features/localization_editor_navigation.feature, which
supersedes features/article_title_rules.feature's "never editable"; the body is edited in
ArticleEditor
And the body is edited in ArticleEditor (modelValue bound to the fetched HTML body); any
change moves useArticleDraft's state from "clean" to "dirty", and PublishControl's label
switches from "Publish" (or "Unpublish", if online) to "Save and publish" while dirty and
@@ -32,17 +34,21 @@ Feature: Editor screen — load, edit and save an article's body
And a successful save (200) sets useArticleDraft's state to "saved" and records the save
time; SaveStatus shows "Saved at {HH:MM}" for the first hour and "Saved {n} min ago" after
an hour has passed, per docs/design/components.md
And the Save button reads "Save" for every caller allowed to edit; the "Save draft" label of
callers who cannot publish no longer exists
And a 500 response or a request that never reaches the server sets useArticleDraft's state
to "error"; SaveStatus shows "Save failed" with an inline Retry button (UButton size="xs"
variant="outline"); the edited body is kept exactly as typed, both in the editor and for
the next Retry
to "error"; SaveStatus shows "Save failed" and the header's primary button reads "Try saving
again", per features/localization_editor_navigation.feature (the inline Retry button of
SaveStatus is no longer rendered in the editor); the edited body is kept exactly as typed,
both in the editor and for the next retry
And a 404 response to a save (the article was deleted elsewhere) does not use the "error"
SaveStatus; it shows a toast "That article no longer exists.", per docs/design/states.md's
HTTP-status-to-copy table
And a 401 response to a save is handled by useApi's global handling
(features/api_client_global_error_handling.feature): before the redirect, useArticleDraft
mirrors the current dirty body to sessionStorage keyed by the article's id, so it survives
the redirect to /login?redirect=/article/{id}/edit&reason=expired
mirrors the current dirty title, body and search fields to sessionStorage keyed by the user
id and the article id, with the time of the mirror, so it survives the redirect to
/login?redirect=/article/{id}/edit&reason=expired
And EditorStatusBar shows a neutral "{words} words · {characters} characters" count with
en-US thousands separators (e.g. "1,204 words · 7,318 characters", "0 words · 0 characters"
for an empty body); characters is the body's HTML string length, words are the
@@ -65,10 +71,11 @@ Feature: Editor screen — load, edit and save an article's body
proceeds with the navigation, discarding the edit; "Keep editing" closes the dialog and
keeps the user on the editor with the edit intact; navigating away while "clean" or
"saved" never opens this dialog
And an id absent from the repository (GET resolves 404) replaces the entire editor with an
error state: title "This article doesn't exist.", and a single action "All articles"
navigating to /article; no breadcrumb-title, SaveStatus, Save, PublishControl or
ArticleEditor renders in this state
And an id absent from the repository (GET resolves 404) replaces the entire editor with the
Version unavailable state: title "This version isn't available", text "It may have been
deleted, or you may not have access.", and a single action "Back to Articles" navigating
to /article; no header, SaveStatus, Save, PublishControl or ArticleEditor renders in this
state
And skeleton placeholders (for the title, content and details) replace the editor's content
only after 150 ms of GET /v1/article/{id} being unresolved, so a fast response never
flashes a skeleton, per the Skeleton component spec
@@ -132,12 +139,12 @@ Feature: Editor screen — load, edit and save an article's body
When more than an hour has passed with no further edit or save
Then SaveStatus reads "Saved {n} min ago" instead of "Saved at 11:02"
Scenario Outline: Save failed — a 500 or a network failure keeps the changes and offers Retry
Scenario Outline: Save failed — a 500 or a network failure keeps the changes and offers Try saving again
Given the user is on /article/12/edit with unsaved changes to the body "Revised opening
paragraph."
When the user activates Save
And PATCH /v1/article/12 <failure>
Then SaveStatus reads "Save failed" with a Retry button
Then SaveStatus reads "Save failed" and the primary button reads "Try saving again"
And the editor still shows the body "Revised opening paragraph."
And no navigation occurs
@@ -149,7 +156,7 @@ Feature: Editor screen — load, edit and save an article's body
Scenario: Retry resends the same body and can recover
Given the user is on /article/12/edit in the Save failed state, with unsaved body "Revised
opening paragraph." and PATCH /v1/article/12 having failed with a 500
When the user activates Retry
When the user activates "Try saving again"
Then PATCH /v1/article/12 is sent again with JSON {"body": "Revised opening paragraph."}
When it resolves 200 with the updated article
Then SaveStatus reads "Saved at {current time}"
@@ -214,7 +221,7 @@ Feature: Editor screen — load, edit and save an article's body
When the user activates Save
And PATCH /v1/article/12 resolves 401
Then the body "Paragraph added just before the session expired." is mirrored to
sessionStorage keyed by article id 12
sessionStorage keyed by user id and article id 12
And the app navigates to /login?redirect=/article/12/edit&reason=expired
When the user logs in again and is returned to /article/12/edit
And GET /v1/article/12 resolves 200 with the last-saved body (without that paragraph)
@@ -225,8 +232,8 @@ Feature: Editor screen — load, edit and save an article's body
Scenario: An unknown or deleted article id replaces the editor with an error state
Given GET /v1/article/9999 resolves 404
When the user navigates to /article/9999/edit
Then the editor shows a single error state: title "This article doesn't exist." and one
action "All articles"
And no breadcrumb-title, SaveStatus, Save, PublishControl or ArticleEditor renders
When the user activates "All articles"
Then the editor shows a single Version unavailable state: title "This version isn't
available" and one action "Back to Articles"
And no header, SaveStatus, Save, PublishControl or ArticleEditor renders
When the user activates "Back to Articles"
Then the app navigates to /article