@xenosystem/blocks 0.1.1 → 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.
@@ -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,7 +117,68 @@ 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;
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
+ };
120
182
 
121
183
  /**
122
184
  * `mountXenoAuthGate` — the per-product plug, exactly as `XENO AUTH - AUTH GATE DELTA.md` §4 specifies:
@@ -158,4 +220,4 @@ interface MountedXenoAuthGate {
158
220
  }
159
221
  declare function mountXenoAuthGate(root: HTMLElement, options: MountXenoAuthGateOptions): MountedXenoAuthGate;
160
222
 
161
- export { AUTH_GATE_INITIAL, AUTH_GATE_PANEL_ID, AuthGateController, type AuthGateHostBridge, AuthGateView, type AuthGateViewProps, type CreateAuthGatePanelOptions, type MountXenoAuthGateOptions, type MountedXenoAuthGate, PHASE_INTENTS, type XenoAuthGateBridge, type XenoAuthGateIntent, type XenoAuthGateIntentKind, type XenoAuthGatePhase, type XenoAuthGateState, type XenoLicenceState, authGateManifest, authGatePanel, createAuthGatePanel, deriveAuthGatePhase, formatSince, hostContradiction, intentOffered, mountXenoAuthGate };
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 };
@@ -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
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
131
- function AuthGateView({ controller, productName, children, heroSrc, now = () => Date.now() }) {
132
- const [, force] = useState(0);
133
- useEffect(() => controller.subscribe(() => force((n) => n + 1)), [controller]);
134
- const phase = controller.getPhase();
135
- if (phase === "open") return /* @__PURE__ */ jsx(Fragment, { children });
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
- const identity = state.identity && phase !== "sign-in" ? state.identity.email ?? state.identity.name ?? state.identity.sub : void 0;
149
- return /* @__PURE__ */ jsx(
150
- AuthDoor,
151
- {
152
- label: `${productName} sign-in`,
153
- rootAttributes: { "data-xeno-auth-gate": phase },
154
- hero: {
155
- imageSrc: heroSrc,
156
- headline: /* @__PURE__ */ jsxs(Fragment, { children: [
157
- "One account.",
158
- /* @__PURE__ */ jsx("br", {}),
159
- "Every XENO product."
160
- ] }),
161
- lead: `Sign in once and ${productName} \u2014 and every other XENO app \u2014 runs under your plan, your credits and your files.`,
162
- stats: [
163
- { value: "1", label: "Account" },
164
- { value: "Yours", label: "Files stay put" },
165
- { value: "Browser", label: "Sign-in, never in-app" }
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,6 +273,47 @@ 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
+
259
317
  // src/auth/gate/mount.ts
260
318
  import { createElement as createElement2 } from "react";
261
319
  import { createRoot as createRoot2 } from "react-dom/client";
@@ -289,17 +347,21 @@ function Behind({ render }) {
289
347
  // src/auth/index.ts
290
348
  export * from "@xenosystem/panel-account";
291
349
  export {
350
+ AUTH_GATE_DECLARATION,
292
351
  AUTH_GATE_INITIAL,
293
352
  AUTH_GATE_PANEL_ID,
294
353
  AuthGateController,
295
354
  AuthGateView,
355
+ BEHAVIOURS,
296
356
  PHASE_INTENTS,
297
357
  authGateManifest,
298
358
  authGatePanel,
359
+ authGateView,
299
360
  createAuthGatePanel,
300
361
  deriveAuthGatePhase,
301
362
  formatSince,
302
363
  hostContradiction,
303
364
  intentOffered,
304
- mountXenoAuthGate
365
+ mountXenoAuthGate,
366
+ plateCopy
305
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.1.1";
7
+ declare const BLOCKS_VERSION = "0.2.0";
8
8
 
9
9
  export { BLOCKS_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var BLOCKS_VERSION = "0.1.1";
2
+ var BLOCKS_VERSION = "0.2.0";
3
3
  export {
4
4
  BLOCKS_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.1.1",
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,19 +26,20 @@
26
26
  "test": "vitest run"
27
27
  },
28
28
  "peerDependencies": {
29
- "@xenosystem/components": "^0.3.1",
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
33
  "@xenosystem/workbench": "^1.0.0",
34
34
  "react": ">=18.0.0",
35
- "react-dom": ">=18.0.0"
35
+ "react-dom": ">=18.0.0",
36
+ "@xenosystem/block-sdk": "^0.1.0"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/node": "^22.0.0",
39
40
  "@types/react": "^19.0.0",
40
41
  "@types/react-dom": "^19.0.0",
41
- "@xenosystem/components": "^0.3.1",
42
+ "@xenosystem/components": "^0.6.0",
42
43
  "@xenosystem/elements-react": "0.0.1",
43
44
  "@xenosystem/panel-account": "^0.1.0",
44
45
  "@xenosystem/panel-sdk": "^1.1.0",
@@ -48,7 +49,8 @@
48
49
  "react-dom": "^19.0.0",
49
50
  "tsup": "^8.0.0",
50
51
  "typescript": "^5.4.0",
51
- "vitest": "^4.1.0"
52
+ "vitest": "^4.1.0",
53
+ "@xenosystem/block-sdk": "^0.1.0"
52
54
  },
53
55
  "publishConfig": {
54
56
  "access": "public"