@plitzi/sdk-interactions 0.38.4 → 0.38.6

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,357 @@
1
1
  # @plitzi/sdk-interactions
2
2
 
3
+ ## 0.38.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 3bce653: ## Fixes
8
+
9
+ - **The dev tools draw their icons again.** The panel lives in a shadow root of its own, which the page's stylesheets
10
+ never reach, and Font Awesome moved to a sheet of its own in 0.38.5 (`plitzi-sdk-icons.css`): the badge and every tab
11
+ lost their icons. The panel now links that sheet inside its root, beside its own. Where it is served is the SDK's
12
+ new `sdkIconsStylePath` — the page server sends `/sdk-assets/plitzi-sdk-icons.css`, the same sheet the document
13
+ links — which the `iframe` and `shadow` render modes read too: they had `/plitzi-sdk-icons.css` hard-coded, a path a
14
+ page server does not serve.
15
+
16
+ - Updated dependencies [3bce653]
17
+ - @plitzi/sdk-auth@0.38.6
18
+ - @plitzi/sdk-event-bridge@0.38.6
19
+ - @plitzi/sdk-shared@0.38.6
20
+
21
+ ## 0.38.5
22
+
23
+ ### Patch Changes
24
+
25
+ - 3bce653: ## Agents work through the MCP: the builder's AI chat is gone
26
+
27
+ An agent reaches a space through the MCP, from whatever harness the person already uses; Plitzi no longer runs one of
28
+ its own inside the builder. **Breaking** for whoever imported what that chat was built on:
29
+
30
+ - **The builder's Assistant panel is removed**, with its conversations, attachments, previews and provider settings.
31
+ - **`@plitzi/sdk-mcp` no longer exports the in-process AI engine**: `AIEngine`, `toolResponseOk`, `toolResponseErr`,
32
+ `zodToJsonSchema`, `getAllowedModes`, `bindTools`, `isToolActive`, `resolveToolHandler`, `isCallToolResult`,
33
+ `toolResponseFromResult` and `buildAgentGuide`. The MCP server, its tools and `@plitzi/sdk-mcp/server` are unchanged;
34
+ the tool functions are still exported to run in-process.
35
+ - **`@plitzi/sdk-shared` drops the chat's types** — `AITypes` (`AiContext`, `AiMode`, `AiMessageAttachment`,
36
+ `AiUsage`, …) and `McpTypes` (`McpTool`, `McpToolHandler`, `McpContent`, …), with their `./types/AITypes` and
37
+ `./types/McpTypes/index` subpaths — and no longer depends on `@modelcontextprotocol/sdk`.
38
+ - **A change's `origin` is never `coworker`**: `ChangeOrigin` is `builder`, `mcp`, `autofix`, `api` or `system`, and
39
+ the History panel's filter follows.
40
+ - **The builder's platform flags are gone**: `PlatformFlags` is no longer part of the builder's first query, nor
41
+ `platformFlags` of its store — `assistanceAI` was the only one.
42
+ - **Plitzi's own deployment no longer offers the `ai.complete` task** to flows.
43
+ - **Each MCP tool says whether it only reads** (`readOnlyHint`, from the tool's `access`), so a host can run a read
44
+ without asking and ask before a write. Until now `access` was read only by the builder's chat.
45
+ - **The MCP works over the JSON adapters.** `createJsonAdapters` offers `getSchema` and `getStyle`, and — for a space
46
+ read from a path — `saveSchema` and `saveStyle`, each writing its document back into the file. **Breaking**:
47
+ `saveOfflineData` is gone from `SSRAdapters`; nothing called it, and it was the JSON adapters' only write, so an MCP
48
+ over them could neither read nor save.
49
+
50
+ ## Lighter pages: icons on their own
51
+ - **Font Awesome is a stylesheet of its own**, `plitzi-sdk-icons.css`, with its fonts as files beside it
52
+ (`webfonts/`) instead of base64 inside `plitzi-sdk.css` — which drops from 287 KB to 103 KB. A page fetches a face
53
+ only when it shows an icon of that style, and a new SDK version no longer downloads the fonts again. The page server
54
+ links it after `plitzi-sdk.css` (`SSRTemplateProps.iconsCssPath`), the SDK's iframe and shadow modes load it beside
55
+ the stylesheet, and the package exports it: **a project that imports `@plitzi/plitzi-sdk/plitzi-sdk.css` imports
56
+ `@plitzi/plitzi-sdk/plitzi-sdk-icons.css` too** — `plitzi upgrade` adds it to `src/main.ts`, and a new project has
57
+ it. The MCP's rendered widgets inline the sheet, fonts and all, only for a widget that draws an icon.
58
+ - **Material Icons is no longer loaded.** Nothing in Plitzi used it; every page paid a request to Google for it.
59
+ - **A page loads only the plugins it draws.** The page server sends the rest as their declaration
60
+ (`PluginEntry.deferred`, `pluginDeclarationOf` in `@plitzi/sdk-shared/plugins/declaration`); the SDK registers each
61
+ as a stand-in that knows its types from the start, and loads its code and stylesheet the first time one of its
62
+ elements is drawn — on a page reached by navigating too, with no server involved. What a page draws is
63
+ `pageElementTypes` (`@plitzi/sdk-shared/schema/pageElements`): the page, its layouts, the components and references
64
+ it holds. `render()` takes a plugin as `{ load, css, declaration }` beside `{ component }`.
65
+ - **The space travels beside the page, not inside it.** The page names it (`<link id="plitzi-space">`,
66
+ `SSRTemplateProps.spaceDocumentPath`), the browser fetches it while the scripts load, and the page server answers
67
+ `/_plitzi/space/<hash>.json` behind the same gates as its pages — `immutable`, since the name is its content's hash:
68
+ one download for every page and every visit until the space changes. The website's docs page went from 2.6 MB to
69
+ 0.5 MB. Still the whole space, so every navigation after the first page stays in the browser. A draft preview and a
70
+ deployment's own `templateFn` keep it inline.
71
+ - **An arrival on screen plays as the page arrives.** A `motion: { on: 'view' }` element already in view was held
72
+ invisible until the SDK hydrated — the page's content, and a page rendered `ssrOnly` forever. The page server's
73
+ document now sees it as soon as it is parsed, and hands over to the SDK once the page is live.
74
+
75
+ ## Server data: one request per question, the newest winning, and a way to stop
76
+ - **A link asks for its page's server data once.** The navigation's prefetch already brought it; the route change that
77
+ followed asked again — two renders per click. `useRscSync` now asks only for a location the store does not hold.
78
+ - **Answers land in the order they were asked.** `refreshRsc` joins a refresh already in flight for the same URL and
79
+ aborts one a newer refresh would overwrite (a whole payload asked again, an element asked again); a whole payload
80
+ keeps what an element asked for after it. A slow answer no longer paints a page the visitor has left.
81
+ - **Stopping.** `cancelRsc(store, ids)`, an `apiContainer`'s `cancelQuery` callback (`cancelApi(id)` in authoring) and
82
+ `queryCache.cancel(key)` for a browser provider: the request is dropped and what is shown stays. The server hears it:
83
+ `SSRRscContext.signal` aborts when the browser hangs up, and the default `getRscData` stops every element still
84
+ resolving — a shared render run only once nobody is waiting on it.
85
+ - **Pending.** A server provider's `isLoading` is true while it is asked again (`rsc.refreshing`). The `navigation`
86
+ source says `pending` and `pendingLocation` while a link waits for its destination's data; a second link clicked
87
+ before the first went supersedes it, and the first never goes.
88
+ - **A bound `input` asks again.** An `apiContainer`'s `input` is a prop now: bound to state, every change refetches a
89
+ server provider. What a refresh asks for (`req.ctx.rscParams`) wins over the `input` the element was saved with.
90
+ - **A section cut by its budget says how to give it more** (`rsc.elementTimeoutMs`).
91
+
92
+ ## Flows
93
+ - **`whileRunning('latest')`**: a new firing stops the run in progress — no further step; its `runServerAction` is
94
+ cancelled (request and server run) and its `webHook` aborted — and runs. A search as you type. The step's context
95
+ carries the `signal`. Offered in the builder and the MCP.
96
+ - `onApiSuccess` / `onApiError` fire once per answer: a cancelled request or a loading flag that came and went no
97
+ longer runs the flow again.
98
+ - **A debounce is `whileRunning('latest')` and a `delay` first**: `delayTime` ends its wait the moment its run is
99
+ superseded, so only the last firing gets past it. Its `time` is a number.
100
+
101
+ ## Server actions
102
+ - **The request budget is never silent.** A task that caught the refused fetch hid `maxRequests`; the step that ran
103
+ into it now logs it, and the server logs it once, naming `createServer({ action: { limits: { maxRequests } } })`.
104
+ - **`invalid_input` says what the value was and which type takes it** — `windows (text, got a list; declare the field
105
+ \`json\` to take it)`. The templates reference documents that a param that is only `|json_encode` is the value it
106
+ encodes.
107
+ - **`kv` survives a restart with no database.** `createFileKv({ file })` (`@plitzi/sdk-server/actions`) keeps the
108
+ in-process store in one JSON file, written whole as soon as anything changes — for one process.
109
+ `createSqliteKv({ file } | { db })` (`@plitzi/sdk-server/sqlite`, new entry) is a table in a SQLite file over
110
+ `node:sqlite`, every operation one statement, shared safely by every process on the file. Both pass the adapter
111
+ contract every store is held to. The `09-schedules` example uses it rather than its own copy, whose counters read
112
+ `22.0`.
113
+
114
+ ## Authoring
115
+ - **What a space declares and never uses** is suggested now: `unused-class`, `unused-token`, `unused-component`, and
116
+ `literal-colour` — a class painted from the palette typing out a scheme token's light value. A suggestion about
117
+ declarations names them in `subjects`, so the MCP says one a batch left unused beside the ones the space had.
118
+ `unusedDeclarations(schema, style)` is the rule itself, for anything else that says "unused" (the builder does).
119
+ - **`pageFamily(shape, entries)`** writes pages of one shape from data: each page its id, slug, titles, folder and
120
+ layout, and its body built inside `scope(entry.id, …)` so two pages never give one id.
121
+ - **`action-output-path`** (warned): `.data` read on a provider fed by a server action, which publishes its output at
122
+ the root. **`actionSource(id, sample)`** types such a provider by a sample of its output.
123
+ - A value template (`returns: 'value'`, a computed, a step param) may name its parts with `{% set %}` before its one
124
+ expression, and is still that expression's value.
125
+ - `abs()`, `floor()`, `ceil()`, `clamp()` are refused with how they are written here (`x|abs`,
126
+ `x|round(0, 'floor')`, `min(max(x, low), high)`).
127
+ - An unknown attribute is reported against the element's props, not as "'id' does not exist in type ElementSpec[]".
128
+ - `explain` answers a builder's name (`reloadApi`, `cancelApi`) with its step, and knows `whileRunning`.
129
+ - A component's instance takes `quiet`, like any element — `component(id, { quiet: ['repeated-shape'] })`: instances
130
+ whose slots are filled alike on purpose stop being offered as a repeat, and an exported instance that carries one
131
+ typechecks.
132
+ - `container` and `text` take a `title`. A trigger's `preview` may hold numbers, flags, lists and `null`.
133
+ - **A Markdown element's headings are sections a link can name.** Each renders with the anchor its words read as
134
+ (`## Server data` → `#server-data`, numbered when repeated), and a link's `hash` may name one: `anchor-missing` counts
135
+ them, so a table of contents built from the Markdown is checked like any other link.
136
+ - `answerAction(page, actionId, output)` (testing): a test that would save something answers that server action in
137
+ the browser — the server's `kv`, and what the developer kept, are never written. A stream step gets its `done` frame.
138
+
139
+ ## Plugins
140
+ - **A plugin's stylesheet sits below the space's.** The cascade order is now `… utilities, plitzi-sdk-plugin,
141
+ plitzi-sdk-runtime`, and whatever builds a plugin writes its CSS into `plitzi-sdk-plugin` (`inPluginLayer` from
142
+ `@plitzi/sdk-shared/style`): `plitzi pack plugin`, a server compiling one (`action: 'compile'`), and the stylesheet a
143
+ server copies or downloads for one. A space's classes and `customCss` now win over what the plugin's author shipped,
144
+ whatever the specificity — as they do over a built-in element. A plugin packed before this ships unlayered and still
145
+ wins until it is packed again.
146
+ - **Declared param types reach the callback.** A param declared `number` (new, a text box in the builder) or `boolean`
147
+ is handed over as one — written `5000`, or bound to text that says it; an empty number as nothing, so the component's
148
+ default applies.
149
+ - **`useElementVisible(id)`** (`@plitzi/plitzi-sdk`): whether another element is on the page — its own `visible`, every
150
+ container around it, the breakpoint — kept current while the plugin is mounted, at that plugin's cost alone.
151
+ - **A page open on a development server loads again when the server restarts** (`devReload`): `start:dev` restarting
152
+ on a change to the server's code reached the open page only when somebody reloaded it.
153
+ - **A saved plugin is swapped into the open page** (`devReload`, server mode): the page server rebuilds the plugin
154
+ whose source changed and the page renders the new component in place, keeping its state — no restart, no reload. A
155
+ change to its declaration (what the builder and the linter know of it) still reloads the page. `render()` takes
156
+ `{ hotPlugins: true }` and answers `{ unmount, replacePlugin }`; `PluginManager.rebuild(name)` and `onSources`. A new
157
+ folder in `src/plugins` is registered without a restart (`server.plugins.register`), from `create` and `create --from` alike.
158
+ - **A plugin brings its own server half.** `functions/index.ts` beside the component — the `defineFunctions` a
159
+ space's functions use — answers routes under `/fn/plugins/<type>/` and runs steps named `<type>.<action>` (origin
160
+ `'plugin'`, the builder's **Plugins** group). It runs with a narrower `ctx`: its own `kv` (prefixed
161
+ `plugin:<type>:`, its `rateLimit` and `sign` too), none of the space's credentials, no realtime publishing or grants;
162
+ its routes and tasks outside its namespace are refused, as are a space's routes under `/fn/plugins/`. The component
163
+ reaches its routes with `usePluginRoute(type)` (`@plitzi/plitzi-sdk`; `undefined` where no server runs code).
164
+ A page server takes them as `functions.plugins` (`{ [type]: FunctionsDefinition }`), swaps one with
165
+ `server.functions.setPlugin`, and asks a cloud adapter with `actionLookups.getPluginFunctions(spaceId, version)`.
166
+ `plitzi pack plugin` carries the source in the zip as `functions.source.json` (`PLUGIN_FUNCTIONS_SOURCE`, named by
167
+ the manifest's `functions`; `loadFunctionsSource` builds it) — kept privately by the platform, never published.
168
+ - **A plugin lays out the space's elements it holds.** `elementChildren(children)` (`@plitzi/plitzi-sdk`) hands
169
+ over each child element with the id it was authored under, for a dock, tabs or a masonry to place in boxes of its
170
+ own — instead of writing styles onto elements it does not render.
171
+ - **`useDisplayMode()`** (`@plitzi/plitzi-sdk`): `desktop`, `tablet` or `mobile`, at the widths the space's styles are
172
+ compiled at — not a breakpoint of the plugin's own.
173
+ - **A file a library needs whole travels inside the bundle**, imported as Vite imports it: `worker.js?raw` (its text — a
174
+ worker from a Blob), `engine.wasm?inline` (a base64 data URI). `plitzi pack` and a server compiling a plugin share
175
+ one build (`@plitzi/sdk-shared/plugins/bundle`), so a server now inlines `.avif`, `.ttf` and `.otf` as `pack` did.
176
+
177
+ ## CLI and page checks
178
+ - **`plitzi lint`** reads a local space's source the way eslint reads code, for what the document cannot show:
179
+ `file-too-long`, `pages-in-one-file`, `inline-records` (rows of data written in code → `src/data/`), `repeated-css`,
180
+ `special-case-in-map`, `colour-not-token`, `positional-id`, `unused-file` — each at file:line with what to write
181
+ instead — and authoring's suggestions at the line that wrote them. `--json`, `--strict`, `--max-warnings <n>`;
182
+ `// plitzi-lint-disable-next-line <code> -- why` for a departure on purpose. Projects get `npm run lint:space`
183
+ (`upgrade` adds it to older ones). `doctor` stays the project's and never reads the space.
184
+ - **`--template blank`** starts with only the colours its page uses: a token declared and read by nothing is one
185
+ authoring now points out.
186
+ - **The welcome space is written as a folder**, not one 800-line file: `src/space/index.ts` (the page), `tokens.ts`,
187
+ `theme.ts`, `content.ts` — the shape a space keeps as it grows, and clean under `plitzi lint`. **Breaking** for
188
+ whoever called it: `blankSpaceSource()` is now `blankTemplateFiles({ name, dir, plugin })`, which returns the files by
189
+ path; `toPortableSource` keeps imports of the files beside it. A plugin package's preview space is
190
+ `preview/space/index.ts`.
191
+ - **`check`, `shot` and the generated visual tests settle instead of waiting for `networkidle`**, which never came on a
192
+ page with a realtime channel: `openPage` (`@plitzi/sdk-authoring`) waits for load, then quiet, counting no stream
193
+ that stays open.
194
+ - **The server is the project's in `src/config/serverOptions.ts`; `src/main.ts` stays the CLI's.** `create` writes
195
+ `src/config/serverOptions.ts` (handed to `createServer`, typed from `ServerConfig`, now exported by
196
+ `@plitzi/sdk-server`) and, with `--source local`, `src/actions/index.ts` (the space’s server actions), which `main.ts` wires
197
+ for calls, renders and schedules. What `main.ts` wires itself (the space's adapters, the plugins, `public/`,
198
+ `src/data/`, `src/functions/`, the actions' lookups) is left out of `serverOptions`' type and comes after it, so no option unwires it.
199
+ `upgrade` writes either file into a project that has none — only when the `main.ts` reading it is the CLI's — and
200
+ never replaces it; `create --from` projects read `serverOptions.ts` too.
201
+ - **`upgrade packages` brings up the scripts the CLI wrote and nobody changed** (`.plitzi/scaffold.json` now records
202
+ them); a script the project changed is left and said, as before.
203
+ - `start:dev` restarts on a change to the server's code — `src/main.ts`, `src/config/`, `src/actions/` and
204
+ `src/functions/` (there from the start, kept by a `.gitkeep`); a plugin is swapped in the open page instead, and its
205
+ `functions/` set again, without a restart. A function imports its siblings with `.ts`, as `src/`.
206
+ - `add plugin --server` writes the plugin's server half (`functions/index.ts`, a `GET`/`POST /state` example on its
207
+ `kv`); a package gets `@plitzi/sdk-server` as a devDependency for its types. Refused in a client-mode project.
208
+ - `add plugin`: `--prop rows:list` and `--prop meta:json` for data a binding fills; `--headless` writes
209
+ `drawsNothing: true`; the generated events hook never fires on the builder's canvas.
210
+ - A `channel` with no tag is boxless to a page check, like a provider; an empty list is said to have no rows, with what
211
+ to do, instead of "no size (0×0)".
212
+ - **A server-mode project keeps its `kv` in `state/kv.json`** (`createFileKv`; ignored by git): what the space's actions
213
+ save outlives a restart, `start:dev`'s included. `action.kv` in `src/config/serverOptions.ts` names another store.
214
+ - A server-mode project types what its plugins import besides code (`plitzi/assets.d.ts`): a stylesheet, an image,
215
+ `?raw`, `?inline` — a client-mode one has them from `vite/client`.
216
+ - **A project made from a space runs the server `create` writes.** It has the same `src/main.ts` as any project: it
217
+ now takes a free port and writes `tmp/dev-server.json` (which `check`, `shot` and the visual tests read), answers
218
+ `/health`, and re-authors its pages on save instead of waiting for a restart. Its actions are `src/actions/` and its
219
+ connectors `src/connectors/`, both there from the start, and `start:dev` restarts on them; `src/actions/index.ts`
220
+ exports `actions` and `connectors`, as a `create` project's does (`push` reads that). Before, `upgrade` showed its
221
+ `main.ts` as the project's own, and `--take all` would have put `create`'s server in place of the space's.
222
+ - **A `--source cloud` server project starts.** Its key is in `.env`, which nothing read: `npm start` stopped on "Set
223
+ PLITZI_HOST_KEY". Every server project's `src/main.ts` now reads `.env` itself (`process.loadEnvFile`), and every
224
+ one is given a signing key there (`PLITZI_SIGNING_SECRET`, made for it by `create`) — `ctx.sign` refused in a project
225
+ `create` wrote, a plugin's server half included. `PORT` is no longer written into `.env`, where it pinned 8080. A
226
+ cloud project keeps its `kv` in `state/kv.json` too.
227
+ - **`upgrade` keeps the package manager a project was written for** (`.plitzi/scaffold.json`) when it has no lockfile
228
+ of its own yet: a yarn or pnpm project not installed (`--no-install`), or sitting in a monorepo folder, was taken for
229
+ npm and its `AGENTS.md`, Playwright config and `.gitignore` replaced with npm's commands.
230
+ - `author`, `check`, `fix` and `push` know the element types of a project's built-only plugins
231
+ (`vendor/plugins/*/plugin-manifest.json`, every element each provides), as its server does.
232
+ - **`import` reaches Plitzi only when told to.** A site not served from this machine needs `--account` — ask the
233
+ person's Plitzi account whether one of their spaces verified its domain, signing in — and without it is refused,
234
+ saying so, before any request: an agent running `import` in a local project opened a sign-in nobody asked for. The
235
+ CLI skill and the generated `AGENTS.md` say to run `import` only when the user asks, and to ask before `--account`.
236
+ - **A server project's data is no longer on the internet.** It was `public/data/*.json`, served to anyone as a file;
237
+ it is `src/data/*.json`, which the server reads and never serves (`dataDir`, new in `createServer`): a provider with
238
+ `runtime: 'server'` and `query: '/data/<file>'` reads it, and the page arrives with it. `projectData`
239
+ (`@plitzi/sdk-authoring/node`) and `authorSpace`'s `serverData` hold bindings to those files, and a provider asking
240
+ for `/data/…` from the browser is refused (`server-data-in-browser`): nothing would answer it. What a provider reads
241
+ is still in the page it renders — data a page must not carry is a server action's to read. A client-mode project
242
+ keeps `public/data/`, which the browser has to fetch. The catalog template follows the mode
243
+ (`catalogTemplateFiles({ mode })`). `public/` holds only what is meant for everyone.
244
+ - **The project's own server code is `src/functions/`**, with the rest of its source — typechecked with it, left out of
245
+ `tsconfig.build.json` (the server builds it at boot). `functions pull`/`push`/`dev`, `push`, `pull` and `create
246
+ --from` follow. The `kv` folder is `state/` (it was `data/`, beside a data folder that was something else).
247
+ - **What is the CLI's is in `plitzi/`, apart from `src/`.** `plitzi/author.ts`, `plitzi/assets.d.ts` (server mode) or
248
+ `plitzi/preflight.css` (client mode), and `plitzi/README.md` — which says what each folder of `src/` is, instead of a
249
+ README in every one. `src/main.ts` stays where an entry point is looked for, the CLI's all the same.
250
+ `src/plugins/declarations.ts` is gone: a plugin is declared by its folder's `declaration.ts`, found by the server,
251
+ `author`, `check`, `fix`, `push` and the visual test alike (`pluginDeclarations` from `@plitzi/sdk-authoring/node`;
252
+ Vite's `import.meta.glob` in client mode).
253
+ - `functions.plugins` is in `SSRServerConfig`'s type and checked as the server starts: a project passing its plugins'
254
+ server halves did not typecheck.
255
+ - **A project's source is folders that grow.** The space is `src/space/` — its `index.ts` exports it as `space` and
256
+ assembles the rest (the catalog template's `src/site/` is `src/space/` now; `create --from` writes the space's own
257
+ `index.ts` there, exported as `space` too). The server actions are `src/actions/` — `index.ts` lists them, one action
258
+ a file, as `create --from` already had them — and what the server does besides serving the space is
259
+ `src/config/serverOptions.ts`. `start:dev` watches `src/config` and `src/actions` whole.
260
+ - **One server for every project, kept by `upgrade`.** `src/main.ts` runs what a project holds from where it lands — a
261
+ runtime in `src/runtime/` (or built only, `vendor/runtime.bundle`), plugins built only (`vendor/plugins/`), the
262
+ actions' connectors — so `create --from` writes no `main.ts` or `.prettierignore` of its own any more, and `upgrade`
263
+ keeps both current in projects made from a space too. `loadRuntimeModule` (`@plitzi/sdk-server/runtime`): a project's
264
+ runtime module, or nothing when it has none.
265
+ - **`plitzi add runtime`** writes `src/runtime/index.ts` — run by the project's server (`npm start` answers its
266
+ endpoints) and sent by `plitzi runtime push`, whose default entry it is now — and has `start:dev` restart on it. A
267
+ space taken out with a runtime gets `src/runtime/index.ts` handing over the module its source starts at.
268
+ - **`push` sends the space's files back as their CDN addresses.** `create --from` and `pull` write each CDN address as
269
+ the project's path (`/assets/a.png`, served from `public/assets/`); `push` sent those paths as they were, and the
270
+ space's pictures and data pointed at nothing on Plitzi. It also says, before sending, what Plitzi would not have: a
271
+ provider reading a file `src/data/` does not hold, and a file the space names that is not on its CDN.
272
+ - **`push` sends a project's data and its files.** Two new parts: `data` — `src/data/` whole, kept by Plitzi as the
273
+ space's own data (private, frozen with each publish, read by the page server of the version it renders; refused when
274
+ the space's copy changed since the project last had it, unless `--force`) — and `files` — each changed file of
275
+ `public/assets/`, put at the same path under the space's `assets/` on its CDN, so a pull brings it back where it was.
276
+ `create --from` and `pull` write the space's data into `src/data/`. `getData` joins the action lookups
277
+ (`ActionLookupsConfig`), and the page server resolves `/data/<file>` through it when there is no `dataDir`.
278
+ - **A space's data is edited in the builder and by agents.** The builder's Server view has a **Data** tab: the JSON
279
+ files the space's server providers read (`/data/<file>`), a file list and a JSON editor, saved whole against the
280
+ copy it read (⌘S; a newer copy is refused, a broken file named). The MCP reads it as `plitzi://data/{env}` and writes
281
+ it with `upsertDataFile` / `deleteDataFile` (`getData` / `saveData` among the adapters; a file that is not JSON is
282
+ refused as it is written), and the guide has a Data section. One write path for the builder, agents and `plitzi push`
283
+ (`SpaceData` / `SpaceSaveData` over GraphQL, under `spaceManage`), one history. `DataDraft` / `DataSaveResult` are
284
+ in `@plitzi/sdk-shared`. The builder's file list is shared by Functions and Data (`modules/FileTree`).
285
+ - **`plitzi doctor`**: whether the project the CLI set up is whole, checked against what it is now — a developer may
286
+ change any file. Packages (declared, installed at versions that agree, one copy of the SDK and of React), the CLI's
287
+ files and scripts (as `upgrade` sees them, one planner for both), the file each Node script starts and every folder
288
+ `start:dev` watches, the TypeScript configs, `.gitignore` and `.env` (never in git; the signing secret as long as the
289
+ project's server wants it), the code Node runs as written (relative imports with their extension, JSON with its
290
+ attribute, no JSX, every package declared — walked with esbuild), each plugin folder against its declaration, the data
291
+ files, `src/functions/` built by the project's own sdk-server, `.plitzi/` and the skills. Each finding has an area, a
292
+ code, the file and its fix; `--json`, `--strict`; exit 1 on an error. It never checks the space — `npm run author`
293
+ and `check` do — and every report says so. A layout an older CLI left (`src/space.ts`, `src/author.ts`,
294
+ `functions/` at the root…) is its own area, checked first and alone. `--fix` repairs what is simple and safe —
295
+ that layout moved with every import, URL and script following (and the scaffold record with it), dead files,
296
+ `.gitignore`, `"type": "module"`, a watched folder, a signing secret — then checks again; `--dry-run` says what.
297
+ Each report ends with what to run next. `upgrade` writes no file over an older layout, and says `doctor --fix`.
298
+ `buildFunctions` and `FunctionsBuildError` are exported from
299
+ `@plitzi/sdk-server/functions-runner`, `MIN_SIGNING_SECRET_LENGTH` from `@plitzi/sdk-server/actions`.
300
+ - **`--dry-run`** on every command that writes or sends — `create`, `add plugin`, `add runtime`, `pull`, `push`,
301
+ `pack plugin`, `source`, `import`, `upload plugin`, `functions pull`/`push`, `runtime push`/`start`/`stop`/`size`/
302
+ `vars`, `skills update`, `doctor --fix`: each file it would write (`+` new, `~` replaced, `-` removed), what it would install or run, what it would
303
+ send and where, and none of it done. It still reads what it needs to say so.
304
+ - **A new project is formatted from the start**, every template and mode: its first `format` changes nothing. The CLI's
305
+ own files are in its `.prettierignore`, so formatting never turns one into a file `upgrade` believes was changed.
306
+
307
+ ## Builder
308
+ - **The pages panel keeps its folders as you left them** — open or closed, per space, across reloads — the way the
309
+ style inspector keeps its sections. A folder starts closed.
310
+ - **Usages**, a panel beside Layers: where each component, class, token, space variable and data source is used,
311
+ page by page — what reads it and which elements — and which nothing uses, by authoring's own rule. A click selects
312
+ the element, inside a component too.
313
+ - **Revealing an element inside a component opens the component** (the Usages panel, ⌘P, the issues and the history all
314
+ reveal through it); before, the selection was dropped. **Panels stay open** when a component opens or closes.
315
+ - **The canvas dims a page's layout around its body again when the layout or its slot draws no box**
316
+ (`display: contents`, which the efficiency guide recommends): the hole was measured as a rect of zeros and the whole
317
+ page was dimmed. A box-less element is measured by what it holds, and the mask by the box that positions it.
318
+ - **Elements is one category at a time.** A row of chips — each category with its count, and the space's components as
319
+ one more — picks what the panel shows, remembered between sessions, so the panel stays the same height however many
320
+ elements plugins add. A search looks through every category and the components at once. Each element is a tile: its
321
+ icon, its whole name, and what it is on hover. Container, Button, Form, Form Control, Dropdown Popup, List Item, Rich
322
+ Text, Dialog Container and Tab Container Item have icons of their own (`sdk-elements`), none shared with another
323
+ element or with the component and snippet icons.
324
+ - **Layers reads as a tree and is driven from the keyboard.** A guide per depth, a chevron that turns, the component's
325
+ name beside an element that is one, and the selected row kept in view. ↑/↓, Home/End, → to open or go in, ← to close
326
+ or go up. A row under a closed ancestor is no longer shown. (plitzi-ui's `Tree`.)
327
+ - **Motion and preview no longer share an icon.** Playing the page's motion is a wand, which pulses while it plays;
328
+ preview is an eye, and a pen to go back to editing.
329
+ - **A component's settings fit their modal, however many props it has.** The modal is wider; the name and folder
330
+ share a line, and each prop is one compact row — its name, its kind, required, remove — with what a binding writes to
331
+ read it (`{{ props.<name> }}`) or what is wrong with it, and what it is for, beneath. The fields scroll and the buttons
332
+ stay in view. Props and slots are titled sections, and a slot shows its id.
333
+
334
+ ## Packages
335
+ - **`GET /auth/continue` takes `?fallback=`**: where to go when `redirect` is refused, vetted by the same check. A sign-in
336
+ screen's way back sends its site there, so leaving without signing in never lands on "you are signed in".
337
+ - **Markdown renders headings you can link to and code you can copy.** A heading carries its anchor and a `#` link to
338
+ itself; a fenced block a header with its language and a **Copy** button. The anchor is `@plitzi/sdk-shared`'s:
339
+ `anchorOf`, `uniqueAnchor` and `markdownHeadings` (`schema/anchor`, `schema/markdownHeadings`) — what the element
340
+ renders, what authoring checks a link against and what a table of contents is built from are one function. Needs
341
+ `@plitzi/plitzi-ui` 1.6.31 (its `Markdown` takes `headingAnchor`).
342
+ - **Every package declares what it imports, and nothing more.** `react` is a peer of `sdk-auth`, `sdk-event-bridge`,
343
+ `sdk-interactions`, `sdk-style` and `sdk-variables`; `sdk-schema` depends on `immer`, `sdk-elements` on
344
+ `@dr.pogodin/react-helmet`, `sdk-plugins` on `@plitzi/plitzi-ui`, `sdk-style` on `@plitzi/sdk-event-bridge` and
345
+ `sdk-dev-tools` on `@plitzi/sdk-plugins` — each worked only because `@plitzi/plitzi-sdk` brought them, and failed
346
+ installed alone or under a strict linker. `prop-types` and the `@plitzi/*` dependencies nothing imported are gone:
347
+ `sdk-mcp` no longer declares `@plitzi/sdk-elements`, which its code does not import — it still arrives through
348
+ `@plitzi/plitzi-sdk`.
349
+
350
+ - Updated dependencies [3bce653]
351
+ - @plitzi/sdk-auth@0.38.5
352
+ - @plitzi/sdk-event-bridge@0.38.5
353
+ - @plitzi/sdk-shared@0.38.5
354
+
3
355
  ## 0.38.4
4
356
 
5
357
  ### Patch Changes
@@ -10,9 +10,11 @@ import { ElementInteraction } from '@plitzi/sdk-shared';
10
10
  export type ReadGlobals = () => Record<string, unknown>;
11
11
  declare const flowTrigger: (triggerNode: ElementInteraction, nodes?: {}, callbacksAvailables?: {}, flowParams?: Record<string, unknown>, readGlobals?: ReadGlobals,
12
12
  /** The id of the element this fired on, carried through purely so the log can name it. */
13
- hostElementId?: string, postCallbacksTotal?: never[]) => Promise<void>;
13
+ hostElementId?: string,
14
+ /** Aborted when a newer run of this flow supersedes this one (`whileRunning: 'latest'`). */
15
+ signal?: AbortSignal, postCallbacksTotal?: never[]) => Promise<void>;
14
16
  export { flowTrigger };
15
17
  declare const InteractionsHelper: {
16
- flowTrigger: (triggerNode: ElementInteraction, nodes?: {}, callbacksAvailables?: {}, flowParams?: Record<string, unknown>, readGlobals?: ReadGlobals, hostElementId?: string, postCallbacksTotal?: never[]) => Promise<void>;
18
+ flowTrigger: (triggerNode: ElementInteraction, nodes?: {}, callbacksAvailables?: {}, flowParams?: Record<string, unknown>, readGlobals?: ReadGlobals, hostElementId?: string, signal?: AbortSignal, postCallbacksTotal?: never[]) => Promise<void>;
17
19
  };
18
20
  export default InteractionsHelper;
@@ -1,30 +1,31 @@
1
- import e from "./utility/index.mjs";
2
- import { get as t, omit as n } from "@plitzi/plitzi-ui/helpers";
3
- import { QueryBuilderEvaluator as r } from "@plitzi/plitzi-ui/QueryBuilder";
4
- import { pConsole as i } from "@plitzi/sdk-shared/devTools/utils/PlitziConsole";
5
- import { MAX_PARAM_PASSES as a, resolveStepParam as o } from "@plitzi/sdk-shared/helpers/twigWrapper";
6
- import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
1
+ import { coerceDeclaredParams as e } from "./helpers/coerceDeclaredParams.mjs";
2
+ import t from "./utility/index.mjs";
3
+ import { get as n, omit as r } from "@plitzi/plitzi-ui/helpers";
4
+ import { QueryBuilderEvaluator as i } from "@plitzi/plitzi-ui/QueryBuilder";
5
+ import { pConsole as a } from "@plitzi/sdk-shared/devTools/utils/PlitziConsole";
6
+ import { MAX_PARAM_PASSES as o, resolveStepParam as s } from "@plitzi/sdk-shared/helpers/twigWrapper";
7
+ import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
7
8
  //#region src/InteractionsHelper.tsx
8
- var u = 5, d = (e, t, n, r, s) => {
9
- if (e === "utility" && s === "twigTemplate") return t;
10
- let d = {
9
+ var d = 5, f = (e, t, n, r, i) => {
10
+ if (e === "utility" && i === "twigTemplate") return t;
11
+ let c = {
11
12
  ...n,
12
13
  ...r
13
14
  }, f = (e, t, n) => {
14
15
  if (typeof e == "string") {
15
- let { value: n, unresolved: r } = o(e, d);
16
- return r && i.warning("interactions", /* @__PURE__ */ l("span", { children: [
16
+ let { value: n, unresolved: r } = s(e, c);
17
+ return r && a.warning("interactions", /* @__PURE__ */ u("span", { children: [
17
18
  "Twig token resolution exceeded ",
18
- a,
19
+ o,
19
20
  " passes for ",
20
- /* @__PURE__ */ c("b", { children: t }),
21
+ /* @__PURE__ */ l("b", { children: t }),
21
22
  ", leaving unresolved tokens"
22
23
  ] }), {
23
24
  param: t,
24
25
  value: n
25
26
  }), n;
26
27
  }
27
- return n >= u || typeof e != "object" || !e ? e : Array.isArray(e) ? e.map((e) => f(e, t, n + 1)) : Object.fromEntries(Object.entries(e).map(([e, r]) => [e, f(r, t, n + 1)]));
28
+ return n >= d || typeof e != "object" || !e ? e : Array.isArray(e) ? e.map((e) => f(e, t, n + 1)) : Object.fromEntries(Object.entries(e).map(([e, r]) => [e, f(r, t, n + 1)]));
28
29
  };
29
30
  return Object.keys(t).reduce((n, r) => {
30
31
  let i = e === "trigger" ? t[r] : f(t[r], r, 0);
@@ -33,132 +34,133 @@ var u = 5, d = (e, t, n, r, s) => {
33
34
  [r]: i
34
35
  };
35
36
  }, {});
36
- }, f = (e) => typeof e != "function" && typeof e != "symbol" && typeof e != "bigint", p = (e) => Object.fromEntries(Object.entries(e).filter((e) => f(e[1]))), m = (e) => typeof e == "object" && !!e && "then" in e && typeof e.then == "function", h = (n, a = {}, o = {}, s = {}, u = {}) => {
37
- let f = [], { id: h, action: g, enabled: _, params: v, elementId: y, type: b, when: x } = n;
38
- if (!g || !_) return {
37
+ }, p = (e) => typeof e != "function" && typeof e != "symbol" && typeof e != "bigint", m = (e) => Object.fromEntries(Object.entries(e).filter((e) => p(e[1]))), h = (e) => typeof e == "object" && !!e && "then" in e && typeof e.then == "function", g = (r, o = {}, s = {}, c = {}, d = {}) => {
38
+ let p = [], { id: g, action: _, enabled: v, params: y, elementId: b, type: x, when: S } = r;
39
+ if (!_ || !v) return {
39
40
  status: "disabled",
40
41
  result: {},
41
- postCallbacks: f
42
+ postCallbacks: p
42
43
  };
43
- let S = p({
44
+ let C = m({
45
+ ...c,
44
46
  ...s,
45
- ...o,
46
- [h]: v
47
+ [g]: y
47
48
  });
48
- if (x && !r(x, S)) return {
49
+ if (S && !i(S, C)) return {
49
50
  status: "skipped",
50
51
  result: {},
51
- postCallbacks: f,
52
- whenParams: S
52
+ postCallbacks: p,
53
+ whenParams: C
53
54
  };
54
- let C = (e) => (i.danger("interactions", /* @__PURE__ */ l("span", { children: ["Interaction node failed ", /* @__PURE__ */ c("b", { children: g })] }), {
55
+ let w = (e) => (a.danger("interactions", /* @__PURE__ */ u("span", { children: ["Interaction node failed ", /* @__PURE__ */ l("b", { children: _ })] }), {
55
56
  error: e instanceof Error ? e.message : String(e),
56
- node: n
57
+ node: r
57
58
  }), {
58
59
  status: "failed",
59
60
  result: {},
60
- postCallbacks: f,
61
- whenParams: S
62
- }), w;
63
- switch (b) {
61
+ postCallbacks: p,
62
+ whenParams: C
63
+ }), T;
64
+ switch (x) {
64
65
  case "callback":
65
66
  case "globalCallback":
66
- if (!y) return i.warning("interactions", /* @__PURE__ */ l("span", { children: [
67
+ if (!b) return a.warning("interactions", /* @__PURE__ */ u("span", { children: [
67
68
  "Step ",
68
- /* @__PURE__ */ c("b", { children: g }),
69
+ /* @__PURE__ */ l("b", { children: _ }),
69
70
  " names no element, so there is nothing to run it on"
70
- ] }), { node: n }), {
71
+ ] }), { node: r }), {
71
72
  status: "failed",
72
73
  result: {},
73
- postCallbacks: f,
74
- whenParams: S
74
+ postCallbacks: p,
75
+ whenParams: C
75
76
  };
76
- if (w = t(a, `${y}.${g}`), !w) return i.warning("interactions", /* @__PURE__ */ l("span", { children: [
77
+ if (T = n(o, `${b}.${_}`), !T) return a.warning("interactions", /* @__PURE__ */ u("span", { children: [
77
78
  "Nothing is registered as ",
78
- /* @__PURE__ */ c("b", { children: `${y}.${g}` }),
79
+ /* @__PURE__ */ l("b", { children: `${b}.${_}` }),
79
80
  ", so this step did nothing"
80
81
  ] }), {
81
- node: n,
82
- available: Object.keys(t(a, y, {}))
82
+ node: r,
83
+ available: Object.keys(n(o, b, {}))
83
84
  }), {
84
85
  status: "failed",
85
86
  result: {},
86
- postCallbacks: f,
87
- whenParams: S
87
+ postCallbacks: p,
88
+ whenParams: C
88
89
  };
89
90
  break;
90
91
  case "utility":
91
- w = t(e, g, {});
92
+ T = n(t, _, {});
92
93
  break;
93
94
  default: return {
94
95
  status: "success",
95
96
  result: {},
96
- postCallbacks: f,
97
- whenParams: S
97
+ postCallbacks: p,
98
+ whenParams: C
98
99
  };
99
100
  }
100
101
  try {
101
- let e = {
102
- ...o,
102
+ let t = {
103
103
  ...s,
104
- ...d(b, v, o, s, g)
105
- }, { callback: t, postCallback: n } = w, r = (t) => (n && f.push({
106
- id: h,
107
- callback: n,
104
+ ...c,
105
+ ...e(T.params, f(x, y, s, c, _))
106
+ }, { callback: n, postCallback: r } = T, i = (e) => (r && p.push({
107
+ id: g,
108
+ callback: r,
108
109
  params: {
109
- ...e,
110
- [h]: t
110
+ ...t,
111
+ [g]: e
111
112
  }
112
113
  }), {
113
114
  status: "success",
114
- result: t,
115
- postCallbacks: f,
116
- whenParams: S
117
- }), i = t ? t(e, u) : {};
118
- return m(i) ? Promise.resolve(i).then(r, C) : r(i);
115
+ result: e,
116
+ postCallbacks: p,
117
+ whenParams: C
118
+ }), a = n ? n(t, d) : {};
119
+ return h(a) ? Promise.resolve(a).then(i, w) : i(a);
119
120
  } catch (e) {
120
- return C(e);
121
+ return w(e);
121
122
  }
122
- }, g = async (e = []) => {
123
+ }, _ = async (e = []) => {
123
124
  let t = {};
124
- return await Promise.all(e.reverse().map(async ({ id: e, callback: r, params: i }) => {
125
- t[e] = await r?.(n(i, [e]), i[e]);
125
+ return await Promise.all(e.reverse().map(async ({ id: e, callback: n, params: i }) => {
126
+ t[e] = await n?.(r(i, [e]), i[e]);
126
127
  })), t;
127
- }, _ = () => ({}), v = async (e, n = {}, r = {}, a = {}, o = _, s = [], c = {}, l = {}) => {
128
- let u = e, d = a;
128
+ }, v = () => ({}), y = async (e, t = {}, r = {}, i = {}, o = v, s = [], c = {}, l = {}) => {
129
+ let u = e, d = i;
129
130
  for (; u;) {
130
- let e = t(n, u.afterNode);
131
- if (!e) return s.length > 0 && await g(s), c;
132
- let a = i.getTime().valueOf(), f = h(e, r, d, o(), l), { status: p, result: _, postCallbacks: v, whenParams: y } = m(f) ? await f : f;
131
+ if (l.signal?.aborted) return c;
132
+ let e = n(t, u.afterNode);
133
+ if (!e) return s.length > 0 && await _(s), c;
134
+ let i = a.getTime().valueOf(), f = g(e, r, d, o(), l), p = h(f) ? await f : f, { result: m, postCallbacks: v, whenParams: y } = p, b = p.status === "failed" && l.signal?.aborted ? "skipped" : p.status;
133
135
  c[e.id] = {
134
136
  node: e,
135
- status: p,
136
- result: _,
137
+ status: b,
138
+ result: m,
137
139
  postCallbacks: v,
138
140
  whenParams: y,
139
- startTime: a,
140
- endTime: i.getTime().valueOf()
141
+ startTime: i,
142
+ endTime: a.getTime().valueOf()
141
143
  }, s.push(...v), d = {
142
144
  ...d,
143
- [e.id]: _
145
+ [e.id]: m
144
146
  }, u = e;
145
147
  }
146
148
  return c;
147
- }, y = (e, t, n = {}, r, a) => {
148
- let o = i.getTime().valueOf(), u = r === "skipped" ? "skipped" : "success", d = /* @__PURE__ */ l("span", { children: [
149
+ }, b = (e, t, n = {}, r, i) => {
150
+ let o = a.getTime().valueOf(), s = r === "skipped" ? "skipped" : "success", d = /* @__PURE__ */ u("span", { children: [
149
151
  "Interaction triggered ",
150
- /* @__PURE__ */ c("b", { children: `${e.title} [${e.action}]` }),
151
- a ? /* @__PURE__ */ l(s, { children: [" on ", /* @__PURE__ */ c("b", { children: a })] }) : null
152
+ /* @__PURE__ */ l("b", { children: `${e.title} [${e.action}]` }),
153
+ i ? /* @__PURE__ */ u(c, { children: [" on ", /* @__PURE__ */ l("b", { children: i })] }) : null
152
154
  ] }), f = {
153
155
  status: r,
154
156
  node: e,
155
157
  elementId: e.elementId,
156
- hostElementId: a,
158
+ hostElementId: i,
157
159
  nodes: {
158
160
  ...n,
159
161
  [e.id]: {
160
162
  node: e,
161
- status: u,
163
+ status: s,
162
164
  result: void 0,
163
165
  postCallbacks: [],
164
166
  startTime: t,
@@ -169,21 +171,24 @@ var u = 5, d = (e, t, n, r, s) => {
169
171
  endTime: o
170
172
  };
171
173
  if (r === "failed") {
172
- i.danger("interactions", d, f);
174
+ a.danger("interactions", d, f);
173
175
  return;
174
176
  }
175
- i.info("interactions", d, f);
176
- }, b = (e) => Object.values(e).some(({ status: e }) => e === "failed") ? "failed" : "completed", x = async (e, t = {}, n = {}, a = {}, o = _, s, c = []) => {
177
- let l = i.getTime().valueOf(), { action: u, enabled: d, when: f } = e;
178
- if (!u || !d || f && !r(f, p({
177
+ a.info("interactions", d, f);
178
+ }, x = (e) => Object.values(e).some(({ status: e }) => e === "failed") ? "failed" : "completed", S = async (e, t = {}, n = {}, r = {}, o = v, s, c, l = []) => {
179
+ let u = a.getTime().valueOf(), { action: d, enabled: f, when: p } = e;
180
+ if (!d || !f || p && !i(p, m({
179
181
  ...o(),
180
- ...a
182
+ ...r
181
183
  }))) {
182
- y(e, l, {}, "skipped", s);
184
+ b(e, u, {}, "skipped", s);
183
185
  return;
184
186
  }
185
- let m = await v(e, t, n, a, o, c, {}, { hostElementId: s });
186
- y(e, l, m, b(m), s);
187
- }, S = { flowTrigger: x };
187
+ let h = await y(e, t, n, r, o, l, {}, {
188
+ hostElementId: s,
189
+ ...c ? { signal: c } : {}
190
+ });
191
+ b(e, u, h, x(h), s);
192
+ }, C = { flowTrigger: S };
188
193
  //#endregion
189
- export { S as default, x as flowTrigger };
194
+ export { C as default, S as flowTrigger };
@@ -13,6 +13,8 @@ declare class InteractionsManager {
13
13
  * event: two flows on one click are two things, and one still running says nothing about the other.
14
14
  */
15
15
  private flowsRunning;
16
+ /** The run a `latest` flow is on now, by the same key: what the next firing stops. */
17
+ private latestRuns;
16
18
  lastUpdate: number;
17
19
  private listeners;
18
20
  constructor(currentPageId?: string, routeParams?: RouteParams, queryParams?: QueryParams);
@@ -18,6 +18,7 @@ var s = (e, t) => {
18
18
  subscriptors;
19
19
  callbacksAvailables;
20
20
  flowsRunning = /* @__PURE__ */ new Map();
21
+ latestRuns = /* @__PURE__ */ new Map();
21
22
  lastUpdate;
22
23
  listeners = /* @__PURE__ */ new Set();
23
24
  constructor(e = "", t = {}, n = {}) {
@@ -35,13 +36,20 @@ var s = (e, t) => {
35
36
  ...this.interactionsData,
36
37
  ...typeof c == "function" ? c().dataSource : void 0
37
38
  }), u = Object.values(n).filter((e) => e.type === "trigger" && e.action === i && e.enabled && s(e, a));
38
- await Promise.all(u.map((t) => this.runFlow(`${r}.${t.id}`, t.whileRunning ?? "skip", () => e(t, n, this.getCallbacksAvailables(), { [t.id]: a }, l, r))));
39
+ await Promise.all(u.map((t) => this.runFlow(`${r}.${t.id}`, t.whileRunning ?? "skip", (i) => e(t, n, this.getCallbacksAvailables(), { [t.id]: a }, l, r, i))));
39
40
  };
40
41
  runFlow(e, t, n) {
42
+ if (t === "latest") {
43
+ this.latestRuns.get(e)?.abort();
44
+ let t = new AbortController();
45
+ return this.latestRuns.set(e, t), n(t.signal).finally(() => {
46
+ this.latestRuns.get(e) === t && this.latestRuns.delete(e);
47
+ });
48
+ }
41
49
  let r = this.flowsRunning.get(e);
42
50
  if (t === "parallel") return n();
43
51
  if (r && t === "skip") return Promise.resolve();
44
- let i = (r ? r.then(n, n) : n()).finally(() => {
52
+ let i = (r ? r.then(() => n(), () => n()) : n()).finally(() => {
45
53
  this.flowsRunning.get(e) === i && this.flowsRunning.delete(e);
46
54
  });
47
55
  return this.flowsRunning.set(e, i), i;
@@ -0,0 +1,11 @@
1
+ import { InteractionCallback } from '@plitzi/sdk-shared';
2
+ /**
3
+ * A step's params, each as the type its callback DECLARES: a `number` param written `5000` — or bound to text that
4
+ * says it — arrives as the number, a `boolean` one written `'true'` as `true`, an empty number as nothing at all (so the
5
+ * component's default applies). Everything else arrives as written.
6
+ *
7
+ * The callback declares the type, so the conversion happens once, here, for every action — a plugin's above all, whose
8
+ * author otherwise wrote `Number(params.interval)` in every callback. Only the step's own params: what the flow and
9
+ * the page hand along beside them is theirs.
10
+ */
11
+ export declare const coerceDeclaredParams: (declared: InteractionCallback["params"] | undefined, params: Record<string, unknown>) => Record<string, unknown>;
@@ -0,0 +1,18 @@
1
+ //#region src/helpers/coerceDeclaredParams.ts
2
+ var e = (e, t) => typeof e.type == "function" ? e.type(t) : e.type, t = (e, t) => {
3
+ if (typeof t != "string") return t;
4
+ if (e === "number") {
5
+ let e = t.trim();
6
+ if (e === "") return;
7
+ let n = Number(e);
8
+ return Number.isFinite(n) ? n : t;
9
+ }
10
+ return e === "boolean" && (t === "true" || t === "false") ? t === "true" : t;
11
+ }, n = (n, r) => {
12
+ if (!n) return r;
13
+ let i = typeof n == "function" ? n(r) : n, a = { ...r };
14
+ for (let [n, o] of Object.entries(i)) Object.hasOwn(r, n) && (a[n] = t(e(o, r), r[n]));
15
+ return a;
16
+ };
17
+ //#endregion
18
+ export { n as coerceDeclaredParams };
@@ -85,8 +85,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
85
85
  actionId: n,
86
86
  mode: r,
87
87
  input: o
88
- }), _ = new AbortController(), v = "";
89
- p(g, () => {
88
+ }), _ = new AbortController(), v = "", T = () => {
90
89
  _.abort(), h(g, {
91
90
  status: "aborted",
92
91
  endedAt: Date.now(),
@@ -95,9 +94,12 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
95
94
  method: "DELETE",
96
95
  credentials: "same-origin"
97
96
  }).catch(() => void 0);
98
- });
99
- let T = (t) => {
100
- m(g), h(g, {
97
+ };
98
+ p(g, T);
99
+ let D = t?.signal;
100
+ D?.addEventListener("abort", T, { once: !0 });
101
+ let O = (t) => {
102
+ D?.removeEventListener("abort", T), m(g), h(g, {
101
103
  endedAt: Date.now(),
102
104
  cancellable: !1,
103
105
  ...t
@@ -125,7 +127,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
125
127
  }).then(async (e) => {
126
128
  let i = await e.json().catch(() => ({}));
127
129
  if (!e.ok) {
128
- w(e.status, i, E), T({
130
+ w(e.status, i, E), O({
129
131
  status: "failed",
130
132
  ...i.runId ? { runId: i.runId } : {},
131
133
  ...i.reason ? { reason: i.reason } : {},
@@ -139,7 +141,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
139
141
  });
140
142
  return;
141
143
  }
142
- T({
144
+ O({
143
145
  status: i.status === "completed" ? "completed" : "accepted",
144
146
  ...i.runId ? { runId: i.runId } : {},
145
147
  ...i.output ? { output: i.output } : {},
@@ -162,7 +164,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
162
164
  output: i.output ?? {}
163
165
  });
164
166
  }).catch((e) => {
165
- T(C(_, e)), a.warning("actions", /* @__PURE__ */ u("span", { children: [
167
+ O(C(_, e)), a.warning("actions", /* @__PURE__ */ u("span", { children: [
166
168
  "Server action ",
167
169
  /* @__PURE__ */ l("b", { children: n }),
168
170
  " could not be sent"
@@ -205,7 +207,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
205
207
  body: s
206
208
  });
207
209
  } catch (e) {
208
- return T(C(_, e)), a.warning("actions", /* @__PURE__ */ u("span", { children: [
210
+ return O(C(_, e)), a.warning("actions", /* @__PURE__ */ u("span", { children: [
209
211
  "Server action ",
210
212
  /* @__PURE__ */ l("b", { children: n }),
211
213
  " could not be reached"
@@ -222,7 +224,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
222
224
  }
223
225
  if (!e.ok || !e.body) {
224
226
  let r = await e.json().catch(() => ({}));
225
- return w(e.status, r, E), T({
227
+ return w(e.status, r, E), O({
226
228
  status: "failed",
227
229
  ...r.runId ? { runId: r.runId } : {},
228
230
  ...r.reason ? { reason: r.reason } : {},
@@ -256,7 +258,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
256
258
  }
257
259
  if (e.event === "error") {
258
260
  let r = y(e.data.steps);
259
- T({
261
+ O({
260
262
  status: "failed",
261
263
  ...typeof e.data.reason == "string" ? { reason: e.data.reason } : {},
262
264
  ...typeof e.data.error == "string" ? { error: e.data.error } : {},
@@ -270,7 +272,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
270
272
  }
271
273
  if (e.event === "done") {
272
274
  let r = y(e.data.steps);
273
- T({
275
+ O({
274
276
  status: e.data.status === "completed" ? "completed" : "failed",
275
277
  ...e.data.output ? { output: e.data.output } : {},
276
278
  ...typeof e.data.error == "string" ? { error: e.data.error } : {},
@@ -284,7 +286,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
284
286
  }).catch((e) => {
285
287
  if (_.signal.aborted) return;
286
288
  let o = e instanceof Error ? e.message : String(e);
287
- T({
289
+ O({
288
290
  status: "failed",
289
291
  error: o
290
292
  }), a.warning("actions", /* @__PURE__ */ u("span", { children: [
@@ -309,9 +311,9 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
309
311
  output: {}
310
312
  };
311
313
  }
312
- let D;
314
+ let A;
313
315
  try {
314
- D = await fetch(E, {
316
+ A = await fetch(E, {
315
317
  method: "POST",
316
318
  headers: { "Content-Type": "application/json" },
317
319
  credentials: "same-origin",
@@ -319,7 +321,7 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
319
321
  body: s
320
322
  });
321
323
  } catch (e) {
322
- return T(C(_, e)), a.warning("interactions", /* @__PURE__ */ u("span", { children: [
324
+ return O(C(_, e)), a.warning("interactions", /* @__PURE__ */ u("span", { children: [
323
325
  "Server action ",
324
326
  /* @__PURE__ */ l("b", { children: n }),
325
327
  " could not be reached"
@@ -333,25 +335,25 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
333
335
  reason: "failed"
334
336
  }), S;
335
337
  }
336
- let O = await D.json().catch(() => ({}));
337
- return D.ok ? (T({
338
- status: O.status === "completed" ? "completed" : "failed",
339
- ...O.runId ? { runId: O.runId } : {},
340
- ...O.output ? { output: O.output } : {},
341
- ...O.error ? { error: O.error } : {},
342
- ...O.steps ? { steps: O.steps } : {},
343
- ...O.trace ? { trace: O.trace } : {}
338
+ let j = await A.json().catch(() => ({}));
339
+ return A.ok ? (O({
340
+ status: j.status === "completed" ? "completed" : "failed",
341
+ ...j.runId ? { runId: j.runId } : {},
342
+ ...j.output ? { output: j.output } : {},
343
+ ...j.error ? { error: j.error } : {},
344
+ ...j.steps ? { steps: j.steps } : {},
345
+ ...j.trace ? { trace: j.trace } : {}
344
346
  }), {
345
- status: O.status ?? "completed",
346
- runId: O.runId ?? "",
347
- output: O.output ?? {},
348
- ...O.error ? { error: O.error } : {}
349
- }) : (w(D.status, O, E), T({
347
+ status: j.status ?? "completed",
348
+ runId: j.runId ?? "",
349
+ output: j.output ?? {},
350
+ ...j.error ? { error: j.error } : {}
351
+ }) : (w(A.status, j, E), O({
350
352
  status: "failed",
351
- ...O.runId ? { runId: O.runId } : {},
352
- ...O.reason ? { reason: O.reason } : {},
353
- ...O.error ? { error: O.error } : {},
354
- ...O.steps ? { steps: O.steps } : {}
353
+ ...j.runId ? { runId: j.runId } : {},
354
+ ...j.reason ? { reason: j.reason } : {},
355
+ ...j.error ? { error: j.error } : {},
356
+ ...j.steps ? { steps: j.steps } : {}
355
357
  }), a.warning("actions", /* @__PURE__ */ u("span", { children: [
356
358
  "Server action ",
357
359
  /* @__PURE__ */ l("b", { children: n }),
@@ -359,16 +361,16 @@ var y = (e) => Array.isArray(e) ? e : void 0, b = async (e, t) => {
359
361
  ] }), {
360
362
  actionId: n,
361
363
  mode: r,
362
- runId: O.runId,
363
- reason: O.reason,
364
- error: O.error,
365
- status: String(D.status)
364
+ runId: j.runId,
365
+ reason: j.reason,
366
+ error: j.error,
367
+ status: String(A.status)
366
368
  }), {
367
369
  status: "failed",
368
- reason: O.reason ?? "failed",
369
- runId: O.runId ?? "",
370
+ reason: j.reason ?? "failed",
371
+ runId: j.runId ?? "",
370
372
  output: {},
371
- ...O.error ? { error: O.error } : {}
373
+ ...j.error ? { error: j.error } : {}
372
374
  });
373
375
  }, [E, k]), j = r(async (e) => {
374
376
  if (!E || !e.runId) return { cancelled: !1 };
@@ -1,4 +1,11 @@
1
1
  import { InteractionCallback } from '@plitzi/sdk-shared';
2
+ /**
3
+ * Waits `time` milliseconds before the next step.
4
+ *
5
+ * The wait ends early when the flow is superseded (`whileRunning: 'latest'`): the run is over, and holding a timer
6
+ * for it only keeps it alive for nothing. That pair is how a flow is debounced — `whileRunning('latest')` and a
7
+ * `delay(450)` first: every new firing stops the one still waiting, and only the last gets past the wait.
8
+ */
2
9
  declare const delayTime: InteractionCallback<{
3
10
  time: number;
4
11
  }>;
@@ -5,11 +5,19 @@ var e = {
5
5
  type: "utility",
6
6
  params: { time: {
7
7
  label: "Time (Milliseconds)",
8
- type: "text"
8
+ type: "number"
9
9
  } },
10
10
  preview: {},
11
- callback: ({ time: e }) => new Promise((t) => {
12
- setTimeout(t, e);
11
+ callback: ({ time: e }, t) => new Promise((n) => {
12
+ let r = t?.signal;
13
+ if (r?.aborted) {
14
+ n(void 0);
15
+ return;
16
+ }
17
+ let i = () => {
18
+ clearTimeout(a), r?.removeEventListener("abort", i), n(void 0);
19
+ }, a = setTimeout(i, e);
20
+ r?.addEventListener("abort", i, { once: !0 });
13
21
  })
14
22
  };
15
23
  //#endregion
@@ -9,41 +9,42 @@ var a = /* @__PURE__ */ new Set(["GET", "HEAD"]), o = /* @__PURE__ */ new Set([
9
9
  ]), s = (t) => {
10
10
  let n = Number(t);
11
11
  return (Number.isFinite(n) && n >= 0 ? n : Number(e.params.staleTime.default)) * 1e3;
12
- }, c = (e) => e ? { Authorization: `Bearer ${e}` } : {}, l = (e) => typeof e == "object" && e ? e : {}, u = /* @__PURE__ */ new Set(["authorization", "content-type"]), d = (e) => typeof e == "object" && e ? Object.fromEntries(Object.entries(e).flatMap(([e, t]) => (typeof t == "string" || typeof t == "number") && t !== "" && !u.has(e.toLowerCase()) ? [[e, String(t)]] : [])) : {}, f = async ({ url: e, authorizationToken: t, headers: n, body: r, credentials: i }, a) => {
13
- let s = l(r), u = Object.values(s).some((e) => e instanceof Blob), f = {
12
+ }, c = (e) => e ? { Authorization: `Bearer ${e}` } : {}, l = (e) => typeof e == "object" && e ? e : {}, u = /* @__PURE__ */ new Set(["authorization", "content-type"]), d = (e) => typeof e == "object" && e ? Object.fromEntries(Object.entries(e).flatMap(([e, t]) => (typeof t == "string" || typeof t == "number") && t !== "" && !u.has(e.toLowerCase()) ? [[e, String(t)]] : [])) : {}, f = async ({ url: e, authorizationToken: t, headers: n, body: r, credentials: i }, a, s) => {
13
+ let u = l(r), f = Object.values(u).some((e) => e instanceof Blob), p = {
14
14
  method: a,
15
15
  headers: {
16
16
  ...d(n),
17
- ...u ? {} : { "Content-Type": "application/json" },
17
+ ...f ? {} : { "Content-Type": "application/json" },
18
18
  ...c(t)
19
19
  },
20
- credentials: i
20
+ credentials: i,
21
+ ...s ? { signal: s } : {}
21
22
  };
22
23
  if (!o.has(a)) {
23
- if (!u) f.body = JSON.stringify(s);
24
+ if (!f) p.body = JSON.stringify(u);
24
25
  else {
25
26
  let e = new FormData();
26
- Object.entries(s).forEach(([t, n]) => {
27
+ Object.entries(u).forEach(([t, n]) => {
27
28
  e.append(t, n);
28
- }), f.body = e;
29
+ }), p.body = e;
29
30
  }
30
31
  }
31
- let p = await fetch(e, f).catch((t) => {
32
+ let m = await fetch(e, p).catch((t) => {
32
33
  throw Error(`${a} ${e} got no answer: ${t instanceof Error ? t.message : String(t)}`);
33
- }), m = "";
34
+ }), h = "";
34
35
  try {
35
- m = await p.json();
36
+ h = await m.json();
36
37
  } catch {}
37
38
  return {
38
- status: p.status,
39
- data: m
39
+ status: m.status,
40
+ data: h
40
41
  };
41
- }, p = t("webHook", e, async (e) => {
42
- let t = (e.method || "get").toUpperCase();
43
- if (a.has(t)) {
44
- if (e.cache !== !0 && e.cache !== "true") return { response: await f(e, t) };
42
+ }, p = t("webHook", e, async (e, t) => {
43
+ let o = (e.method || "get").toUpperCase();
44
+ if (a.has(o)) {
45
+ if (e.cache !== !0 && e.cache !== "true") return { response: await f(e, o, t?.signal) };
45
46
  let n = i({
46
- method: t,
47
+ method: o,
47
48
  url: e.url,
48
49
  credentials: e.credentials,
49
50
  headers: {
@@ -53,18 +54,18 @@ var a = /* @__PURE__ */ new Set(["GET", "HEAD"]), o = /* @__PURE__ */ new Set([
53
54
  });
54
55
  return { response: await r.fetchQuery(n, {
55
56
  meta: { url: e.url },
56
- fetcher: () => f(e, t),
57
+ fetcher: () => f(e, o),
57
58
  staleTime: s(e.staleTime),
58
59
  isCacheable: (e) => e.status < 400
59
60
  }) };
60
61
  }
61
- let o = await f(e, t);
62
- return o.status >= 200 && o.status < 300 && n({
62
+ let l = await f(e, o, t?.signal);
63
+ return l.status >= 200 && l.status < 300 && n({
63
64
  mode: e.invalidateQueries,
64
65
  fallback: "origin",
65
66
  elements: e.invalidateElements,
66
67
  url: e.url
67
- }), { response: o };
68
+ }), { response: l };
68
69
  });
69
70
  //#endregion
70
71
  export { p as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-interactions",
3
- "version": "0.38.4",
3
+ "version": "0.38.6",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist"
@@ -30,6 +30,10 @@
30
30
  "types": "./dist/InteractionsSourcesProvider.d.ts",
31
31
  "import": "./dist/InteractionsSourcesProvider.mjs"
32
32
  },
33
+ "./helpers/coerceDeclaredParams": {
34
+ "types": "./dist/helpers/coerceDeclaredParams.d.ts",
35
+ "import": "./dist/helpers/coerceDeclaredParams.mjs"
36
+ },
33
37
  "./hooks/useInteractions": {
34
38
  "types": "./dist/hooks/useInteractions.d.ts",
35
39
  "import": "./dist/hooks/useInteractions.mjs"
@@ -184,17 +188,18 @@
184
188
  "sideEffects": false,
185
189
  "devDependencies": {
186
190
  "eslint": "^9.39.5",
191
+ "react": "^19.3.0",
187
192
  "typescript": "^6.0.3",
188
193
  "vite": "^8.3.2",
189
194
  "vitest": "^5.0.3"
190
195
  },
191
196
  "dependencies": {
192
- "@plitzi/plitzi-ui": "^1.6.29",
193
- "@plitzi/sdk-auth": "0.38.4",
194
- "@plitzi/sdk-dev-tools": "0.38.4",
195
- "@plitzi/sdk-event-bridge": "0.38.4",
196
- "@plitzi/sdk-navigation": "0.38.4",
197
- "@plitzi/sdk-schema": "0.38.4",
198
- "@plitzi/sdk-shared": "0.38.4"
197
+ "@plitzi/plitzi-ui": "^1.6.31",
198
+ "@plitzi/sdk-auth": "0.38.6",
199
+ "@plitzi/sdk-event-bridge": "0.38.6",
200
+ "@plitzi/sdk-shared": "0.38.6"
201
+ },
202
+ "peerDependencies": {
203
+ "react": "^19"
199
204
  }
200
205
  }