@stealthscale/sdk-host 0.1.0 → 0.3.1

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/README.md CHANGED
@@ -12,51 +12,218 @@ A product creates one host per page in the browser and one per request on a serv
12
12
  pnpm add @stealthscale/sdk-host
13
13
  ```
14
14
 
15
- The package peers on `react`, `react-dom`, `@tanstack/react-router-ssr-query`,
15
+ The package peers on `react`, `react-dom`, `lucide-react`, `@tanstack/react-router-ssr-query`,
16
16
  `@stealthscale/sdk-plugin`, the providers it reads and the component packages it renders:
17
17
 
18
18
  - `provider-data`, `provider-router`, `provider-i18n`, `provider-hotkeys`, `provider-form`,
19
- `settings` and `hooks`
20
- - `component-actions`, `component-feedback`, `component-forms`, `component-layout`,
21
- `component-modals`, `component-navigation`, `component-screen` and `component-typography`
22
-
23
- The `./openfeature` entry peers on `@openfeature/web-sdk` as well, which is optional. The
24
- `./standalone/app` entry peers on `provider-color-mode`, `provider-locale` and `provider-shell`,
25
- which are optional for the other entries. The host's words are in the `host` catalogue namespace,
19
+ `provider-viewport`, `settings` and `hooks`
20
+ - `component-a11y`, `component-actions`, `component-feedback`, `component-forms`,
21
+ `component-layout`, `component-media`, `component-modals`, `component-navigation`,
22
+ `component-screen` and `component-typography`
23
+
24
+ The `./openfeature` entry peers on `@openfeature/web-sdk` as well, which is optional. The `./app`,
25
+ `./releases` and `./standalone/app` entries peer on `provider-color-mode`, `provider-locale` and
26
+ `provider-shell`, which are optional for the other entries. The `./deploy` entry imports
27
+ `@stealthscale/sdk-core` alone. The host's words are in the `host` catalogue namespace,
26
28
  `locales/<language>/host.json`.
27
29
 
28
- ## Compose the application
30
+ ## Render a product
29
31
 
30
- ```tsx
32
+ `renderProduct` from `@stealthscale/sdk-host/app` renders a product from its definition. It creates
33
+ the host over the product's sources and the router with the host's frame at its root, and renders
34
+ both inside the product's `Shell`.
35
+
36
+ ```ts
37
+ import { catalogues } from "virtual:i18n";
31
38
  import { product } from "virtual:product";
32
39
 
33
- const routes = createHostRoutes(product);
34
- const root = createAppRootRoute<HostRouterContext>()({
35
- component: () => (
36
- <HostRoot>
37
- <Frame />
38
- </HostRoot>
39
- ),
40
- notFoundComponent: HostNotFound,
40
+ import { renderProduct } from "@stealthscale/sdk-host/app";
41
+
42
+ await renderProduct({ catalogues, product, themes: ["ink", "pine"] });
43
+ ```
44
+
45
+ | Option | Where left out | States |
46
+ | ------------ | ------------------------------ | ----------------------------------------------------- |
47
+ | `product` | Required | The resolved product, from `virtual:product` |
48
+ | `catalogues` | Every key renders as itself | The catalogues, from `virtual:i18n` |
49
+ | `themes` | The application's one theme | The themes the product offers, the first until a pick |
50
+ | `glyphs` | Lucide's icons | The glyphs the settings sections' forms render |
51
+ | `basepath` | The site's root | The base the product is served at, such as `/docs/` |
52
+ | `element` | The element with the id `root` | The element the product renders into |
53
+ | `refused` | None | The releases the page refused before it rendered |
54
+
55
+ - The definition's `sources` imports the module that exports the host's `session`, `access`,
56
+ `flags`, `data` and `store`. Without a session nobody is signed in, and without a store a person's
57
+ choices are kept in the page's local storage.
58
+ - The definition's `frame` states the frame's layout and options, or imports the product's own
59
+ frame, which the router then renders at its root.
60
+ - `renderProduct` waits for `host.ready()`, renders into a React root that takes `onCaughtError`,
61
+ and resolves with that root.
62
+ - The site root opens the first section the person may open. Without one it sends a person who is
63
+ not signed in to the sign-in route, and renders the not-found page otherwise. A plugin's route at
64
+ `/` takes the site root's place.
65
+ - The host reports each entry of `refused` as `plugin-refused` when the page starts.
66
+
67
+ ## Load releases at run time
68
+
69
+ `renderDeployed` from `@stealthscale/sdk-host/releases` loads the plugins that the page's deployment
70
+ lists, then renders the product with them through `renderProduct`. The page that
71
+ `product.app({ runtime: true })` of `@stealthscale/vite-config-product` generates calls it.
72
+
73
+ ```ts
74
+ import { catalogues } from "virtual:i18n";
75
+ import { definition, product } from "virtual:product";
76
+
77
+ import { renderDeployed } from "@stealthscale/sdk-host/releases";
78
+
79
+ await renderDeployed({ catalogues, definition, product, themes: ["ink"] });
80
+ ```
81
+
82
+ `renderDeployed` takes the options of `renderProduct` except `refused`, and `definition`, the
83
+ product's definition from `virtual:product`.
84
+
85
+ - It reads the deployment from the document's `script` element with the id `stealth-deployment`. A
86
+ document without that element renders the build's product.
87
+ - It registers the page's preloader with `registerPreloader` of `@stealthscale/sdk-core`. The
88
+ preloader runs when a release imports one of its modules lazily. It appends a module preload to
89
+ the document for each module that the imported module needs.
90
+ - Before any request, it refuses a release whose requirements the page's versions do not meet. It
91
+ also refuses a release without a catalogue in the page's fallback language, and a release whose id
92
+ names a namespace of the page's catalogues.
93
+ - It reads each release's words in the fallback language from the deployment and imports the
94
+ release's `manifest.js`. When the deployment lists a release without words, the loader fetches
95
+ them with their integrity while the page imports the manifest. The page fetches the release's
96
+ other languages when i18next requests them.
97
+ - It adds the releases to the definition's plugins one at a time and resolves each step. A release
98
+ whose step reports a problem is refused, and the other releases render.
99
+ - The host reports each refused release as `plugin-refused`. Its `reason` is `catalogue`,
100
+ `deployment`, `manifest`, `requires` or `resolution`, and its `detail` names the fault.
101
+
102
+ The loader is an entry of its own, so a product that does not load a release does not download it.
103
+
104
+ A page that a server rendered loads its releases with `deployedProduct` from the same entry before
105
+ it hydrates. Create the host with the `product`, the `catalogues` and the `refused` releases that it
106
+ returns.
107
+
108
+ ```ts
109
+ import { deployedProduct } from "@stealthscale/sdk-host/releases";
110
+
111
+ const deployed = await deployedProduct({ catalogues, definition, document, product });
112
+ ```
113
+
114
+ - The product's `releases` contains the ids of the releases in the document. The list includes the
115
+ refused releases, and it is empty where the page cannot read the deployment.
116
+
117
+ ## Write a deployment's document
118
+
119
+ `deployDocument` from `@stealthscale/sdk-host/deploy` writes the parts of a product's document that
120
+ depend on its deployment. `documentWith` writes them into the document of the product's build.
121
+
122
+ ```ts
123
+ import { deployDocument, documentWith } from "@stealthscale/sdk-host/deploy";
124
+
125
+ const deployed = await deployDocument(deployment, provided);
126
+ const html = documentWith(built, deployed);
127
+ ```
128
+
129
+ - `deployment` lists each release by the URL of its directory, the integrity of its `plugin.json`
130
+ and the options the product installs it with. `provided` is the build's `.product/provided.json`.
131
+ - The step fetches each `plugin.json`, checks its digest against the deployment and checks each
132
+ member. It leaves out a release that fails, and lists it in `refused` with its base and the fault.
133
+ - For each release whose requirements the provided versions meet, the step fetches the release's
134
+ catalogue in the page's fallback language. It checks the catalogue's digest against `plugin.json`
135
+ and writes the words into the deployment. When the deployment lists a release without words, the
136
+ page fetches the words itself.
137
+ - `importMap` maps the provided specifiers and lists the integrity of every file of every release.
138
+ `csp` is a `script-src` policy with the page's origin, each release's origin and the import map's
139
+ digest. `deployment` is the deployment that the page reads. It contains each release's description
140
+ and words, and the modules of the provided specifiers that the releases import.
141
+ - `preloads` lists each module once. It contains the `manifest.js` of each loaded release, the
142
+ release modules and provided entries that the manifest imports, and the chunks that those entries
143
+ import. The preload of a release's module has the module's integrity.
144
+ - `releases` contains the ids of the releases in the document. A server passes them to the host as
145
+ the product's `releases` (see "Server rendering").
146
+ - `documentWith` puts the policy as a `meta` element, the import map, a module preload per entry of
147
+ `preloads` and the deployment's `script` element in place of the build's import map. It throws for
148
+ a document without an import map.
149
+ - A server that writes its own document writes `importMap`, the preloads and `deployment` into it,
150
+ and sends `csp` as a header. `DEPLOYMENT_ELEMENT` is the id of the deployment's element.
151
+ - The step uses `fetch` and Web Crypto alone, so it runs in Node and in any runtime that has both.
152
+ `fetching` replaces `fetch`.
153
+
154
+ ## The product's frame
155
+
156
+ `ProductFrame` renders the frame the product's definition states, from data alone. `renderProduct`
157
+ and `createProductRouter` render it at the router's root.
158
+
159
+ | Layout | Main menu | Sidebar |
160
+ | ------------------- | -------------------------------------------- | --------------------------------------------- |
161
+ | `sidebar` (default) | The sidebar's first block | The main menu, then every plugin's navigation |
162
+ | `stacked` | Links in the bar | None |
163
+ | `split` | Links in the bar | The current section's navigation |
164
+ | `rail` | Icons in a rail, and a dock on a narrow page | The current section's navigation |
165
+
166
+ - The sections are the main menu's top-level entries. The current section contains the address on
167
+ screen, or belongs to the plugin of the page on screen, so a plugin's settings pages keep its
168
+ section current.
169
+ - A section's navigation is a block per menu of the section's plugin, the plugin's extensions in the
170
+ `navigation` region, and a block of its settings pages. A menu nests an entry under the entry of
171
+ its route's parent.
172
+ - An entry with an icon renders lucide's icon from the entry's data. A top-level entry without one
173
+ renders the initials of its text.
174
+ - A region nobody fills renders no part of the shell. The `aside` and `panel` regions render in
175
+ panels at the end side, each a complementary landmark named in the host's words.
176
+ - The bar renders the sidebar's opener, the brand, the `header` region, a search that opens the
177
+ palette, the switchers and the panel's opener in one toolbar. The `userMenu` region renders after
178
+ the toolbar, or at the sidebar's foot.
179
+ - The rail, the sidebar, the aside and the panel keep a person's open state and width under
180
+ `stealth.<productId>.<subject>.frame.<panel>`. A panel over a narrow page starts closed whatever
181
+ the stored state.
182
+ - A skip link leads to the page, and one leads to each panel beside the page.
183
+ - A part with a `mode` renders in that colour mode, such as a dark sidebar on a light page.
184
+
185
+ ## Compose the application yourself
186
+
187
+ A product that renders on a server creates the host and the router itself. `createProductRouter`
188
+ from `@stealthscale/sdk-host/app` returns the router with the frame and the site root:
189
+
190
+ ```ts
191
+ const router = createProductRouter(host, product, {
192
+ history: createMemoryHistory({ initialEntries: [request.url] }),
193
+ shell: Document,
41
194
  });
42
- const tree = root.addChildren([...routes(root)]);
195
+ ```
196
+
197
+ | Option | Where left out | States |
198
+ | ---------- | ---------------- | ------------------------------------------------------------------------------------- |
199
+ | `basepath` | The site's root | The base the product is served at |
200
+ | `frame` | The host's frame | The root route's component, for a product with a frame of its own |
201
+ | `history` | The browser's | The history the router reads its address from |
202
+ | `shell` | No document | The document around the frame, the root route's `shellComponent`, for a server render |
203
+
204
+ `GLYPHS` from the same entry contains the form glyphs `renderProduct` passes to the host, for a
205
+ product that calls `createHost` itself.
43
206
 
207
+ A product that creates the host itself renders the same pieces `renderProduct` renders:
208
+
209
+ ```tsx
210
+ import { product } from "virtual:product";
211
+
212
+ const store = localStore();
44
213
  const host = createHost({
45
214
  data: { changes: changesSubscription, transport: gatewayTransport(gateway) },
46
215
  flags: openFeatureFlags(),
216
+ glyphs: GLYPHS,
47
217
  product,
48
218
  session: identitySession,
49
- store: localStore(),
50
- });
51
- const router = createRouter({
52
- ...routerOptions({ data: host.data, host, routes: routeMap(tree) }),
53
- routeTree: tree,
219
+ store,
54
220
  });
221
+ const router = createProductRouter(host, product);
55
222
 
56
223
  await host.ready();
57
224
 
58
- createRoot(element).render(
59
- <Shell app={product.productId} catalogues={catalogues}>
225
+ createRoot(element, { onCaughtError }).render(
226
+ <Shell app={product.productId} catalogues={catalogues} locales={product.locales} store={store}>
60
227
  <HostProvider host={host} router={router}>
61
228
  <RouterProvider router={router} />
62
229
  </HostProvider>
@@ -64,15 +231,19 @@ createRoot(element).render(
64
231
  );
65
232
  ```
66
233
 
67
- | Piece | Created | Renders or keeps |
68
- | ---------------------- | --------------------------------- | -------------------------------------------------------------------------------------- |
69
- | `createHostRoutes` | Once per route tree | The plugins' routes, the settings route, its index and a route per settings page |
70
- | `createHost` | Once per page, once per request | The stores, the event bus, the data client, the toaster, the quarantine, the reports |
71
- | `HostRoot` | The root route's component | The `layout` region around the frame, the `overlay` region, the palette and the toasts |
72
- | `HostNotFound` | The root's not-found component | The not-found page, with the reason a plugin's page is not found |
73
- | `HostProvider` | Once per render tree | The host's context, `DataProvider` and the `root` region around the product's tree |
74
- | `HostContent` | In the frame, where the page goes | The `content` region around the matched page |
75
- | `setupHostIntegration` | Once per router | The host's state in a server render, and the browser's host started from it |
234
+ A frame of the product's own renders `HostRoot` around its shell and `HostContent` where the page
235
+ goes, and the definition's `frame` names it, or `createProductRouter` takes it as `frame`.
236
+
237
+ | Piece | Created | Renders or keeps |
238
+ | ---------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------- |
239
+ | `createHostRoutes` | Once per route tree | The plugins' routes, the settings route, its index and a route per settings page |
240
+ | `createHost` | Once per page, once per request | The stores, the event bus, the data client, the toaster, the quarantine, the reports |
241
+ | `HostRoot` | The frame's outermost part | The `layout` region around the frame, the `overlay` region, the palette and the toasts |
242
+ | `HostNotFound` | The root's not-found component | The not-found page, with the reason a plugin's page is not found |
243
+ | `HostProvider` | Once per render tree | The host's context, `DataProvider` and the `root` region around the product's tree |
244
+ | `HostContent` | In the frame, where the page goes | The `content` region around the matched page |
245
+ | `setupHostIntegration` | Once per router | The host's state in a server render, and the browser's host started from it |
246
+ | `onCaughtError` | Passed to `createRoot` or `hydrateRoot` | Every error a boundary catches, written with `console.error`, except the router's not-found |
76
247
 
77
248
  - Build the tree once. A plugin that turns off makes its routes not found, and the router keeps the
78
249
  tree it was built from.
@@ -84,19 +255,20 @@ createRoot(element).render(
84
255
 
85
256
  ## Create a host
86
257
 
87
- | Option | Where left out | States |
88
- | ----------------- | ------------------------------------------------- | ---------------------------------------------------------------------------- |
89
- | `product` | Required | The resolved product with its plugins' manifests, from `virtual:product` |
90
- | `session` | Required | The `SessionSource` the session comes from |
91
- | `store` | Required | The `SettingStore` a person's switches, settings and placements are kept in |
92
- | `access` | Every decision is the tenant-wide one | The `AccessSource` of decisions on single resources |
93
- | `data` | A transport over the contracts' samples | The transport and the changes subscription the data client runs on |
94
- | `flags` | Every flag has the product's value or its default | The `FlagSource` of flag values per session |
95
- | `flagsTimeout` | 1,000 ms | How long `ready` waits for the flag source's first `identify` |
96
- | `glyphs` | No glyphs | The glyphs the settings sections' forms render, such as a checked box's mark |
97
- | `overrides` | On outside a production build | Whether the tab's flag overrides are read and written |
98
- | `quarantineAfter` | 3 | Failed renders in a row after which a route or an extension is quarantined |
99
- | `report` | The console | The function that receives every `HostReport` entry |
258
+ | Option | Where left out | States |
259
+ | ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
260
+ | `product` | Required | The resolved product with its plugins' manifests, from `virtual:product` |
261
+ | `session` | Required | The `SessionSource` the session comes from |
262
+ | `store` | Required | The `SettingStore` a person's switches, settings and placements are kept in |
263
+ | `access` | Every decision is the tenant-wide one | The `AccessSource` of decisions on single resources |
264
+ | `data` | A transport over the contracts' samples | The transport and the changes subscription the data client runs on |
265
+ | `flags` | Every flag has the product's value or its default | The `FlagSource` of flag values per session |
266
+ | `flagsTimeout` | 1,000 ms | How long `ready` waits for the flag source's first `identify` |
267
+ | `glyphs` | No glyphs | The glyphs the settings sections' forms render, such as a checked box's mark |
268
+ | `overrides` | On outside a production build | Whether the tab's flag overrides are read and written, as `useFlagActions().overridable` reports |
269
+ | `quarantineAfter` | 3 | Failed renders in a row after which a route or an extension is quarantined |
270
+ | `refused` | None | The releases the page refused, which the host reports as `plugin-refused` when it is created |
271
+ | `report` | The console | The function that receives every `HostReport` entry |
100
272
 
101
273
  - `createHost` throws where a manifest lacks code for a declared name, and names the plugin and the
102
274
  name.
@@ -114,25 +286,44 @@ in one `compileRoutes` call:
114
286
 
115
287
  - A plugin route loads its page with the modules of the plugins in its `loads`, loads its declared
116
288
  data through the data client, and validates its search with its contract's validator.
289
+ - Where a condition requires a signed-in person and nobody is signed in, the route redirects to the
290
+ product's sign-in route, with the entered address as the `redirect` search. The redirect comes
291
+ before the plugin's availability, which can read the entitlements of a session that does not exist
292
+ yet.
117
293
  - Its condition is evaluated against the host in the router's context. A route whose plugin is off,
118
294
  stopped by its kill switch or quarantined is not found, with `NotFoundData` that states the
119
295
  reason: `PluginUnavailable` or `PageQuarantined`.
120
- - Where a condition that requires a signed-in person is false for a person who is not signed in, the
121
- route redirects to the product's sign-in route, with the entered address as the `redirect` search.
296
+ - Each plugin route renders the root's `notFoundComponent` in its own place, inside the frame. A
297
+ page that throws the router's `notFound()` while it renders, for parameters no record matches,
298
+ renders it there and counts no failure. React reports every error a boundary catches, so a root
299
+ without `onCaughtError` writes that not-found to the console.
122
300
  - A page that throws renders its manifest's fallback, or the host's error page. Each error counts
123
301
  once towards the route's quarantine.
302
+ - A page whose module failed to import renders the host's error page with a button that reloads the
303
+ page, in place of its fallback, and does not count towards the quarantine.
124
304
 
125
305
  `HostNotFound` renders the not-found page. For a plugin a switch turned off it offers a button that
126
306
  turns the plugin on, and for a quarantined page a retry. Where the page replaced the page a person
127
- was on, focus moves to its heading.
307
+ was on and focus was in that page, focus moves to its heading. A control outside the page keeps
308
+ focus, and a server-rendered not-found page takes none while it hydrates.
309
+
310
+ Where the page's document contains a deployment, a plain not-found page renders the loading page on
311
+ a server and while it hydrates. Once the page hydrated, the host loads the address again with the
312
+ router. A release's page that matches the address then renders without taking focus, because it is
313
+ the first page of the load. The not-found page renders where the routes do not match the address.
128
314
 
129
- ## The frame
315
+ ## The parts of every frame
130
316
 
131
317
  - `HostProvider` renders the host's context, `DataProvider` and the `root` region around the
132
318
  product's tree. It invalidates the router after a change a route condition reads, emits
133
319
  `host/navigated`, and titles the document with the product's name.
134
320
  - `HostRoot` renders the frame inside the `layout` region, then the `overlay` region with the
135
321
  command palette and the toast region, and binds the commands' keys. `Mod+K` opens the palette.
322
+ - The palette lists each command a key could run under its plugin's name, and the main menu's pages
323
+ under `Go to`. While a query matches them, it also lists the entries of every other menu and the
324
+ pages of every plugin's page index. A settings page is listed under the settings menu's heading,
325
+ every other row under its plugin's name. The palette imports the indexes the first time it opens,
326
+ and reports an index that fails as `index-failed`.
136
327
  - `HostContent` renders the matched page inside the `content` region. Render it in the frame where
137
328
  the page belongs.
138
329
  - `HostProvider` and `HostRoot` each render a last boundary. An error outside every plugin renders a
@@ -154,13 +345,15 @@ Pass `glyphs` to `createHost`, or the sections' forms render no marks.
154
345
  ## A chunk that no longer exists
155
346
 
156
347
  A page loaded before a deployment requests chunks that the deployment replaced. The host reloads the
157
- page once per build version, the product's `version`:
348
+ page once per key. The key is the product's `version`, followed by `+` and the deployment's id on a
349
+ page that loads releases:
158
350
 
159
351
  - A plugin's module that fails to import starts the recovery, and so does Vite's `vite:preloadError`
160
352
  while `HostProvider` is mounted. The import does not settle, so no error renders while the page
161
353
  reloads.
162
- - The version is recorded under `stealth.<productId>.reloaded` in session storage. A second failure
163
- for the same version renders the error.
354
+ - The key is recorded under `stealth.<productId>.reloaded` in session storage. A second failure for
355
+ the same key rejects with `PluginImportError`, whose `cause` is the import's error, and the
356
+ route's error component renders it without a second reload.
164
357
  - A server, and a page whose storage the browser refuses, render the error without a reload.
165
358
 
166
359
  Each plugin's first import is measured as `stealth:load:<plugin id>`.
@@ -181,10 +374,26 @@ setupHostIntegration({ host, router });
181
374
  server's flags until `HostProvider` mounts, and takes the decisions where the browser's subject is
182
375
  the server's, so the first render matches the server's.
183
376
  - `HostProvider`'s first effect applies the tab's overrides and the browser's flag source.
377
+ - A boundary that hydrates after that effect, such as the router's matches once a lazy page loads,
378
+ reads the server's flags and the availability they give as React's server snapshot. It hydrates
379
+ against the server's HTML, and React renders it again with the browser's values.
184
380
 
185
381
  Create a host per request on a server, with `constantSession` and a setting store over the request's
186
382
  cookies.
187
383
 
384
+ A product that loads releases renders their pages in the browser alone. Its server passes the
385
+ `releases` that `deployDocument` returned as the product's `releases`, and the page loads them with
386
+ `deployedProduct` before it hydrates:
387
+
388
+ - Inside a server snapshot the availability store withholds the plugin of each release. Each such
389
+ plugin is off with the reason `unavailable`. The menus, the settings sections and
390
+ `usePluginStatuses` read the store, so the frame hydrates without the releases' entries, sections
391
+ and rows. React renders them once the page hydrated.
392
+ - The server sends `csp` as a header with a nonce of the response in `script-src`, and sets the
393
+ nonce as the router's `ssr.nonce`. It writes the import map as the first child of the document's
394
+ head, because React writes every `link` of the head before the head's inline scripts.
395
+ - `examples/app-plugins` serves the notes release this way.
396
+
188
397
  ## A plugin on its own
189
398
 
190
399
  `@stealthscale/sdk-host/standalone` builds the product a plugin runs in on its own, for its tests
@@ -223,16 +432,21 @@ into the element with the id `root`, or `element`, and resolves with its React r
223
432
  | `themes` | The application's one theme | The themes the page offers |
224
433
 
225
434
  The page opens at the plugin's first menu entry, else at the first route a sample fills. It renders
226
- a frame of every region the plugins fill and the development panel over it. The panel is a floating
227
- panel at the bottom-end corner, with one group of controls each:
435
+ the product's frame in the `sidebar` layout, and the development panel over it. The panel is a
436
+ floating panel at the bottom-end corner, with one group of controls each:
228
437
 
229
438
  - Page: every route a sample fills, by its id.
230
- - Session: signed in, and a switch per declared permission and entitlement.
439
+ - Session: signed in, and a switch per declared permission and entitlement. Signing out writes
440
+ `NOBODY`, whose permission and entitlement lists are empty. The permission and entitlement
441
+ switches are disabled until somebody signs in. Signing in again restores the session they showed
442
+ before.
231
443
  - Checks on one resource: Allow, Deny and Pending.
232
444
  - Feature flags: a switch per boolean flag and a picker per experiment, through `flags.override`.
445
+ The group is left out where no plugin declares a flag.
233
446
  - Plugin: the plugin's switch, and its kill switch.
234
447
  - Operations: a picker per query and mutation: the sample, the sample after 2 seconds, or a refusal
235
- of each kind a `DataError` states. A query's pick loads the page's data again.
448
+ of each kind a `DataError` states. A query's pick loads the page's data again. The group is left
449
+ out where the product declares neither a query nor a mutation.
236
450
  - Display: the language, the theme and the color mode.
237
451
 
238
452
  ## OpenFeature
@@ -242,6 +456,7 @@ the OpenFeature client of a domain, `stealth.host` by default.
242
456
 
243
457
  ```ts
244
458
  import { OpenFeature } from "@openfeature/web-sdk";
459
+
245
460
  import { openFeatureFlags } from "@stealthscale/sdk-host/openfeature";
246
461
 
247
462
  await OpenFeature.setProviderAndWait("stealth.host", provider);
package/dist/app.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { n as renderProduct, t as ProductRenderOptions } from "./render-DRndY-TD.js";
2
+ import { t as Host } from "./options-DBsArZKT.js";
3
+ import { Product, ResolvedProduct } from "@stealthscale/sdk-core";
4
+ import { HostStores } from "@stealthscale/sdk-plugin";
5
+ import { AnyRoute, AnyRouter, RouterHistory } from "@stealthscale/provider-router";
6
+ import { FunctionComponent, ReactElement, ReactNode } from "react";
7
+ import { FormGlyphs } from "@stealthscale/component-forms/form";
8
+ //#region src/app/frame.d.ts
9
+ /**
10
+ * Renders the shell inside the host's root, around the page the router matched.
11
+ *
12
+ * @remarks
13
+ * The frame reads the resolved frame, the main menu's sections and the filled regions, so it
14
+ * renders a part only while the layout calls for it and a plugin fills it. The skip links come
15
+ * first, then the bar across the top, the body, and the bars at the foot. The `stacked` and
16
+ * `split` layouts list the sections in the bar.
17
+ * @returns The host's root around the shell.
18
+ * @throws {@link Error} Where the product renders its own frame.
19
+ */
20
+ export declare function ProductFrame(): ReactElement;
21
+ //#endregion
22
+ //#region src/app/glyphs.d.ts
23
+ /**
24
+ * The marks of the settings forms: a check in a checked box and beside a picked option, a select's
25
+ * chevron, a number field's steppers, and the mark before an error.
26
+ */
27
+ export declare const GLYPHS: FormGlyphs;
28
+ //#endregion
29
+ //#region src/app/tree.d.ts
30
+ /**
31
+ * Describes the props of the document around the frame.
32
+ */
33
+ interface ProductShellProps {
34
+ /**
35
+ * The frame.
36
+ */
37
+ readonly children: ReactNode;
38
+ }
39
+ /**
40
+ * Renders the document around the frame, for a page a server renders and its hydration.
41
+ */
42
+ type ProductShell = (props: ProductShellProps) => ReactNode;
43
+ //#endregion
44
+ //#region src/app/router.d.ts
45
+ /**
46
+ * Lists what a product's router is created with beside the host and the product.
47
+ */
48
+ export interface ProductRouterOptions {
49
+ /**
50
+ * The base the product is served at, such as `/docs/`. The site's root where left out.
51
+ */
52
+ readonly basepath?: string | undefined;
53
+ /**
54
+ * The component of the root route, for a product that renders its own frame. The host's frame
55
+ * where left out.
56
+ */
57
+ readonly frame?: FunctionComponent | undefined;
58
+ /**
59
+ * The history the router reads its address from, such as a memory history on a server. The
60
+ * browser's where left out.
61
+ */
62
+ readonly history?: RouterHistory | undefined;
63
+ /**
64
+ * The document around the frame, the root route's `shellComponent`, for a page a server renders
65
+ * and its hydration. The frame renders alone where left out.
66
+ */
67
+ readonly shell?: ProductShell | undefined;
68
+ }
69
+ /**
70
+ * Returns a product's router: the frame at the root, the site root, and the plugins' routes, with
71
+ * the host and its data client in the router's context.
72
+ *
73
+ * @param host - The host the router's context contains.
74
+ * @param product - The product, whose routes the host compiles.
75
+ * @param options - The base path, the frame, the history and the document around the frame.
76
+ */
77
+ export declare function createProductRouter(host: Host, product: Product, options?: ProductRouterOptions): AnyRouter;
78
+ //#endregion
79
+ export { ProductRenderOptions, renderProduct };
package/dist/app.js ADDED
@@ -0,0 +1,3 @@
1
+ import { t as ProductFrame } from "./frame-alSrG57-.js";
2
+ import { n as createProductRouter, r as GLYPHS, t as renderProduct } from "./render-CGRJqVed.js";
3
+ export { GLYPHS, ProductFrame, createProductRouter, renderProduct };