# Web Manager The CMS behind the public website. Editors assemble localized pages from ready-made blocks in the admin panel; the public Next.js site renders what they publish. | Part | Source | Owns | | ---------------------- | ------------------------------------------- | -------------------------------------------------------- | | Backend (`WebManager`) | `apps/backend/app/base_modules/WebManager/` | Data, validation, draft/publish transactions, APIs | | Admin (`Web Studio`) | `apps/admin/src/core/WebStudio/` | Editor UI, drafts, publishing actions, preview, settings | | Public site | `apps/web/web-runtime/`, `apps/web/app/` | Server rendering, layouts, blocks, caching, SEO | ## Documentation | Page | Purpose | | ------------------------------- | ------------------------------------------------------------------ | | [Backend](./backend.md) | Data model, HTTP API, query budget, tuning constants. | | [Admin](./admin.md) | Web Studio: routes, editor, config files, UI conventions. | | [Web](./web.md) | Public site rendering, layouts, hero, SEO, degradation. | | [Integration](./integration.md) | Contracts, cross-app flows, caching, env wiring, extending, tests. | ## For Editors A page is a title, an optional hero, an ordered list of blocks, and SEO fields. Blocks are the prebuilt sections — text columns, FAQ, media, banner, article list — so building a page is picking blocks and filling them in, not writing markup. | Term | What it means | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | **Page** | One address in one language, e.g. `/en/about-us`. | | **Group key** | Ties the same page across languages together (`about-us` links `/en/about-us` and `/uk/pro-nas`) so the site can offer a language switch. | | **Page type** | Landing, Blog, or Blog item. Decides the layout and which blocks are offered. | | **Hero** | The opening section. Four intensities: none, low, middle, high. | | **Draft** | Your working copy. Never visible to visitors. | | **Published** | Live on the site. | | **Unpublished** | Taken down, but kept — content, blocks and address stay reserved, so you can put it back. | | **Preview** | A private, time-limited link that renders the draft exactly as the site would. | | **Site settings** | Header menus, footer, and legal links — shared by every page in one language. | Everything you edit is a draft. A live page keeps serving the old version until you press Publish, so an unfinished edit can never reach visitors. Nothing is deleted unless you press Delete. ## Page Lifecycle ```txt create publish (none) ────────► draft ───────────► published ◄──────┐ │ │ │ discard│ │unpublish │publish ▼ ▼ │ deleted unpublished ───────┘ published + open draft clone: visitors see the published row, the editor edits the clone ``` Every write happens in one transaction with rows locked `FOR UPDATE`; routes and permissions are in [backend.md](./backend.md), purge behavior in [integration.md](./integration.md#caching-and-revalidation). - **Create** — `createDraft` validates the payload, asserts the `locale` + `path` is free across every root row (any status), and inserts the page with `status = draft` plus its components. Nothing public yet. - **Save a draft** — `ensureDraft` edits a `draft` or `unpublished` page in place; for a `published` page it reuses or creates the draft clone. Components are replaced wholesale in the submitted order; a clone inherits its original's address. - **Edit a published page** — the clone is a second row with `draft_of_id` pointing at the original (one clone per page, enforced by a partial unique index). Edit always opens the clone; the original keeps serving until the clone is published. - **Preview** — a signed token (HMAC over `APP_KEY`, TTL 900s clamped to 60–3600s) lets the web app render the draft in an iframe sheet: `force-dynamic`, `noindex`, disallowed in robots. Absent, tampered, or expired tokens render "preview unavailable". - **Publish** — publishes the **last saved** draft (the confirmation dialog says so). A pending clone is always preferred, so publishing can never drop the editor's work: a root draft goes live directly; a clone is folded into its original and deleted. A _structural_ change (first publish, republish, or changed locale/path/title/group key/page type) purges the whole cache tag; anything else purges only the affected page tags. - **Unpublish** — same permission as publish; only valid on a published page (else 409). Sets `status = unpublished`, keeping content, components, and any open clone; the address stays reserved. Full purge — the URL now 404s. - **Republish** — publish again; an unpublished page is edited in place, and republishing counts as structural. - **Discard a draft** — deletes the draft row and its components; a clone's original is untouched. No purge. - **Delete** — the only destructive action, behind a confirmation dialog. Components and any open clone cascade with the row; full purge if it was published.