Widgets: platform blocks and their settings
/docs/widgets-areacurrentEN· проверено 2026-08-19
Резюме по-русски. Платформенные виджеты — быстрый путь: живые данные и рабочая пагинация ценой вида, ограниченного пропами. Правда о пропах живёт только вGET /docs/widgets: он генерится из настоящего разбора виджетов и разойтись с платформой не может. Главная ошибка агентов — §5: пропы вложены (props.listing.…), запись в кореньpropsпринимается и не делает ничего.
Status: current · Verified: 2026-08-19, live GET /docs/widgets + widget sources Owner: agent-api · Serves: GET /docs/widgets-area · Skill: vizen-widgets Model note: composition moved to groups on 2026-08-21 (chrome.md §3) — the widgets themselves and every prop below are unchanged by it. Machine reference: /docs/widgets — the generated props schema; that name was taken, hence -area
1. What you can do here
Place a platform block on a page and configure it. The block reads live shop data — prices, stock and the category tree stay current by themselves. What you give up is design freedom: the look is exactly as wide as the props the widget declares, and no wider.
2. Decide first
| If you want… | Take this path | Cost |
|---|---|---|
| a product row with live prices | productListing here | look bounded by props; the strip cannot be recoloured (§7) |
| a product row with any design | own markup — own-markup.md | prices and stock freeze into the markup |
| a header/menu/footer unmistakably yours | own markup in a chrome group — chrome.md | sticky needs a workaround today |
Say the fork to the owner before building. The most expensive defect measured on a real run was a fork nobody stated.
3. Where the truth about props lives
GET /docs/widgets # public, no token, Cache-Control: max-age=300Per kind: label (ru/en), placement (chrome | page), render (slot | section), `writePath`, a JSON Schema of the props with defaults, and choices — the allowed values of every enum.
It is generated, not written: buildWidgetRegistry() runs each widget's own manifest.normalize on an empty object, so the reference reports what the parser understands, with the defaults the renderer uses. props: null means no settings of its own at all; props: {} means settings exist and all are optional.
Never write a prop from memory: a value outside an enum is accepted and the default is rendered, as the reference states in its own note.
4. The kinds, as the live reference returns them
Seventeen, grouped by where their settings go. Counts are schema sizes — read the schema itself before writing.
Page blocks that draw themselves (render: section, writePath: props): slideshow 14 · reels 19 · cards 3 · steps 22 · faq 16 · features 16 · cta 15 · columns 9.
Page blocks fed by the page (render: slot) — three of them nest their props, and that is where writes get lost: productListing → `props.listing` (4) · productCard → `props.card` (1) · form → `props.form` (0, all optional) · breadcrumbs, pageHeading, html → props, no props of their own (html is configured as own markup — own-markup.md).
Chrome (render: section, writePath: props): siteHeader 32 · siteMenu 16 · siteFooter 13.
Containers are listed under structural, not widgets:
- `group` —
props.widgets, an ordered list of block ids. The one form the platform stores composition in: a header, a footer, a body template and a page feed are all groups, told apart by the block'sgroup_typelabel. Seechrome.md§3. heading— legacy: fields sit on the section, not underprops.zone(props.refs+ the flagshideSiteHeader/hideSiteFooter/hideSystemBlock) andchromeKit(props.kit={top:[], bottom:[]}) — superseded by `group`. Still read for shops that predate it; do not write new ones.
5. The write path is where agents go wrong
GET /content-blocks # → result[]: { id, name, sections[] }
PUT /content-blocks/{id} # { "id": …, "item": { "sections": [ … ] } }{ "type": "text", // coarse legacy field: image|slideshow|text|video
"payload": { "v": 2,
"kind": "productListing", // ← the widget's real identity
"props": { "listing": { "categoryId": 12, "limit": 24 },
"blockWidth": "content" } } }type is not the widget — it is validated against ["image","slideshow","text","video"] and says nothing about what is drawn. payload.kind does.
Props are nested per widget. writePath for productListing is props.listing, so the limit is payload.props.listing.limit. Writing payload.props.limit is accepted and does nothing — the parser never looks there. It misleads because the read shape is not the write shape: the schema shows a flat limit because the parser returns flat. That is why writePath exists at all.
`sections` is a replace-set. Read the block, change one element, send the whole list back. Send a single section and the rest disappear: proto3 cannot express "clear all", so a non-empty sections means "replace with exactly this".
So the only safe edit is read → change one leaf → PUT the whole list. Verify by re-reading the block, asserting the leaf came back as sent, then opening the page and counting the cards. A 200 is not evidence — see §8.
6. The wrapper is configurable too
Its axes are written next to `kind` — at the root of props, not inside the widget's writePath:
| Axis | Type | Default | |
|---|---|---|---|
props.blockWidth | "content" \ | "full" | "content" |
props.paddingSides | {top,right,bottom,left} px | 12 vertical, 24 horizontal (0 horizontal when full) | |
props.containerPadding | {top,right,bottom,left} px | — | |
props.blockRadius | px or {tl,tr,br,bl} | 16 | |
props.container | {fill, borderTop, borderBottom, borderColor} | — | |
props.innerMax | px or "content" | — | |
props.overflow | "visible" \ | "clip" | "visible" |
props.layer | number (z-order) | unset |
Device layers are props.tablet and props.mobile, same keys; a missing layer falls back to the wider one. Side effects: overflow: "clip" also breaks position: sticky inside the block, layer makes the block positioned and can clip a neighbour's dropdown, and kind: "html" gets zero padding and radius.
7. Known limits — say them out loud before promising a look
container.fillonproductListingis accepted and does nothing: the listing's own strip cannot be recoloured today;- the listing template is
defaultand there are no others —oneOf ["default"], empty means the shop default, unknown reads asdefault; siteMenu.level2builds a real dropdown from the category tree, but it opens on click (Section.tsxtoggles the active item inonClick). The schema description says "drop down on hover" — the description is wrong, not the code;- a style value outside its enum falls back to the default instead of clamping toward it:
WEIGHTSis['400','500','600','700'], so weight 800 on the header name renders as its default600; siteMenu.labelStyle.sizeinkind: "text"renders atsize + 2— set 13, the browser computes 15px.
Past these bounds the answer is own-markup.md — and it costs live data in product rows. Say so before building, not after.
8. Silently ignored
| What | What actually happens |
|---|---|
a prop at the root of props for a nested widget (props.limit, not props.listing.limit) | accepted, stored, never read |
| a value outside an enum | accepted, the default is rendered |
container.fill on productListing | accepted, the strip keeps its colour |
an unknown template on productListing | accepted, reads as default |
a weight outside 400/500/600/700 | accepted, the style's default weight is rendered |
a PUT carrying one section out of several | the rest are deleted — not ignored, but just as silent |
9. Limits
- widget kinds 17 (+3 structural); reference ~53 KB,
max-age=300; sectionsper content block: max 50 (validate.rules.repeated.max_items);- block
name: 1–255 characters.
10. How this was verified
- 2026-08-19, live
GET https://api.vizen.shop/docs/widgets→ 200, 53 200 bytes; re-fetched and re-parsed 2026-08-20. §4 and §6 are copied from that response. - Sources read:
vizen-market/storefront/scripts/gen-widgets-json.mjs,packages/sections/src/widgets/registry.ts,.../siteMenu/Section.tsx,packages/sections/src/siteChrome/config.ts,backend-3D/api/catalog/catalog.proto. - §7 items not traced to those sources come from the browser measurements of the storefront run of 2026-08-19; restated here unchanged.
Исходник: https://api.vizen.shop/docs/widgets-area