# Web Manager — Admin (Web Studio) Admin side of [Web Manager](./Readme.md). Source: `apps/admin/src/core/WebStudio/`. ## Routes ```txt /web-studio/pages/listing list, search, filters, row actions /web-studio/pages/-/create new draft /web-studio/pages/-/:id editor (page or draft clone) /web-studio/settings/:section header | footer | legal ``` The list shows title with full path, locale, slug, group key, page type, status, draft marker and updated time; search is debounced and filters, pagination and locale live in the query string (`useWebStudioQueryParams`), so links and the back arrow keep the editor's context. Row actions are Edit, Preview, Open in web app, Publish, Unpublish, Discard and Delete — Edit opens the draft clone when `draftId` is present, so an editor always lands on the working copy. ## Editor The editor is a flat two-column layout: a sticky ~320px Page details column, and Hero / Blocks / SEO as line tabs beside it. One React Hook Form instance (`PAGE_EDITOR_FORM_ID`) owns everything; the header's Save button submits it by form id, and submitting disables the whole fieldset. Actions sit in a split button: Save draft, then Preview / Publish / Unpublish / Discard / Delete in the dropdown, each behind its own control component in `view/controls/` and its own permission. Publish is offered whenever the page is not already live, so it doubles as republish. Blocks are added from a sheet filtered to the page type's `allowedBlockTypes`, reordered by drag (`view/components/SortableList.tsx`), and duplicated or removed from their own header. Nested collections — content columns, CTAs, FAQ items, navigation links — behave the same way one level down. ## Key Files | Concern | File | | ------------------- | ------------------------------------------------------------------------------------------------------ | | Module registration | `config/module.ts`, `config/routing.tsx`, `config/menu.ts` | | Languages | `config/languages.ts` — code, labels, direction, public prefix | | Page types | `config/pages.ts` — allowed blocks and default path per type | | Block catalogue | `config/blocks.ts` — label, description, preview image, factory | | Settings shape | `config/siteSettings.ts` | | HTTP + multipart | `data/api/WebStudioAPI.ts`, `data/api/PagePayloadSerializer.ts` | | Domain types | `entities/` — `PageType`, `PageStatus`, `BlockType`, `HeroType`, block and field shapes | | Form ↔ DTO | `pages/PageEditPage/helpers/toEditorValues.ts`, `view/forms/PageEditorForm/helpers/toCreatePayload.ts` | Page types decide what an editor can build: | Type | Layout | Blocks offered | Default path | | ----------- | ---------- | --------------------------------------------------- | ------------ | | `landing` | Standard | all six | `/` | | `blog` | Blog index | none — the listing is rendered from published posts | `/blog` | | `blog-item` | Editorial | all except `articles` | `/` | ## UI Conventions The module is a content tool, not a dashboard of cards. Flat surfaces, few separators, one primary action per screen. | Element | Treatment | | ------------------- | -------------------------------------------------------- | | Screen heading | `text-mono text-xl font-semibold` | | Supporting copy | `text-muted-foreground text-sm` | | Editor settings | `bg-muted/40 space-y-5 p-5` | | Top-level block | `bg-muted/35`, compact header, one `border-b` while open | | Nested field group | `border-l-2 pl-4` (`view/components/FieldCard.tsx`) | | Inline actions | small ghost icon buttons | | Destructive trigger | ghost button, destructive text, confirmation dialog | | Empty collection | `bg-muted/35`, generous padding, short copy | `view/components/BlockCard.tsx` is named for compatibility but is a flat section, not a card — no shadow, no hover lift, no bordered container nested inside every block. The block picker stays a sheet with a divided list, because picking is a temporary task. Shared visual principles live in the [Admin UI Style Guide](../../../apps/admin/ui-style-guide.md); style Web Studio from Web Studio and do not edit `apps/admin/src/shared` for a module-only visual change. A visual change must not alter routes, `locale` / `group_key` query behavior, list filtering or DTO mapping, draft-clone semantics, form values or validation, block factory output, settings payload shape, or the toast / error / loading states. Anything that does is a feature change.