Vizen Shop

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 pathCost
a product row with live pricesproductListing herelook bounded by props; the strip cannot be recoloured (§7)
a product row with any designown markup — own-markup.mdprices and stock freeze into the markup
a header/menu/footer unmistakably yoursown markup in a chrome group — chrome.mdsticky 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=300

Per 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, htmlprops, 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's group_type label. See chrome.md §3.
  • heading — legacy: fields sit on the section, not under props.
  • zone (props.refs + the flags hideSiteHeader / hideSiteFooter / hideSystemBlock) and chromeKit (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:

AxisTypeDefault
props.blockWidth"content" \"full""content"
props.paddingSides{top,right,bottom,left} px12 vertical, 24 horizontal (0 horizontal when full)
props.containerPadding{top,right,bottom,left} px
props.blockRadiuspx or {tl,tr,br,bl}16
props.container{fill, borderTop, borderBottom, borderColor}
props.innerMaxpx or "content"
props.overflow"visible" \"clip""visible"
props.layernumber (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.fill on productListing is accepted and does nothing: the listing's own strip cannot be recoloured today;
  • the listing template is default and there are no others — oneOf ["default"], empty means the shop default, unknown reads as default;
  • siteMenu.level2 builds a real dropdown from the category tree, but it opens on click (Section.tsx toggles the active item in onClick). 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: WEIGHTS is ['400','500','600','700'], so weight 800 on the header name renders as its default 600;
  • siteMenu.labelStyle.size in kind: "text" renders at size + 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

WhatWhat 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 enumaccepted, the default is rendered
container.fill on productListingaccepted, the strip keeps its colour
an unknown template on productListingaccepted, reads as default
a weight outside 400/500/600/700accepted, the style's default weight is rendered
a PUT carrying one section out of severalthe rest are deleted — not ignored, but just as silent

9. Limits

  • widget kinds 17 (+3 structural); reference ~53 KB, max-age=300;
  • sections per 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

Работает в демо-режиме