@xenosystem/blocks 0.1.0 → 0.2.0
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/dist/auth/index.d.ts +104 -2
- package/dist/auth/index.js +138 -45
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +10 -5
package/dist/auth/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
|
|
|
3
3
|
import { PanelModule, PanelManifest } from '@xenosystem/panel-sdk';
|
|
4
4
|
import { XenoAccountStatus, XenoAccountIdentity, XenoAccountActionKind } from '@xenosystem/panel-account';
|
|
5
5
|
export * from '@xenosystem/panel-account';
|
|
6
|
+
import { BlockDeclaration } from '@xenosystem/block-sdk';
|
|
6
7
|
|
|
7
8
|
interface CreateAuthGatePanelOptions {
|
|
8
9
|
/** What renders behind the gate once it is `open`. A product passes its app; the harness a fixture. */
|
|
@@ -116,6 +117,107 @@ interface AuthGateViewProps {
|
|
|
116
117
|
heroSrc?: string;
|
|
117
118
|
now?: () => number;
|
|
118
119
|
}
|
|
119
|
-
declare function AuthGateView({ controller, productName, children, heroSrc, now }: AuthGateViewProps): react.JSX.Element;
|
|
120
|
+
declare function AuthGateView$1({ controller, productName, children, heroSrc, now }: AuthGateViewProps): react.JSX.Element;
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The auth gate AS A DECLARATION — the first block written the way `xeno-apps/SPEC.md` §8d says every
|
|
124
|
+
* block is: components + bindings + the manifest, with ONE behaviour (`auth/authGateView`) for the only
|
|
125
|
+
* logic it has. The code view (`AuthGateView`) and this declaration render the same door from the same
|
|
126
|
+
* behaviour; `test/auth-gate.declaration.test.tsx` holds them equal in every phase.
|
|
127
|
+
*
|
|
128
|
+
* Inputs: `state` (the host's `XenoAuthGateState` push). Output: `intent` ({ kind }). Config:
|
|
129
|
+
* `productName`. The door opens (renders the app behind it) when `view.open` — a host that mounts the
|
|
130
|
+
* declaration passes its app as the composition's `children` slot.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
declare const AUTH_GATE_DECLARATION: BlockDeclaration;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* `authGateView` — the gate's ONE behaviour: host state + product → the view model the door renders.
|
|
137
|
+
* Pure. Used by the code view (`AuthGateView`) AND by the declaration (`AUTH_GATE_DECLARATION`,
|
|
138
|
+
* `behaviour: 'auth/authGateView'`), so there is exactly one derivation of copy and offered actions.
|
|
139
|
+
* `xeno-apps/SPEC.md` §8d: "code only where a block genuinely needs a behaviour" — this is that code.
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
interface AuthGateAction {
|
|
143
|
+
id: string;
|
|
144
|
+
label: string;
|
|
145
|
+
variant: 'primary' | 'quiet';
|
|
146
|
+
disabled?: boolean;
|
|
147
|
+
}
|
|
148
|
+
interface AuthGateView {
|
|
149
|
+
phase: XenoAuthGatePhase;
|
|
150
|
+
open: boolean;
|
|
151
|
+
closed: boolean;
|
|
152
|
+
productName: string;
|
|
153
|
+
rootAttributes: Record<string, string>;
|
|
154
|
+
label: string;
|
|
155
|
+
hero: {
|
|
156
|
+
headline: string;
|
|
157
|
+
lead: string;
|
|
158
|
+
stats: {
|
|
159
|
+
value: string;
|
|
160
|
+
label: string;
|
|
161
|
+
}[];
|
|
162
|
+
};
|
|
163
|
+
tag?: string;
|
|
164
|
+
title: string;
|
|
165
|
+
body: string;
|
|
166
|
+
note?: string;
|
|
167
|
+
warning?: string;
|
|
168
|
+
identity?: string;
|
|
169
|
+
actions: AuthGateAction[];
|
|
170
|
+
}
|
|
171
|
+
declare function authGateView(state: XenoAuthGateState, productName: string, now?: number): AuthGateView;
|
|
172
|
+
declare function plateCopy(phase: XenoAuthGatePhase, product: string, message: string | undefined, since: string | null): {
|
|
173
|
+
tag?: string;
|
|
174
|
+
title: string;
|
|
175
|
+
body: string;
|
|
176
|
+
note?: string;
|
|
177
|
+
};
|
|
178
|
+
/** The registry entry a host merges in: `behaviour: 'auth/authGateView'` → (inputs, config) → view. */
|
|
179
|
+
declare const BEHAVIOURS: {
|
|
180
|
+
'auth/authGateView': (inputs: Record<string, unknown>, config: Readonly<Record<string, unknown>>) => AuthGateView;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* `mountXenoAuthGate` — the per-product plug, exactly as `XENO AUTH - AUTH GATE DELTA.md` §4 specifies:
|
|
185
|
+
*
|
|
186
|
+
* mountXenoAuthGate(root, {
|
|
187
|
+
* product: { slug: 'canvas', name: 'XENO Canvas' },
|
|
188
|
+
* bridge: window.xenoAuthGate, // preload-exposed: subscribe(state) + send(intent)
|
|
189
|
+
* children: () => renderTheApp(), // called ONLY once phase === 'open'
|
|
190
|
+
* })
|
|
191
|
+
*
|
|
192
|
+
* A product is NOT a workbench host, so it does not go through `createAuthGatePanel` (a PanelModule
|
|
193
|
+
* that needs a PanelHost). This mounts the same controller + view directly over a two-verb bridge:
|
|
194
|
+
* the host pushes state, the gate emits intents, nothing else crosses. The gate holds no token and
|
|
195
|
+
* calls no SDK (LICENCE-ENFORCEMENT §1: main process, never the renderer).
|
|
196
|
+
*
|
|
197
|
+
* Not per product: the design, the copy, the phase derivation. A product that edits those has found a
|
|
198
|
+
* gate defect, fixed once here — MOVE, never port.
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
interface XenoAuthGateBridge {
|
|
202
|
+
/** Receive every state push; returns the unsubscribe. The host pushes the current state on subscribe. */
|
|
203
|
+
subscribe: (listener: (state: XenoAuthGateState) => void) => () => void;
|
|
204
|
+
/** Route an intent to the host (main process). */
|
|
205
|
+
send: (intent: XenoAuthGateIntent) => void;
|
|
206
|
+
}
|
|
207
|
+
interface MountXenoAuthGateOptions {
|
|
208
|
+
product: {
|
|
209
|
+
slug: string;
|
|
210
|
+
name: string;
|
|
211
|
+
};
|
|
212
|
+
bridge: XenoAuthGateBridge;
|
|
213
|
+
/** Rendered ONLY when the phase is `open`. */
|
|
214
|
+
children: () => ReactNode;
|
|
215
|
+
heroSrc?: string;
|
|
216
|
+
}
|
|
217
|
+
interface MountedXenoAuthGate {
|
|
218
|
+
controller: AuthGateController;
|
|
219
|
+
unmount: () => void;
|
|
220
|
+
}
|
|
221
|
+
declare function mountXenoAuthGate(root: HTMLElement, options: MountXenoAuthGateOptions): MountedXenoAuthGate;
|
|
222
|
+
|
|
223
|
+
export { AUTH_GATE_DECLARATION, AUTH_GATE_INITIAL, AUTH_GATE_PANEL_ID, type AuthGateAction, AuthGateController, type AuthGateHostBridge, AuthGateView$1 as AuthGateView, type AuthGateView as AuthGateViewModel, type AuthGateViewProps, BEHAVIOURS, type CreateAuthGatePanelOptions, type MountXenoAuthGateOptions, type MountedXenoAuthGate, PHASE_INTENTS, type XenoAuthGateBridge, type XenoAuthGateIntent, type XenoAuthGateIntentKind, type XenoAuthGatePhase, type XenoAuthGateState, type XenoLicenceState, authGateManifest, authGatePanel, authGateView, createAuthGatePanel, deriveAuthGatePhase, formatSince, hostContradiction, intentOffered, mountXenoAuthGate, plateCopy };
|
package/dist/auth/index.js
CHANGED
|
@@ -127,15 +127,12 @@ var authGateManifest = {
|
|
|
127
127
|
// src/auth/gate/react/AuthGateView.tsx
|
|
128
128
|
import { useEffect, useState } from "react";
|
|
129
129
|
import { AuthDoor, SignInForm } from "@xenosystem/components/auth";
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
const state = controller.getState();
|
|
137
|
-
const copy = plateCopy(phase, productName, state.message, formatSince(state.lastVerifiedAt, now()));
|
|
138
|
-
const action = (id, label, variant = "primary") => controller.offers(id) ? [{ id, label, variant, onClick: () => controller.intend(id) }] : [];
|
|
130
|
+
|
|
131
|
+
// src/auth/gate/behaviour.ts
|
|
132
|
+
function authGateView(state, productName, now = Date.now()) {
|
|
133
|
+
const phase = deriveAuthGatePhase(state);
|
|
134
|
+
const copy = plateCopy(phase, productName, state.message, formatSince(state.lastVerifiedAt, now));
|
|
135
|
+
const action = (id, label, variant = "primary") => intentOffered(state, id) ? [{ id, label, variant }] : [];
|
|
139
136
|
const actions = [
|
|
140
137
|
...phase === "sign-in" && state.pending ? [{ id: "pending", label: "Finish in your browser\u2026", variant: "primary", disabled: true }] : [],
|
|
141
138
|
...action("signIn", "Sign in to XENO"),
|
|
@@ -145,41 +142,26 @@ function AuthGateView({ controller, productName, children, heroSrc, now = () =>
|
|
|
145
142
|
...action("refresh", phase === "unlicensed" || phase === "expired-offline" ? "Check again" : "Try again", phase === "unlicensed" ? "quiet" : "primary"),
|
|
146
143
|
...action("signOut", "Sign out", "quiet")
|
|
147
144
|
];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
children: /* @__PURE__ */ jsx(
|
|
169
|
-
SignInForm,
|
|
170
|
-
{
|
|
171
|
-
phase,
|
|
172
|
-
tag: copy.tag,
|
|
173
|
-
title: copy.title,
|
|
174
|
-
body: copy.body,
|
|
175
|
-
actions,
|
|
176
|
-
note: phase === "sign-in" && !state.pending ? `Your browser opens at xenostudio.ai, where you can continue with Google, GitHub or email. ${productName} never sees your password.` : void 0,
|
|
177
|
-
warning: copy.note,
|
|
178
|
-
identity
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
);
|
|
145
|
+
return {
|
|
146
|
+
phase,
|
|
147
|
+
open: phase === "open",
|
|
148
|
+
closed: phase !== "open",
|
|
149
|
+
productName,
|
|
150
|
+
rootAttributes: { "data-xeno-auth-gate": phase },
|
|
151
|
+
label: `${productName} sign-in`,
|
|
152
|
+
hero: {
|
|
153
|
+
headline: "One account.\nEvery XENO product.",
|
|
154
|
+
lead: `Sign in once and ${productName} \u2014 and every other XENO app \u2014 runs under your plan, your credits and your files.`,
|
|
155
|
+
stats: [{ value: "1", label: "Account" }, { value: "Yours", label: "Files stay put" }, { value: "Browser", label: "Sign-in, never in-app" }]
|
|
156
|
+
},
|
|
157
|
+
tag: copy.tag,
|
|
158
|
+
title: copy.title,
|
|
159
|
+
body: copy.body,
|
|
160
|
+
note: phase === "sign-in" && !state.pending ? `Your browser opens at xenostudio.ai, where you can continue with Google, GitHub or email. ${productName} never sees your password.` : void 0,
|
|
161
|
+
warning: copy.note,
|
|
162
|
+
identity: state.identity && phase !== "sign-in" ? state.identity.email ?? state.identity.name ?? state.identity.sub : void 0,
|
|
163
|
+
actions
|
|
164
|
+
};
|
|
183
165
|
}
|
|
184
166
|
function plateCopy(phase, product, message, since) {
|
|
185
167
|
switch (phase) {
|
|
@@ -199,6 +181,41 @@ function plateCopy(phase, product, message, since) {
|
|
|
199
181
|
return { title: "", body: "" };
|
|
200
182
|
}
|
|
201
183
|
}
|
|
184
|
+
var BEHAVIOURS = {
|
|
185
|
+
"auth/authGateView": (inputs, config) => authGateView(inputs.state ?? { account: "unknown", licence: "checking", sdk: "ready" }, typeof config.productName === "string" && config.productName ? config.productName : "XENO")
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/auth/gate/react/AuthGateView.tsx
|
|
189
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
190
|
+
function AuthGateView({ controller, productName, children, heroSrc, now = () => Date.now() }) {
|
|
191
|
+
const [, force] = useState(0);
|
|
192
|
+
useEffect(() => controller.subscribe(() => force((n) => n + 1)), [controller]);
|
|
193
|
+
const phase = controller.getPhase();
|
|
194
|
+
if (phase === "open") return /* @__PURE__ */ jsx(Fragment, { children });
|
|
195
|
+
const v = authGateView(controller.getState(), productName, now());
|
|
196
|
+
const actions = v.actions.map((a) => ({ ...a, onClick: a.disabled ? void 0 : () => controller.intend(a.id) }));
|
|
197
|
+
return /* @__PURE__ */ jsx(
|
|
198
|
+
AuthDoor,
|
|
199
|
+
{
|
|
200
|
+
label: v.label,
|
|
201
|
+
rootAttributes: { "data-xeno-auth-gate": phase },
|
|
202
|
+
hero: { imageSrc: heroSrc, headline: v.hero.headline, lead: v.hero.lead, stats: v.hero.stats },
|
|
203
|
+
children: /* @__PURE__ */ jsx(
|
|
204
|
+
SignInForm,
|
|
205
|
+
{
|
|
206
|
+
phase,
|
|
207
|
+
tag: v.tag,
|
|
208
|
+
title: v.title,
|
|
209
|
+
body: v.body,
|
|
210
|
+
actions,
|
|
211
|
+
note: v.note,
|
|
212
|
+
warning: v.warning,
|
|
213
|
+
identity: v.identity
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
202
219
|
|
|
203
220
|
// src/auth/gate/panel.ts
|
|
204
221
|
function createAuthGatePanel(options = {}) {
|
|
@@ -256,19 +273,95 @@ function createAuthGatePanel(options = {}) {
|
|
|
256
273
|
}
|
|
257
274
|
var authGatePanel = createAuthGatePanel();
|
|
258
275
|
|
|
276
|
+
// src/auth/gate/declaration.ts
|
|
277
|
+
var AUTH_GATE_DECLARATION = {
|
|
278
|
+
format: "xeno.block@1",
|
|
279
|
+
manifest: authGateManifest,
|
|
280
|
+
behaviour: "auth/authGateView",
|
|
281
|
+
composition: {
|
|
282
|
+
component: "fragment",
|
|
283
|
+
children: [
|
|
284
|
+
{ component: "slot", when: { input: "view.open" } },
|
|
285
|
+
{
|
|
286
|
+
component: "auth/AuthDoor",
|
|
287
|
+
when: { input: "view.closed" },
|
|
288
|
+
props: {
|
|
289
|
+
label: { input: "view.label" },
|
|
290
|
+
rootAttributes: { input: "view.rootAttributes" },
|
|
291
|
+
hero: { input: "view.hero" }
|
|
292
|
+
},
|
|
293
|
+
children: [
|
|
294
|
+
{
|
|
295
|
+
component: "auth/SignInForm",
|
|
296
|
+
props: {
|
|
297
|
+
phase: { input: "view.phase" },
|
|
298
|
+
tag: { input: "view.tag" },
|
|
299
|
+
title: { input: "view.title" },
|
|
300
|
+
body: { input: "view.body" },
|
|
301
|
+
note: { input: "view.note" },
|
|
302
|
+
warning: { input: "view.warning" },
|
|
303
|
+
identity: { input: "view.identity" },
|
|
304
|
+
actions: { input: "view.actions" },
|
|
305
|
+
animate: { value: false }
|
|
306
|
+
},
|
|
307
|
+
on: {
|
|
308
|
+
onAction: { emit: "intent", value: { arg: "" } }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// src/auth/gate/mount.ts
|
|
318
|
+
import { createElement as createElement2 } from "react";
|
|
319
|
+
import { createRoot as createRoot2 } from "react-dom/client";
|
|
320
|
+
function mountXenoAuthGate(root, options) {
|
|
321
|
+
const controller = new AuthGateController({ emit: (_port, value) => options.bridge.send(value) });
|
|
322
|
+
const reactRoot = createRoot2(root);
|
|
323
|
+
const draw = () => reactRoot.render(
|
|
324
|
+
createElement2(AuthGateView, {
|
|
325
|
+
controller,
|
|
326
|
+
productName: options.product.name,
|
|
327
|
+
heroSrc: options.heroSrc,
|
|
328
|
+
children: createElement2(Behind, { render: options.children })
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
const unsubscribe = options.bridge.subscribe((state) => controller.setState(state));
|
|
332
|
+
const stop = controller.subscribe(draw);
|
|
333
|
+
draw();
|
|
334
|
+
return {
|
|
335
|
+
controller,
|
|
336
|
+
unmount: () => {
|
|
337
|
+
stop();
|
|
338
|
+
unsubscribe();
|
|
339
|
+
reactRoot.unmount();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function Behind({ render }) {
|
|
344
|
+
return render();
|
|
345
|
+
}
|
|
346
|
+
|
|
259
347
|
// src/auth/index.ts
|
|
260
348
|
export * from "@xenosystem/panel-account";
|
|
261
349
|
export {
|
|
350
|
+
AUTH_GATE_DECLARATION,
|
|
262
351
|
AUTH_GATE_INITIAL,
|
|
263
352
|
AUTH_GATE_PANEL_ID,
|
|
264
353
|
AuthGateController,
|
|
265
354
|
AuthGateView,
|
|
355
|
+
BEHAVIOURS,
|
|
266
356
|
PHASE_INTENTS,
|
|
267
357
|
authGateManifest,
|
|
268
358
|
authGatePanel,
|
|
359
|
+
authGateView,
|
|
269
360
|
createAuthGatePanel,
|
|
270
361
|
deriveAuthGatePhase,
|
|
271
362
|
formatSince,
|
|
272
363
|
hostContradiction,
|
|
273
|
-
intentOffered
|
|
364
|
+
intentOffered,
|
|
365
|
+
mountXenoAuthGate,
|
|
366
|
+
plateCopy
|
|
274
367
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* `/agent`. This root re-exports nothing on purpose: import the family you mount, so a product
|
|
5
5
|
* carries only it. Naming rule: `XENO PACKAGE NAMING - STANDARD.md`.
|
|
6
6
|
*/
|
|
7
|
-
declare const BLOCKS_VERSION = "0.
|
|
7
|
+
declare const BLOCKS_VERSION = "0.2.0";
|
|
8
8
|
|
|
9
9
|
export { BLOCKS_VERSION };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenosystem/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "XENO Blocks — rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block — XENO PACKAGE NAMING - STANDARD.md.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -26,26 +26,31 @@
|
|
|
26
26
|
"test": "vitest run"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@xenosystem/components": "^0.
|
|
29
|
+
"@xenosystem/components": "^0.6.0",
|
|
30
30
|
"@xenosystem/elements-react": ">=0.0.1",
|
|
31
31
|
"@xenosystem/panel-account": "^0.1.0",
|
|
32
32
|
"@xenosystem/panel-sdk": "^1.1.0",
|
|
33
|
+
"@xenosystem/workbench": "^1.0.0",
|
|
33
34
|
"react": ">=18.0.0",
|
|
34
|
-
"react-dom": ">=18.0.0"
|
|
35
|
+
"react-dom": ">=18.0.0",
|
|
36
|
+
"@xenosystem/block-sdk": "^0.1.0"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
39
|
"@types/node": "^22.0.0",
|
|
38
40
|
"@types/react": "^19.0.0",
|
|
39
41
|
"@types/react-dom": "^19.0.0",
|
|
40
|
-
"@xenosystem/components": "^0.
|
|
42
|
+
"@xenosystem/components": "^0.6.0",
|
|
41
43
|
"@xenosystem/elements-react": "0.0.1",
|
|
42
44
|
"@xenosystem/panel-account": "^0.1.0",
|
|
43
45
|
"@xenosystem/panel-sdk": "^1.1.0",
|
|
46
|
+
"@xenosystem/workbench": "^1.0.0",
|
|
47
|
+
"jsdom": "^26.0.0",
|
|
44
48
|
"react": "^19.0.0",
|
|
45
49
|
"react-dom": "^19.0.0",
|
|
46
50
|
"tsup": "^8.0.0",
|
|
47
51
|
"typescript": "^5.4.0",
|
|
48
|
-
"vitest": "^4.1.0"
|
|
52
|
+
"vitest": "^4.1.0",
|
|
53
|
+
"@xenosystem/block-sdk": "^0.1.0"
|
|
49
54
|
},
|
|
50
55
|
"publishConfig": {
|
|
51
56
|
"access": "public"
|