Files
RenewCMS/docs/design/states.md
T

30 KiB

State matrix and error copy

Every state is drawn at desktop and mobile on the screen boards, with final copy. Each screen's file in screens/ has the full copy per state.

Screen Route State Trigger Spec
App boot any verified route Checking session First navigation, while GET /v1/auth/me is pending boot
App boot any verified route Verified 200 with is_verified: the route renders boot
App boot any verified route Unverified 200 with is_verified false: goes to /register/pending boot
App boot any verified route No session 401: goes to /login?redirect= boot
Log in /login Default Registration closed log-in
Log in /login Fresh install registration.open is true log-in
Log in /login Validation errors Client rules fail on submit log-in
Log in /login Submitting Request in flight log-in
Log in /login Wrong credentials 403 log-in
Log in /login Too many attempts 429 with Retry-After log-in
Log in /login Session expired ?reason=expired from a 401 log-in
Log in /login Password changed ?reason=password-changed log-in
Log in /login Server error 500 or network log-in
Register /register Default Open instance register
Register /register Validation errors Client rules fail register
Register /register Submitting In flight register
Register /register Registration closed 403 register
Register /register Server error 500 or network register
Pending /register/pending Default After registering (email known) pending
Pending /register/pending Email unknown After logging in unverified pending
Pending /register/pending Confirm cancel Cancel registration clicked pending
Pending /register/pending Cancelling DELETE in flight pending
Pending /register/pending Cancelled 200: /register with toast pending
Pending /register/pending Already verified 403: /home with toast pending
Verify /register/validate?c= Verifying On load verify
Verify /register/validate?c= Success 200: /home with toast verify
Verify /register/validate?c= Invalid or expired 403 verify
Verify /register/validate?c= No session 401: inline log in, then retry verify
Verify /register/validate?c= Missing code No c parameter verify
Forgot password /register/reset/request Default Page load forgot-password
Forgot password /register/reset/request Validation Invalid email forgot-password
Forgot password /register/reset/request Submitting In flight forgot-password
Forgot password /register/reset/request Sent Any 200 (neutral copy) forgot-password
Forgot password /register/reset/request Too many attempts 429 forgot-password
Forgot password /register/reset/request Server error 500 or network forgot-password
Reset password /register/reset/validate Default c and email present reset-password
Reset password /register/reset/validate Too short Under 8 characters reset-password
Reset password /register/reset/validate Mismatch Confirm field differs reset-password
Reset password /register/reset/validate Submitting In flight reset-password
Reset password /register/reset/validate Success 200: /login?reason=password-changed reset-password
Reset password /register/reset/validate Invalid or expired 403 reset-password
Reset password /register/reset/validate Missing parameters No c or no email reset-password
Home /home Loading List in flight (after 150 ms) home
Home /home Default List loaded home
Home /home Empty Zero articles home
Home /home Error 500 or network home
Articles /article Loading List in flight articles
Articles /article Default Loaded articles
Articles /article Empty Zero articles articles
Articles /article No results Search matches nothing articles
Articles /article Empty tab No drafts, or nothing online articles
Articles /article Row busy Publish or unpublish in flight articles
Articles /article Action failed 500 or network: toast with Retry articles
Articles /article Row vanished 404 on any row action articles
Articles /article Delete confirm, in progress, failed, done ConfirmDialog, then DELETE: 204 or 500 articles
New article modal · /article/new Default Opened new-article
New article modal · /article/new Validation Title under 3 or body empty new-article
New article modal · /article/new Submitting POST in flight new-article
New article modal · /article/new Title rejected Server 400 new-article
New article modal · /article/new Error 500 or network new-article
New article modal · /article/new Created 201: editor, with a toast new-article
Editor /article/:id/edit Loading GET in flight editor
Editor /article/:id/edit Not found 404 editor
Editor /article/:id/edit Saved Clean editor
Editor /article/:id/edit Unsaved Any edit editor
Editor /article/:id/edit Saving PATCH in flight editor
Editor /article/:id/edit Save failed 500 or network editor
Editor /article/:id/edit Near the limit At least 9 000 HTML characters editor
Editor /article/:id/edit Too long Over 10 000: Save and Publish disabled editor
Editor /article/:id/edit Preview Switch on editor
Editor /article/:id/edit Choose, crop and resize, uploading, rejected, inserted ImageUploader editor
Editor /article/:id/edit Publishing, unpublishing PATCH publish or unpublish editor
Editor /article/:id/edit Leave guard Navigating away while dirty editor
Editor /article/:id/edit Offline navigator.onLine is false editor
Editor /article/:id/edit Deleted 204: /article with toast editor
Editor /article/:id/edit Session expired 401: draft kept in sessionStorage, then /login editor
Media /media Grid, many images Default view media
Media /media Grid, few images Three images media
Media /media List view The list icon in the toggle media
Media /media Loading The first load, a retry, or a change of search or filter media
Media /media Empty library, first run Zero images media
Media /media No results Search or filter matched nothing media
Media /media Hover, focus and keyboard Hover (second item) darkens the border, adds the pop shadow and reveals the checkbox media
Media /media Selection and bulk bar Checkbox on any item, or Space media
Media /media Bulk delete: confirm Delete in the bulk bar media
Media /media Bulk delete: in progress Requests in flight media
Media /media Bulk delete: partly failed Some requests failed media
Media /media Bulk delete: done 204 for all media
Media /media Detail panel: in use Click or Enter on an item media
Media /media Detail panel: unused Used in nothing media
Media /media Detail panel: duplicate The image has identical copies media
Media /media Copy URL Copy URL media
Media /media Rename: dialog Rename in the panel media
Media /media Rename: saving PATCH in flight media
Media /media Rename: validation Empty or only symbols, on submit or blur media
Media /media Rename: failed 500 or network failure media
Media /media Rename: done 200: the panel title and list update in place media
Media /media Delete: unused image Delete in the panel media
Media /media Delete: image in use Stronger confirm media
Media /media Delete: in progress DELETE in flight media
Media /media Delete: failed 500 or network failure media
Media /media Delete: done 204: the panel closes, the item leaves the grid and focus moves to the next item media
Media /media Image already gone (404) Any action on an image that was deleted elsewhere media
Media /media Upload: drag over the page Files dragged onto the page media
Media /media Upload: several files, mixed Upload button or drop, several files media
Media /media Upload: summary All rows settled media
Media /media Upload: rejected files (413, 400) Type and size are checked in the browser first, so nothing is sent media
Media /media Upload: too many requests (429) Rate limited media
Media /media Duplicates: groups The Duplicates tab media
Media /media Duplicates: empty No groups media
Media /media Merge: confirm "Merge into this one" on floorboards.jpg media
Media /media Merge: in progress Request in flight media
Media /media Merge: failed 500 or network failure media
Media /media Merge: conflict (409) A copy was deleted, or a new copy appeared, since the page loaded media
Media /media Merge: done 200: the group leaves the list media
Media /media List failed to load A 500 or network failure on the list request media
Media /media Offline navigator.onLine is false media
Media picker /article/:id/edit Upload tab The image button in the toolbar media-picker
Media picker /article/:id/edit Upload tab: already in your library A chosen file has the same content as an existing image media-picker
Media picker /article/:id/edit Library: loading The Library tab was opened, or a search changed media-picker
Media picker /article/:id/edit Library: nothing selected Newest first media-picker
Media picker /article/:id/edit Library: selected One image selected (single selection) media-picker
Media picker /article/:id/edit Library: alt text entered Alt text is optional media-picker
Media picker /article/:id/edit Library: loading more "Load more" pressed media-picker
Media picker /article/:id/edit Library: empty Zero images media-picker
Media picker /article/:id/edit Library: no results Search matched nothing media-picker
Media picker /article/:id/edit Library: failed to load A 500 or network failure media-picker
Media picker /article/:id/edit Inserted Insert image from either tab media-picker
Media picker /article/:id/edit Remove image: confirm Remove image on an image in the article media-picker
Media picker /article/:id/edit Removed Confirmed media-picker
Integration /integration Default Page load, every endpoint card collapsed integration
Integration /integration Group scrolled, one card expanded A card opened, one example visible integration
Integration /integration Copied: base URL Copy clicked (2 s) integration
Integration /integration Copied: CodeBlock Copy clicked in a code block (2 s) integration
Integration /integration Authentication API key card, link and notes integration
Integration /integration Session only Routes API keys cannot call integration
Integration /integration Errors Status table and JSON error shape integration
Integration /integration Navigation Section index active item; mobile Jump to open integration
API keys /integration/api-keys Owner: list Owner sees every key, with Created by integration
API keys /integration/api-keys Admin: own keys only Admin sees only their own keys integration
API keys /integration/api-keys Loading First load or retry integration
API keys /integration/api-keys Empty No keys yet integration
API keys /integration/api-keys Error 500 or network integration
API keys /integration/api-keys Create key Modal opened integration
API keys /integration/api-keys Create key: validation Name empty or expiry not in the future integration
API keys /integration/api-keys Create key: name too long More than 60 characters integration
API keys /integration/api-keys One-time reveal Key created, shown once integration
API keys /integration/api-keys One-time reveal: copied Copy clicked (2 s) integration
API keys /integration/api-keys Revoke: confirm Revoke pressed on a key integration
API keys /integration/api-keys Revoked Confirmed, key stays listed as revoked integration
Users /users Default List loaded, newest first users
Users /users Loading First load or retry users
Users /users Only you Nobody invited yet users
Users /users Error 500 or network users
Users /users Invite: default, validation, sending Invite modal opened, invalid email, POST in flight users
Users /users Invite: already a user 409 users
Users /users Invite: failed 500, network or mail failure users
Users /users Invite sent 201: dialog closes, Pending row on top, toast users
Users /users Cancel invite: confirm, sending, failed "Cancel invite" on a Pending row; DELETE in flight; 500 or network users
Users /users Cancel invite: done 204: row removed, toast users
Users /users Renew invite: sending "Renew invite" on an Expired row; POST in flight users
Users /users Renew invite: done 200: row Pending on top, toast users
Users /users Renew invite: failed 500 or network: toast, row stays Expired users
Settings /settings Up to date Opened with no newer version settings
Settings /settings Checking Check for updates pressed, or the first open after 24 hours settings
Settings /settings Check failed Offline, or the update server didn't answer (500, network) settings
Settings /settings Update available A newer version exists settings
Settings /settings Update available, notes open "What's new in 2.3.0" expanded settings
Settings /settings Downloading Update pressed (202) settings
Settings /settings Verifying The download finished settings
Settings /settings Ready to install The file passed its checks settings
Settings /settings Restart: confirm Restart pressed settings
Settings /settings Restart: request failed 500 or network failure before the restart began settings
Settings /settings Restarting to install Full-page overlay, so it survives the server going away settings
Settings /settings Restarting, taking longer No answer after 60 seconds settings
Settings /settings Update done The page reloaded on the new version settings
Settings /settings Update failed, rolled back The site came back on v2.2.0-beta settings
Settings /settings Download failed A 500, or the connection dropped settings
Settings /settings Verification failed The downloaded file isn't trusted settings
Settings /settings Too many requests (429) Retry-After counted down in the button settings
Settings /settings Not prepared The server can't update from the browser settings
Settings /settings Not prepared: command copied Copy pressed settings
Settings /settings Plain restart: confirm Restart with no update waiting settings
Settings /settings Plain restart: restarting Same overlay as the update restart with shorter copy settings
Settings /settings Plain restart: done The page reloaded settings
Settings /settings Admin view Admins see the version and the note, with no buttons settings
Settings /settings Role changed (403) An owner was changed to admin while the page was open and pressed a button settings
Settings /settings Update already running (409) Another owner started it settings
Home /home Update available (owner) Owner only: quiet banner and Settings pill home
Home /home Update ready to install (owner) Download checked: banner copy changes home
Profile /profile Account menu Account chip clicked profile
Profile /profile Loading Opened by URL before /auth/me returns profile
Profile /profile Default, no display name First visit profile
Profile /profile Editing, too long Typing; over 60 characters profile
Profile /profile Saving, saved, save failed PATCH in flight, 200, 500 or network profile
Profile /profile Reset: sending, sent, 429, failed POST /v1/auth/password-reset profile
Profile /profile Display names in the Users list Accounts with a display name profile
Accept invite /invite/accept?c= Checking the link On load: GET invite accept-invite
Accept invite /invite/accept?c= Default, validation errors, filled, submitting Valid, unused code; email read-only accept-invite
Accept invite /invite/accept?c= Username taken 409 accept-invite
Accept invite /invite/accept?c= Invalid or used, expired 404 or no c parameter, 410 accept-invite
Accept invite /invite/accept?c= Server error 500 or network accept-invite
Accept invite /invite/accept?c= Success 200: logged in, /home with toast accept-invite

HTTP status to copy

Response bodies are plain text and are never shown. useApi throws an ApiError with the status and a context key, and each screen maps them to this copy.

Status Where Copy Surface
400 Any form The field error from the client rules. A server 400 that got through: "Something in the form isn't valid. Check the highlighted fields." Field + alert
401 Any back-office call Goes to /login?redirect=&reason=expired. "Your session expired. Log in again to pick up where you left off." Redirect + alert
401 Verify "Log in to finish verifying." Inline log in, then retry Inline form
403 Log in "That username and password don't match. Check both and try again." Alert
403 Register "This site already has an owner. Log in instead." ResultPanel
403 Any back-office call Unverified: goes to /register/pending Redirect
403 Verify "This verification link doesn't work." Offers Cancel registration and start over ResultPanel
403 Cancel registration "Your account is already verified." Goes to /home Toast
403 Reset confirm "This reset link has expired." Offers Request a new link ResultPanel
409 Invite "This address already has an account." or, for a live invite, "There's already a pending invite for this address." Field error
404 Accept invite "This invite link doesn't work." Offers Go to log in ResultPanel
409 Accept invite "That username is taken. Try another." Field
410 Accept invite "This invite has expired. Ask the person who invited you to send a new one." ResultPanel
404 Editor load "This article doesn't exist." Offers All articles ErrorView
404 Row or editor action "That article no longer exists." The row is removed Toast
404 Unknown route "Page not found" ErrorView
429 Profile, reset link "Too many requests. You've asked for several links recently. Wait a few minutes, then try again." Button disabled Note
429 Log in, reset request "Too many attempts. For your security, wait a moment before trying again." With a countdown Alert + disabled button
500 Save "Save failed" + Retry. Changes are kept SaveStatus
500 Publish / unpublish "Couldn't publish" / "Couldn't unpublish" + Retry Toast
500 Load users "Couldn't load users. Check your connection and try again." + Try again Alert
500 Cancel invite "Couldn't cancel the invite. Try again in a moment." The dialog stays open Notice in dialog
500 Renew invite "Couldn't renew the invite. Try again in a moment." The row stays Expired Toast
500 Invite "Couldn't send the invite. Try again in a moment." The address stays Alert in dialog
500 Save display name "Couldn't save your display name. Your text is still here. Try again in a moment." Alert in card
500 Send reset link (profile) "Couldn't send the link. Check your connection and try again." Note
500 Delete "Couldn't delete the article, so it's still there. Try again in a moment." Inline in dialog
500 Upload "Upload failed. The server didn't accept the file. Your article hasn't changed." ImageUploader
network Anywhere "Couldn't reach RenewCMS. Check your connection and try again." Offline: banner Alert / toast / banner

API the design marked pending

Existing: GET /v1/auth/me, GET /v1/auth/registration, 429 with Retry-After on /v1/auth/login and /v1/auth/password-reset, and /static/uploadedImages/*.

Added by the Users and Profile screens, none of which exist yet:

  • GET /v1/user/: the users and their invites, newest first.
  • POST /v1/user/invite: sends an invitation email. 201, 409 for an existing account or a live pending invite.
  • DELETE /v1/user/invite/:id: cancels a Pending invite. 204. Owner only.
  • POST /v1/user/invite/:id/renew: replaces the code, resets the 3 days and created date, and resends the email. 200 with the invite. Owner only.
  • GET /v1/user/ and GET /v1/auth/me gain a role ("owner" or "admin"); the list gains email on accepted users.
  • POST /v1/user/invite becomes owner only, and always creates an admin.
  • GET /v1/settings: { "version": "..." }.
  • PATCH /v1/user/me: saves the display name. GET /v1/auth/me returns it.
  • GET /v1/auth/invite?c=CODE: looks up the invite on load, returns { email }, 404 or 410. POST /v1/auth/invite/accept takes { code, username, password } and returns 200 with the session cookie, 409 for a taken username, 404 or 410.

Still not supported, so shown disabled or omitted:

Renaming a title (shown read-only, with an explanation in Details), resending the verification email (the pending page explains how to start over), changing the email or username, removing users, promoting or demoting anyone, and any role other than owner and admin. Changing the password while logged in works only through the emailed reset link on the Profile page.

Added by the Updates card, none of which exists yet (owner only unless noted, see settings):

  • GET /v1/system/update (any verified user): { current, latest, available, notes: { version, released_at, items }, checked_at, can_apply, state, progress, error, started_at }. state is idle, downloading, verifying, ready, restarting, succeeded or failed. can_apply is false when the server hasn't been prepared, or for admins.
  • POST /v1/system/update/check: asks the release feed. 200, 429 with Retry-After, 500 when the feed can't be reached.
  • POST /v1/system/update/download: 202 starts the download and verification, 400 when there is nothing to update, 403, 409 when one is already running or the server isn't prepared, 422 when the file fails verification, 429.
  • POST /v1/system/update/restart: 202 starts the install and restart (or only a restart when no update is ready), 403, 409, 500.
  • A restart must survive the server going away: the SPA polls /healthz and GET /v1/system/update until they answer.

Added by the Media screens, none of which exist yet:

  • GET /v1/media/?q=&filter=&sort=&limit=&cursor=&hash=: the library, newest first by default. hash (SHA-256 hex) returns only the images with that content, which is how the browser checks for a duplicate before uploading. Each item carries id, filename, url, size, content_type, width, height (null for SVG), uploaded_by, created_at, used_in (count) and duplicate (bool). The response also carries the counts for the Unused and Duplicates tabs.
  • POST /v1/media/: multipart file, no article id. 201 with the item. It always stores the file: the duplicate check happens in the browser (hash with crypto.subtle, then GET /v1/media/?hash=), and when crypto.subtle is unavailable the check is skipped and any duplicate shows up under Duplicates. The server keeps each image's SHA-256. 413 for over 10 MB, 415 for another type, 400 for an unreadable image, 429 with Retry-After.
  • PATCH /v1/media/{id}: { filename }, the extension is kept and the URL does not change. 200, 404.
  • DELETE /v1/media/{id}: 204, 404. Deleting an image that is in use is allowed.
  • POST /v1/media/merge: { keep_id, merge_ids }. Rewrites article bodies from the merged copies' URLs to the kept image's URL, then deletes the copies. 200 with { articles_updated, merged }, 404, 409 when a listed copy is gone or the group changed.
  • GET /v1/media/{id}/usage: [{ id, title, published }], read from article bodies.
  • Images no longer belong to an article, and deleting an article no longer deletes images. POST /v1/article/{id}/image and DELETE /v1/article/{id}/image/{imageId} are replaced by the endpoints above.