@plitzi/sdk-server 0.37.9 → 0.37.10

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 (101) hide show
  1. package/CHANGELOG.md +589 -0
  2. package/README.md +17 -0
  3. package/dist/adapters/cloudAdapters.d.ts +5 -3
  4. package/dist/adapters/cloudAdapters.js +120 -34
  5. package/dist/adapters/space/types.d.ts +3 -0
  6. package/dist/core/configSeam.d.ts +14 -0
  7. package/dist/core/configSeam.js +114 -0
  8. package/dist/core/configSeam.test.d.ts +1 -0
  9. package/dist/core/createServer.js +11 -5
  10. package/dist/core/createServer.publicData.test.d.ts +1 -0
  11. package/dist/core/freePort.d.ts +10 -0
  12. package/dist/core/freePort.js +36 -0
  13. package/dist/core/freePort.test.d.ts +1 -0
  14. package/dist/core/http/stages/images.d.ts +6 -0
  15. package/dist/core/http/stages/images.js +47 -0
  16. package/dist/core/http/stages/images.test.d.ts +1 -0
  17. package/dist/core/server/fleet/channel.js +1 -1
  18. package/dist/core/server/fleet/relay.js +1 -1
  19. package/dist/core/services/action.js +5 -4
  20. package/dist/core/services/registry.js +2 -0
  21. package/dist/helpers/cache/keys.d.ts +23 -10
  22. package/dist/helpers/cache/keys.js +16 -2
  23. package/dist/helpers/flagOverrides.d.ts +27 -0
  24. package/dist/helpers/flagOverrides.js +25 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +2 -1
  27. package/dist/modules/actions/moduleFor.js +5 -3
  28. package/dist/modules/actions/runtime/flags.d.ts +16 -0
  29. package/dist/modules/actions/runtime/flags.js +49 -0
  30. package/dist/modules/actions/runtime/flags.test.d.ts +1 -0
  31. package/dist/modules/actions/runtime/kvList.js +1 -1
  32. package/dist/modules/actions/runtime/runAction.js +2 -0
  33. package/dist/modules/actions/transport/callHandler.js +8 -0
  34. package/dist/modules/actions/types.d.ts +14 -1
  35. package/dist/modules/connectors/engine.js +1 -1
  36. package/dist/modules/functions/capabilities.js +1 -1
  37. package/dist/modules/functions/load.js +2 -2
  38. package/dist/modules/functions/manifest.js +1 -1
  39. package/dist/modules/functions/runner/messages.js +1 -1
  40. package/dist/modules/images/allowlist.d.ts +5 -0
  41. package/dist/modules/images/allowlist.js +18 -0
  42. package/dist/modules/images/imageProxy.d.ts +36 -0
  43. package/dist/modules/images/imageProxy.js +273 -0
  44. package/dist/modules/images/imageProxy.test.d.ts +1 -0
  45. package/dist/modules/images/transform.d.ts +10 -0
  46. package/dist/modules/images/transform.js +28 -0
  47. package/dist/modules/images/transform.test.d.ts +1 -0
  48. package/dist/modules/rsc/connectorRscData.d.ts +12 -7
  49. package/dist/modules/rsc/connectorRscData.js +12 -8
  50. package/dist/modules/rsc/handler.js +18 -4
  51. package/dist/modules/rsc/publicFileResolver.d.ts +13 -0
  52. package/dist/modules/rsc/publicFileResolver.js +39 -0
  53. package/dist/modules/rsc/publicFileResolver.test.d.ts +1 -0
  54. package/dist/modules/rsc/resolveRscData.d.ts +4 -2
  55. package/dist/modules/rsc/resolveRscData.js +10 -2
  56. package/dist/modules/runtime/bundle.js +2 -2
  57. package/dist/modules/ssr/Component.d.ts +5 -1
  58. package/dist/modules/ssr/Component.js +3 -1
  59. package/dist/modules/ssr/prepareRender.js +36 -8
  60. package/package.json +9 -4
  61. package/skills/plitzi-authoring/CHEATSHEET.md +135 -0
  62. package/skills/plitzi-authoring/SKILL.md +71 -71
  63. package/skills/plitzi-authoring/recipes/carousel.ts +114 -0
  64. package/skills/plitzi-authoring/recipes/detail-page.ts +70 -0
  65. package/skills/plitzi-authoring/recipes/embed-and-svg.ts +48 -0
  66. package/skills/plitzi-authoring/recipes/every-few-seconds.ts +119 -0
  67. package/skills/plitzi-authoring/recipes/feature-flag.ts +24 -0
  68. package/skills/plitzi-authoring/recipes/filter-a-list.ts +63 -0
  69. package/skills/plitzi-authoring/recipes/forms-and-modals.ts +64 -0
  70. package/skills/plitzi-authoring/recipes/from-tailwind.ts +52 -0
  71. package/skills/plitzi-authoring/recipes/link-to-a-section.ts +35 -0
  72. package/skills/plitzi-authoring/recipes/link-with-data.ts +40 -0
  73. package/skills/plitzi-authoring/recipes/marquee.ts +57 -0
  74. package/skills/plitzi-authoring/recipes/plugin.ts +53 -0
  75. package/skills/plitzi-authoring/recipes/scroll-a-row.ts +79 -0
  76. package/skills/plitzi-authoring/recipes/show-data.ts +73 -0
  77. package/skills/plitzi-authoring/recipes/style-a-page.ts +45 -0
  78. package/skills/plitzi-authoring/recipes/typed-data.ts +64 -0
  79. package/skills/plitzi-authoring/recipes/usable-without-sight.ts +39 -0
  80. package/skills/plitzi-authoring/reference/accessibility.md +2 -2
  81. package/skills/plitzi-authoring/reference/authoring-errors.md +195 -53
  82. package/skills/plitzi-authoring/reference/colours-and-motion.md +63 -0
  83. package/skills/plitzi-authoring/reference/components.md +9 -1
  84. package/skills/plitzi-authoring/reference/data-and-visibility.md +29 -29
  85. package/skills/plitzi-authoring/reference/efficiency.md +53 -0
  86. package/skills/plitzi-authoring/reference/elements-and-styles.md +39 -24
  87. package/skills/plitzi-authoring/reference/feature-flags.md +78 -0
  88. package/skills/plitzi-authoring/reference/flows.md +25 -36
  89. package/skills/plitzi-authoring/reference/kept-state.md +28 -0
  90. package/skills/plitzi-authoring/reference/layouts.md +20 -3
  91. package/skills/plitzi-authoring/reference/lists.md +45 -16
  92. package/skills/plitzi-authoring/reference/plugins.md +15 -0
  93. package/skills/plitzi-authoring/reference/realtime.md +35 -0
  94. package/skills/plitzi-authoring/reference/review-checklist.md +8 -3
  95. package/skills/plitzi-authoring/reference/{templates-and-export.md → snippets-and-export.md} +9 -8
  96. package/skills/plitzi-authoring/reference/structure.md +29 -20
  97. package/skills/plitzi-authoring/reference/tailwind.md +19 -0
  98. package/skills/plitzi-authoring/reference/templates.md +6 -5
  99. package/skills/plitzi-authoring/reference/testing.md +11 -0
  100. package/skills/plitzi-authoring/reference/typed-sources.md +20 -0
  101. package/skills/plitzi-authoring/reference/validation.md +6 -2
@@ -1,7 +1,9 @@
1
1
  # Structuring a space that grows
2
2
 
3
- A one-screen space fits in `src/space.ts`. Past a few pages, split it by what changes together — and keep every
4
- repeated thing in exactly one place.
3
+ A one-screen space fits in `src/space.ts`. Past a few pages, split it by what changes together — a file per part, short
4
+ enough to read whole — and keep every repeated thing in exactly one place. `npx @plitzi/cli create shop --template
5
+ catalog` writes a complete small site in this shape (tokens, a layout, a card component, typed data, a page per file)
6
+ to read before writing one.
5
7
 
6
8
  ```text
7
9
  src/space/
@@ -20,29 +22,36 @@ src/space/
20
22
 
21
23
  ## Components are functions
22
24
 
23
- The same block in two places is a function with parameters. Its ids come from a parameter, because ids are one
24
- namespace for the whole space:
25
+ The same block in two places is a function with parameters. Ids are one namespace for the whole space, so the
26
+ function builds inside `scope()`: every `id` given in it is prefixed by the scope's name, and `ref()` names one of them
27
+ in full for a binding, a step's target or a template:
25
28
 
26
29
  ```ts
27
30
  export const panel = (params: { id: string; title: string; sub?: string; link?: { href: string; label: string }; children: ElementSpec[] }): ElementSpec =>
28
- container({
29
- id: params.id,
30
- class: panelCard,
31
- children: [
32
- container({
33
- id: `${params.id}-head`,
34
- class: panelHead,
35
- children: [
36
- heading(params.title, { id: `${params.id}-title`, subType: 'h3', class: panelTitle }),
37
- ...(params.sub ? [text(params.sub, { id: `${params.id}-sub`, class: panelSub })] : []),
38
- ...(params.link ? [link({ id: `${params.id}-link`, href: params.link.href, class: panelLink, children: [text(params.link.label)] })] : [])
39
- ]
40
- }),
41
- ...params.children
42
- ]
43
- });
31
+ scope(params.id, () =>
32
+ container({
33
+ id: 'panel',
34
+ class: panelCard,
35
+ children: [
36
+ container({
37
+ id: 'head',
38
+ class: panelHead,
39
+ children: [
40
+ heading(params.title, { id: 'title', subType: 'h3', class: panelTitle }),
41
+ ...(params.sub ? [text(params.sub, { id: 'sub', class: panelSub })] : []),
42
+ ...(params.link ? [link({ id: 'link', href: params.link.href, class: panelLink, children: [text(params.link.label)] })] : [])
43
+ ]
44
+ }),
45
+ ...params.children
46
+ ]
47
+ })
48
+ );
44
49
  ```
45
50
 
51
+ `panel({ id: 'hm-traffic', … })` writes `hm-traffic-panel`, `hm-traffic-title`… Whatever names one of them from inside
52
+ takes it from the function's argument — `scope(params.id, ref => …)`, then `scrollBy(ref('row'), …)` or
53
+ `` `{{ list_${ref('row')}.index }}` ``. Scopes nest; a list's `row` already reads its list by its full name.
54
+
46
55
  A page then reads as what it shows: `panel({ id: 'hm-traffic', title: 'Traffic', link: { href: 'analytics', label:
47
56
  'Open analytics →' }, children: [chart] })`.
48
57
 
@@ -0,0 +1,19 @@
1
+ # From Tailwind classes
2
+
3
+ `tw()` turns Tailwind classes into the rules `styles()` takes, when the space is written — nothing of Tailwind runs in
4
+ the page, and the class is an ordinary one the builder edits:
5
+
6
+ ```ts
7
+ const pill = styles('nav-pill', tw('inline-flex items-center gap-2 px-5 py-2 rounded-full bg-slate-950/90 hover:scale-105 md:text-sm'));
8
+ const t = createTw({ colors: tokens(variables) }); // bg-surface → var(--surface), text-ink/60 → a tint of it
9
+ ```
10
+
11
+ - Tailwind v4's scales and palette, its arbitrary values (`w-[220px]`, `shadow-[0_0_20px_rgb(6_182_212/0.35)]`), CSS
12
+ variables (`bg-(--accent)`) and any property (`[mask-type:luminance]`).
13
+ - Breakpoints become Plitzi's ranges: `md:` is tablet and desktop, `lg:` desktop, `max-md:` mobile, `max-lg:` tablet and
14
+ mobile. A property only a wider range sets is `revert` below it.
15
+ - `hover:`, `focus-visible:`, `active:`… are the class's states; `group-hover/card:` is how it looks inside an
16
+ ancestor wearing the class `card`.
17
+ - What has no exact equivalent is refused with what to write instead (`tw-no-equivalent`): `sm:`/`xl:`, `dark:` (a
18
+ token with both values), `space-x-*` (`gap`), `animate-*` (keyframes in `customCss`). An unknown class is
19
+ `tw-unknown-class` with the closest one; two classes that tie on a property are `tw-class-conflict`.
@@ -16,7 +16,7 @@ An attribute holding a condition — `href: "/x/{{ on ? 'a' : 'b' }}"` — is us
16
16
  `template-never-resolved`). Move the expression into a binding with `bindTemplate`:
17
17
 
18
18
  ```ts
19
- link({ href: '/', mode: 'internal', bind: [bindTemplate('href', 'state.workspace.id', "{{ source ? '/workspace/' ~ source : '/' }}")] })
19
+ link({ href: '/', bind: [bindTemplate('href', 'state.workspace.id', "{{ source ? '/workspace/' ~ source : '/' }}")] })
20
20
  ```
21
21
 
22
22
  A value that came from a BINDING is data, never a template: a comment reading `{{ auth.accessToken }}` prints those
@@ -39,8 +39,8 @@ A text template feeding `items` is refused: the list would receive the array wri
39
39
  ## Naming sources inside a template
40
40
 
41
41
  Always the full name — the element's kind, an underscore, its id: `apiContainer_stats`, `list_rows`,
42
- `modalContainer_credits`. The globals (`state`, `auth`, `navigation`, `variables`, `host`, `theme`, `computed`) are named
43
- as themselves. In a flow, an earlier step is read by the name given with `named('quote', …)`: `{{ quote.output.total }}`.
42
+ `modalContainer_credits`. The globals (`state`, `auth`, `navigation`, `variables`, `host`, `theme`, `flags`, `computed`) are
43
+ named as themselves — `{{ flags.newCheckout }}` is `true` or `false`, see [feature flags](feature-flags.md). In a flow, an earlier step is read by the name given with `named('quote', …)`: `{{ quote.output.total }}`.
44
44
 
45
45
  An id with a hyphen is one name inside a template — `{{ apiContainer_tn-data.data.total }}` reads that provider. So
46
46
  subtraction is written with spaces: `{{ total - used }}`, never `total-used`.
@@ -69,7 +69,7 @@ outside the element that publishes it — each with the name it should have been
69
69
  interpreter would read past, rather than letting a template render a value nobody wrote.
70
70
 
71
71
  Filters: `default upper lower trim capitalize title camelize kebab snake ltrim rtrim pad padRight replace slice split
72
- join reverse length first last contains startswith endswith number number_format round abs format sort batch chunk map
72
+ join reverse length first last contains startswith endswith number number_format currency percent round abs format sort batch chunk map
73
73
  reduce merge keys values filter column find pluck unique flatten sum without only index_by group_by url_encode nl2br
74
74
  striptags spaceless json_encode to_json object_as_json raw date base64_encode base64_decode md5 random`.
75
75
 
@@ -93,7 +93,8 @@ A lookup across two sources — a row joined to the stats around it:
93
93
  step's params and an action's output, `"{{ text }}"` in quotes is safe too — a value inside a string literal of a
94
94
  param written as a JSON document is escaped for it. Anywhere else (an attribute, a binding's template) it is not:
95
95
  encode.
96
- - **Numbers**: `number_format(decimals, point, thousands)`; `round(precision)`.
96
+ - **Numbers**: `number_format(decimals, point, thousands)`; `round(precision)`; `currency('USD', 'en', { trimZeros: true })`
97
+ (`$2,040.57`, `$440`); `percent(1)` (`0.256` → `25.6%`). Locale `en` unless named, so server and browser agree.
97
98
  - **A step param that is one `{{ expression }}` is that value, with its type**: a number stays a number, and a
98
99
  string stays a string however it looks — a password typed `1234` arrives as `"1234"`. Nothing is guessed from the
99
100
  text: to convert, declare the type where the value lands (`setState`'s `type`, the action's input field). Text around
@@ -57,6 +57,13 @@ for it; over `max`, `problems` says so. It reads the SDK's render tracing, which
57
57
  the space with it (the e2e harness: `renderSpace(page, space, { debugMode: true })`). What to do with the answer is in
58
58
  [performance](performance.md).
59
59
 
60
+ ## Asking the page what it holds
61
+
62
+ `plitzi check` lists every flow that failed while the page loaded; `--state` adds the state and every source by its
63
+ full name, `--element <id>` one element — what it reads, its own state, whether it is on screen. They come from the
64
+ page's dev tools (debug mode, any development server). In a test, `readDevTools(page, { state: true })` from
65
+ `@plitzi/sdk-authoring` answers the same.
66
+
60
67
  ## Spaces written for a test
61
68
 
62
69
  Author them like any space — never as JSON: the validator is what tells a fixture that tests something from one that
@@ -112,6 +119,10 @@ expect(await page.evaluate(() => (window as unknown as { __painted: string[] }).
112
119
 
113
120
  ## Screenshots and scrolling
114
121
 
122
+ A browser holds back CSS animations and transitions in a tab that is not in view, and `onInterval` stops ticking there:
123
+ check motion with `getComputedStyle(element).animationName` (and `animationPlayState`), or two screenshots taken
124
+ apart, in a page that is in front — not a background tab.
125
+
115
126
  The page scrolls the document in development exactly as in production while the dev-tools panel is folded away (the
116
127
  badge). With the panel OPEN, the page shares the window with it and scrolls inside its own pane — close the panel, or
117
128
  run with `debugMode: false`, before a `fullPage` screenshot or a scroll measurement. For a screenshot at a width and a
@@ -0,0 +1,20 @@
1
+ # Data typed by a sample
2
+
3
+ `source('site', home)` names a provider's source from a sample of its answer — the JSON file it reads, imported
4
+ (`import home from '../../public/data/home.json' with { type: 'json' }`). Every path is then completed by the editor
5
+ and checked: `site.data.hero.titel` is a type error, and refused `source-field-unknown` if it gets past the types.
6
+
7
+ ```ts
8
+ const site = source('site', home);
9
+ apiContainer({ id: site.id, query: '/data/home.json', children: [
10
+ heading({ from: site.data.hero.title }),
11
+ text({ content: twig`{{ ${site.data.products}|length }} products` }),
12
+ list({ id: 'products', items: site.data.products, row: p => text({ from: p.item.title }) })
13
+ ] })
14
+ ```
15
+
16
+ - A path is the source's full name (`apiContainer_site.data.hero.title`), so it goes in `from`, `items`, `bind` and
17
+ `visible` as it is, and into a template through `twig` (a plain template literal writes the same text, but a
18
+ type-checked lint refuses an object in one).
19
+ - A list fed by a path hands its `row` the item typed: every field any item of the sample has.
20
+ - An item of a list is read by position: `site.data.sections[1]`.
@@ -36,15 +36,19 @@ bar.
36
36
  import { space } from './src/space.ts';
37
37
 
38
38
  try {
39
- const { schema, warnings } = authorSpace(space, { plugins: [] /* your declarations */ });
39
+ const { schema, warnings, suggestions } = authorSpace(space, { plugins: [] /* your declarations */ });
40
40
  console.log(`${Object.keys(schema.flat).length} elements, ${warnings.length} warnings`);
41
41
  warnings.forEach(warning => console.log(`[${warning.code}] ${warning.message}`));
42
+ suggestions.forEach(({ code, message, saves }) => console.log(`[suggest] ${code} (saves ${saves}): ${message}`));
42
43
  } catch (error) {
43
44
  console.log((error as Error).message);
44
45
  }
45
46
  ```
46
47
 
47
- 2. **Fix, re-run, until it prints zero warnings.** Then typecheck the project.
48
+ 2. **Fix, re-run, until it prints zero warnings.** What has one fix (`author` says how many) is written for you: `npx
49
+ plitzi fix` shows it as a diff of your source, `--write` writes it and keeps it only if the space authors with each
50
+ fix gone and nothing new — a fix it cannot place, or that would add a problem, is said with the reason. Then
51
+ typecheck the project.
48
52
  3. **Look at it** — in a browser, at desktop and phone width, in both themes (see [testing](testing.md)). Stage 3 is
49
53
  thorough about MEANING and blind to what follows.
50
54