17 KiB
Sites
Route: /settings/sites. API: GET /v1/site/ · POST /v1/site/ · PATCH /v1/site/{id} · DELETE /v1/site/{id}?reassign_to={id}.
A site is a frontend that displays content: name, key, base URL, default social image, default robots policy, optional locale and a default flag. Each article belongs to exactly one site. A default site always exists and cannot be deleted; the first is seeded once, at migration, from PUBLIC_URL (the CMS's own address, so only a starting value: the owner is expected to set the frontend's address). The base URL must use https, except for loopback hosts (localhost, 127.0.0.1, ::1), so a local frontend works. is_online stays the global publish switch.
Roles. Only the owner can create, edit, set the default or delete a site. Admins and other roles don't see the Sites entry under Settings, and the "Publishing" card is hidden for them. A direct link to /settings/sites shows a 403 page. Choosing a site for an article is not site management: anyone who can edit an article can set its site in the editor and read the site list (GET /v1/site/).
Site key. The key is set when the site is created and can't be edited afterwards. The edit form shows it read-only with a lock and the text "Used in API paths, so it can't change." There is no editable-key state and no key-change warning.
Shared pieces. The list, slide-over, delete-with-reassign dialog, slug field and media field are defined once in DirectoryFrame.dc.html and referenced by both Sites and Authors; nothing is drawn separately for either screen. AppFrame.dc.html holds the shell (sidebar, Settings card, editor section, Profile card).
Visual reference: RenewCMS Screens Back-office.dc.html, groups "Sites", "Sites: form", "Sites: delete". Every state is drawn in light and dark (theme switch) at 1440×900 and 390×844. Copy is exact. Strings in a Copy cell are separated by " · " and listed in reading order. Dialog and form submit states ("Saving…", "Moving articles…") use the button loading pattern from the controls matrix and are not drawn per screen.
Sites (group "Sites", 9 states)
Only the owner can create, edit, set the default or delete a site. Other roles don't see the Sites entry under Settings; a direct link shows a 403 page. A site name can still appear read-only in the article editor and the Articles list. New child of Settings, under a "Publishing" heading in the sidebar (Settings also gets a "Publishing" card with a "Manage sites" row, owner only). Columns: name with key, base URL, article count. The default site carries a "Default" badge and has no delete action. A site with robots set to noindex shows a "Noindex" badge. List, row menu and dialogs come from DirectoryFrame and are shared with Authors.
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | Default | The list loaded. Four sites, Renew is the default. Owner only. | Sites · The frontends that display your content. · Add site · Site · Base URL · Articles · Default · Noindex · 268 articles · 38 articles · 6 articles · No articles |
| 2 | Only the default site | First run: the seeded site only. A hint under the list, no empty-state wall. | Add another site if your content is shown on more than one frontend. Each article belongs to one site. |
| 3 | Loading | The first load, or a retry. Skeleton rows appear after 150 ms. | |
| 4 | Error | A 500 or network failure on the list request. | Couldn't load sites · Check your connection and try again. · Try again |
| 5 | Row menu | The ellipsis button, or Enter on the focused menu button. Mobile: bottom sheet titled with the site name. | Edit · Set as default · Delete… |
| 6 | Default site: delete unavailable | Menu on the default site. "Delete…" is aria-disabled, stays focusable and explains itself: tooltip on desktop, text in the sheet on touch. | The default site can't be deleted. Make another site the default first. |
| 7 | Set as default: confirm | "Set as default" in the row menu. On 200 the badge moves and a toast confirms. The button reads "Saving…" while the request runs. | Make Journal the default site? · New articles start on this site. Articles that are already on other sites stay where they are. · Cancel · Make default · Default site changed · "Journal" is now the default site. |
| 8 | Hidden entry (admin) | An admin opens Settings. The sidebar has no Publishing group and no Sites entry, and the page has no Publishing card. Roles without Settings have nothing to hide. | No Publishing group · No Sites entry · No "Manage sites" row |
| 9 | Forbidden (403) | A role other than owner opens /settings/sites from a bookmark or a link. The sidebar has no Sites entry, so the page explains instead of redirecting. The API answers 403. | You don't have access to Sites · Only the owner can manage sites. Ask the owner of this site if you need a change. · Back to Settings |
Sites: add and edit (group "Sites: form", 7 states)
Owner only. A slide-over, because the form has a live preview (the canonical URL). Fields: name, key, base URL, default social image (media picker), default robots policy, optional locale, default switch. The key is set when the site is added and can't be edited afterwards: the edit form shows it read-only with a lock and the text "Used in API paths, so it can't change." Closing with changes asks "Discard changes?" with the existing discard dialog. The slug field, media field and picker are the shared ones from DirectoryFrame (the picker is drawn once, in Authors: form). Locale error: "Use a language tag like en or fr-CH."
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | Add: empty | "Add site" in the page header, or the bottom button on mobile. | Add site · Name · e.g. Journal · Key · Generated from the name · Base URL · https://blog.example.com · The public address of the frontend. Use https and no path. · Default social image (optional) · Choose image · Used when an article has no image of its own. · Default robots policy · Index · Noindex · Search engines may list this site's pages. · Locale (optional) · e.g. en or fr-CH · A language tag. Leave empty if the site has no single language. · Make this the default site · New articles start here. The current default is Renew. · Canonical URL preview · Cancel · Add site |
| 2 | Add: key from the name | Typing a name fills the key. The pencil unlocks it until the site is added. | journal · Auto · Lowercase letters, numbers and hyphens. Used in API paths. It can't be changed after the site is added. |
| 3 | Add: URL not valid | On blur or submit. Nothing is sent. A URL with a path and text that is not a URL get their own messages, drawn the same way. | Use https. For example https://blog.example.com. · Remove the path. Use only the host, like https://blog.example.com. · Enter a full URL, like https://blog.example.com. |
| 4 | Add: duplicate key | 409 on submit, or on blur after a check. | A site with the key "journal" already exists. Choose another key. · Use journal-2 |
| 5 | Add: save failed | 500 or network failure. The values stay. | Couldn't save the site. Your changes are still here. Try again in a moment. |
| 6 | Edit: default site | Edit on the default site. The key is read-only with a lock, because it is used in API paths. The switch is on and locked. | Edit Renew · Key · renew · Used in API paths, so it can't change. · Default site · New articles start on the default site. To change it, make another site the default. · Save changes |
| 7 | Edit: other site, noindex | Edit on a non-default site. | Edit Projects · Key · projects · Used in API paths, so it can't change. · Noindex · Asks search engines not to list this site's pages. · Make this the default site |
Sites: delete (group "Sites: delete", 3 states)
Owner only. A site with articles cannot be deleted without a destination: the dialog has a select and shows how many articles move. Destination defaults to the default site. Without reassign_to the server answers 409. This is the shared ReassignDialog from DirectoryFrame; its failed state is drawn once, in Authors: delete.
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | With articles | "Delete…" on a site that has articles. The button reads "Moving articles…" while the request runs. On 204 the row leaves the list and a toast reads "Site deleted" · "38 articles moved to Renew." | Delete this site? · "Journal" will be deleted. 38 articles belong to it, so choose a site to move them to. This can't be undone. · Move articles to · Renew · renew.example · 38 articles will move to Renew. Their canonical host changes to renew.example. · Cancel · Move 38 articles and delete |
| 2 | Choose destination | The select is open. The site being deleted is not listed. | Journal is not offered. Options show name and host. |
| 3 | No articles | A site with zero articles: plain confirm, no select. | Delete this site? · "Archive" will be deleted. No articles belong to it. This can't be undone. · Delete site |
Sites: pagination (group "Sites: pagination", 10 states)
Route: /settings/sites?page=2&size=50. API: GET /v1/site/?q=&page=&size= (design assumption to confirm: q, page, size and X-Total-Count on the site list; today the list returns every site). Owner only, like the rest of Sites. Drawn with 61 sites (3 pages) and generated rows.
URL. /settings/sites?page=2&size=50. page and size are the parameter names (as Articles and Activity). size is one of 5, 25, 50, 100; default 25. Defaults are omitted from the URL (page=1, size=25). Combined with search: /settings/sites?q=renew&page=2. A search change or a size change resets page to 1 and drops it from the URL. An invalid size or page (not a positive integer) is replaced silently by the default with history.replace. A page past the last page falls back to the last page: the URL is replaced by ?page=<last> and the info notice "That page doesn't exist. Showing the last page." is shown, as on Articles. Browser Back returns to the previous page of results.
Pager. The same component as Articles and Media, in the same footer position (below the list, right-aligned controls, summary and page-size select on the left; mobile: summary and select on one row, then Previous, "Page X of Y", Next). Nothing new is drawn. Sizes are the sizes of the existing pattern: 5, 25, 50, 100 (default 25). API: total in X-Total-Count, array body unchanged. Keyboard and aria as Articles. The only difference from Articles: on a single page the page-size select is hidden too and the footer holds only the count line "N sites" (Articles keeps its summary and select). Existing frames of this screen that show a short list (the "Default" frames above) don't draw the count line yet; it appears whenever the list has loaded.
Search on Sites. The Sites screen has no search field today. States 8 and 9 need one, so the pagination frames add the Authors field with the placeholder "Search by name or key" (name, key and base URL are matched). Treat it as a proposal; if you prefer no search on Sites, drop states 8 and 9 and the q parameter.
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | First page | /settings/sites. Previous and First are disabled (40% opacity, aria-disabled). Mobile: Previous disabled. | Showing 1–25 of 61 · 25 per page · Page 1 of 3 |
| 2 | Middle page | /settings/sites?page=2. Current page uses --rc-sand and --rc-on-sand with a --ui-primary border; ellipses mark skipped ranges once there are more than 7 pages. | Showing 26–50 of 61 · Page 2 of 3 |
| 3 | Last page | /settings/sites?page=3. Next and Last are disabled. | Showing 51–61 of 61 · Page 3 of 3 |
| 4 | Single page | Everything fits on one page (4 sites). The pager and the page-size select are hidden; the count line stays in the footer position. Zero results use state 8 instead. | 4 sites |
| 5 | Page size menu | The select opens upward. Sizes are the Articles sizes: 5, 25, 50, 100; default 25. The chosen size is saved as size and the page resets to 1. | 5 per page · 25 per page · 50 per page · 100 per page |
| 6 | Loading a page | Next, Previous, a page number or a size change was used. The rows are replaced by the existing skeleton rows (8 desktop, 6 mobile). The pager stays and is disabled (40% opacity, not focusable); the summary reads "Loading page 3…" and the page number shown is still 2. | Loading page 3… |
| 7 | Error loading a page | 500 or network failure on a page change. The rows of page 2 stay, the pager keeps "Page 2", and the inline error with Retry sits above the pager. Retry repeats the request for page 3. | Couldn't load page 3. Showing page 2. · Check your connection and try again. · Retry |
| 8 | Empty after search | A search matched nothing. The existing empty state replaces the list; no pager, no count. Clear search returns to /settings/sites and page 1. | No sites match "blog" · Check the spelling, or search for a shorter part of the name. · Clear search |
| 9 | Search resets to page 1 | Searching "renew" from page 4 goes to /settings/sites?q=renew (page omitted). Any search change, and any size change, resets the page to 1. | Showing 1–25 of 27 |
| 10 | Long labels on mobile | A large total on a 390 px screen. The summary and the page-size select share one row and wrap onto two if needed; Previous, the "Page 6 of 13" label and Next stay on one row (the two buttons flex, the label never wraps). Totals over 999 use thousands separators in both labels. | Showing 126–150 of 312 · Page 6 of 13 |
Route: /settings/sites. API: PATCH /v1/site/{id} {site_name, title_template, default_description, social_image_id, default_robots}.
Owner only, like the rest of Sites. The existing add and edit form gains three fields under the default social image (reused, not duplicated): Site name, Title template with a live example, and Default description. The default robots policy keeps its place. A box under the form explains how articles use these defaults. Sites has no separate read-only details view: the edit form is the details, and other roles get the existing 403 page (403 owner_only on PATCH, 401 without a session).
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | Edit: SEO defaults | Edit on the default site. | Default social image (optional) · Site name · Shown in titles through the template, and sent as the Open Graph site name. · Title template · Use %title% for the article title and %site% for the site name. · Example · How we price a bathroom renovation | Renew · Default description (optional) · Used when an article has no description, excerpt or body text. · How articles use these defaults · Title · Article title, then the template. · Description · Used last, after the article's description, excerpt and body. · Image · Used when an article has no cover image. · Robots · Used unless an article is hidden from search engines. |
| 2 | Add: defaults | New sites start with the default template and nothing else. | Site name · e.g. Renew · %title% | %site% · One or two sentences about the site. |
| 3 | Template: custom | The example updates as the owner types. | Example · How we price a bathroom renovation – Journal |
| 4 | Template: not valid | The template needs %title%. Nothing is sent. | The template must contain %title%. |
| 5 | Save failed | 500 or network failure. | Couldn't save the site. Your changes are still here. Try again in a moment. |
Owner only. Non-owners get the existing 403 page. Sites has no read-only details view; the edit form is the details.
Sites: SEO defaults (SEO round)
Route: /settings/sites. API: PATCH /v1/site/{id} {site_name, title_template, default_description, social_image_id, default_robots}.
Owner only, like the rest of Sites. The existing add and edit form gains three fields under the default social image (reused, not duplicated): Site name, Title template with a live example, and Default description. The default robots policy keeps its place. A box under the form explains how articles use these defaults. Sites has no separate read-only details view: the edit form is the details, and other roles get the existing 403 page (403 owner_only on PATCH, 401 without a session).
| # | State | Trigger | Copy |
|---|---|---|---|
| 1 | Edit: SEO defaults | Edit on the default site. | Default social image (optional) · Site name · Shown in titles through the template, and sent as the Open Graph site name. · Title template · Use %title% for the article title and %site% for the site name. · Example · How we price a bathroom renovation | Renew · Default description (optional) · Used when an article has no description, excerpt or body text. · How articles use these defaults · Title · Article title, then the template. · Description · Used last, after the article's description, excerpt and body. · Image · Used when an article has no cover image. · Robots · Used unless an article is hidden from search engines. |
| 2 | Add: defaults | New sites start with the default template and nothing else. | Site name · e.g. Renew · %title% | %site% · One or two sentences about the site. |
| 3 | Template: custom | The example updates as the owner types. | Example · How we price a bathroom renovation – Journal |
| 4 | Template: not valid | The template needs %title%. Nothing is sent. | The template must contain %title%. |
| 5 | Save failed | 500 or network failure. | Couldn't save the site. Your changes are still here. Try again in a moment. |
Owner only. Non-owners get the existing 403 page. Sites has no read-only details view; the edit form is the details.