@plitzi/sdk-server 0.32.15 → 0.32.16

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 (65) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/adapters/jsonAdapters.js +2 -1
  3. package/dist/core/createServer.js +10 -2
  4. package/dist/core/http/stages/middlewares.js +3 -2
  5. package/dist/core/http/stages/pluginAssets.js +3 -2
  6. package/dist/core/http/stages/static.js +3 -2
  7. package/dist/core/requestParser.js +1 -1
  8. package/dist/core/server/{ssrServer.js → pageServer.js} +15 -6
  9. package/dist/core/services/registry.js +2 -2
  10. package/dist/helpers/cache/defaults.js +3 -3
  11. package/dist/index.js +1 -1
  12. package/dist/modules/mcp/catalogs/cssCatalog/border.js +30 -0
  13. package/dist/modules/mcp/catalogs/cssCatalog/box.js +31 -0
  14. package/dist/modules/mcp/catalogs/cssCatalog/helpers.js +185 -0
  15. package/dist/modules/mcp/catalogs/cssCatalog/index.js +131 -0
  16. package/dist/modules/mcp/catalogs/cssCatalog/layout.js +98 -0
  17. package/dist/modules/mcp/catalogs/cssCatalog/visual.js +144 -0
  18. package/dist/modules/mcp/catalogs/registry.js +1 -1
  19. package/dist/modules/mcp/helpers/guide.js +23 -13
  20. package/dist/modules/mcp/resources/core.js +5 -2
  21. package/dist/modules/mcp/resources/primer.js +2 -1
  22. package/dist/modules/mcp/resources/register.js +6 -3
  23. package/dist/modules/mcp/server.js +1 -1
  24. package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +3 -2
  25. package/dist/modules/mcp/tools/operations/style/shared.js +2 -2
  26. package/dist/modules/mcp/tools/operations/style/write.js +2 -2
  27. package/dist/modules/mcp/tools/shared/validator/audit.js +1 -4
  28. package/dist/modules/mcp/tools/shared/validator/css.js +3 -11
  29. package/dist/modules/mcp/tools/shared/validator/elements.js +4 -1
  30. package/dist/modules/mcp/tools/shared/validator/index.js +15 -15
  31. package/dist/modules/mcp/tools/shared/validator/interactions.js +1 -4
  32. package/dist/modules/oauth/authorize.js +2 -1
  33. package/dist/modules/oauth/metadata.js +8 -2
  34. package/dist/modules/oauth/records.js +1 -1
  35. package/dist/modules/oauth/token.js +2 -2
  36. package/dist/modules/ssr/registerExternalPlugins.js +1 -1
  37. package/dist/plugins/manager.js +1 -1
  38. package/dist/src/core/createServer.d.ts +9 -1
  39. package/dist/src/core/server/pageServer.d.ts +10 -0
  40. package/dist/src/core/services/registry.d.ts +1 -1
  41. package/dist/src/modules/mcp/catalogs/cssCatalog/border.d.ts +4 -0
  42. package/dist/src/modules/mcp/catalogs/cssCatalog/box.d.ts +4 -0
  43. package/dist/src/modules/mcp/catalogs/cssCatalog/helpers.d.ts +34 -0
  44. package/dist/src/modules/mcp/catalogs/{cssCatalog.d.ts → cssCatalog/index.d.ts} +10 -5
  45. package/dist/src/modules/mcp/catalogs/cssCatalog/layout.d.ts +9 -0
  46. package/dist/src/modules/mcp/catalogs/cssCatalog/visual.d.ts +8 -0
  47. package/dist/src/modules/mcp/helpers/guide.d.ts +2 -2
  48. package/dist/src/modules/mcp/resources/index.d.ts +1 -1
  49. package/dist/src/modules/mcp/tests/apply.test.d.ts +1 -0
  50. package/dist/src/modules/mcp/tests/bindings.test.d.ts +1 -0
  51. package/dist/src/modules/mcp/tests/css.test.d.ts +1 -0
  52. package/dist/src/modules/mcp/tests/helpers.d.ts +13 -0
  53. package/dist/src/modules/mcp/tests/interactions.test.d.ts +1 -0
  54. package/dist/src/modules/mcp/tests/pages.test.d.ts +1 -0
  55. package/dist/src/modules/mcp/tests/reads.test.d.ts +1 -0
  56. package/dist/src/modules/mcp/tests/search.test.d.ts +1 -0
  57. package/dist/src/modules/mcp/tests/styles.test.d.ts +1 -0
  58. package/dist/src/modules/mcp/tests/tools.test.d.ts +1 -0
  59. package/dist/src/modules/mcp/tests/validation.test.d.ts +1 -0
  60. package/dist/src/modules/mcp/tools/screenshot.d.ts +1 -1
  61. package/dist/src/modules/oauth/metadata.d.ts +7 -1
  62. package/package.json +9 -9
  63. package/dist/modules/mcp/catalogs/cssCatalog.js +0 -205
  64. package/dist/src/core/server/ssrServer.d.ts +0 -2
  65. /package/dist/src/modules/mcp/{mcp.test.d.ts → catalogs/cssCatalog/cssCatalog.test.d.ts} +0 -0
@@ -0,0 +1,144 @@
1
+ import { ANIMATION_DIRECTIONS, ANIMATION_FILL_MODES, ANIMATION_PLAY_STATES, FONT_STYLE_KEYWORDS, FONT_VARIANT_KEYWORDS, FONT_WEIGHT_KEYWORDS, LENGTH_RE, LIST_STYLE_POSITIONS, LIST_STYLE_TYPES, NUMBER_RE, TEXT_DECORATION_LINES, TEXT_DECORATION_STYLES, TIME_RE, isTimingFunction, splitOn, splitPair, splitTokens } from "./helpers.js";
2
+ import { classifyBorderTokens } from "./border.js";
3
+ //#region src/modules/mcp/catalogs/cssCatalog/visual.ts
4
+ var expandLayers = (value, out, expandLayer) => {
5
+ const layers = splitOn(value, ",");
6
+ if (layers.length === 1) {
7
+ expandLayer(layers[0], out);
8
+ return;
9
+ }
10
+ const perLayer = layers.map((layer) => {
11
+ const layerOut = {};
12
+ expandLayer(layer, layerOut);
13
+ return layerOut;
14
+ });
15
+ const keys = new Set(perLayer.flatMap((layer) => Object.keys(layer)));
16
+ for (const key of keys) out[key] = perLayer.map((layer) => String(layer[key] ?? "initial")).join(", ");
17
+ };
18
+ var expandOutline = (value, out) => {
19
+ for (const [kind, token] of Object.entries(classifyBorderTokens(value))) out[`outline-${kind}`] = token;
20
+ };
21
+ var expandListStyle = (value, out) => {
22
+ for (const token of splitTokens(value)) if (token.startsWith("url(")) out["list-style-image"] = token;
23
+ else if (LIST_STYLE_POSITIONS.has(token)) out["list-style-position"] = token;
24
+ else if (LIST_STYLE_TYPES.has(token)) out["list-style-type"] = token;
25
+ };
26
+ var expandTextDecoration = (value, out) => {
27
+ for (const token of splitTokens(value)) if (TEXT_DECORATION_LINES.has(token)) out["text-decoration-line"] = token;
28
+ else if (TEXT_DECORATION_STYLES.has(token)) out["text-decoration-style"] = token;
29
+ else out["text-decoration-color"] = token;
30
+ };
31
+ var expandTransitionLayer = (layer, out) => {
32
+ let foundDuration = false;
33
+ for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["transition-timing-function"] = token;
34
+ else if (TIME_RE.test(token) || NUMBER_RE.test(token)) if (foundDuration) out["transition-delay"] = token;
35
+ else {
36
+ out["transition-duration"] = token;
37
+ foundDuration = true;
38
+ }
39
+ else if (!out["transition-property"]) out["transition-property"] = token;
40
+ };
41
+ var expandTransition = (value, out) => expandLayers(value, out, expandTransitionLayer);
42
+ var expandAnimationLayer = (layer, out) => {
43
+ let foundDuration = false;
44
+ for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["animation-timing-function"] = token;
45
+ else if (TIME_RE.test(token)) if (foundDuration) out["animation-delay"] = token;
46
+ else {
47
+ out["animation-duration"] = token;
48
+ foundDuration = true;
49
+ }
50
+ else if (token === "infinite" || NUMBER_RE.test(token)) out["animation-iteration-count"] = token;
51
+ else if (ANIMATION_DIRECTIONS.has(token) && !out["animation-direction"]) out["animation-direction"] = token;
52
+ else if (ANIMATION_FILL_MODES.has(token) && !out["animation-fill-mode"]) out["animation-fill-mode"] = token;
53
+ else if (ANIMATION_PLAY_STATES.has(token) && !out["animation-play-state"]) out["animation-play-state"] = token;
54
+ else if (!out["animation-name"]) out["animation-name"] = token;
55
+ };
56
+ var expandAnimation = (value, out) => expandLayers(value, out, expandAnimationLayer);
57
+ var BACKGROUND_REPEATS = /* @__PURE__ */ new Set([
58
+ "repeat",
59
+ "no-repeat",
60
+ "repeat-x",
61
+ "repeat-y",
62
+ "space",
63
+ "round"
64
+ ]);
65
+ var BACKGROUND_ATTACHMENTS = /* @__PURE__ */ new Set([
66
+ "scroll",
67
+ "fixed",
68
+ "local"
69
+ ]);
70
+ var BACKGROUND_BOXES = /* @__PURE__ */ new Set([
71
+ "border-box",
72
+ "padding-box",
73
+ "content-box"
74
+ ]);
75
+ var BACKGROUND_POSITIONS = /* @__PURE__ */ new Set([
76
+ "center",
77
+ "top",
78
+ "bottom",
79
+ "left",
80
+ "right"
81
+ ]);
82
+ var isImage = (token) => token.startsWith("url(") || token.includes("-gradient(");
83
+ var isPosition = (token) => BACKGROUND_POSITIONS.has(token) || LENGTH_RE.test(token);
84
+ var isSize = (token) => token === "cover" || token === "contain" || token === "auto" || LENGTH_RE.test(token);
85
+ var expandBackgroundLayer = (layer, out) => {
86
+ const [beforeSlash, afterSlash] = splitPair(layer, "/");
87
+ const tokens = splitTokens(beforeSlash);
88
+ if (afterSlash !== void 0) {
89
+ let positionStart = tokens.length;
90
+ while (positionStart > 0 && isPosition(tokens[positionStart - 1])) positionStart -= 1;
91
+ const position = tokens.splice(positionStart, tokens.length - positionStart);
92
+ if (position.length > 0) out["background-position"] = position.join(" ");
93
+ const afterTokens = splitTokens(afterSlash);
94
+ let sizeEnd = 0;
95
+ while (sizeEnd < afterTokens.length && isSize(afterTokens[sizeEnd])) sizeEnd += 1;
96
+ if (sizeEnd > 0) out["background-size"] = afterTokens.slice(0, sizeEnd).join(" ");
97
+ tokens.push(...afterTokens.slice(sizeEnd));
98
+ }
99
+ const positions = [];
100
+ for (const token of tokens) if (isImage(token)) out["background-image"] = token;
101
+ else if (BACKGROUND_REPEATS.has(token)) out["background-repeat"] = token;
102
+ else if (token === "cover" || token === "contain") out["background-size"] = token;
103
+ else if (BACKGROUND_ATTACHMENTS.has(token)) out["background-attachment"] = token;
104
+ else if (BACKGROUND_BOXES.has(token)) if (out["background-origin"]) out["background-clip"] = token;
105
+ else out["background-origin"] = token;
106
+ else if (isPosition(token)) positions.push(token);
107
+ else if (!out["background-color"]) out["background-color"] = token;
108
+ if (positions.length > 0) out["background-position"] = positions.join(" ");
109
+ };
110
+ var expandBackground = (value, out) => expandLayers(value, out, expandBackgroundLayer);
111
+ var expandFont = (value, out) => {
112
+ const [beforeSlash, afterSlash] = splitPair(value, "/");
113
+ const tokens = splitTokens(beforeSlash);
114
+ if (afterSlash !== void 0) {
115
+ const [lineHeight, ...family] = splitTokens(afterSlash);
116
+ out["line-height"] = lineHeight;
117
+ if (family.length > 0) out["font-family"] = family.join(" ");
118
+ }
119
+ const family = [];
120
+ let foundSize = false;
121
+ for (const token of tokens) {
122
+ if (foundSize) {
123
+ family.push(token);
124
+ continue;
125
+ }
126
+ if (token === "normal") {
127
+ const slot = [
128
+ "font-style",
129
+ "font-variant",
130
+ "font-weight"
131
+ ].find((prop) => !out[prop]);
132
+ if (slot) out[slot] = token;
133
+ } else if (FONT_STYLE_KEYWORDS.has(token) && !out["font-style"]) out["font-style"] = token;
134
+ else if (FONT_VARIANT_KEYWORDS.has(token) && !out["font-variant"]) out["font-variant"] = token;
135
+ else if (FONT_WEIGHT_KEYWORDS.has(token) && !out["font-weight"]) out["font-weight"] = token;
136
+ else if (LENGTH_RE.test(token)) {
137
+ out["font-size"] = token;
138
+ foundSize = true;
139
+ }
140
+ }
141
+ if (family.length > 0) out["font-family"] = family.join(" ");
142
+ };
143
+ //#endregion
144
+ export { expandAnimation, expandBackground, expandFont, expandListStyle, expandOutline, expandTextDecoration, expandTransition };
@@ -1,5 +1,5 @@
1
1
  import { BUILTIN_COMPONENTS } from "./builtinComponents.js";
2
- import { cssProperties } from "./cssCatalog.js";
2
+ import { cssProperties } from "./cssCatalog/index.js";
3
3
  //#region src/modules/mcp/catalogs/registry.ts
4
4
  var enrichType = (typeName, info, catalog) => {
5
5
  const entry = catalog?.[typeName];
@@ -1,5 +1,5 @@
1
1
  //#region src/modules/mcp/helpers/guide.ts
2
- 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 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. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is kebab-case and ATOMIC (shorthands like border/padding expanded; compound ones like flex/background/font rejected use 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 refs by label/type/attribute; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. 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). 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.";
2
+ 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 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. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<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 refs by label/type/attribute; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. 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). 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.";
3
3
  var guideQuickstart = `# Plitzi AI MCP — quickstart
4
4
  This is the condensed guide; read \`plitzi://guide\` for the full reference (every resource, op and example).
5
5
 
@@ -19,10 +19,12 @@ interactions target by it. A dot would split that path; an **underscore is fine*
19
19
  from the idRef (element types have none), so \`list_food_item\` reads unambiguously as type \`list\`, idRef \`food_item\`.
20
20
 
21
21
  **Styling:** CSS keys are **kebab-case** (\`background-color\`); \`var(--token)\` for style vars, \`{{name}}\` for schema
22
- vars. Shorthands \`border\`/\`padding\`/\`margin\`/\`gap\` are expanded for you. Write **atomically no compound
23
- shorthands** (\`flex\`, \`background\`, \`font\`, \`transition\`, \`grid\` are rejected/warned): use their longhands, and
24
- express flex layout as \`display: flex\` + \`flex-direction\`/\`align-items\`/… Mind a type's \`defaultStyle\` (\`text\`
25
- is \`display: inline\`). Global styles (\`button {…}\`) and id styles (\`#id\`) have their own ops.
22
+ vars. **Write plain CSS** shorthands (\`border\`, \`padding\`, \`margin\`, \`gap\`, \`overflow\`, \`flex\`, \`background\`,
23
+ \`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-row\`/\`grid-column\`, \`place-*\`, \`outline\`, \`columns\`,
24
+ \`list-style\`, \`text-decoration\`) are expanded to their longhands for you, so \`border: 1px solid red\` is stored as
25
+ \`border-top-color\`/\`border-top-width\`/\`border-top-style\`/… That is also how you read them back. Flex layout is
26
+ still \`display: flex\` + \`flex-direction\`/\`align-items\`/…, not a \`flex\` value. Mind a type's \`defaultStyle\`
27
+ (\`text\` is \`display: inline\`). Global styles (\`button {…}\`) and id styles (\`#id\`) have their own ops.
26
28
 
27
29
  **Data bindings** (\`upsertBinding\`, category attributes|style|initialState): connect a \`source\` to a \`to\` field.
28
30
  A source \`<type>_<idRef>\` is scoped to the provider's **DESCENDANTS only** — bind inside the provider's subtree
@@ -202,14 +204,22 @@ pointed at the old name is repointed with it, so the element stays wired. You do
202
204
  - A definition lives in the **style schema**; an element's \`style.base\` (element schema) is the link that applies
203
205
  it. Styling an element = upsertDefinition + upsertElement with that ref in \`style.base\`, in one batch.
204
206
  - CSS keys are **kebab-case** (\`background-color\`). camelCase is rejected — read \`plitzi://css-properties\`.
205
- - Common **shorthands are accepted** and expanded for you: \`border\`, \`border-{side}\`, \`border-radius\`,
206
- \`padding\`, \`margin\`, \`inset\`, \`gap\` (they persist as their longhand keys).
207
- - **Write CSS atomically no multi-value compound shorthands.** Plitzi stores each property on its own so a
208
- breakpoint/state/variant can override just that one, so \`flex\`, \`background\`, \`font\`, \`transition\`,
209
- \`animation\`, \`grid\`, \`place-*\`, \`outline\`, \`columns\` are **rejected** (or warned) use their longhands
210
- instead: \`background\` \`background-color\`/\`background-image\`/…, \`font\` \`font-size\`/\`font-weight\`/
211
- \`line-height\`, \`flex: 1\` \`flex-grow\`/\`flex-shrink\`/\`flex-basis\`. **Flex layout is not a \`flex\` value**: set
212
- \`display: flex\` **plus** \`flex-direction\`, \`align-items\`, \`justify-content\` as separate properties.
207
+ - **Write normal CSS — shorthands are accepted and expanded for you.** \`border\`, \`border-{side}\`,
208
+ \`border-width\`/\`-color\`/\`-style\`, \`border-radius\`, \`padding\`, \`margin\`, \`inset\`, \`gap\`, \`overflow\`,
209
+ \`flex\`, \`flex-flow\`, \`background\`, \`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-template\`,
210
+ \`grid-area\`, \`grid-row\`, \`grid-column\`, \`place-content\`, \`place-items\`, \`place-self\`, \`outline\`,
211
+ \`columns\`, \`list-style\`, \`text-decoration\` the full list is in \`plitzi://css-properties\` under
212
+ \`shorthands\`. They **persist as longhand keys** (that is what a read gives back), so a breakpoint/state/variant
213
+ can still override one property at a time.
214
+ - \`border: 1px solid red\` \`border-top-width: 1px\`, \`border-top-style: solid\`, \`border-top-color: red\`,
215
+ for all four sides. A shorthand also RESETS what it omits, so \`border: none\` clears a width a previous
216
+ definition set.
217
+ - Comma-separated layers are kept per longhand: \`transition: opacity 200ms, transform 300ms\` →
218
+ \`transition-property: opacity, transform\` + \`transition-duration: 200ms, 300ms\`.
219
+ - In a **patch**, a shorthand replaces the longhands it controls (\`padding: 8px\` overwrites a previous
220
+ \`padding-left\`), and \`"border": null\` removes all twelve border longhands.
221
+ - **Flex layout is not a \`flex\` value**: set \`display: flex\` **plus** \`flex-direction\`, \`align-items\`,
222
+ \`justify-content\` as separate properties.
213
223
  - CSS is grouped by breakpoint: \`desktop\`, \`tablet\`, \`mobile\`.
214
224
  - Reference a style variable in CSS as \`var(--name)\`; a schema variable in a prop as \`{{name}}\`.
215
225
  - \`element.style.base\` is a **list** of definition refs; other slots go under \`element.style.slots\`.
@@ -1,4 +1,4 @@
1
- import { cssProperties } from "../catalogs/cssCatalog.js";
1
+ import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
2
2
  import { buildTypeRegistry } from "../catalogs/registry.js";
3
3
  import "../helpers/uris.js";
4
4
  import { guideText } from "../helpers/guide.js";
@@ -9,7 +9,10 @@ import { envelope } from "./envelope.js";
9
9
  var readCoreResource = (space, uri) => {
10
10
  if (uri === "plitzi://guide") return envelope(guideText);
11
11
  if (uri === "plitzi://types") return envelope(buildTypeRegistry(space.schema, space.catalog));
12
- if (uri === "plitzi://css-properties") return envelope(cssProperties);
12
+ if (uri === "plitzi://css-properties") return envelope({
13
+ properties: cssProperties,
14
+ shorthands: cssShorthands
15
+ });
13
16
  };
14
17
  //#endregion
15
18
  export { readCoreResource };
@@ -1,4 +1,4 @@
1
- import { cssProperties } from "../catalogs/cssCatalog.js";
1
+ import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
2
2
  import { buildDataSourceCatalog, buildInteractionCatalog } from "../catalogs/observed.js";
3
3
  import { buildTypeRegistry } from "../catalogs/registry.js";
4
4
  import { guideQuickstart } from "../helpers/guide.js";
@@ -16,6 +16,7 @@ var readPrimerResource = (space, env, uri) => {
16
16
  guide: guideQuickstart,
17
17
  types: buildTypeRegistry(space.schema, space.catalog),
18
18
  cssProperties,
19
+ cssShorthands,
19
20
  pages: pageSummariesToAI(space.schema),
20
21
  folders: foldersToAI(space.schema),
21
22
  definitions: definitionRefs(space.style),
@@ -1,4 +1,4 @@
1
- import { cssProperties } from "../catalogs/cssCatalog.js";
1
+ import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
2
2
  import { guideText } from "../helpers/guide.js";
3
3
  import { resourceErrorMessage } from "./canonical.js";
4
4
  import { envelope, jsonContents } from "./envelope.js";
@@ -36,9 +36,12 @@ var registerResources = (server, getSpace, env, log) => {
36
36
  text: guideText
37
37
  }] })));
38
38
  server.registerResource("CSS properties", "plitzi://css-properties", {
39
- description: "Valid kebab-case CSS property keys",
39
+ description: "Valid kebab-case CSS property keys, plus the shorthands that are accepted and auto-expanded",
40
40
  mimeType: "application/json"
41
- }, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope(cssProperties))));
41
+ }, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope({
42
+ properties: cssProperties,
43
+ shorthands: cssShorthands
44
+ }))));
42
45
  registerRenderResources(server, log);
43
46
  const fixed = [
44
47
  [
@@ -43,7 +43,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) =>
43
43
  const getSpace = () => spacePromise ??= loadSpace();
44
44
  const server = new McpServer({
45
45
  name: "plitzi-mcp",
46
- version: "0.32.15"
46
+ version: "0.32.16"
47
47
  }, { instructions: serverInstructions });
48
48
  registerResources(server, getSpace, MCP_ENV, log);
49
49
  registerApps(server);
@@ -16,13 +16,14 @@ var upsertInteractionFlow = (space, env, op) => {
16
16
  const found = resolveElement(space, env, op.pageRef, op.ref);
17
17
  if ("error" in found) return found.error;
18
18
  const ownerRef = ensureIdRef(space, found.el);
19
- const { flowId, record } = materializeFlow((op.flowId && !op.nodes[0].id ? [{
19
+ const nodes = (op.flowId && !op.nodes[0].id ? [{
20
20
  ...op.nodes[0],
21
21
  id: op.flowId
22
22
  }, ...op.nodes.slice(1)] : op.nodes).map((node) => node.elementId === void 0 || node.nodeType === "utility" ? node : {
23
23
  ...node,
24
24
  elementId: resolveTargetRef(space, node.elementId)
25
- }), ownerRef);
25
+ });
26
+ const { flowId, record } = materializeFlow(nodes, ownerRef);
26
27
  const interactions = found.el.definition.interactions ?? {};
27
28
  const targetFlow = op.flowId ?? flowId;
28
29
  let replaced = false;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  //#region src/modules/mcp/tools/operations/style/shared.ts
3
- var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("kebab-case CSS props; use var(--name) for tokens and {{name}} for schema vars");
3
+ var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("Plain kebab-case CSS. Shorthands are welcome — `border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`, `transition: opacity 200ms ease` — and are stored as their longhands so a breakpoint/state/variant can override each property on its own. Use var(--name) for style tokens and {{name}} for schema vars.");
4
4
  var displayModeCss = z.object({
5
5
  desktop: cssMap.optional(),
6
6
  tablet: cssMap.optional(),
@@ -14,7 +14,7 @@ var cssPatchMap = z.record(z.string(), z.union([
14
14
  z.string(),
15
15
  z.number(),
16
16
  z.null()
17
- ])).describe("kebab-case CSS props; a value of null removes the property, others are merged onto the existing CSS");
17
+ ])).describe("Plain kebab-case CSS merged onto the existing declarations; shorthands are accepted and expanded, so `padding: 8px` replaces all four sides. A value of null removes the property null on a shorthand (`border: null`) removes every longhand it controls.");
18
18
  var displayModeCssPatch = z.object({
19
19
  desktop: cssPatchMap.optional(),
20
20
  tablet: cssPatchMap.optional(),
@@ -1,4 +1,4 @@
1
- import { expandShorthand } from "../../../catalogs/cssCatalog.js";
1
+ import { expandShorthand, expandShorthandPatch } from "../../../catalogs/cssCatalog/index.js";
2
2
  import { defUri, defsUri, globalUri, globalsUri, idUri, idsUri, styleVarUri, styleVarsUri } from "../../../helpers/uris.js";
3
3
  import { fail } from "../../../helpers/opResult.js";
4
4
  import processSelector from "@plitzi/sdk-style/helpers/processSelector";
@@ -65,7 +65,7 @@ var writeStyleItem = (style, ref, base, slots, itemType, componentType) => {
65
65
  };
66
66
  var mergeCss = (base, patch) => {
67
67
  const merged = { ...base };
68
- for (const [key, value] of Object.entries(patch ?? {})) if (value === null) Reflect.deleteProperty(merged, key);
68
+ for (const [key, value] of Object.entries(expandShorthandPatch(patch ?? {}))) if (value === null) Reflect.deleteProperty(merged, key);
69
69
  else merged[key] = value;
70
70
  return Object.keys(merged).length > 0 ? merged : void 0;
71
71
  };
@@ -83,10 +83,7 @@ var collectTouched = (ops) => {
83
83
  touched.globalStyles.add(op.componentType);
84
84
  break;
85
85
  case "upsertIdStyle":
86
- case "patchIdStyle":
87
- touched.idStyles.add(op.targetId);
88
- break;
89
- default: break;
86
+ case "patchIdStyle": touched.idStyles.add(op.targetId);
90
87
  }
91
88
  return touched;
92
89
  };
@@ -1,20 +1,12 @@
1
- import { compoundLonghands, expandShorthand, isCssProperty, suggestCssProperty } from "../../../catalogs/cssCatalog.js";
2
- import { checkStyleVarRefs, checkVarRefs, warnOnce } from "./context.js";
1
+ import { expandShorthand, isCssProperty, suggestCssProperty } from "../../../catalogs/cssCatalog/index.js";
2
+ import { checkStyleVarRefs, checkVarRefs } from "./context.js";
3
3
  //#region src/modules/mcp/tools/shared/validator/css.ts
4
4
  var checkCss = (css, path, ctx) => {
5
5
  if (!css) return;
6
6
  const declared = {};
7
7
  for (const [key, value] of Object.entries(css)) if (value !== null) declared[key] = value;
8
8
  for (const [key, value] of Object.entries(expandShorthand(declared))) {
9
- const longhands = compoundLonghands(key);
10
- if (longhands) if (!isCssProperty(key)) ctx.errors.push({
11
- path: `${path}.${key}`,
12
- message: `Compound CSS shorthand "${key}" is not supported — write its atomic longhand properties`,
13
- hint: `Use: ${longhands.join(", ")}. (Note: flex layout is display + flex-direction + flex-grow/shrink/basis, not "flex".)`,
14
- validValues: longhands
15
- });
16
- else warnOnce(ctx, `CSS shorthand "${key}" at ${path}.${key} is accepted but stored as one compound value. Plitzi styling is atomic — prefer the longhands (${longhands.join(", ")}) so a breakpoint/state/variant can override each property independently.`);
17
- else if (!isCssProperty(key)) {
9
+ if (!isCssProperty(key)) {
18
10
  const suggestion = suggestCssProperty(key);
19
11
  ctx.errors.push({
20
12
  path: `${path}.${key}`,
@@ -25,7 +25,10 @@ var checkVariantApplication = (initialState, path, ctx) => {
25
25
  const batch = ctx.batchVariants.get(cls);
26
26
  for (const [selector, variant] of Object.entries(selectors)) {
27
27
  const names = Array.isArray(variant) ? variant : [variant];
28
- for (const name of names) if (!((declared?.[selector]?.includes(name) ?? false) || (batch?.has(name) ?? false))) warnOnce(ctx, `Element applies variant "${name}" on class "${cls}" (${selector}) at ${path}, but that class defines no such variant${declared ? ` (declares: ${Object.entries(declared).map(([s, v]) => `${s}:${v.join("/")}`).join(", ")})` : ""}. Create it via upsertDefinition/patchDefinition "variants", or fix the name.`);
28
+ for (const name of names) if (!((declared?.[selector]?.includes(name) ?? false) || (batch?.has(name) ?? false))) {
29
+ const avail = declared ? ` (declares: ${Object.entries(declared).map(([s, v]) => `${s}:${v.join("/")}`).join(", ")})` : "";
30
+ warnOnce(ctx, `Element applies variant "${name}" on class "${cls}" (${selector}) at ${path}, but that class defines no such variant${avail}. Create it via upsertDefinition/patchDefinition "variants", or fix the name.`);
31
+ }
29
32
  }
30
33
  }
31
34
  };
@@ -191,19 +191,21 @@ var validateOperations = (space, ops) => {
191
191
  case "patchInteractionNode": {
192
192
  checkRef(op.ref, `${base}.ref`, ctx);
193
193
  const existing = findElementByRef(space.schema, op.ref)?.definition.interactions?.[op.nodeId];
194
- if (existing) checkInteractionNode({
195
- id: existing.id,
196
- title: op.title ?? existing.title,
197
- nodeType: existing.type,
198
- action: op.action ?? existing.action,
199
- params: {
200
- ...existing.params,
201
- ...op.params ?? {}
202
- },
203
- enabled: op.enabled ?? existing.enabled,
204
- elementId: op.elementId ?? existing.elementId ?? void 0
205
- }, base, ctx, op.ref);
206
- else checkObservedName(op.action, ctx.observedActions, "Interaction action", "plitzi://interactions", `${base}.action`, ctx);
194
+ if (existing) {
195
+ const merged = {
196
+ id: existing.id,
197
+ title: op.title ?? existing.title,
198
+ nodeType: existing.type,
199
+ action: op.action ?? existing.action,
200
+ params: {
201
+ ...existing.params,
202
+ ...op.params ?? {}
203
+ },
204
+ enabled: op.enabled ?? existing.enabled,
205
+ elementId: op.elementId ?? existing.elementId ?? void 0
206
+ };
207
+ checkInteractionNode(merged, base, ctx, op.ref);
208
+ } else checkObservedName(op.action, ctx.observedActions, "Interaction action", "plitzi://interactions", `${base}.action`, ctx);
207
209
  break;
208
210
  }
209
211
  case "deleteInteraction":
@@ -213,8 +215,6 @@ var validateOperations = (space, ops) => {
213
215
  message: "Provide exactly one of flowId or nodeId",
214
216
  hint: "flowId removes a whole flow; nodeId removes a single step"
215
217
  });
216
- break;
217
- default: break;
218
218
  }
219
219
  });
220
220
  return {
@@ -96,10 +96,7 @@ var checkInteractionNode = (node, base, ctx, hostRef) => {
96
96
  case "callback":
97
97
  checkElementCallback(node, base, ctx, hostRef);
98
98
  break;
99
- case "utility":
100
- checkUtility(node, base, ctx);
101
- break;
102
- default: break;
99
+ case "utility": checkUtility(node, base, ctx);
103
100
  }
104
101
  };
105
102
  //#endregion
@@ -28,7 +28,8 @@ var hiddenFieldsFor = (request, pendingId) => {
28
28
  return hidden;
29
29
  };
30
30
  var renderConsent = async (config, res, view) => {
31
- sendHtml(res, 200, await (config.renderConsent ?? renderConsentPage)(view));
31
+ const html = await (config.renderConsent ?? renderConsentPage)(view);
32
+ sendHtml(res, 200, html);
32
33
  };
33
34
  /** Validates the parts of an authorization request that decide WHERE a failure may be reported. Until the client
34
35
  * and its redirect target check out, nothing may be sent back to the client. */
@@ -22,11 +22,17 @@ var resourceMetadataUrl = (config, req) => `${issuerOf(config, req)}${PROTECTED_
22
22
  *
23
23
  * `resource` echoes the path the document was asked for: Claude requires it to match the server URL the user
24
24
  * typed, path included, and a dedicated MCP server answers JSON-RPC on every path — so `/.well-known/…/mcp`
25
- * describes `https://host/mcp` while the bare path describes the origin. */
25
+ * describes `https://host/mcp` while the bare path describes the origin.
26
+ *
27
+ * A server mounted at the ORIGIN is `https://host/`, WITH the trailing slash: a host parses the URL the user
28
+ * typed before it does anything with it, and an empty path renders as `/` — that normalized form is what the
29
+ * client sends back as the `resource` parameter (asserted in the connector e2e). A document claiming to be
30
+ * `https://host` then disagrees with the request made against it, and Claude's connector drops the grant it
31
+ * just completed — it takes the token and never opens the session. */
26
32
  var protectedResourceMetadata = (config, req) => {
27
33
  const issuer = issuerOf(config, req);
28
34
  return {
29
- resource: `${issuer}${canonicalPath(req.path.slice(37))}`,
35
+ resource: `${issuer}${canonicalPath(req.path.slice(37)) || "/"}`,
30
36
  authorization_servers: [issuer],
31
37
  scopes_supported: scopesOf(config),
32
38
  bearer_methods_supported: ["header"]
@@ -1,5 +1,5 @@
1
1
  //#region src/modules/oauth/records.ts
2
- var CLIENT_TTL_SECONDS = 3600 * 24 * 90;
2
+ var CLIENT_TTL_SECONDS = 7776e3;
3
3
  var PENDING_TTL_SECONDS = 600;
4
4
  var keyOf = (kind, id) => `oauth:${kind}:${id}`;
5
5
  var readJson = async (store, kind, id) => {
@@ -4,8 +4,8 @@ import { randomId, verifyChallenge } from "./pkce.js";
4
4
  import { dropCode, dropRefresh, getCode, getRefresh, putAccess, putRefresh } from "./records.js";
5
5
  import { sendErrorJson, sendJson } from "./respond.js";
6
6
  //#region src/modules/oauth/token.ts
7
- var DEFAULT_REFRESH_TTL_SECONDS = 3600 * 24 * 30;
8
- var DEFAULT_ACCESS_TTL_SECONDS = 3600 * 24 * 30;
7
+ var DEFAULT_REFRESH_TTL_SECONDS = 2592e3;
8
+ var DEFAULT_ACCESS_TTL_SECONDS = 2592e3;
9
9
  var refreshTtlOf = (config) => config.refreshTtlSeconds ?? DEFAULT_REFRESH_TTL_SECONDS;
10
10
  var scopeOf = (config, requested) => requested ?? scopesOf(config).join(" ");
11
11
  /** The token response, plus a rotated refresh grant when refresh is enabled. Rotation is unconditional: a refresh
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs/promises";
4
4
  //#region src/modules/ssr/registerExternalPlugins.ts
5
- var MANIFEST_TTL_MS = 600 * 1e3;
5
+ var MANIFEST_TTL_MS = 6e5;
6
6
  var manifestCache = /* @__PURE__ */ new Map();
7
7
  var refreshing = /* @__PURE__ */ new Set();
8
8
  var cacheDir;
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import fs from "node:fs/promises";
7
7
  //#region src/plugins/manager.ts
8
8
  var META_FILE = "meta.json";
9
- var DEFAULT_TTL_MS = 10080 * 60 * 1e3;
9
+ var DEFAULT_TTL_MS = 6048e5;
10
10
  var DEFAULT_CACHE_DIR = ".sdk-plugins";
11
11
  var PluginManager = class {
12
12
  urlPrefix = "/sdk-plugins";
@@ -1,6 +1,14 @@
1
1
  import { SSRServer, SSRServerConfig } from '@plitzi/sdk-shared';
2
- export { createSSRServer } from './server/ssrServer';
2
+ export { createSSRServer } from './server/pageServer';
3
3
  export { createMCPServer } from './server/mcpServer';
4
4
  export { resolveServices } from './services/resolve';
5
5
  export type { ResolvedServices } from './services/resolve';
6
+ /** The general server: mounts whatever the config asks for — SSR, RSC and MCP together, if that is what it
7
+ * enables — and is what a deployment reaches for when it serves more than one surface from a single port (the
8
+ * package's own standalone harness does exactly that).
9
+ *
10
+ * Prefer the surface-specific factories whenever the surface IS known, so the process states what it serves by
11
+ * the name it calls: createSSRServer for pages + RSC, createMCPServer for a dedicated MCP server. An MCP-only
12
+ * config is handed to the latter here, since the lean pipeline builds none of the render template, caches or
13
+ * plugin manager a page server would carry unused. */
6
14
  export declare const createServer: (config: SSRServerConfig) => SSRServer;
@@ -0,0 +1,10 @@
1
+ import { ResolvedServices } from '../services/resolve';
2
+ import { SSRServer, SSRServerConfig } from '@plitzi/sdk-shared';
3
+ /** The page-serving machinery: html/rsc caches, the render template and the plugin manager, driving the page
4
+ * pipeline. Which services it mounts is the CALLER's decision — {@link createServer} passes whatever the config
5
+ * enables, createSSRServer pins the page surface — so this unit never second-guesses a factory's promise. */
6
+ export declare const createPageServer: (config: SSRServerConfig, services: ResolvedServices) => SSRServer;
7
+ /** The page server: SSR and RSC, and nothing else. MCP is pinned OFF here even when the config asks for it —
8
+ * what a factory named after a surface mounts must be readable from its name alone. A deployment that wants
9
+ * both surfaces in one process asks {@link createServer} for them; a dedicated MCP server is createMCPServer. */
10
+ export declare const createSSRServer: (config: SSRServerConfig) => SSRServer;
@@ -1,4 +1,4 @@
1
1
  import { ResolvedServices } from './resolve';
2
2
  import { BaseContext, SSRContext, Stage } from '../http/types';
3
- export declare const buildSSRPipeline: (services: ResolvedServices) => Stage<SSRContext>[];
3
+ export declare const buildPagePipeline: (services: ResolvedServices) => Stage<SSRContext>[];
4
4
  export declare const buildMCPPipeline: () => Stage<BaseContext>[];
@@ -0,0 +1,4 @@
1
+ import { CssProps } from '../../types';
2
+ export declare const classifyBorderTokens: (value: string) => Record<"width" | "style" | "color", string>;
3
+ export declare const expandBorder: (key: string, value: string, out: CssProps) => void;
4
+ export declare const expandBorderGroup: (suffix: string, value: string, out: CssProps) => void;
@@ -0,0 +1,4 @@
1
+ import { CssProps } from '../../types';
2
+ export declare const expandBox: (key: string, value: string, out: CssProps) => void;
3
+ export declare const expandBorderRadius: (value: string, out: CssProps) => void;
4
+ export declare const expandGap: (value: string, out: CssProps) => void;
@@ -0,0 +1,34 @@
1
+ /** Split a shorthand value into whitespace-separated tokens, keeping parenthesized and quoted runs whole. */
2
+ export declare const splitTokens: (value: string) => string[];
3
+ /** Split a value on a top-level separator. Used for the comma-separated layers of transition/animation/background
4
+ * and for the `/` of border-radius/grid-template/grid-row. */
5
+ export declare const splitOn: (value: string, separator: "," | "/") => string[];
6
+ /** Split a value on its first top-level `/` — the `a / b` form of border-radius, grid-template, background and
7
+ * font. Returns the half after the slash as undefined when there is none, which is what tells those expanders the
8
+ * second half was not declared at all. */
9
+ export declare const splitPair: (value: string, separator: "/") => [string, string | undefined];
10
+ export declare const TIME_RE: RegExp;
11
+ export declare const NUMBER_RE: RegExp;
12
+ /** A numeric value with an optional unit or `%` — matches both `12px` and `50%`. */
13
+ export declare const LENGTH_RE: RegExp;
14
+ export declare const QUOTE_RE: RegExp;
15
+ /** Resolve the 1–4 value syntax shared by padding/margin/inset (top/right/bottom/left) and border-radius
16
+ * (top-left/top-right/bottom-right/bottom-left): 1 value fills all, 2 pair up, 3 leave the 4th mirroring the 2nd. */
17
+ export declare const expandTo4: (tokens: string[]) => [string, string, string, string];
18
+ export declare const SIDES: readonly ["top", "right", "bottom", "left"];
19
+ export declare const FLEX_DIRECTIONS: Set<string>;
20
+ export declare const FLEX_WRAPS: Set<string>;
21
+ export declare const LIST_STYLE_TYPES: Set<string>;
22
+ export declare const LIST_STYLE_POSITIONS: Set<string>;
23
+ export declare const TEXT_DECORATION_LINES: Set<string>;
24
+ export declare const TEXT_DECORATION_STYLES: Set<string>;
25
+ /** A timing function may also be a function call — cubic-bezier(…) or steps(…). */
26
+ export declare const isTimingFunction: (token: string) => boolean;
27
+ export declare const ANIMATION_DIRECTIONS: Set<string>;
28
+ export declare const ANIMATION_FILL_MODES: Set<string>;
29
+ export declare const ANIMATION_PLAY_STATES: Set<string>;
30
+ export declare const FONT_STYLE_KEYWORDS: Set<string>;
31
+ export declare const FONT_VARIANT_KEYWORDS: Set<string>;
32
+ export declare const FONT_WEIGHT_KEYWORDS: Set<string>;
33
+ export declare const BORDER_STYLES: Set<string>;
34
+ export declare const BORDER_WIDTH_KEYWORDS: Set<string>;