@plitzi/sdk-shared 0.38.13 → 0.38.15
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 +247 -0
- package/dist/authoring/declare.d.ts +7 -0
- package/dist/authoring/index.mjs +2 -2
- package/dist/authoring/spaceCallbacks.d.ts +29 -0
- package/dist/authoring/spaceCallbacks.mjs +41 -20
- package/dist/helpers/clipboard.d.ts +10 -0
- package/dist/helpers/clipboard.mjs +17 -0
- package/dist/helpers/enclosingSpace.d.ts +9 -0
- package/dist/helpers/enclosingSpace.mjs +5 -0
- package/dist/helpers/spaceRoot.d.ts +4 -0
- package/dist/helpers/spaceRoot.mjs +4 -0
- package/dist/project/network.d.ts +12 -0
- package/dist/project/network.mjs +4 -0
- package/dist/realtime/topics.mjs +3 -3
- package/dist/store/index.d.ts +33 -33
- package/dist/store/renderSettings.d.ts +1 -0
- package/dist/store/renderSettings.mjs +9 -5
- package/dist/types/ActionTypes.d.ts +38 -0
- package/dist/types/RealtimeTypes.d.ts +12 -0
- package/dist/types/ServerTypes.d.ts +22 -2
- package/dist/types/StoreTypes.d.ts +8 -2
- package/package.json +17 -2
- package/setupTests.ts +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,252 @@
|
|
|
1
1
|
# @plitzi/sdk-shared
|
|
2
2
|
|
|
3
|
+
## 0.38.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6cdd5cf: ## Security
|
|
8
|
+
|
|
9
|
+
- **A write another site caused is refused, cookie or none** (`@plitzi/sdk-server`, `@plitzi/sdk-shared`): the page
|
|
10
|
+
server guarded nothing it answers past `/auth` — a server action (`/_action`), a realtime publish, a space's `/fn/`
|
|
11
|
+
route, its runtime's endpoints, the deployment's own `data` stages, the adapter-only `loginPath`, `logoutPath` and
|
|
12
|
+
`exchangePath`. A plain `text/plain` POST from any page open in the same browser ran them, with the visitor's session
|
|
13
|
+
cookie when there was one (`SameSite=None` off this machine) and, in a project nobody signs in to, as whoever holds
|
|
14
|
+
the server's own credentials. Every unsafe request a browser says another site caused (`Sec-Fetch-Site: cross-site`,
|
|
15
|
+
or a foreign `Origin`) is now refused `403 reason: 'foreign'`, saying how to allow it: `crossSiteWrites.origins`
|
|
16
|
+
(with `auth`, `identity.platformOrigins` too) and `crossSiteWrites.paths` for a form other sites post to by design.
|
|
17
|
+
`Authorization`, and callers that are not browsers — webhooks, scripts — pass as before. One rule for this and the
|
|
18
|
+
sign-in check (`crossSiteRequest`, `csrf.ts`). A self-hosted runtime's endpoints now sit where the platform forwards a
|
|
19
|
+
space's — after the auth chain and this guard (`data`, was `preAuth`).
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
- **`page check` and `page shot` anywhere the project is served** (`@plitzi/cli`): `--url <origin>` for a second server
|
|
23
|
+
of it, staging or a deploy, instead of `npm start`'s port; `page check --space <module>` holds the pages to that space
|
|
24
|
+
instead of the project's — an agent's page — authored with `authorIsolated`.
|
|
25
|
+
- **A route with a query is checked as its page** (`@plitzi/cli`): `page check "/?v=2"` matched the query as part of
|
|
26
|
+
the path, found no page, and checked the space's "not found" page in its place.
|
|
27
|
+
|
|
28
|
+
- **What an element that chooses its children draws is checked** (`@plitzi/sdk-authoring`, `@plitzi/cli`): with
|
|
29
|
+
`choosesChildren`, the page checks owed none of its children — and checked none of them either: a dashboard drawing
|
|
30
|
+
three panels reported `checked: 1`. Each child it may draw, and what is inside one under no condition of its own,
|
|
31
|
+
now carries `shownWith` (the child it comes with) on its handle: once that child is on screen it and everything
|
|
32
|
+
named in it are owed like the rest of the page, and `checked` counts them. `onScreenIfChosen(handles, page)` lists
|
|
33
|
+
them.
|
|
34
|
+
- **A page that met a server error is looked at once more** (`@plitzi/cli`): `page check` and `verify` failed a page
|
|
35
|
+
for a `500` from somebody else's service having a bad second (`/fn/<space>/weather`), and passed on the next run with
|
|
36
|
+
nothing changed. A page whose requests got a `5xx` is checked again once; its report is the second look, and says
|
|
37
|
+
what failed the first time (`retriedAfter`). A `4xx` fails the same way twice, and is never retried.
|
|
38
|
+
|
|
39
|
+
## Plugins
|
|
40
|
+
- **`useNotify`** (`@plitzi/plitzi-sdk`): a plugin shows the page's notifications — `notify('Deleted 79 recordings', {
|
|
41
|
+
appearance: 'danger' })` — the same toasts an `addNotification` step shows, in the space's theme and `notifications`
|
|
42
|
+
look, with no trigger, flow or `window` event of the space's to wire. The step's vocabulary (`NOTIFICATION_PLACEMENTS`,
|
|
43
|
+
`NOTIFICATION_APPEARANCES`, `NOTIFICATION_DEFAULTS`) is one list in `@plitzi/sdk-shared`, read by both.
|
|
44
|
+
- **`appearance: 'danger'` looks like one** (`@plitzi/plitzi-sdk`): it reached the toast library as a type it does not
|
|
45
|
+
know, so a danger notification showed neither the error icon nor the colour `notifications.danger` sets.
|
|
46
|
+
A value a notification does not take — from a flow, or a plugin in plain JavaScript — is set aside by name and said
|
|
47
|
+
in the console, rather than shown as the default in silence (`readNotificationOptions`, `@plitzi/sdk-shared`).
|
|
48
|
+
- **`useServerAction`** (`@plitzi/plitzi-sdk`, `@plitzi/sdk-authoring`, `@plitzi/sdk-interactions`,
|
|
49
|
+
`@plitzi/sdk-server`): a plugin runs one of the space's server actions, awaited —
|
|
50
|
+
`useServerAction<typeof habitDone>('habits-done')({ id })` answers `{ ok, output }` or `{ ok: false, reason, error }`
|
|
51
|
+
— through the `runServerAction` step itself: the same access, refusals, session handling and dev-tools entry.
|
|
52
|
+
`defineAction` keeps the literal types of its `call` way in, so the input is typed by the fields it declares (the
|
|
53
|
+
required ones required); `taskAction` types both ends from the task's `run`, and `FunctionTask<T, R>` takes what
|
|
54
|
+
`run` answers. `CallInputOf` / `CallOutputOf` read them. One operation is one action, never a `case` in one route.
|
|
55
|
+
What a run answers is typed (`ServerActionStatus`): a status the server never uses reads as a run that did not
|
|
56
|
+
complete, never as one that did.
|
|
57
|
+
The key those types ride under (`CALL_TYPES`) is exported, by `@plitzi/sdk-shared` and `@plitzi/sdk-authoring` with
|
|
58
|
+
`TypedActionEntry`, `CallInputOf` and `CallOutputOf`: a package that emits declarations could not write the type of an
|
|
59
|
+
action it exports (`TS4023` on every `export const actions = [defineAction(…)]`).
|
|
60
|
+
- **A plugin is drawn on the server first** (docs): a preference per device belongs in kept state (`keepState`,
|
|
61
|
+
`paintedState`) as a bound prop, never read from `localStorage` while rendering — React discards the whole tree on the
|
|
62
|
+
mismatch, and only in the browser of whoever saved something. `@plitzi/plitzi-ui` is not the page's: a plugin
|
|
63
|
+
importing it bundles its own copy, with its own contexts and its own look. The authoring skill's `plugin-reach.md`
|
|
64
|
+
says what a plugin says and to whom: a notification, another element (an event and the space's flow), a server
|
|
65
|
+
action, shared state, other pages.
|
|
66
|
+
|
|
67
|
+
## Realtime
|
|
68
|
+
- **A channel that retains** (`@plitzi/sdk-shared`, `@plitzi/sdk-server`, `@plitzi/sdk-elements`): declared
|
|
69
|
+
`retain: true` (with `publish: 'server'`, which authoring requires), the server keeps the latest message of each type
|
|
70
|
+
on a topic — said by `ctx.publish` or a flow's `realtime.publish` — and hands them to every page that joins, marked
|
|
71
|
+
`retained`. A page reads them as `<channel>.retained.<type>`, a plugin as `useChannel(topic).retained`, the newest of
|
|
72
|
+
a type holding whichever order they arrive in; `null` forgets a type. Kept in the store the actions' `kv` uses (beside
|
|
73
|
+
the grants, by compare-and-swap), so every replica hands out the same; a topic keeps at most 64 types and 512 KB, and
|
|
74
|
+
a publish past that is refused before anything is sent. State every screen shares is one publish and one binding —
|
|
75
|
+
no kv key, initial read or copy in `state` of the project's own. The fleet store `realtime.grants` is now
|
|
76
|
+
`realtime.kv`.
|
|
77
|
+
- **The MCP writes every field a channel takes** (`@plitzi/sdk-mcp`): `patchSettings`'s channel had no `grant` — which
|
|
78
|
+
the guide taught — nor `retain` or the limits, so an agent could not declare a private channel at all. All are there
|
|
79
|
+
now, and the guide says what `retain` is for and that `channel_<id>.retained` holds it.
|
|
80
|
+
|
|
81
|
+
## Authoring
|
|
82
|
+
- **`plitzi explain` knows every `@plitzi` package installed** (`@plitzi/cli`): a name that was not
|
|
83
|
+
`@plitzi/sdk-authoring`'s (`createServer`, `createJsonAdapters`, `useChannel`) was answered as the project's own code,
|
|
84
|
+
to `grep` in `src/`. It is read from the published types of every one installed, through their re-exports and entry
|
|
85
|
+
points (`@plitzi/sdk-server/handlers`), and a name two packages export — `createServer` is sdk-server's and sdk-mcp's
|
|
86
|
+
— is answered with both.
|
|
87
|
+
|
|
88
|
+
- **A space module somebody else wrote, authored where it reaches nothing** (`@plitzi/sdk-authoring`):
|
|
89
|
+
`authorIsolated(file, { timeoutMs })` of `@plitzi/sdk-authoring/node` runs `authorSpace` on it in a process Node's
|
|
90
|
+
permission model holds to reading the module's folder and its packages — no file written, no process started, no
|
|
91
|
+
variable of the caller's environment — kills one that runs too long, and validates what it answers again, so a forged
|
|
92
|
+
answer gains nothing. A refusal comes back as `authorSpace` says it, with the problems as data.
|
|
93
|
+
- **Pages an agent writes, served** (`@plitzi/sdk-server`): `createAuthoringHost({ dir, frameAncestors })` serves each
|
|
94
|
+
space it is handed at an address of its own (`put(space, { id }) → /?__pt=<id>`) as a draft of its one space, kept in
|
|
95
|
+
`dir` across restarts; `drop(id)` forgets one, and anything else answers `404` saying where the pages are.
|
|
96
|
+
- **A space is chosen by host or credential** (`@plitzi/sdk-server`, docs): `getSpaceDeployment` is asked on every
|
|
97
|
+
request, and what a page fetches after it carries nothing of its address but the host — a space chosen by the page's
|
|
98
|
+
query or path lost everything it asked for, and `/_plitzi/space/<hash>.json` answered a bare `404`. The contract and
|
|
99
|
+
the README say so (the README offered "by path prefix"), and that `404` says why.
|
|
100
|
+
|
|
101
|
+
- **`plitzi explain on` says how to read what a trigger carries** (`@plitzi/sdk-authoring`): its doc sat above a
|
|
102
|
+
helper, so `explain` printed only the signature. It now leads with `[named('picked', on('onPick')), …('{{ picked.seat
|
|
103
|
+
}}')]`, and the CLI skill's plugin section says the same.
|
|
104
|
+
|
|
105
|
+
## 0.38.14
|
|
106
|
+
|
|
107
|
+
### Patch Changes
|
|
108
|
+
|
|
109
|
+
- 493955a: ## SDK
|
|
110
|
+
|
|
111
|
+
- **An embedded space leaves the page's theme alone** (`@plitzi/plitzi-sdk`, `@plitzi/plitzi-builder`): plitzi-ui's
|
|
112
|
+
`Provider` stamps `dark` on `<html>` by default — and, being `light`, takes it off — so every space mounted with
|
|
113
|
+
`themeScope: 'container'` turned the page around it light, and nothing put it back. The SDK and the builder now leave
|
|
114
|
+
the class to their own theme provider (`applyColorModeClass={false}`).
|
|
115
|
+
- **A space inside a space** (`@plitzi/plitzi-sdk`, `@plitzi/sdk-shared`): one drawn inside another — the `plitziSdk`
|
|
116
|
+
element, a plugin rendering one, the builder's canvas — knows it (`render.enclosed`). It is `container` unless it
|
|
117
|
+
says otherwise, wears its theme on its own root and starts from the page's when its own is `system`, keeps its
|
|
118
|
+
stylesheet to its own root (`@scope` — its `:root` palette and `.dark` rules repainted the outer page, its
|
|
119
|
+
`.plitzi-sdk` rule restyled the outer root), writes no `<title>` (`ownsHead`) and no `theme` cookie.
|
|
120
|
+
- **The `plitziSdk` element draws** (`@plitzi/plitzi-sdk`): it rendered in `widget` mode with no page to show, so it
|
|
121
|
+
drew nothing. A widget with no `currentPageId` now shows the page its home address would. And it takes a space's
|
|
122
|
+
documents as well as a key: `offlineData`, bound whole (an `apiContainer`'s answer, a runtime's route, what
|
|
123
|
+
`plitzi_render` returns) or as `offlineData.schema` and `offlineData.style` from two elements — waiting, without an
|
|
124
|
+
error, for the half that has not answered; one that cannot be drawn is said why, in the console and on the canvas.
|
|
125
|
+
- **`<PlitziSdk>` inside a plugin draws** (`@plitzi/plitzi-sdk`): it mounted a router of its own inside the page's, and
|
|
126
|
+
React Router refused it — `routing: 'memory'` or not. A space inside another is a widget: `raw` becomes `widget`
|
|
127
|
+
there, one page and no router (a frame of its own, `iframe` or `shadow`, is left as asked).
|
|
128
|
+
- **A space inside another is as tall as what it holds** (`@plitzi/plitzi-sdk`): its root was `min-h-screen`, as a
|
|
129
|
+
space that IS the page is, so a card of three lines drawn in a panel took the window's height.
|
|
130
|
+
- **`<PlitziSdk.Plugin component>` takes a component with its own props** (`@plitzi/plitzi-sdk`), as `render()`'s
|
|
131
|
+
plugins do: typed `ComponentPluginFC`, it refused any component that read its attributes.
|
|
132
|
+
- **An edited page keeps its address in step** (`@plitzi/plitzi-sdk`): the routes are built from the pages' own
|
|
133
|
+
elements, and those were read again only when the list of pages changed — an edit to one page (its slug, its flag)
|
|
134
|
+
leaves that list the same, and the routes kept its old address until something else rebuilt them.
|
|
135
|
+
- **Packages declare only what they use** (`@plitzi/sdk-dev-tools`, `@plitzi/sdk-shared`): `@plitzi/sdk-plugins`, which
|
|
136
|
+
the dev tools no longer import, and `terser`, which no build here runs, are no longer dependencies.
|
|
137
|
+
- **An edited page keeps its route up to date** (`@plitzi/plitzi-sdk`): the routes are built from the pages' own
|
|
138
|
+
elements, which were read again only when the list of pages changed — an edit to one page (its slug, its flag) left
|
|
139
|
+
the list the same and the route at the old address. They follow every edit to a page now, and stay the same object
|
|
140
|
+
while no page changes.
|
|
141
|
+
- **`@plitzi/sdk-dev-tools` no longer depends on `@plitzi/sdk-plugins`**, which nothing in it imports.
|
|
142
|
+
- **A dropdown's menu opens inside a card that lifts or clips** (`@plitzi/sdk-elements`, `@plitzi/sdk-shared`): drawn
|
|
143
|
+
inside the dropdown, an ancestor with a `transform` — a card lifting on hover — made itself the containing block of
|
|
144
|
+
the menu's `position: fixed`, and one with `overflow: hidden` cut it off: the menu opened and nobody saw it. The open
|
|
145
|
+
menu and its backdrop are drawn at the space's root (`spaceRootOf`, `@plitzi/sdk-shared/helpers/spaceRoot`); a
|
|
146
|
+
style written for the popup by an ancestor's selector no longer reaches it — its own classes do.
|
|
147
|
+
- **A copy button says when it could not copy** (`@plitzi/sdk-elements`, `@plitzi/sdk-shared`,
|
|
148
|
+
`@plitzi/sdk-interactions`, `@plitzi/sdk-variables`, `@plitzi/sdk-dev-tools`): a page not served over https has no
|
|
149
|
+
clipboard — a phone opening a development server by its address — and a code block's "Copy" threw there. It reads
|
|
150
|
+
"Not copied", and says why. `clipboardWriter` and `copyText` (`@plitzi/sdk-shared/helpers/clipboard`) are the one
|
|
151
|
+
check, the `copyToClipboard` step's too.
|
|
152
|
+
|
|
153
|
+
## Authoring
|
|
154
|
+
- **`plitziSdk` is authored like any element** (`@plitzi/sdk-authoring`, `@plitzi/sdk-elements`): its declaration moved
|
|
155
|
+
to `@plitzi/sdk-elements` beside the others, so it has a factory — `plitziSdk({ id, bind: { offlineData: 'view.data'
|
|
156
|
+
} })` —, its attributes typed, and `plitzi explain` knows it.
|
|
157
|
+
- **A binding may write part of an attribute** (`@plitzi/sdk-authoring`): `offlineData.schema` writes into
|
|
158
|
+
`offlineData`, as the runtime always did; authoring refused it as an attribute nothing reads.
|
|
159
|
+
- **`choosesChildren`** (`@plitzi/sdk-shared`, `@plitzi/sdk-authoring`): a plugin that shows only some of the elements
|
|
160
|
+
put inside it — a dashboard's panels, a wizard's step — says so in its declaration, and the page checks owe none of
|
|
161
|
+
them on screen; what it draws is still read by the checks of the whole page (images, sideways scroll, legibility).
|
|
162
|
+
`verify` was red on every such page.
|
|
163
|
+
|
|
164
|
+
- **A page says how the document scrolls** (`@plitzi/sdk-elements`, `@plitzi/sdk-authoring`, `@plitzi/sdk-mcp`):
|
|
165
|
+
`documentScroll: 'locked'` holds the document still while the page is open — a dashboard or a board used with a
|
|
166
|
+
finger, where the whole page was dragged and bounced back though nothing in it scrolls — and `'noBounce'` keeps the
|
|
167
|
+
scroll without the rubber band or pull-to-refresh. The page draws the rule itself, so it is in the server's HTML and
|
|
168
|
+
leaves with the page; never inside a widget, a space drawn in another, or the builder's canvas. A Settings select,
|
|
169
|
+
`PageSpec.documentScroll`, `upsertPage`'s `documentScroll` (`null` undoes it) and the page read; an unknown value is
|
|
170
|
+
refused (`attribute-value`). `upsertPage` is now held to the lint of the page it writes, as element writes are.
|
|
171
|
+
|
|
172
|
+
## MCP
|
|
173
|
+
- **`@plitzi/sdk-mcp/render`** (`@plitzi/sdk-mcp`): `plitzi_render`'s widget render on its own — no MCP server, no SSR,
|
|
174
|
+
no OAuth — for a project whose own agent draws in its own pages: `render`, `renderWidgetShape` to declare it as a
|
|
175
|
+
tool, `renderGuideText` for the agent. `base`, the style of the project's space, brings its tokens, classes and fonts
|
|
176
|
+
to the widget.
|
|
177
|
+
|
|
178
|
+
## Server
|
|
179
|
+
- **`publicUrl` says where people reach the server** (`@plitzi/sdk-server`): it was `http://127.0.0.1:<port>` whatever
|
|
180
|
+
the server was. It is `https` once `serverOptions.tls` gives it a certificate, and this machine's network address
|
|
181
|
+
with `HOST` open to the network (what a tablet opens, what an OAuth provider sends it back to); `PUBLIC_URL` still
|
|
182
|
+
wins. The network addresses it prints say `https` too.
|
|
183
|
+
- **A server serving TLS reaches itself** (`@plitzi/sdk-server`): with a local certificate (mkcert) Node trusts nothing
|
|
184
|
+
the browsers were told to, so a runtime calling its own MCP failed. Its requests to its own address now connect to its
|
|
185
|
+
listener and are taken when it presents exactly its certificate (`reachOwnServer`, exported from `/runtime`).
|
|
186
|
+
- **A runtime reads compressed answers again** (`@plitzi/sdk-server`): the dispatcher `reachOwnServer` (and the
|
|
187
|
+
platform's `reachSpaceInside`) installs reached every other host with an undici connector that offers HTTP/2, and an
|
|
188
|
+
answer that came back over h2 reached Node's `fetch` without its `content-encoding` — every API that compresses read
|
|
189
|
+
as Brotli bytes. Other hosts are reached over HTTP/1.1 now, as Node's own `fetch` reaches them.
|
|
190
|
+
- **A project's server serves HTTPS from `.env`** (`@plitzi/sdk-server`, `@plitzi/sdk-shared`): `TLS_CERT` and
|
|
191
|
+
`TLS_KEY`, the certificate's and the key's paths — what `plitzi cert` writes — with no code of the project's;
|
|
192
|
+
`serverOptions.tls` still wins. A network address the certificate does not name is said at boot, the machine's
|
|
193
|
+
`.local` name is printed among the addresses when it is named, and a server on loopback alone says how to open it to
|
|
194
|
+
the network.
|
|
195
|
+
- **A session survives a phone on the Wi-Fi** (`@plitzi/sdk-server`): a private network address (`192.168.…`, `10.…`,
|
|
196
|
+
Tailscale's `100.64/10`, IPv6 unique- and link-local) and a machine's mDNS name (`studio.local`) get the local
|
|
197
|
+
cookie profile — `SameSite=Lax`, no `Secure`, no `Domain`. They got `SameSite=None; Secure`, which a browser drops
|
|
198
|
+
over http: nobody could sign in from a tablet. A deployment's own `.local` sub-domains (`app.example.local`) keep the
|
|
199
|
+
strict profile.
|
|
200
|
+
- **Hiding the dev tools on a server over HTTPS hydrates** (`@plitzi/sdk-server`): a server with a certificate speaks
|
|
201
|
+
HTTP/2, which sends no `Host` — only `:authority` — and the cookies named for the origin's port (the dev tools'
|
|
202
|
+
`plitzi_debug_<port>`, forced flags, painted state) were looked for without it. Shift+F12 hid the dev tools in the
|
|
203
|
+
browser, the server rendered them back, and the page failed to hydrate. Every one is read off `requestAuthority`.
|
|
204
|
+
- **A rebuild of plugins says it finished** (`@plitzi/sdk-server`): bundles removed under a running server (`tmp/`
|
|
205
|
+
deleted) were each a warning, "rebuilding…", and their "ready" at a level a project's terminal does not show. It is
|
|
206
|
+
one episode now: said when noticed, and again when the last is built — how many, how long, which failed.
|
|
207
|
+
- **`tmp/dev-server.json` records the scheme** (`@plitzi/sdk-server`): `url` is `https` with a certificate, and the
|
|
208
|
+
file adds the network addresses and the `publicUrl`.
|
|
209
|
+
- **`public/` is asked about every time** (`@plitzi/sdk-server`): it was cached for an hour — a `.js` or `.css` in it
|
|
210
|
+
for a year, `immutable` — so a file a project rewrites while it runs arrived stale. It is `no-cache` now, answered
|
|
211
|
+
`304` by its `ETag` while unchanged; `immutable` stays for what is versioned (the SDK, the plugins).
|
|
212
|
+
|
|
213
|
+
## CLI
|
|
214
|
+
- **`check`, `shot`, `verify` and `visual` find a server on HTTPS** (`@plitzi/cli`): they rebuilt
|
|
215
|
+
`http://127.0.0.1:<port>`, so a project serving a certificate had no page checked. They read the URL `npm start`
|
|
216
|
+
recorded, and take a local certificate on this machine only. A project's `playwright.config.ts` comes from
|
|
217
|
+
`plitzi upgrade`.
|
|
218
|
+
- **`verify` runs the project's tests** (`@plitzi/cli`): a step `test`, after the lint, when `package.json` has a
|
|
219
|
+
`test` script.
|
|
220
|
+
- **`tsconfig.json` is the project's** (`@plitzi/cli`): the compiler options moved to `plitzi/tsconfig.base.json`, which
|
|
221
|
+
the CLI keeps up and `tsconfig.json` extends — so a folder of the project's own (`scripts/`, `tools/`) goes in its
|
|
222
|
+
`include` without changing a file of the CLI's. `plitzi doctor --fix` makes an older `tsconfig.json` extend it,
|
|
223
|
+
keeping only the options the project changed.
|
|
224
|
+
- **`doctor --fix` and `upgrade` write as the project's Prettier does** (`@plitzi/cli`): a repaired `tsconfig.json` or
|
|
225
|
+
`package.json` left `verify`'s format step red.
|
|
226
|
+
- **`upgrade` renames `usePlitziServiceContext`** (`@plitzi/cli`): with `PlitziServiceProvider` and
|
|
227
|
+
`PlitziServiceContextValue`, renamed in 0.38.12 with no alias — import, use and the module path that named it.
|
|
228
|
+
- **`skills update` compares every file** (`@plitzi/cli`): only `SKILL.md` was, so a change in a reference alone was
|
|
229
|
+
"up to date".
|
|
230
|
+
- **`NOTES.md`, the project's notes for agents** (`@plitzi/cli`): `AGENTS.md` and `CLAUDE.md` are the CLI's and
|
|
231
|
+
`upgrade` replaces them, so what a project's own sessions had to know had nowhere to live. `CLAUDE.md` imports
|
|
232
|
+
`NOTES.md`, `AGENTS.md` sends every agent to it, and nothing the CLI does touches it; `doctor --fix` writes one for
|
|
233
|
+
a project that has none.
|
|
234
|
+
|
|
235
|
+
- **`start:dev-inspect`** (`@plitzi/cli`): a server project's `start:dev` with Node's inspector open on
|
|
236
|
+
127.0.0.1:9229, so a debugger (VS Code, `chrome://inspect`) attaches to the server, and again after each restart.
|
|
237
|
+
`plitzi upgrade` adds it; `plitzi runtime add` has it restart on the runtime too.
|
|
238
|
+
- **`plitzi cert`** (`@plitzi/cli`): HTTPS while developing, for a phone or a tablet on the Wi-Fi — over http its
|
|
239
|
+
browser gives a page no microphone, camera or clipboard. A certificate from mkcert's local authority for this machine,
|
|
240
|
+
its network addresses and its `.local` name, in `tmp/tls/`, named in `.env`; what to install on the phone, said.
|
|
241
|
+
- **A browser project opens to the network** (`@plitzi/cli`): its `vite.config.ts` reads `HOST`, `TLS_CERT` and
|
|
242
|
+
`TLS_KEY` from `.env`, as a server project does, and writes `tmp/dev-server.json` — the port Vite took and the scheme
|
|
243
|
+
— so `page check`, `page shot` and `visual` find it on another port or over HTTPS. `plitzi upgrade` brings it.
|
|
244
|
+
|
|
245
|
+
## Docs
|
|
246
|
+
- **Two plugins share through `state`** (`@plitzi/sdk-authoring` skill): each plugin is a bundle of its own, so a module
|
|
247
|
+
both import is two copies, and what one keeps there the other never sees.
|
|
248
|
+
- **`verify --help`** names the project's tests among its steps.
|
|
249
|
+
|
|
3
250
|
## 0.38.13
|
|
4
251
|
|
|
5
252
|
### Patch Changes
|
|
@@ -68,6 +68,13 @@ export interface ElementDeclarationData {
|
|
|
68
68
|
* `plitzi page check`), which would otherwise wait for it to be on screen and report it hidden.
|
|
69
69
|
*/
|
|
70
70
|
drawsNothing?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Whether the element shows only some of the children put inside it, chosen as it runs — a dashboard showing the
|
|
73
|
+
* panels of the layout saved for this device, a wizard its current step. Read by the page checks, which then owe none
|
|
74
|
+
* of them on screen; the ones it draws are checked like the rest of the page — there, visible, and what is named
|
|
75
|
+
* inside them too.
|
|
76
|
+
*/
|
|
77
|
+
choosesChildren?: boolean;
|
|
71
78
|
/**
|
|
72
79
|
* The attributes the page server evaluates against a context of its own, never where the element renders: a
|
|
73
80
|
* provider's `notFound`, read against its answer; a page's title and description, read against its server providers.
|
package/dist/authoring/index.mjs
CHANGED
|
@@ -2,5 +2,5 @@ import { toBuilderParams as e, toInteractionCallback as t, toInteractionCallback
|
|
|
2
2
|
import { definePlugin as r, elementDeclaration as i, valuesOf as a } from "./declare.mjs";
|
|
3
3
|
import { BUILTIN_PARAM_TYPES as o, hiddenParams as s, invalidParams as c, missingRequiredParams as l, reconcileParams as u } from "./paramSpec.mjs";
|
|
4
4
|
import { BUILTIN_ELEMENT_CALLBACKS as d, SCROLL_CALLBACKS as f, applyElementCallback as p, getElementCallback as m } from "./elementCallbacks.mjs";
|
|
5
|
-
import {
|
|
6
|
-
export { d as BUILTIN_ELEMENT_CALLBACKS, o as BUILTIN_PARAM_TYPES, f as SCROLL_CALLBACKS, p as applyElementCallback, r as definePlugin, i as elementDeclaration, m as getElementCallback, s as hiddenParams, c as invalidParams, l as missingRequiredParams, u as reconcileParams,
|
|
5
|
+
import { NOTIFICATION_APPEARANCES as h, NOTIFICATION_DEFAULTS as g, NOTIFICATION_PLACEMENTS as _, readNotificationOptions as v, spaceCallbacks as y } from "./spaceCallbacks.mjs";
|
|
6
|
+
export { d as BUILTIN_ELEMENT_CALLBACKS, o as BUILTIN_PARAM_TYPES, h as NOTIFICATION_APPEARANCES, g as NOTIFICATION_DEFAULTS, _ as NOTIFICATION_PLACEMENTS, f as SCROLL_CALLBACKS, p as applyElementCallback, r as definePlugin, i as elementDeclaration, m as getElementCallback, s as hiddenParams, c as invalidParams, l as missingRequiredParams, v as readNotificationOptions, u as reconcileParams, y as spaceCallbacks, e as toBuilderParams, t as toInteractionCallback, n as toInteractionCallbacks, a as valuesOf };
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { BuiltinGlobalCallback } from './builder';
|
|
2
|
+
/** Where a notification may appear: the `addNotification` step's `placement`, and `useNotify`'s. */
|
|
3
|
+
export declare const NOTIFICATION_PLACEMENTS: readonly ["top-right", "top-center", "top-left", "bottom-right", "bottom-center", "bottom-left"];
|
|
4
|
+
export type NotificationPlacement = (typeof NOTIFICATION_PLACEMENTS)[number];
|
|
5
|
+
/** How a notification looks: the `addNotification` step's `appearance`, and `useNotify`'s. */
|
|
6
|
+
export declare const NOTIFICATION_APPEARANCES: readonly ["success", "danger", "warning", "info"];
|
|
7
|
+
export type NotificationAppearance = (typeof NOTIFICATION_APPEARANCES)[number];
|
|
8
|
+
/** What a notification takes besides its text — every one with the default the `addNotification` step has. */
|
|
9
|
+
export type NotificationOptions = {
|
|
10
|
+
placement?: NotificationPlacement;
|
|
11
|
+
appearance?: NotificationAppearance;
|
|
12
|
+
autoDismiss?: boolean;
|
|
13
|
+
/** Milliseconds before it dismisses itself, with `autoDismiss`. */
|
|
14
|
+
autoDismissTimeout?: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* What a notification was handed, kept to what it takes — a flow's values arrive as written, and a plugin may be plain
|
|
18
|
+
* JavaScript — with each field set aside named, so the caller says so rather than showing the default in silence.
|
|
19
|
+
*/
|
|
20
|
+
export declare const readNotificationOptions: (given: Readonly<Record<string, unknown>>) => {
|
|
21
|
+
options: NotificationOptions;
|
|
22
|
+
ignored: string[];
|
|
23
|
+
};
|
|
24
|
+
/** The defaults of a notification, the step's and the hook's alike. */
|
|
25
|
+
export declare const NOTIFICATION_DEFAULTS: {
|
|
26
|
+
readonly placement: "top-right";
|
|
27
|
+
readonly appearance: "success";
|
|
28
|
+
readonly autoDismiss: true;
|
|
29
|
+
readonly autoDismissTimeout: 5000;
|
|
30
|
+
};
|
|
2
31
|
/**
|
|
3
32
|
* The `space` source, which the SDK application registers rather than this package.
|
|
4
33
|
*
|
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
//#region src/authoring/spaceCallbacks.ts
|
|
2
|
-
var e =
|
|
2
|
+
var e = [
|
|
3
|
+
"top-right",
|
|
4
|
+
"top-center",
|
|
5
|
+
"top-left",
|
|
6
|
+
"bottom-right",
|
|
7
|
+
"bottom-center",
|
|
8
|
+
"bottom-left"
|
|
9
|
+
], t = [
|
|
10
|
+
"success",
|
|
11
|
+
"danger",
|
|
12
|
+
"warning",
|
|
13
|
+
"info"
|
|
14
|
+
], n = (n) => {
|
|
15
|
+
let r = e.find((e) => e === n.placement), i = t.find((e) => e === n.appearance), { autoDismiss: a, autoDismissTimeout: o } = n, s = typeof o == "number" && Number.isFinite(o) && o > 0 ? o : void 0, c = {
|
|
16
|
+
placement: r !== void 0,
|
|
17
|
+
appearance: i !== void 0,
|
|
18
|
+
autoDismiss: typeof a == "boolean",
|
|
19
|
+
autoDismissTimeout: s !== void 0
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
options: {
|
|
23
|
+
...r === void 0 ? {} : { placement: r },
|
|
24
|
+
...i === void 0 ? {} : { appearance: i },
|
|
25
|
+
...typeof a == "boolean" ? { autoDismiss: a } : {},
|
|
26
|
+
...s === void 0 ? {} : { autoDismissTimeout: s }
|
|
27
|
+
},
|
|
28
|
+
ignored: Object.entries(c).filter(([e, t]) => !t && n[e] !== void 0).map(([e]) => e)
|
|
29
|
+
};
|
|
30
|
+
}, r = {
|
|
31
|
+
placement: "top-right",
|
|
32
|
+
appearance: "success",
|
|
33
|
+
autoDismiss: !0,
|
|
34
|
+
autoDismissTimeout: 5e3
|
|
35
|
+
}, i = { addNotification: {
|
|
3
36
|
source: "space",
|
|
4
37
|
title: "Add Notification",
|
|
5
38
|
strictParams: !0,
|
|
@@ -12,40 +45,28 @@ var e = { addNotification: {
|
|
|
12
45
|
placement: {
|
|
13
46
|
type: "select",
|
|
14
47
|
description: "Where the toast appears on screen.",
|
|
15
|
-
default:
|
|
16
|
-
options: [
|
|
17
|
-
"top-right",
|
|
18
|
-
"top-center",
|
|
19
|
-
"top-left",
|
|
20
|
-
"bottom-right",
|
|
21
|
-
"bottom-center",
|
|
22
|
-
"bottom-left"
|
|
23
|
-
]
|
|
48
|
+
default: r.placement,
|
|
49
|
+
options: [...e]
|
|
24
50
|
},
|
|
25
51
|
appearance: {
|
|
26
52
|
type: "select",
|
|
27
53
|
description: "Visual style of the notification: success, danger, warning or info.",
|
|
28
|
-
default:
|
|
29
|
-
options: [
|
|
30
|
-
"success",
|
|
31
|
-
"danger",
|
|
32
|
-
"warning",
|
|
33
|
-
"info"
|
|
34
|
-
]
|
|
54
|
+
default: r.appearance,
|
|
55
|
+
options: [...t]
|
|
35
56
|
},
|
|
36
57
|
autoDismiss: {
|
|
37
58
|
type: "boolean",
|
|
38
59
|
description: "Whether the notification dismisses itself after a timeout.",
|
|
39
|
-
default:
|
|
60
|
+
default: r.autoDismiss,
|
|
40
61
|
canBind: !1
|
|
41
62
|
},
|
|
42
63
|
autoDismissTimeout: {
|
|
43
64
|
type: "number",
|
|
44
65
|
description: "Milliseconds before auto-dismiss. Only applies when autoDismiss is true.",
|
|
45
|
-
default:
|
|
66
|
+
default: r.autoDismissTimeout,
|
|
46
67
|
when: (e) => e.autoDismiss === !0
|
|
47
68
|
}
|
|
48
69
|
}
|
|
49
70
|
} };
|
|
50
71
|
//#endregion
|
|
51
|
-
export { e as spaceCallbacks };
|
|
72
|
+
export { t as NOTIFICATION_APPEARANCES, r as NOTIFICATION_DEFAULTS, e as NOTIFICATION_PLACEMENTS, n as readNotificationOptions, i as spaceCallbacks };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clipboard's `writeText`, bound — or `undefined` where the browser offers none. Typed as always there, it is not:
|
|
3
|
+
* a page not served over https has none (a phone opening a development server by its network address), nor has the
|
|
4
|
+
* server. A copy button that called it straight threw on such a page.
|
|
5
|
+
*/
|
|
6
|
+
export declare const clipboardWriter: () => ((text: string) => Promise<void>) | undefined;
|
|
7
|
+
/** Copies `text`: whether it reached the clipboard — not where there is none, nor when the browser refuses it. */
|
|
8
|
+
export declare const copyText: (text: string) => Promise<boolean>;
|
|
9
|
+
/** What a copy button says when it could not copy, for its `title`. */
|
|
10
|
+
export declare const NOT_COPIED_REASON = "This page cannot reach the clipboard: it is not served over https, or the browser refused.";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/helpers/clipboard.ts
|
|
2
|
+
var e = () => {
|
|
3
|
+
let e = typeof navigator > "u" ? void 0 : Reflect.get(navigator, "clipboard"), t = typeof e == "object" && e ? Reflect.get(e, "writeText") : void 0;
|
|
4
|
+
if (typeof t == "function") return async (n) => {
|
|
5
|
+
await Reflect.apply(t, e, [n]);
|
|
6
|
+
};
|
|
7
|
+
}, t = async (t) => {
|
|
8
|
+
let n = e();
|
|
9
|
+
if (!n) return !1;
|
|
10
|
+
try {
|
|
11
|
+
return await n(t), !0;
|
|
12
|
+
} catch {
|
|
13
|
+
return !1;
|
|
14
|
+
}
|
|
15
|
+
}, n = "This page cannot reach the clipboard: it is not served over https, or the browser refused.";
|
|
16
|
+
//#endregion
|
|
17
|
+
export { n as NOT_COPIED_REASON, e as clipboardWriter, t as copyText };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True under a space: what a space drawn inside it — the `plitziSdk` element, a plugin rendering a space of its own —
|
|
3
|
+
* reads to know the document is not its own. The space itself keeps the answer in `render.enclosed`, where everything
|
|
4
|
+
* in it reads it; this context is only how it learns it.
|
|
5
|
+
*
|
|
6
|
+
* Shared, because the space around may be another copy of the runtime: a plugin imports the SDK through the page's
|
|
7
|
+
* import map.
|
|
8
|
+
*/
|
|
9
|
+
export declare const EnclosingSpaceContext: import('react').Context<boolean>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The class on a space's root element, where what floats above its page — a dropdown's open menu — is drawn. */
|
|
2
|
+
export declare const SPACE_ROOT_CLASS = "plitzi-sdk";
|
|
3
|
+
/** The root of the space `node` is drawn in: the nearest one, for a space drawn inside another. */
|
|
4
|
+
export declare const spaceRootOf: (node: Element | null | undefined) => HTMLElement | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NetworkInterfaceInfo } from 'node:os';
|
|
2
|
+
/**
|
|
3
|
+
* This machine as another device on its network reaches it — a phone on the same Wi-Fi opening the project's server —
|
|
4
|
+
* said once for the server, which prints where to open it, and the CLI, which makes a certificate naming it.
|
|
5
|
+
*/
|
|
6
|
+
/** Its IPv4 addresses on the network (`os.networkInterfaces()`): what a phone opens it by today. */
|
|
7
|
+
export declare const networkAddresses: (interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>) => string[];
|
|
8
|
+
/**
|
|
9
|
+
* The name mDNS gives it (`os.hostname()` → `studio.local`), which outlives the address the router hands it: a
|
|
10
|
+
* certificate made for it still serves after the address changes.
|
|
11
|
+
*/
|
|
12
|
+
export declare const localNetworkName: (machine: string) => string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/project/network.ts
|
|
2
|
+
var e = (e) => Object.values(e).flat().flatMap((e) => e && e.family === "IPv4" && !e.internal ? [e.address] : []), t = (e) => `${(e.split(".").at(0) ?? "").toLowerCase()}.local`;
|
|
3
|
+
//#endregion
|
|
4
|
+
export { t as localNetworkName, e as networkAddresses };
|
package/dist/realtime/topics.mjs
CHANGED
|
@@ -14,9 +14,9 @@ var t = /^[A-Za-z0-9:_.-]{1,128}$/, n = "[A-Za-z0-9_.-]{1,64}", r = (e) => t.tes
|
|
|
14
14
|
]), c = (t, n) => {
|
|
15
15
|
let r = [];
|
|
16
16
|
if (o.test(t) || r.push("a pattern is letters, digits and `:_.-`, with `{name}` for the part a page fills in — `board:{id}`"), !e(n)) return [...r, "a channel is an object: `{ access: { mode: \"public\" } }`"];
|
|
17
|
-
let { access: i, publish: a, presence: c, grant: l,
|
|
18
|
-
typeof
|
|
19
|
-
for (let [e, t] of [["maxMessageBytes",
|
|
17
|
+
let { access: i, publish: a, presence: c, grant: l, retain: u, maxMessageBytes: d, messagesPerSecond: f } = n, p = e(i) ? i.mode : void 0;
|
|
18
|
+
typeof p != "string" || !s.has(p) ? r.push("`access` is { mode: \"public\" }, { mode: \"session\" } or { mode: \"role\", permissions: […] }") : p === "role" && !(e(i) && Array.isArray(i.permissions) && i.permissions.every((e) => typeof e == "string")) && r.push("`access: { mode: \"role\" }` names the permissions it needs: `permissions: [\"boardEdit\"]`"), a !== void 0 && a !== "clients" && a !== "server" && r.push("`publish` is \"clients\" (pages send) or \"server\" (only a flow's realtime.publish)"), c !== void 0 && typeof c != "boolean" && r.push("`presence` is true or false"), l !== void 0 && typeof l != "boolean" && r.push("`grant` is true (a page needs a grant from `realtime.grant` to open a topic) or false"), u !== void 0 && typeof u != "boolean" ? r.push("`retain` is true (the server keeps the latest message of each type for pages that join) or false") : u === !0 && a !== "server" && r.push("`retain: true` keeps what the server says, so only the server may speak: add `publish: \"server\"` — a page changes it through an action");
|
|
19
|
+
for (let [e, t] of [["maxMessageBytes", d], ["messagesPerSecond", f]]) t !== void 0 && (typeof t != "number" || !Number.isInteger(t) || t <= 0) && r.push(`\`${e}\` is a whole number above zero`);
|
|
20
20
|
return r;
|
|
21
21
|
}, l = (e) => ({
|
|
22
22
|
maxMessageBytes: e.maxMessageBytes ?? 4096,
|