@plitzi/sdk-mcp 0.38.6 → 0.38.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +397 -0
  2. package/README.md +9 -9
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +2 -3
  5. package/dist/modules/mcp/helpers/guide.js +74 -35
  6. package/dist/modules/mcp/index.d.ts +2 -2
  7. package/dist/modules/mcp/server.d.ts +4 -4
  8. package/dist/modules/mcp/server.js +10 -5
  9. package/dist/modules/mcp/tests/cheapMistakes.test.d.ts +1 -0
  10. package/dist/modules/mcp/tests/helpers.d.ts +9 -2
  11. package/dist/modules/mcp/tests/intents.test.d.ts +1 -0
  12. package/dist/modules/mcp/tests/look.test.d.ts +1 -0
  13. package/dist/modules/mcp/tools/apply/index.d.ts +706 -62
  14. package/dist/modules/mcp/tools/apply/index.js +70 -20
  15. package/dist/modules/mcp/tools/describeOperation.d.ts +11 -0
  16. package/dist/modules/mcp/tools/describeOperation.js +40 -0
  17. package/dist/modules/mcp/tools/describeOperation.test.d.ts +1 -0
  18. package/dist/modules/mcp/tools/index.d.ts +1 -6
  19. package/dist/modules/mcp/tools/index.js +12 -9
  20. package/dist/modules/mcp/tools/intents.d.ts +5 -0
  21. package/dist/modules/mcp/tools/intents.js +201 -0
  22. package/dist/modules/mcp/tools/look.d.ts +17 -0
  23. package/dist/modules/mcp/tools/look.js +32 -0
  24. package/dist/modules/mcp/tools/operations/closed.d.ts +7 -0
  25. package/dist/modules/mcp/tools/operations/closed.js +29 -0
  26. package/dist/modules/mcp/tools/operations/index.d.ts +2928 -359
  27. package/dist/modules/mcp/tools/operations/index.js +72 -51
  28. package/dist/modules/mcp/tools/operations/schema/bindings/upsertBinding.d.ts +1 -1
  29. package/dist/modules/mcp/tools/operations/schema/elements/moveElement.d.ts +1 -1
  30. package/dist/modules/mcp/tools/operations/schema/elements/repeatElement.d.ts +1 -1
  31. package/dist/modules/mcp/tools/operations/schema/elements/upsertElement.d.ts +1 -1
  32. package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.d.ts +1 -1
  33. package/dist/modules/mcp/tools/operations/schema/operations.d.ts +5 -5
  34. package/dist/modules/mcp/tools/operations/schema/shared.d.ts +3 -3
  35. package/dist/modules/mcp/tools/operations/schema/shared.js +7 -6
  36. package/dist/modules/mcp/tools/operations/schemaIds.js +11 -5
  37. package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.d.ts +90 -0
  38. package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.js +2 -1
  39. package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.d.ts +90 -0
  40. package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.js +2 -1
  41. package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.d.ts +90 -0
  42. package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.js +8 -1
  43. package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.d.ts +90 -0
  44. package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.js +2 -1
  45. package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.d.ts +90 -0
  46. package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.js +2 -1
  47. package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.d.ts +90 -0
  48. package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.js +2 -1
  49. package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.d.ts +90 -0
  50. package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.js +2 -1
  51. package/dist/modules/mcp/tools/operations/style/operations.d.ts +639 -9
  52. package/dist/modules/mcp/tools/operations/style/shared.d.ts +394 -0
  53. package/dist/modules/mcp/tools/operations/style/shared.js +26 -8
  54. package/dist/modules/mcp/tools/operations/style/translator.js +26 -1
  55. package/dist/modules/mcp/tools/operations/style/validate.d.ts +4 -0
  56. package/dist/modules/mcp/tools/operations/style/validate.js +63 -0
  57. package/dist/modules/mcp/tools/operations/style/write.d.ts +1 -1
  58. package/dist/modules/mcp/tools/operations/style/write.js +88 -2
  59. package/dist/modules/mcp/tools/render.d.ts +675 -45
  60. package/dist/modules/mcp/tools/render.js +5 -1
  61. package/dist/modules/mcp/tools/shared/draftBatch.d.ts +3 -2
  62. package/dist/modules/mcp/tools/shared/draftBatch.js +16 -5
  63. package/dist/modules/mcp/tools/shared/effects.d.ts +2 -0
  64. package/dist/modules/mcp/tools/shared/effects.js +155 -0
  65. package/dist/modules/mcp/tools/shared/effects.test.d.ts +1 -0
  66. package/dist/modules/mcp/tools/shared/look.d.ts +32 -0
  67. package/dist/modules/mcp/tools/shared/look.js +128 -0
  68. package/dist/modules/mcp/tools/shared/normalizeOperations.d.ts +6 -0
  69. package/dist/modules/mcp/tools/shared/normalizeOperations.js +37 -0
  70. package/dist/modules/mcp/tools/shared/repeats.d.ts +13 -0
  71. package/dist/modules/mcp/tools/shared/repeats.js +38 -0
  72. package/dist/modules/mcp/tools/shared/tool.d.ts +9 -6
  73. package/dist/modules/mcp/tools/shared/tool.js +2 -1
  74. package/dist/modules/mcp/tools/shared/validator/context.d.ts +0 -1
  75. package/dist/modules/mcp/tools/shared/validator/elements.js +0 -1
  76. package/dist/modules/mcp/tools/shared/validator/index.js +25 -2
  77. package/dist/modules/mcp/types/aiSchema.d.ts +13 -1
  78. package/dist/modules/mcp/types/screenshotTypes.d.ts +3 -3
  79. package/dist/modules/mcp/types/toolTypes.d.ts +2 -4
  80. package/dist/options.d.ts +4 -5
  81. package/dist/preview/createPreview.js +9 -18
  82. package/package.json +7 -7
  83. package/skills/plitzi-render/SKILL.md +8 -184
  84. package/skills/plitzi-render/reference/good-call.md +88 -0
  85. package/skills/plitzi-render/reference/interactive.md +64 -0
  86. package/skills/plitzi-render/reference/looks.md +29 -0
  87. package/dist/modules/mcp/tools/preview.d.ts +0 -793
  88. package/dist/modules/mcp/tools/preview.js +0 -37
  89. package/dist/modules/mcp/tools/screenshot.d.ts +0 -1078
  90. package/dist/modules/mcp/tools/screenshot.js +0 -127
  91. package/dist/modules/mcp/tools/validate.d.ts +0 -1069
  92. package/dist/modules/mcp/tools/validate.js +0 -32
@@ -2,7 +2,7 @@ import { BUILTIN_TRANSFORMERS, GLOBAL_SOURCES, MOTION_ENTERS, MOTION_LOOPS } fro
2
2
  //#region src/modules/mcp/helpers/guide.ts
3
3
  var GLOBALS = GLOBAL_SOURCES.map((source) => `\`${source}\``).join(", ");
4
4
  var OTHER_TRANSFORMERS = Object.keys(BUILTIN_TRANSFORMERS).filter((action) => action !== "twigTemplate").map((action) => `\`${action}\``).join(", ");
5
- var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles a quickstart of the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Every element has ONE name and it IS its id ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) — the same name is the runtime wiring key, so a provider source is `<type>_<name>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS elements by label/type/attribute and hands you the name to write ops against; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. CMS / API integrations (Strapi, WordPress, Contentful, any REST service) go through a CONNECTOR: a manifest you author declaring the base URL, endpoints and auth template, which the SERVER executes — read plitzi://connector-presets for working ones and plitzi://connectors/{env} for this space’s. You never see or create the credential; the space owner attaches it, and the connector saves without one. An apiContainer reads through it only with runtime:\"server\". Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Build pages people can use without sight: screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree, so every control needs words and clicks go on buttons and links — see Accessibility in plitzi://guide, and plitzi_screenshot view:\"accessibility\" to check. Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
5
+ var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles a quickstart of the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Every element has ONE name and it IS its id ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) — the same name is the runtime wiring key, so a provider source is `<type>_<name>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS elements by label/type/attribute and hands you the name to write ops against; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. CMS / API integrations (Strapi, WordPress, Contentful, any REST service) go through a CONNECTOR: a manifest you author declaring the base URL, endpoints and auth template, which the SERVER executes — read plitzi://connector-presets for working ones and plitzi://connectors/{env} for this space’s. You never see or create the credential; the space owner attaches it, and the connector saves without one. An apiContainer reads through it only with runtime:\"server\". Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Build pages people can use without sight: screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree, so every control needs words and clicks go on buttons and links — see Accessibility in plitzi://guide, and plitzi_look (its accessibility outline, the default view) to check. Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
6
6
  var widgetsOnlyInstructions = "Plitzi widget server (widgets-only connection). This connection carries NO Plitzi space: nothing can be read or edited in one, and the space tools are not offered here — do not look for them. What it does do is build self-contained UI widgets fully offline, with no backend, account or setup: call plitzi_render to SHOW the user a real rendered layout (card, hero, pricing table, checklist, form, gallery) instead of describing one. Read plitzi://render/guide first — the element/prop table, the style model and a worked example; plitzi://render/types lists every element type you can author (plitzi_read fetches both). To change a widget you already rendered, call plitzi_render again with patch:true and its renderId. If the user wants to edit their real Plitzi space from here, that is a reconnection they make: the integration must be re-authorized and granted a space (the consent screen lists theirs) — you cannot do it from this connection, and no retry will change it.";
7
7
  var guideQuickstart = `# Plitzi AI MCP — quickstart
8
8
  This is the condensed guide; read \`plitzi://guide\` for the full reference (every resource, op and example).
@@ -51,8 +51,8 @@ a QueryBuilder RuleGroup gating the binding.
51
51
  as \`icon\` (\`iconPlacement\` before/after, its class on the \`icon\` slot) — never a \`text\` or \`fontAwesome\` child;
52
52
  a text child also ignores the box's colour and hover. Chrome on several pages is a **layout**; the nav item of the page being shown is marked by the
53
53
  \`current\` state of its class, not by a copy of the header per page. Sibling rows of data are one \`repeatElement\`; a
54
- block repeated with other words is a **component** (\`upsertComponent { fromRef }\`). \`plitzi_validate\` and
55
- \`plitzi_apply\` answer with **\`suggestions\`** for what the batch could have written shorter, each with the elements
54
+ block repeated with other words is a **component** (\`upsertComponent { fromRef }\`). \`plitzi_apply\` (with or
55
+ without \`dryRun\`) answers with **\`suggestions\`** for what the batch could have written shorter, each with the elements
56
56
  it saves — never blocking; take them unless the copies are about to diverge.
57
57
 
58
58
  **Interactions** (\`upsertInteractionFlow\`): a \`trigger\` node first, then callbacks/utilities **in order** (links
@@ -87,7 +87,7 @@ element or page — not \`visible\`. Read anywhere as \`{{ flags.x }}\`.
87
87
  **Accessible by default:** screen readers and browser agents (Claude in Chrome) read the accessibility tree, so every
88
88
  button/link needs words (an icon-only button a \`title\`), every field a \`label\` (\`hideLabel: true\` hides it), every
89
89
  image an \`alt\` or \`decorative: true\`; clicks go on a \`button\`/\`link\`, never a container; headings step down one
90
- level at a time. \`plitzi_screenshot view:"accessibility"\` shows the tree and what has no name.
90
+ level at a time. \`plitzi_look\` shows the tree and what has no name.
91
91
 
92
92
  **Everything the space holds is public.** Its pages, variables, attributes, flow params and mock data ship to every
93
93
  visitor's browser, and a file on a public bucket of its CDN is served to anyone who has its address. A key, a token or
@@ -135,8 +135,8 @@ Never download a whole tree you do not need.
135
135
  \`link\` navigates between pages, \`list\` repeats a template over a data array. \`plugin\` types are custom elements.
136
136
  - \`plitzi://css-properties\` — valid kebab-case CSS property keys.
137
137
  - \`plitzi://explain/{name}\` — what a name means: an element type's attributes, triggers and callbacks; a step's
138
- params; what a trigger hands its flow; a problem code's fix. \`plitzi://explain/steps\` (or elements, triggers,
139
- codes, transformers) lists every one of a kind.
138
+ params and what it publishes; what a trigger hands its flow; a problem code's fix. \`plitzi://explain/steps\` (or
139
+ elements, triggers, codes, transformers) lists every one of a kind.
140
140
  - \`plitzi://schema/{env}/pages\` — page **summaries** (ref, label, elementCount, folder). No element trees.
141
141
  - \`plitzi://schema/{env}/layouts\` — the shared **layout shells** (header/sidebar/footer) and the pages rendered
142
142
  inside each. Read one like a page. See *Shared layouts* below.
@@ -217,25 +217,40 @@ stateVersion) and returns any **style definitions** whose name matches the query
217
217
  When you do hold several refs to open (e.g. from a skeleton), read them together with \`plitzi_read\` rather than one at a time.
218
218
 
219
219
  ## Tools
220
- - \`plitzi_validate\` — check a batch, returns teachable errors/warnings. Writes nothing. Also reports **pre-existing
221
- malformations** in any resource the batch touches (see below), and \`suggestions\` (see *Suggestions*).
222
- - \`plitzi_apply\` — validate → apply → persist atomically. Rejects the whole batch on any error or conflict. Pass
223
- \`dryRun: true\` to apply in memory only and get the same result back (changed versions + full element detail)
224
- without persisting — inspect it, then re-run without \`dryRun\` to commit. Its answer carries \`suggestions\` too.
220
+ - \`plitzi_apply\` — validate → apply → persist atomically. Rejects the whole batch on any error or conflict, and
221
+ reports **pre-existing malformations** in any resource the batch touches (see below) and \`suggestions\` (see
222
+ *Suggestions*). Pass \`dryRun: true\` to check and apply in memory only — the same answer, nothing persisted — and
223
+ \`look\` (\`"html"\`, \`"image"\`, \`"accessibility"\` or \`"both"\`, with \`pageRef\` and \`viewport\`) to see the page
224
+ as the batch leaves it in the same call. Check and look with \`dryRun\`, then send the same operations without it.
225
+ \`effects\` is what the batch did, read off the space before and after it — every element, attribute, class and
226
+ setting it changed, one line each: read it, not your batch, for what is now so. \`Nothing changed\` and \`NOT saved\`
227
+ lead the warnings when they are so.
228
+ - **By intention**, a few parameters and the element named by its ref alone — the page is found, the operations
229
+ written, the batch checked and saved as \`plitzi_apply\` saves it, and the answer its \`effects\`, whether it was
230
+ \`saved\`, and what was already so:
231
+ \`plitzi_set_attributes { ref, set, unset }\`, \`plitzi_set_classes { ref, add, remove }\` (the classes it wears and
232
+ you do not name stay), \`plitzi_bind_attribute { ref, to, source }\`, \`plitzi_place_component { component, ref, into,
233
+ props }\`, \`plitzi_add_page { ref, slug, layout }\`. One of them when it says the whole change; anything else is one
234
+ batch of \`plitzi_apply\`.
235
+ - \`plitzi_describe_operation\` — one operation's schema by its \`type\` (every field, which are required); without a
236
+ type, every operation type there is. Ask for the one you are writing instead of guessing a field: a field an
237
+ operation does not have is refused naming the one meant (\`"prop" (did you mean "props"?)\`), and a type spelt with
238
+ other capitals is read as the catalog spells it and said in \`warnings\`. The same batch refused twice is not run a
239
+ third time (\`REPEATED_BATCH\`) — change what the errors name, or ask the person.
225
240
  - \`plitzi_search\` — find elements (and pages/definitions) across the space.
226
241
  - \`plitzi_read\` — read many resource **uris in one batch** (pages, elements, definitions, variables). Pass the
227
242
  ready-made uris from search / a write response; each result is \`{ uri, stateVersion, data }\` or a teachable error,
228
243
  so one bad uri never fails the batch. Use it instead of N single reads whenever you already hold several refs.
229
- - \`plitzi_preview\` — render a page to HTML, optionally with unsaved \`operations\` applied, to check structure.
230
- - \`plitzi_screenshot\` — render a page to a PNG (desktop, mobile or \`both\`), optionally with unsaved \`operations\`:
231
- the way to SEE overflow, misalignment and broken layout before you commit. \`view: "accessibility"\` reads the same
232
- page as a screen reader and a browser agent (Claude in Chrome) do — its accessibility tree as an outline of roles and
233
- names, with every control and picture that has no name listed in \`unnamed\` — as text, far cheaper than an image
234
- (\`"both"\` returns both). Both need the SSR render service; where it is missing they answer \`PREVIEW_UNAVAILABLE\`.
244
+ - \`plitzi_look { pageRef, view, viewport }\` — see a saved page. \`view\`: \`"accessibility"\` (default) reads it as a
245
+ screen reader and a browser agent (Claude in Chrome) do — an outline of roles and names, every control and picture
246
+ with no name listed in \`unnamed\`, as text and far cheaper than an image; \`"html"\` its markup; \`"image"\` a PNG,
247
+ the way to SEE overflow and misalignment (desktop, mobile or \`both\`); \`"both"\` image and outline. A change not
248
+ saved yet is seen with \`plitzi_apply\`'s \`dryRun\` and \`look\`, the same views. It needs the SSR render service
249
+ (\`PREVIEW_UNAVAILABLE\` without it); without a browser, an image or an outline falls back to the HTML, said.
235
250
  - \`plitzi_render\` — a different job: show the user a self-contained widget built offline. It never touches the space.
236
251
 
237
252
  ## Suggestions — the same page with fewer elements
238
- \`plitzi_validate\` and \`plitzi_apply\` answer with \`suggestions\`: lines like \`[content-attribute] … (saves 4
253
+ \`plitzi_apply\` (with or without \`dryRun\`) answers with \`suggestions\`: lines like \`[content-attribute] … (saves 4
239
254
  elements)\`, each naming a shorter way to what the batch just wrote. Only what THIS batch opened up is said — what the
240
255
  space already had is not repeated on every batch — and none of them blocks anything: the page renders as written.
241
256
  - \`repeated-on-pages\` — the same block at the edge of several pages: put it in a **layout** once (a block only some
@@ -357,7 +372,11 @@ You do not have to rewrite them.
357
372
  \`justify-content\` as separate properties.
358
373
  - CSS is grouped by breakpoint: \`desktop\`, \`tablet\`, \`mobile\`.
359
374
  - Reference a style variable in CSS as \`var(--name)\`; a schema variable in a prop as \`{{name}}\`.
360
- - \`element.style.base\` is a **list** of definition refs; other slots go under \`element.style.slots\`.
375
+ - \`element.style.base\` is a **list** of definition refs; other slots go under \`element.style.slots\` — the slots
376
+ \`plitzi://explain/{type}\` lists, never custom CSS on the SDK's own class names. A \`markdown\` or a \`richText\`
377
+ has a slot per part of the document it writes (\`heading\`, \`heading2\`, \`paragraph\`, \`link\`, \`code\`,
378
+ \`codeBlockFrame\`…); a \`formControl\` its \`input\` box, the \`field\` inside it, \`label\`, \`requiredMark\`, \`error\`;
379
+ a \`pagination\` its \`previous\`, \`page\`, \`next\` (the page shown is the class's \`current\` state).
361
380
  - **An element can attach SEVERAL classes at once, and they all apply.** \`style.base\` holds a list, and each
362
381
  non-base slot holds its own — every attached definition contributes CSS, and they **cascade** (a later class, then a
363
382
  global/id rule, overrides an earlier one on the same property). So when a style looks wrong, the culprit may be
@@ -403,6 +422,15 @@ hovered or in a variant (\`.card:hover .icon\`, a collapsed sidebar hiding its l
403
422
  keyed by a class the ancestor wears: \`ancestors: { "toolbar": { "desktop": { … } }, "card": { "states": { "hover":
404
423
  { "desktop": { … } } } }, "sidebar": { "variants": { "collapsed": … } } }\`.
405
424
  Any ancestor counts, not only the parent; the class's own states and variants win over these. Never customCss for it.
425
+ \`">"\` keys the parent, whatever it wears — a component's part reacting to what it sits in.
426
+
427
+ **Pseudo-elements and conditions** sit beside \`states\` in a definition and in each of its variants:
428
+ \`pseudos: { "after": { "desktop": { "content": "\\"→\\"" }, "states": { "hover": { "desktop": { … } } } } }\`
429
+ (\`before\`/\`after\` are drawn only with a quoted \`content\`; also \`selection\`, \`marker\`, \`placeholder\`,
430
+ \`first-letter\`, \`first-line\`), and \`conditions: { "motion-reduce": { "desktop": { … } }, "container (max-width:
431
+ 30rem)": { … } }\` (the nearest ancestor whose class sets \`container-type\`). A patch keeps the ones it does not name;
432
+ \`null\` removes one. A state beyond \`hover\`/\`focus-visible\`/\`current\`: \`expanded\` (an open trigger), \`first\`,
433
+ \`last\`, \`odd\`, \`even\`.
406
434
 
407
435
  ## Variables, design tokens and fonts
408
436
  - **Schema variables** are space-wide values a prop reads as \`{{name}}\`: \`upsertVariable { name, variableType,
@@ -444,7 +472,7 @@ that apiContainer; inside a \`list\`, the repeated \`listItem\` and its children
444
472
  (\`list_<name>.item.<field>\`). The global sources — ${GLOBALS} — have no \`<type>_<name>\` head and are
445
473
  bindable anywhere. Binding an element to an element source outside its
446
474
  provider's subtree is schema-valid but **broken at runtime** (the source is not in scope), so
447
- \`plitzi_validate\`/\`plitzi_apply\` treat it as an **error and reject the batch** — move the element under the
475
+ \`plitzi_apply\` treats it as an **error and rejects the batch** — move the element under the
448
476
  provider, or bind a source that is in scope.
449
477
 
450
478
  **mockData is builder-only.** An \`apiContainer\`'s \`mockData\` prop feeds sample data **while editing in the
@@ -479,9 +507,11 @@ you — never wire them by hand. Each step also has an \`enabled\` flag (see dis
479
507
 
480
508
  **Each step reads the page as it is when it runs**: a \`when\` or a \`{{ state.x }}\` after a \`setState\` sees the new
481
509
  value, and one after a \`delay\` sees what changed meanwhile. To act on the value from BEFORE a write, put the reading
482
- step first. **A trigger fired again while its flow runs is ignored** (no double submit); set \`"whileRunning": "queue"\`
483
- on the trigger node for a stream of events that must each run in order (\`"parallel"\` runs them at once,
484
- \`"latest"\` stops the running flow — its server action or request too — for a search as you type).
510
+ step first. A step's \`when\` asks an earlier step only for what it publishes (\`plitzi://explain/<action>\` lists it):
511
+ a run's \`<step>.status\` (\`runServerAction\` only), a sign-in's \`<step>.ok\` — any other key is refused
512
+ (\`condition-field-unpublished\`). **A trigger fired again while its flow runs is ignored** (no double submit); set
513
+ \`"whileRunning": "queue"\` on the trigger node for a stream of events that must each run in order (\`"parallel"\` runs
514
+ them at once, \`"latest"\` stops the running flow — its server action or request too — for a search as you type).
485
515
  **Keyboard shortcuts** are a trigger every element has: \`onKey\` with param \`keys\` — \`"f"\`, \`"shift+f"\`,
486
516
  \`"mod+k"\` (⌘ on a Mac, Ctrl elsewhere), \`"plus, ="\`, \`"escape"\`. Heard on the whole page while the element is
487
517
  mounted, ignored while someone types in a field; the flow reads the key pressed as \`{{ <trigger id>.key }}\`.
@@ -522,7 +552,8 @@ mounted, ignored while someone types in a field; the flow reads the key pressed
522
552
  nothing outside the element needs to know it is open; pick the state one when something else binds to it.
523
553
  - \`utility\` — a built-in utility action (no element/source module); nodeType \`utility\`. Use the **exact** param
524
554
  names: \`delayTime\` waits \`time\` milliseconds (**not** \`delay\`), \`twigTemplate\` (\`returnMode\`, \`template\`),
525
- \`webHook\` (\`url\`, \`method\`, …). See \`utilities\` in \`plitzi://interactions/{env}\`.
555
+ \`webHook\` (\`url\`, \`method\`, …), \`copyToClipboard\` (\`text\`). See \`utilities\` in
556
+ \`plitzi://interactions/{env}\`.
526
557
 
527
558
  **A param value can be a data binding.** Any interaction param may hold a \`{{ source }}\` token instead of a literal —
528
559
  it resolves at runtime exactly like a prop binding, using the same source grammar (\`<type>_<name>.<path>\`, or a
@@ -727,8 +758,9 @@ any other (\`<namespace>.<action>\`), listed in \`plitzi://actions/{env}/tasks\`
727
758
  \`defineFunctions\` from \`@plitzi/sdk-server/functions\`. Files import each other by relative path — no package,
728
759
  no Node built-in.
729
760
  - **\`ctx\` is all a function reaches:** \`ctx.kv\` (the space's store), \`ctx.fetch(url, init)\` to the hosts
730
- \`allow.hosts\` names (\`*.example.com\` for subdomains), \`ctx.publish/grant/revoke\` (channels), \`ctx.log\`,
731
- \`ctx.user\` (who asked, never their session). A secret is NAMED, never read: \`ctx.fetch(url, { credential: "stripe",
761
+ \`allow.hosts\` names (\`*.example.com\` for subdomains), \`ctx.publish/grant/revoke\` (channels),
762
+ \`ctx.data("products.json")\` (one file of the space's data, parsed — never imported), \`ctx.log\`, \`ctx.user\` (who
763
+ asked, never their session). A secret is NAMED, never read: \`ctx.fetch(url, { credential: "stripe",
732
764
  headers: { authorization: "Bearer {{ credential.apiKey }}" } })\`.
733
765
  - **Stateless:** nothing a function keeps in memory outlives the call — a module-level cache or counter starts empty
734
766
  every time, on whichever instance runs it. What must last goes in \`ctx.kv\`. A call gets 100 ms of CPU and 10 s;
@@ -885,14 +917,14 @@ the component reads \`{{ props.item.name }}\`.
885
917
  "children": [ { "ref": "lamp-buy", "type": "button", "props": { "content": "Buy", "slot": "product-card-actions" } } ] } }
886
918
  \`\`\`
887
919
  A required prop left out, a prop the component does not declare, a value of the wrong kind or a child naming a slot
888
- the component does not have is reported by \`plitzi_validate\` — fix it before applying.
920
+ the component does not have is reported by \`plitzi_apply\`'s \`dryRun\` — fix it before applying.
889
921
 
890
922
  ## Pages & folders
891
923
  - **A page behind a feature flag**: \`upsertPage { flag: "labs" }\` answers 404 while the flag is off (see **Feature
892
924
  flags**); \`flag: null\` removes it.
893
925
  - **Always set a \`slug\` when creating a page** (\`upsertPage\`) — it is the page's URL path and good practice for a
894
926
  clean, stable route (e.g. \`"pricing"\` or \`"posts/:postId"\`). Omit it and the page ref is used as the slug,
895
- and \`plitzi_validate\`/\`plitzi_apply\` warn so you remember to set a meaningful one.
927
+ and \`plitzi_apply\` warns so you remember to set a meaningful one.
896
928
  - **A page slug is RELATIVE — do NOT start it with \`/\`.** The runtime prepends the leading slash (and any folder
897
929
  path) itself, so a leading slash doubles it. Write \`"pricing"\`, not \`"/pricing"\`. (upsertPage strips a leading
898
930
  slash for you, but write it relative.)
@@ -917,7 +949,10 @@ the component does not have is reported by \`plitzi_validate\` — fix it before
917
949
  - **The link to the page being shown marks itself**: it carries \`aria-current="page"\`, and its class's \`current\`
918
950
  state says how it looks — \`{ "type": "patchDefinition", "ref": "nav-link", "states": { "current": { "desktop": {
919
951
  "color": "var(--primary)" } } } }\`. So a header written once in a layout lights the right item on every page; never
920
- copy the header per page to style one item.
952
+ copy the header per page to style one item. A link for a section — a journal and its articles — takes
953
+ \`"current": "section"\` in its props: current on every page under its path too (\`aria-current="true"\`), the same
954
+ state dressing it. The same state dresses any chosen one of a set: a pressed toggle (\`aria-pressed\` — a theme
955
+ toggle's option slot) and a selected tab.
921
956
  - **A section of a page** is an element with an \`anchor\` (its \`id\` in the DOM: lowercase, digits, \`-\`; one per
922
957
  page, layouts included; not inside a list row or a component). A link lands on it with \`hash\`:
923
958
  \`{ "mode": "page", "href": "home", "hash": "plans" }\` goes to \`/#plans\` and scrolls there, from any page.
@@ -946,10 +981,12 @@ Space-level configuration lives in \`plitzi://settings/{env}\` and is edited wit
946
981
  - \`customCss\` — **raw global CSS** injected for the whole space. Use it only for genuinely site-wide rules
947
982
  (\`@keyframes\`, \`@font-face\`, resets). To style an element, write a **definition** and attach it — never customCss.
948
983
  - \`notifications\` — how the toasts an \`addNotification\` step shows look: colours (\`background\`, \`text\`,
949
- \`success\`, \`danger\`, \`warning\`, \`info\`) and shape (\`radius\`, \`font\`, \`fontSize\`, \`border\`, \`shadow\`,
950
- \`padding\`), any CSS value — a token follows the theme: \`{ "type": "patchSettings", "notifications": { "background":
951
- "var(--card)", "border": "1px solid var(--border)" } }\`. Merged field by field, \`null\` removes one. It is stored as a
952
- rule inside \`customCss\` but reads and changes apart from it: never write a \`.Toastify__toast\` rule yourself.
984
+ \`success\`, \`danger\`, \`warning\`, \`info\`), shape (\`radius\`, \`font\`, \`fontSize\`, \`fontWeight\`, \`lineHeight\`,
985
+ \`minHeight\`, \`border\`, \`shadow\`, \`padding\`) and the parts inside (\`iconSize\`, \`iconGap\`, \`closeColor\`,
986
+ \`closeOpacity\`, \`progressHeight\`), one CSS value each — a token follows the theme: \`{ "type": "patchSettings",
987
+ "notifications": { "background": "var(--card)", "iconSize": "18px" } }\`. Merged field by field, \`null\` removes one.
988
+ It is stored as rules inside \`customCss\` but reads and changes apart from it: never write a \`.Toastify__*\` rule
989
+ yourself.
953
990
  - \`keepState\` / \`stateStorage\` — keep the runtime state (\`state.<key>\`, what \`setState\` writes) across reloads,
954
991
  in \`localStorage\`/\`sessionStorage\`, filed under whoever is signed in. It is the space's setting — pages do not
955
992
  take one. \`transientState\` lists the top-level keys never kept (a filter, a demo, a panel left open); do not
@@ -957,10 +994,12 @@ Space-level configuration lives in \`plitzi://settings/{env}\` and is edited wit
957
994
  \`paintedState\` lists the kept keys the FIRST PAINT shows (the tool a toolbar shows, a name in an avatar): they go
958
995
  in a cookie too, so the server draws with them instead of the page swapping them in after load. Small values only.
959
996
  - **User provider / auth**: \`userProvider\` — \`basic\` for any HTTP+JSON backend, the name of a provider registered
960
- in the page, or \`""\` to disable auth — plus \`tokenStorage\`, the \`loginUrl\`/\`userUrl\`/\`refreshUrl\`/\`logoutUrl\`
997
+ in the page, or \`""\` for none — plus \`tokenStorage\`, the \`loginUrl\`/\`userUrl\`/\`refreshUrl\`/\`logoutUrl\`
961
998
  endpoints and the \`detailsPath\`/\`tokenPath\`/\`refreshTokenPath\`/\`expirationTimePath\` mapping that says where the
962
999
  values sit in their responses. \`sessionHintCookie\` is worth setting whenever the backend can: it names a readable
963
1000
  cookie carrying only expiries, which is what lets a page answer "nobody is signed in" without a request.
1001
+ A space served by a self-hosted \`createServer({ auth })\` declares none of these: that server tells its pages where
1002
+ they sign in, and only what the space declares overrides it.
964
1003
  On the Plitzi platform, \`userProvider: "server"\` signs visitors in with their Plitzi account on the space's own
965
1004
  host: a \`link\` with \`mode: "external"\` to \`/auth/sign-in?return=/\` starts it, \`authLogout\` ends it.
966
1005
  - \`visitorRoles\` — what each visitor role gives, \`{ "author": ["postPublish"] }\`: the permissions an action's
@@ -1034,7 +1073,7 @@ tree names nothing is invisible to both, however it looks. So build every page t
1034
1073
  The linter warns about these (\`control-without-name\`, \`image-without-alt\`, \`click-on-static-element\`,
1035
1074
  \`dropdown-without-control\`, \`heading-level-skipped\`, \`label-ignored\`, \`control-in-decorative\`) on every element
1036
1075
  you touch; fix them in the same batch. Then look:
1037
- \`plitzi_screenshot\` with \`view: "accessibility"\` shows the page as those readers get it, and lists what has no name.
1076
+ \`plitzi_look\` shows the page as those readers get it, and lists what has no name.
1038
1077
 
1039
1078
  ## Semantics
1040
1079
  - **props are fully replaced** on \`upsertElement\`: send every prop you want to keep. To change only some props,
@@ -1,6 +1,6 @@
1
1
  export { createMcpServer, handleMcp, serveMcp, readMcpBody } from './handler';
2
2
  export { readResource, resourceVersion, registerResources, buildTypeRegistry, cssProperties } from './resources';
3
- export { apply, validate, search, read, validateOperations, operation, tools } from './tools';
3
+ export { apply, search, read, validateOperations, operation, tools } from './tools';
4
4
  export { computeVersion } from './helpers';
5
5
  export { createHttpPreviewClient } from './previewClient';
6
6
  export { createLocalScreenshotClient, resolveLocalBrowser } from './localScreenshotClient';
@@ -12,6 +12,6 @@ export type { LocalScreenshotClientConfig } from './localScreenshotClient';
12
12
  export type { HttpScreenshotClientConfig } from './screenshotClient';
13
13
  export type { AccessibilityNode, UnnamedControl } from './accessibilityOutline';
14
14
  export type { McpServerContext } from './server';
15
- export type { ApplyInput, WriteResponse, Persisters, SearchInput, ReadInput, ReadResponse, ValidateInput, Operation, ToolDef, ToolContext } from './tools';
15
+ export type { ApplyInput, WriteResponse, Persisters, SearchInput, ReadInput, ReadResponse, Operation, ToolDef, ToolContext } from './tools';
16
16
  export type { Space } from './helpers';
17
17
  export type * from './types';
@@ -11,11 +11,11 @@ import { SSRAdapters, ServerLogger, SSRGrant } from '@plitzi/sdk-shared';
11
11
  export interface McpServerContext {
12
12
  adapters: SSRAdapters;
13
13
  getGrant: () => Promise<SSRGrant | undefined>;
14
- /** How the visual-preview tools (plitzi_preview / plitzi_screenshot) reach the renderer. Absent → those tools
15
- * report PREVIEW_UNAVAILABLE, so an MCP-only deployment without a renderer still runs every other tool. */
14
+ /** How a look (plitzi_look, plitzi_apply's `look`) reaches the renderer. Absent → it reports PREVIEW_UNAVAILABLE, so an
15
+ * MCP-only deployment without a renderer still runs every other tool. */
16
16
  preview?: PreviewClient;
17
- /** The dedicated browser service for plitzi_screenshot. Absent → the tool is not registered (only the HTML
18
- * plitzi_preview is offered). */
17
+ /** The dedicated browser service a look takes an image or an accessibility outline with. Absent → a look answers
18
+ * the HTML, and says so. */
19
19
  screenshot?: ScreenshotClient;
20
20
  /** Structured request-log sink. When set, every tool call and resource read emits an McpLogEvent to it (the
21
21
  * consumer renders them); otherwise logging falls back to the console when MCP_DEBUG=1. */
@@ -72,7 +72,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
72
72
  const getSpace = () => spacePromise ??= loadSpace();
73
73
  const server = new McpServer({
74
74
  name: "plitzi-mcp",
75
- version: "0.38.6"
75
+ version: "0.38.8"
76
76
  }, { instructions: hasSpace ? serverInstructions : widgetsOnlyInstructions });
77
77
  const { getChanges } = adapters;
78
78
  registerResources(server, getSpace, MCP_ENV, log, hasSpace, getChanges ? (query) => getChanges(requireSpaceId(), MCP_ENV, query) : void 0);
@@ -103,10 +103,15 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
103
103
  if (hasSpace) return async (args) => tool.execute(args, await toolContext(tool.name));
104
104
  return tool.executePublic ? (args) => tool.executePublic?.(args, MCP_ENV) : void 0;
105
105
  };
106
- for (const tool of tools) {
107
- if (tool.requires === "screenshot" && !screenshot) continue;
106
+ const listed = tools.flatMap((tool) => {
108
107
  const behavior = behaviorOf(tool);
109
- if (!behavior) continue;
108
+ return behavior ? [{
109
+ tool,
110
+ behavior
111
+ }] : [];
112
+ });
113
+ const operationsCarried = listed.some(({ tool }) => tool.carriesOperations);
114
+ for (const { tool, behavior } of listed) {
110
115
  const run = async (args) => {
111
116
  const start = performance.now();
112
117
  try {
@@ -129,7 +134,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
129
134
  const config = {
130
135
  title: tool.title,
131
136
  description: tool.description,
132
- inputSchema: tool.inputShape,
137
+ inputSchema: operationsCarried && tool.compactInputShape ? tool.compactInputShape : tool.inputShape,
133
138
  annotations: { readOnlyHint: tool.access === "read" }
134
139
  };
135
140
  if (tool.ui) {
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { Space } from '../helpers';
2
2
  import { Operation } from '../tools';
3
- import { Persisters } from '../types';
3
+ import { Persisters, ValidationResult } from '../types';
4
4
  declare const buildSpace: () => Space;
5
5
  declare const capturing: (space: Space) => {
6
6
  persisters: Persisters;
@@ -10,4 +10,11 @@ declare const spaceWithRoute: () => Space;
10
10
  declare const varOp: (pageRef: string, type: string, value: string) => Operation;
11
11
  declare const scopeSpace: () => Space;
12
12
  declare const malformedSpace: () => Space;
13
- export { buildSpace, capturing, spaceWithRoute, varOp, scopeSpace, malformedSpace };
13
+ /**
14
+ * What `plitzi_apply`'s dryRun answers of a batch, as one result to assert on: the same `draftBatch` it runs before
15
+ * anything is saved, so a refusal here is the one an agent meets.
16
+ */
17
+ declare const checkBatch: (input: {
18
+ operations: Operation[];
19
+ }, space: Space) => ValidationResult;
20
+ export { buildSpace, capturing, checkBatch, spaceWithRoute, varOp, scopeSpace, malformedSpace };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};