201 lines
25 KiB
Markdown
201 lines
25 KiB
Markdown
# Integration (API reference)
|
||
|
||
Route: `/integration`. Component: `IntegrationPage.dc.html`, mounted by `AppFrame`. The API keys sub-page is in `screens/integration-api-keys.md`.
|
||
|
||
Visual reference: `RenewCMS Screens Back-office.dc.html`, group "Integration".
|
||
|
||
## Navigation decision
|
||
Sticky section index on the right (desktop), "Jump to" select (mobile). A reference is read in one scroll and searched with the browser, so tabs would hide content and break find-in-page and deep links; the index keeps one page with anchors and shows position.
|
||
|
||
## Page copy
|
||
- Header: breadcrumb "Integration", title "API reference", description "Everything a developer needs to build on RenewCMS."
|
||
- Intro: "RenewCMS is headless: it stores your content and your own code displays it. There are two ways in. Public read-only access lets a website read online articles. API keys let a script or service manage articles, media and users without the back-office."
|
||
- Base URL: label "Base URL", a CodeSnippet with one tab ("Base URL") and the button "Copy" / "Copied" (2 s, announced).
|
||
- Authentication, card "Public": "No key needed. Two read-only routes, and only online articles are returned." `GET /v1/article/`, `GET /v1/article/{id}`.
|
||
- Card "API key": "Send `Authorization: Bearer rcms_…`. Requests act as the user who created the key, with that user's role (owner or admin). A key never has more rights than its creator." CodeSnippet tabs curl, fetch (GET /v1/auth/me). "Keys start with `rcms_` and are shown in full once, when you create them." Link "Manage API keys".
|
||
- Note 1: "A key can't create, list or revoke API keys, log in, register, verify or reset a password, or call anything under `/v1/system`. Those routes answer 403 "This action needs a real session." An invalid, unknown, expired or revoked key answers 401, even on public routes, and so does a key whose user was deleted or unverified."
|
||
- Note 2: "Each key can make 120 requests per minute. Beyond that the API answers 429 with a `Retry-After` header."
|
||
- Groups, in index order: Authentication, Articles (7), Search and sharing (2), Content types (5), Entries (5), Built-in types (2), Media (10), Sites and authors (2), Images and variants, Users and invites (6), Settings (1), Session only (35 routes in 6 sub-groups: System, Sign in and account, API key management, Sites and authors writes, Image variants, Activity; every `/v1/system` route is in this group), Errors, Notes. Session-only banner: "These routes need a signed-in browser session. With an API key they answer 403 "This action needs a real session.""
|
||
- Errors: "Every error is a plain-text body with a single sentence. The status code is in the HTTP status line, and the Content-Type is text/plain." Table 400, 401, 403, 404, 429, 500 with the real messages below. The 403 row has a note line under it: "Site SEO defaults are owner only. Article SEO fields need only the right to edit the article." (12/18, `--ui-text-muted`, in the cause column, 2 px under the cause text). It is the only row with a note; rows below it move down by the wrapped note height (two 18 px lines on desktop, three on mobile, plus 2 px). Sample: a status line plus the text body (`HTTP/1.1 403 Forbidden`, `Content-Type: text/plain`, blank line, `This action needs a real session.`).
|
||
- Notes: image URL prefix, CORS (existing copy), and "Article routes accept `?format=markdown`, `?format=html` or `?format=json` to choose how `body` is returned. With `json`, `body` is the stored JSON structure. Articles have `body_format: markdown` by default."
|
||
|
||
## EndpointCard states
|
||
- Collapsed (44 px row): method badge, mono path, one-line description, chips, chevron. Desktop is one line; mobile is three (method and path, description, chips).
|
||
- Expanded: "Request body" table (Name, Type, Required, Rule; "No request body." when empty), tabs fetch / curl / Response in one CodeSnippet with Copy, then "Errors": status chip plus one-line caption (also its `title` tooltip).
|
||
- Session only: dashed border, muted fill, lock icon, "Session only" chip, no method colour, no expand.
|
||
- Chips: access "Public", "Verified user", "Owner only"; key "API key OK" (success soft) or "Session only" (dashed, muted).
|
||
- Method badges: GET `--ui-bg-inverted`, POST success soft, PATCH warning soft, DELETE error soft. Existing tokens only.
|
||
|
||
## States
|
||
Desktop 1440×900, mobile 390×844, light and dark.
|
||
1 Default · 2 Media group, POST /v1/media/ expanded with curl tab · 3a Base URL copied · 3b CodeSnippet copied · 4 Authentication · 5 Session only · 6 Errors · 7 Navigation (index active item Media; mobile select open).
|
||
|
||
## Error messages
|
||
Plain text, one sentence. Used in the table and in the per-endpoint error captions.
|
||
- 400: "The request cannot be processed due to a mismatch in the format of the body." / "The expiry must be a date in the future." Image requests (`/media/photo.png?v=`) answer 400 with a JSON body (`status`, `error`, `message`, `detail`) in two cases: `{ "status": 400, "error": "unknown_variant", "message": "Unknown variant", "detail": "unknown" }` for an unknown or disabled variant, and `{ "status": 400, "error": "unsupported_parameter", "message": "Unsupported parameter", "detail": "width" }` for any other query parameter (`width`, `height`, `format`, `quality`). Both are rows of the "Responses" table in "Images and variants".
|
||
- 401: "You must be logged in to access this resource."
|
||
- 403: "This action needs a real session." (session-only routes called with a key) / "Only the owner can do this." / "You must verify your account to access this resource." Note under the 403 row: "Site SEO defaults are owner only. Article SEO fields need only the right to edit the article." The inline parenthetical that said the same thing in the row's cause text was removed to avoid saying it twice.
|
||
- 404: "Article not found" / "Image not found" / "Invite not found" / "API key not found" / `site_not_found` (GET /v1/site/{key}/sitemap only; body is the code, not a sentence)
|
||
- 429: "Too many requests. Please try again later." A per-key limit also sends `Retry-After`.
|
||
- 500: "Internal server error"
|
||
- 409, only as examples under their endpoints: `POST /v1/user/invite` "This address already has an account."; `POST /v1/media/merge` "Some of the images to merge changed. Refresh and try again." There is no 409 or 422 row in the table.
|
||
- 413 and 415 on `POST /v1/media/` keep their earlier plain captions; no real message is known.
|
||
|
||
## Request bodies
|
||
- `POST /v1/article/`: `title` (string, required, 3 to 200 characters), `body` (string, optional, no length limit).
|
||
- `PATCH /v1/article/{id}`: `body` (string, optional, no length limit).
|
||
- `PATCH /v1/media/{id}` (rename): `filename` (string, required).
|
||
- `POST /v1/media/merge`: `keep_id` (number, required, > 0), `merge_ids` (number[], required, at least 1, each > 0).
|
||
- `POST /v1/media/`: multipart/form-data, 1 file field (shown as "multipart/form-data, 1 file field").
|
||
- `PATCH /v1/user/me`: `display_name` (string, required, max 60 characters).
|
||
- `POST /v1/user/invite`: `email` (string, required, valid email).
|
||
- `POST /v1/system/update/restart`: optional body `{ "kind": "update" | "restart" }`.
|
||
|
||
## Component inventory and Nuxt UI v4
|
||
- EndpointCard (extended): `UCollapsible` (trigger is a button with aria-expanded), `UBadge` (method, chips, status), `UTable` (body fields), CodeSnippet (fetch, curl, Response). CodeSnippet is the only code block in the product: Integration, Integration images, API keys (one-time key) all use it. Selected tab: 2 px `--ui-primary` underline. Syntax colouring was dropped so there is one rendering.
|
||
- SectionIndex (new): custom `<nav>` of anchor buttons with `aria-current`; sticky, scroll-spy. Mobile: `USelectMenu` labelled "Jump to".
|
||
- SessionOnlyGroup (new): `UAlert`-style info note plus list rows; no new colour.
|
||
- ErrorTable: `UTable`. Info notes: existing note style (or `UAlert` variant `naked`).
|
||
|
||
## Assumptions to confirm
|
||
- Paths not in the brief: `/v1/settings`, `/v1/system/update/*`, `POST /v1/auth/register|verify|logout`.
|
||
- [to confirm] The multipart field name on `POST /v1/media/` is written as `file` as a placeholder.
|
||
- Response field names in the samples (for example `articlesUpdated`, `display_name` in the profile response) are illustrative.
|
||
- Code samples use a simple keyword and string colouring.
|
||
|
||
## Tokens
|
||
No new tokens. Foundations check: colours, radius (0) and spacing come from existing `--ui-*` and `--rc-*` values; every control has a 2 px primary focus ring; index, select and cards are keyboard operable. Open question: the board hard-codes a few pixel sizes, as the other screens do.
|
||
|
||
## Copy per state (generated from the board source, pass 2)
|
||
|
||
Every state below is drawn in light and dark (theme switch on the board), at 1440×900 and 390×844 (both frames sit side by side). Board: `RenewCMS Screens Back-office.dc.html`. Copy is exact; where a cell is empty the state reuses the fixed copy.
|
||
|
||
### Integration: API reference (group "Integration", 8 states)
|
||
|
||
Route: `/integration`. API: none (static). Replaces "Connect your website"..
|
||
|
||
Navigation: a sticky section index on the right on desktop, and a "Jump to" select on mobile. A long reference is read in one scroll and searched with the browser, so tabs would hide content and break find-in-page; the index keeps every group on one page, links to anchors and shows where you are. Written for a developer. The base URL is window.location.origin. The API keys screen has its own board.
|
||
|
||
| # | State | Trigger | Copy |
|
||
|---|---|---|---|
|
||
| 1 | 1. Default | Opened from the sidebar or the Home card. Every endpoint card is collapsed. Desktop shows the sticky index on the right; mobile shows the "Jump to" select above the content. | "Integration" |
|
||
| 2 | 2. Group scrolled, one card expanded | POST /v1/media/ opened in the Media group: multipart body with 1 file field, curl tab shown. Only one example block is visible per card. Other cards stay collapsed. | "Integration" |
|
||
| 3 | 3a. Copied: base URL | The base URL was copied. The button reads "Copied" for 2 s and this is announced. | "Integration" |
|
||
| 4 | 3b. Copied: CodeBlock | The Copy button in the API key example after a click. Back to "Copy" after 2 s. | "Integration" |
|
||
| 5 | 4. Authentication | The API key card with its example, the "Manage API keys" link (goes to the API keys board) and the two notes. | "Integration" |
|
||
| 6 | 5. Session only | Routes API keys cannot call. Dashed, muted and locked, so they read as unavailable and not as an error. | "Integration" |
|
||
| 7 | 6. Errors | Status table and the plain-text error response (status line plus a one-sentence body). Key-specific causes are in the 401, 403 and 429 rows. | "Integration" |
|
||
| 8 | 7. Navigation | Desktop: the sticky index with the active item (Media). Mobile: the "Jump to" select open. Keyboard: the index buttons and the select are in the tab order, Enter jumps, Escape closes the list. | "Integration" |
|
||
|
||
### Integration: images and ?v= (group "Integration: images", 9 states)
|
||
|
||
Route: `/integration#images`. API: GET /v1/image-variants (existing).
|
||
|
||
New section "Images and variants" on the Integration page, after Media and before Errors. It explains the ?v=<variant-name> parameter, shows one CodeSnippet with curl, HTML and JavaScript tabs, a table of enabled variants, a URL builder and the response codes. When there are no variants, admins see a link to /settings/media/variants and other users see plain information. A removed variant is not replaced by the original: the request returns 400 with the body shown in the builder.
|
||
|
||
| # | State | Trigger | Copy |
|
||
|---|---|---|---|
|
||
| 1 | Many variants | Twelve enabled variants. The table scrolls at 264 px. On mobile the table and snippet scroll horizontally. | |
|
||
| 2 | One variant | A single enabled variant. The snippets use its name. | |
|
||
| 3 | No variants, admin | Empty state with a link to /settings/media/variants. | |
|
||
| 4 | No variants, not an admin | Plain information, no link. | |
|
||
| 5 | Variant removed | The builder holds "hero", which was deleted. Warning with the exact 400 body. | |
|
||
| 6 | Loading | Skeleton blocks while variants load. Snippets are hidden until the list is known. | |
|
||
| 7 | Error loading | 500 or network failure. Snippets still render with the placeholder name. | |
|
||
| 8 | Snippet copied | Copy pressed on the snippet. The label reads Copied for 2 s and is announced politely. | |
|
||
| 9 | URL builder copied | Copy pressed on the built URL. | |
|
||
|
||
## Pass 2 notes
|
||
- The base URL field and the image URL builder output are CodeSnippets with a single tab.
|
||
- Page padding 24 px (16 px on mobile), page title 20/28, section headings 18/26, card titles 15/24, sub headings 14/22 (foundations.md).
|
||
|
||
## Search and sharing (SEO round)
|
||
New group "Search and sharing" after Articles, in the sidebar index and the mobile Jump to select. Two cards in the existing endpoint style (collapsed, method badge, auth badge "Public", example, 401 and 404 rows, copy buttons):
|
||
- GET /v1/article/{id}/seo: the resolved SEO object. Fields: title, description, canonical_url, robots, og{type,title,description,url,site_name,image,image_alt,locale}, twitter{card,title,description,image,image_alt}, image{url,alt,source}, authors[{name,url,same_as}], published_at, modified_at, fallbacks, json_ld. Drafts answer 404 to anonymous callers.
|
||
- GET /v1/site/{key}/sitemap?page=&size=: yes, shown per site by key. Items: { url, modified_at, noindex }. Total in X-Total-Count. Sizes 25, 50, 100, 500, default 100. Errors: 400 "The request cannot be processed due to a mismatch in the format of the body." (same text as the other routes; bad `page` or `size`), 401, 404 `site_not_found` (unknown site key; this is the body the API returns, shown as a code and not rewritten as prose).
|
||
PATCH /v1/article/{id} lists the new optional fields (slug, excerpt, seo_title, seo_description, cover_image_id, cover_image_alt, social_title, social_description, social_image_id, canonical_url, noindex, seo_dismissed). The 403 row of the HTTP status table adds: site SEO defaults are owner only; article SEO fields need only the right to edit the article.
|
||
|
||
|
||
## Search (jump-to palette)
|
||
Board: group "Integration", states 8a to 8h, desktop 1440×900 and mobile 390×844, light and dark. Search never hides or filters the page. It only jumps, so find-in-page and deep links keep working.
|
||
|
||
### Behaviour
|
||
- Trigger, desktop: a field-style button in the page header, right of the title block, 320×36. Placeholder "Search the API reference…", "/" key hint. Mobile: 44×44 icon button right of "Jump to", inside the sticky bar. Opens with "/" (when focus is not in an input), Ctrl+K and Cmd+K. Both shortcuts are announced with aria-keyshortcuts.
|
||
- Palette: modal, 720 px wide, 96 px from the top, on the existing overlay. Mobile: full-screen sheet with a "Cancel" button (44 px) in the input row. Focus goes to the input; on close it returns to the trigger.
|
||
- Groups: "Endpoints" (max 6 rows, then "N more endpoints. Keep typing to narrow the list."), then "Sections". Empty query: one group "Suggested" with 5 rows.
|
||
- Match: every space-separated term must appear (case-insensitive substring) in method, path, description or access chip, or in a request field name, or in an error code or message. Rows matched only in a field or error show a second line ("Body field", "Query parameter" or "Error") with the match. Terms are highlighted with --rc-sand / --rc-on-sand. Rows matched in the path or description sort first.
|
||
- Selecting: Enter or click closes the palette, scrolls to the card (or section heading), expands the card, moves focus to the card button, and flashes the row (--rc-sand fill, 2 px --ui-primary ring, 1.5 s, then the open-card fill). With prefers-reduced-motion the flash is a static ring with no fade. The index active item updates through the existing scroll-spy. The URL hash becomes #ep-<id> so the result can be shared.
|
||
- Keys: Up and Down move (wrap), Enter opens, Esc closes, Home and End go to the first and last row. Typing resets the selection to the first row.
|
||
- Roles: input role="combobox" with aria-expanded, aria-controls, aria-autocomplete="list", aria-activedescendant; list role="listbox"; groups role="group" with aria-labelledby; rows role="option" with aria-selected and a full aria-label (method, path, description, access). A polite live region announces the count.
|
||
|
||
### Row anatomy
|
||
- Endpoint: method badge (existing colours), mono path 13/500, one-line description (ellipsis), access chip. Session-only: dashed muted badge, 400 weight path, dashed chip with lock icon, text --ui-text-muted.
|
||
- Section: icon in the badge column (hash, or lock for Session only), title 500, muted description.
|
||
- Selected: --ui-bg-elevated with a 2 px --ui-primary bar on the left (same as the section index active item). Hover: --ui-bg-elevated.
|
||
- Mobile rows: 56 px min height, badge and path on line 1, chip then description on line 2.
|
||
|
||
### Copy per state
|
||
| # | State | Copy |
|
||
|---|---|---|
|
||
| 8a | Trigger focused | Placeholder "Search the API reference…"; key hint "/"; aria-label "Search the API reference" |
|
||
| 8b | Open, empty | Input placeholder "Search the API reference…"; group "Suggested": Authentication ("Public access and API keys"), POST /v1/media/ ("Upload an image (multipart)." Verified user), Errors ("Status codes and messages"), GET /v1/article/ ("List articles, one page at a time." Public), PATCH /v1/user/me ("Update my profile." Verified user); footer "↑↓ Move", "↵ Open", "Esc Close", count "5 suggestions"; live "5 suggestions" |
|
||
| 8c | Typing "media" | Groups "Endpoints" (6 media routes) and "Sections" (Media, "10 routes"); count and live "17 results, showing 7" |
|
||
| 8d | "404", third row selected | Endpoints list the six first 404 routes with an "Error" line, for example "404 Article not found"; Sections: Errors ("Status codes and messages"); "N more endpoints. Keep typing to narrow the list."; count "22 results"; live "22 results, showing 7" |
|
||
| 8e | "keep_id" | POST /v1/media/merge, "Merge identical copies into one image.", Verified user, line "Body field keep_id"; "1 result" |
|
||
| 8f | "login" | POST /v1/auth/login, "Log in", chip "Session only" (lock); Section "Session only", "35 routes, not available to API keys"; "2 results" |
|
||
| 8g | No results | No match for "xyz". Try a path, a method or a status code like 404. Count "0 results"; live "No results" |
|
||
| 8h | After selecting | Page only: POST /v1/media/merge expanded and highlighted; index and Jump to read "Media" |
|
||
|
||
### Nuxt UI v4 mapping
|
||
- Trigger: UButton variant="outline" color="neutral" with a UIcon and UKbd (desktop); UButton icon-only (mobile). Shortcut: defineShortcuts({ '/': open, meta_k: open, ctrl_k: open }).
|
||
- Palette: UModal (fullscreen on mobile via the fullscreen prop) wrapping UCommandPalette with :groups, v-model:search-term, placeholder, :close (mobile "Cancel"). Fuse options off for the endpoint group (our own multi-field match), highlight via the #item-label and #item-description slots. UCommandPalette provides the combobox and listbox roles, aria-activedescendant and arrow-key handling.
|
||
- Rows: custom #item slot with UBadge (method, access chip), mono path, UIcon (lock, hash). Footer: #footer slot with UKbd for ↑ ↓ ↵ Esc. Empty: #empty slot. Live region: a visually hidden element with aria-live="polite".
|
||
- Flash: a CSS class on the EndpointCard row driven by a ref; uses var(--rc-sand) and var(--ui-primary) only.
|
||
|
||
### Assumptions to confirm
|
||
- Search is client-side over a static index built from the same data as the cards (method, path, first sentence of the description, access, request fields, error codes and messages). It does not search code samples, prose or response examples.
|
||
- Matching is substring per term, not fuzzy ("mdia" does not match "media").
|
||
- The palette shows the first sentence of each endpoint description, not the full text.
|
||
- Endpoints are capped at 6 rows with a count line; sections are never capped.
|
||
- Recent searches are left out. Open question: store the last 3 queries in localStorage?
|
||
- "/" is ignored while a text field has focus. Cmd+K on this route does not clash with any back-office shortcut (to confirm).
|
||
- The search trigger scrolls away with the header on desktop. Open question: keep it sticky next to the section index?
|
||
- Highlight uses --rc-sand on --rc-on-sand. On the selected row (--ui-bg-elevated) the two tones are close; the dark text keeps contrast. No new token is needed.
|
||
- The 1.5 s flash duration and the #ep-<id> hash are proposals.
|
||
|
||
|
||
## Custom content types round
|
||
Board: group "Integration", states 9a to 9j. Source: `IntegrationPage.dc.html`, `IntegrationSearch.dc.html`. Every route an API key can call is now documented.
|
||
|
||
### New groups
|
||
- Content types (`ctypes`): description "Define your own kind of content, like events or recipes. An owner or admin creates a type made of 1 to 50 ordered fields; the type then has its own entry routes." A "Field types" table (Type, Value in an entry, Rules) and a sample field object sit above the routes (anchor `ct-fields`).
|
||
- `GET /v1/content-type/` and `GET /v1/content-type/{key}`: Verified user. 404 `content_type_not_found`.
|
||
- `POST /v1/content-type/`: Owner or admin. Body `key` (2 to 40, lowercase letters, digits, hyphens), `name` (max 100), `icon` (optional), `fields` (1 to 50). 409 `content_type_key_taken`.
|
||
- `PUT /v1/content-type/{key}`: Owner or admin. Body `name`, `icon`, `fields`. 409 `schema_incompatible`.
|
||
- `DELETE /v1/content-type/{key}`: Owner or admin. 204. 409 `content_type_has_entries`, 409 `content_type_referenced`.
|
||
- Entries (`entries`): `GET` and `POST /v1/content-type/{key}/entry`, `GET`, `PUT` and `DELETE /v1/content-type/{key}/entry/{id}`. Verified user. List query `page`, `size` (5, 25, 50, 100), `sort` (`id`, `created_at`, `updated_at`, then `:asc` or `:desc`); the response is `{ items, total, page, size }`, not a bare array. Body `data` (object keyed by field key). `media` in a response is keyed by field key. 409 and 422 bodies are both `invalid_entry: <reason>`; 409 only when a unique field clashes. Id 400: "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)."
|
||
- Built-in types (`builtin`): `GET /v1/builtin-type/` and `/{key}`, Owner or admin. 404 `builtin_type_not_found`.
|
||
- Sites and authors (`sites`): `GET /v1/site/` and `GET /v1/author/` (paged, `{ items, total, page, size }`), Verified user.
|
||
- Media adds `GET` and `PATCH /v1/media/purge-settings` (`auto_purge_enabled`, `auto_purge_interval_days` 1 to 365; response also `last_purge_at`, `last_purge_merged`), `GET /v1/media/purge-preview` and `POST /v1/media/purge` (optional `fingerprint` from the preview). Owner or admin.
|
||
- PUT uses the PATCH badge colours.
|
||
|
||
### Access and errors
|
||
- 403 rows: "Only owners and admins can do this." (owner-or-admin routes) and "Only the owner can do this.", both listed in the 403 row; the existing note stays.
|
||
- 404 adds `content_type_not_found`, `entry_not_found`, `builtin_type_not_found`. 409 and 422 are real rows now (earlier text said there is no such row). Code-style text is the literal body.
|
||
- Authentication copy says a key can also manage content types and entries, and owner or admin routes answer 403 to a key whose creator is neither.
|
||
|
||
### Search
|
||
The index covers the new endpoints, body fields (`data`, and the events sample keys such as `capacity`) and error codes. "entry" also matches "entries". "N more endpoints" is singular for one.
|
||
|
||
### States 9a to 9j
|
||
9a create a content type, curl tab · 9b entries list, Response tab · 9c entry replace with the 409 and 422 errors · 9d Errors table · 9e Session only, 6 sub-groups · 9f search "entry" · 9g search "capacity" (Body field) · 9h search "schema_incompatible" (Error) · 9i search "content type" (Sections) · 9j navigation with scroll-spy on Content types. 8c now announces "17 results, showing 7" and 8d "22 results, showing 7" (the copy table above predates this; the board is right).
|
||
|
||
### Final round
|
||
- `POST /v1/media/purge` and the built-in type samples now match the API (no `icon`; keys `article`, `author`, `site`, `media`, `user`). Built-in types are read-only through the API.
|
||
- `GET /v1/media/` takes `page` (default 1) and `size` (5, 25, 50, 100; default 25), returns a JSON array and sends the total in `X-Total-Count`; 400 "Invalid page or size."
|
||
- Counts: Articles 7, Search and sharing 2, Content types 5, Entries 5, Built-in types 2, Media 10, Sites and authors 2, Users and invites 6, Settings 1, Session only 35. Search index: 75 endpoints (40 documented, 35 session-only).
|
||
- Announced: 8c "17 results, showing 7", 8d "22 results, showing 7", 8f "2 results", 9f "8 results, showing 7", 9g "2 results", 9h "1 result", 9i "12 results, showing 8".
|
||
- Illustrative only: built-in sample fields, purge-preview sample, the reason after `invalid_entry:`, session-only row labels, entry `created_by`/`updated_by`, `cover` media data.
|
||
- Not confirmed by the design tool: dark mode, the search palette on mobile and the Session only list on mobile. `task visual:check` is the gate.
|
||
- `POST /v1/content-type/` and `POST …/entry` answer 201; POST cards show only the JSON body.
|