12 KiB
Change list for implementation (status after pass 2; SEO round drawn)
SEO round: decisions (frames drawn)
Placement. A "Search and sharing" section sits under "Site and authors" in the Editor sidebar and in the mobile Details sheet. It is collapsed by default, shows a one-line summary ("Ready", "2 suggestions" or "Hidden from search") and is remembered per browser like Site and authors. It holds only the slug, the full URL and three actions: "Edit search and sharing", "What the frontend gets" and the suggestion list. The fields and live previews open in a slide-over (desktop) or a 92% bottom sheet stacked over the Details sheet (mobile), because the 280 px sidebar is too narrow for previews and the slide-over rule already covers forms with a live preview. The API preview is a 720 px dialog (desktop) or a bottom sheet (mobile).
Principles. RenewCMS does not render pages or inject meta tags. Every field is optional and the UI shows its fallback. No scores. Suggestions never block saving or publishing; publishing is allowed regardless. Suggestions are dismissible per article.
Fallback chain (resolved server-side, shown in the UI).
| Output | Order |
|---|---|
| title | seo_title, then article title; site title_template applied last |
| description | seo_description, then excerpt, then first sentence of the body, then site default_description |
| image | og_image_id, then cover_image_id, then site social image |
| canonical | canonical_url, then site base_url + "/" + slug |
| robots | noindex true gives "noindex, nofollow"; otherwise site default_robots |
| og/twitter title and description | social_title, social_description, then the resolved values above |
Soft ranges (counters, never blocks). Meta title 30–60 characters; description 70–160; slug "very long" above 75 characters. Counter turns neutral-warning (--ui-warning) outside the range, never red.
New article fields (PATCH /v1/article/{id}, all optional, snake_case). slug, excerpt (max 300), seo_title (max 120), seo_description (max 320), cover_image_id, cover_image_alt (max 200; the cover is "ready" only with alt text), social_title, social_description, social_image_id, canonical_url (absolute https URL), noindex (boolean; only sets a flag in the API, it doesn't change visibility), seo_dismissed (string[] of suggestion codes). Slug: lowercase letters, numbers, hyphens; unique per site; 409 slug_taken with suggestion ("-2"); auto-generated from the title until edited.
New site fields (PATCH /v1/site/{id}, owner only). site_name, title_template (default "%title% | %site%", must contain %title%), default_description (max 320), social_image_id (existing), default_robots ("index" | "noindex"; existing as robots).
New endpoints.
GET /v1/article/{id}/seo: resolved object, public for online articles, 404 for drafts to anonymous callers. Shape:{ title, description, canonical_url, robots, og: { type, title, description, image, image_alt, url, site_name, locale }, twitter: { card, title, description, image, image_alt }, image: { url, alt, source }, authors: [{ name, url, same_as: [] }], published_at, modified_at, fallbacks: { title, description, image, canonical }, json_ld }.json_ldis a ready Article object (headline, description, image, author[] with name, url and sameAs from author profile URL and social links, datePublished, dateModified, mainEntityOfPage).GET /v1/site/{key}/sitemap?page=&size=: published URLs only, total inX-Total-Count. Items:{ url, modified_at, noindex }. Decision: yes, shown on Integration under "Search and sharing" with a copy button; noindex articles are included with the flag so the frontend can skip them.GET /v1/article/?seo=ready|suggestions|hidden: filter behind "Needs attention" (suggestions) withseo_statusandseo_suggestions(count) added to list items.
Articles list. Quiet indicator inside the Status cell (and under the title on mobile cards): nothing when ready, a lightbulb chip "N" for suggestions, an eye-off chip "Hidden" for noindex. Toolbar gets a "Needs attention" toggle next to search; tabs and counts are unchanged. It behaves the same on a single-site install (independent of the Site filter, which stays hidden).
Authors. No change to the Authors screen. Profile URL (website) and social links feed authors[].url and same_as, and JSON-LD author[].sameAs; the API preview shows them.
Permissions. Anyone who can edit an article edits its SEO fields. Only the owner edits site SEO defaults. 401 and 403 rows join the existing HTTP table: 403 owner_only on Site SEO fields, 401 on a missing session.
Left out on purpose. Keyword scoring and focus keywords, readability scores, redirects management, schema types beyond Article, hreflang, robots.txt and sitemap rendering (RenewCMS only provides data), automatic image generation for social cards.
Open questions.
- Is
slugimmutable after first publish, or can it change (and break URLs with no redirects)? - Does the API return the SEO object for drafts to authenticated callers (for frontend previews)?
- Should noindex articles be excluded from the sitemap endpoint instead of flagged?
- Is title_template applied by the API (resolved title) or only returned for the frontend to apply?
- Backfill: generate slugs for existing articles from their titles at migration, with
-2suffixes on conflicts? - Should excerpt be exposed in the list payload as the article teaser?
Status. Frames drawn on the Back-office board (groups: Articles: search status, Editor: search and sharing, Editor: what the frontend gets, Sites: SEO defaults), Integration page group added, specs updated. Placement rationale: the sidebar holds only the summary, address and suggestions because it is 280 px wide; previews and fields need a slide-over.
Ordered by implementation dependency: tokens and shell first, then shared components, then screens. "Frontend only" means no backend work is needed.
| # | Screen | Change | Done in design? | Needs backend (yes/no, what) | Needs new tokens or components | Frontend only |
|---|---|---|---|---|---|---|
| 1 | All | Add the --rc-* tokens (sand, on-sand, four soft grounds, overlay, two shadows) to the app CSS, light and dark (foundations.md) |
Yes | No | 9 new tokens | Yes |
| 2 | All | One sidebar, topbar and mobile drawer; Settings parent and child selected states; Activity sits between Users and Integration | Yes | No | NavItem, NavGroup | Yes |
| 3 | All | Overlay pattern: dialog (desktop) and bottom sheet (mobile); slide-over only for the variant form and record details; footer secondary left, primary right | Yes | No | Modal wrapper rules (components.md) | Yes |
| 4 | All | CodeSnippet is the only code block (Integration, Images, API keys, base URL, image URL) | Yes | No | CodeSnippet | Yes |
| 5 | All | Copy glossary pass (Delete vs Remove, Save vs Update, toast phrasing) | Yes, by search; not every line re-read | No | None | Yes |
| 6 | Settings | "Media" card with "Manage image variants" and "Cache usage" | Yes | No | MediaLinksCard | Yes |
| 7 | Media, picker | Variant count badge on grid, list and mobile rows | Yes | No (variant counts already come with GET /v1/media?variants=) | Badge "N variants" | Yes |
| 8 | Media | "With variants / Without" filter and "Largest total size" sort in the Media toolbar | Drawn on the Variants board only, not folded into the Media screen | No (GET /v1/media?variants=&sort=total_size exists) | None | Yes |
| 9 | Image variants | Variant form as slide-over (desktop) and tall sheet (mobile) | Yes | No | None | Yes |
| 10 | New article | Title only. Body removed from the modal; the editor opens straight away | Yes | Maybe: confirm POST /v1/article/ accepts an empty body (it is "required" today); otherwise the frontend sends an empty string |
NewArticleModal | Yes if empty body is accepted |
| 11 | Editor | Remove the 10,000 character limit, counter, limit and over-limit states; neutral count | Yes | Yes: remove the body length validation on POST /v1/article/ and PATCH /v1/article/{id} |
EditorStatusBar | No |
| 12 | Editor | Insert-link button (default, hover, active, disabled), tooltip, LinkModal, link popover and touch sheet, rendered link, toolbar scrolling at 390 px | Yes | No (editor only) | LinkModal, LinkPopover, ToolbarButton | Yes |
| 13 | Articles | Pagination with page and size (5, 25, 50, 100; default 25), URL state, loading, error, out of range |
Yes | Yes: page, size on GET /v1/article/ (same convention and 400 as Activity); total in X-Total-Count, array body unchanged; Integration reference updated |
Paginator, PageSizeSelect | No |
| 13b | Media | Pagination under the grid and list, same Paginator and PageSizeSelect as Articles; page and size in the URL; saved size preference |
Yes (9 states in the Media group) | Yes: page, size on GET /v1/media/ (replaces cursor and limit); total in X-Total-Count, array body unchanged; Integration reference |
None (reuses Paginator, PageSizeSelect) | No |
| 14 | Activity | Failed-action and security cards as toggles, synced chip and segment | Yes | No | FilterCardToggle | Yes |
| 15 | Activity | Export menu and dialog, CSV: current view (active filters) and all activity (no filters, optional from/to). Preparing, started, failed, nothing to export, 403 |
Yes | No: GET /v1/activity/export is implemented (CSV, synchronous, owner and admin, same filters as the list) |
ExportMenu, ExportDialog | Yes |
| 16 | Activity | JSON export (format control CSV or JSON) | Yes, one state, labelled proposed | Yes (proposed): format=json on GET /v1/activity/export |
Segmented format control | No |
| 17 | Integration | Images and variants section, URL builder, response bodies unknown_variant and unsupported_parameter |
Yes | No | UrlBuilder | Yes |
| 18 | All | States matrix for controls (hover, focus-visible, active, disabled) | Yes (board "States matrix: controls") | No | None | Yes |
| 19 | All | Contrast fixes: paginator current page border, no muted text on sand | Yes | No | None | Yes |
| 20 | All | Spacing, type and icon sizes made consistent (foundations.md, "One value per role") | Yes in the frames listed there | No | None | Yes |
| 21 | Settings, sidebar | Sites screen under Settings → Sites ("Publishing" group); "Publishing" card on Settings. Owner only: hidden for other roles, 403 page on a direct link. Key immutable after creation | Yes | Yes: site table, GET/POST/PATCH/DELETE /v1/site/ (writes owner only; PATCH rejects a changed key), reassign_to, seed from PUBLIC_URL |
SitesTable, SiteForm, SlugField (locked variant), ReassignDialog | No |
| 22 | Sidebar | Authors entry between Media and Users, all roles. Owner and admin manage; other roles read-only list and detail | Yes | Yes: author table, GET /v1/author/ (any role), POST/PATCH/DELETE (owner and admin; a user may PATCH their own linked author), POST /v1/author/me (any role) |
AuthorsTable, AuthorForm, SocialLinksRepeater, MediaField | No |
| 23 | Editor | "Site and authors" section in every Editor frame (collapsed by default): Site select (any role that can edit the article), reorderable authors, inline create (owner and admin), hint | Yes | Yes: site_id, author_ids[] on PATCH /v1/article/{id} |
MetaSection, AuthorList, AuthorSelect, SiteSelect, CreateAuthorHint | No |
| 24 | Articles | Authors column; Site column and filter when more than one site | Yes | Yes: site filter, site and authors in the list payload |
AuthorStack | No |
| 25 | Profile | "Author profile" card in every Profile frame, one-click create, edit own author (user link locked) | Yes | Yes: POST /v1/author/me | AuthorProfileCard | No |
| 26 | Backfill | Existing articles get the default site; authors start empty | n/a | Yes: migration | None | No |