@xenosystem/blocks 0.6.0 → 0.8.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,7 +3,7 @@ import "../chunk-2KG3PWR4.js";
3
3
  // src/auth/gate/panel.ts
4
4
  import { createElement } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
- import { bindConfig } from "@xenosystem/panel-sdk";
6
+ import { bindConfig } from "@xenosystem/block-sdk";
7
7
 
8
8
  // src/auth/gate/types.ts
9
9
  function deriveAuthGatePhase(state) {
@@ -348,24 +348,641 @@ function Behind({ render }) {
348
348
  return render();
349
349
  }
350
350
 
351
- // src/auth/index.ts
352
- export * from "@xenosystem/panel-account";
351
+ // src/auth/account/panel.ts
352
+ import { createElement as createElement3 } from "react";
353
+ import { createRoot as createRoot3 } from "react-dom/client";
354
+ import {
355
+ bindConfig as bindConfig2
356
+ } from "@xenosystem/block-sdk";
357
+
358
+ // src/auth/account/types.ts
359
+ var STEP_UP_ACTIONS = /* @__PURE__ */ new Set([
360
+ "signOut",
361
+ "manageBilling",
362
+ "purchase"
363
+ ]);
364
+ function isSignedIn(status) {
365
+ return status === "signedIn" || status === "refreshing" || status === "offline";
366
+ }
367
+ function requiresStepUp(kind) {
368
+ return STEP_UP_ACTIONS.has(kind);
369
+ }
370
+ function creditsAge(credits, now) {
371
+ if (!credits) return null;
372
+ return Math.max(0, now - credits.asOf);
373
+ }
374
+ function formatAmount(amount, unit = "") {
375
+ const sign = amount > 0 ? "+" : "";
376
+ const suffix = unit ? ` ${unit}` : "";
377
+ return `${sign}${amount.toLocaleString()}${suffix}`;
378
+ }
379
+ function formatAge(ms) {
380
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s ago`;
381
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m ago`;
382
+ if (ms < 864e5) return `${Math.round(ms / 36e5)}h ago`;
383
+ return `${Math.round(ms / 864e5)}d ago`;
384
+ }
385
+
386
+ // src/auth/account/controller.ts
387
+ var EMPTY_STATE = { status: "unknown" };
388
+ function ageBucket(ms) {
389
+ if (ms < 6e4) return `s${Math.round(ms / 1e3)}`;
390
+ if (ms < 36e5) return `m${Math.round(ms / 6e4)}`;
391
+ if (ms < 864e5) return `h${Math.round(ms / 36e5)}`;
392
+ return `d${Math.round(ms / 864e5)}`;
393
+ }
394
+ var AccountController = class {
395
+ host;
396
+ now;
397
+ staleAfterMs;
398
+ maxLedger;
399
+ account = EMPTY_STATE;
400
+ ledger = [];
401
+ ledgerHasMore = false;
402
+ ledgerRev = -1;
403
+ ledgerExpanded = false;
404
+ listeners = /* @__PURE__ */ new Set();
405
+ snapshot = null;
406
+ constructor(options) {
407
+ this.host = options.host;
408
+ this.now = options.host.now ?? (() => Date.now());
409
+ this.staleAfterMs = options.staleAfterMs ?? 6e4;
410
+ this.maxLedger = Math.max(1, options.maxLedger ?? 500);
411
+ }
412
+ /* ── Subscription ──────────────────────────────────────────────────────── */
413
+ subscribe = (listener) => {
414
+ this.listeners.add(listener);
415
+ return () => this.listeners.delete(listener);
416
+ };
417
+ getState = () => {
418
+ if (!this.snapshot) {
419
+ const ageMs = creditsAge(this.account.credits, this.now());
420
+ this.snapshot = {
421
+ status: this.account.status,
422
+ identity: this.account.identity ?? null,
423
+ plan: this.account.plan ?? null,
424
+ credits: this.account.credits ?? null,
425
+ entitlements: this.account.entitlements ?? [],
426
+ seats: this.account.seats ?? [],
427
+ message: this.account.message ?? null,
428
+ ledger: this.ledger,
429
+ ledgerHasMore: this.ledgerHasMore,
430
+ ledgerExpanded: this.ledgerExpanded,
431
+ creditsAgeMs: ageMs,
432
+ // Stale is a fact about the DATA, not about the session. A signed-in user with a
433
+ // ten-minute-old balance is looking at a stale number and must be told so.
434
+ creditsStale: ageMs !== null && ageMs > this.staleAfterMs
435
+ };
436
+ }
437
+ return this.snapshot;
438
+ };
439
+ notify() {
440
+ this.snapshot = null;
441
+ for (const listener of this.listeners) listener();
442
+ }
443
+ /* ── Host-pushed state ─────────────────────────────────────────────────── */
444
+ /**
445
+ * Replace the account state.
446
+ *
447
+ * The host is authoritative. The panel does not merge cleverly, does not keep a "better" older
448
+ * value, and does not infer a status the host did not send — a panel that second-guesses its host
449
+ * is a second source of truth about who is signed in.
450
+ *
451
+ * @param next - The new state.
452
+ */
453
+ setAccount(next) {
454
+ this.account = next;
455
+ this.notify();
456
+ }
457
+ /**
458
+ * A partial update, for hosts that push only what changed.
459
+ *
460
+ * `credits` is replaced wholesale rather than field-merged: a balance and its `asOf` are one fact,
461
+ * and merging a new number onto an old timestamp manufactures a lie.
462
+ *
463
+ * @param patch - Fields to overwrite.
464
+ */
465
+ patchAccount(patch) {
466
+ this.account = { ...this.account, ...patch };
467
+ this.notify();
468
+ }
469
+ /** Set the session status alone. */
470
+ setStatus(status, message) {
471
+ if (status === "signedOut") {
472
+ this.account = { status, message };
473
+ this.ledger = [];
474
+ this.ledgerHasMore = false;
475
+ this.ledgerRev = -1;
476
+ this.notify();
477
+ return;
478
+ }
479
+ this.account = { ...this.account, status, message };
480
+ this.notify();
481
+ }
482
+ /* ── Ledger ────────────────────────────────────────────────────────────── */
483
+ /**
484
+ * Apply a ledger delta.
485
+ *
486
+ * Append-oriented, with `rev` gating out-of-order deliveries — the same discipline as runs and
487
+ * console.
488
+ *
489
+ * @param delta - The update.
490
+ * @returns Whether it was applied.
491
+ */
492
+ applyLedger(delta) {
493
+ if (delta.rev !== void 0 && delta.rev <= this.ledgerRev) return false;
494
+ if (delta.rev !== void 0) this.ledgerRev = delta.rev;
495
+ if (delta.clear) {
496
+ this.ledger = [];
497
+ this.ledgerHasMore = false;
498
+ }
499
+ if (delta.replace) {
500
+ this.ledger = delta.replace.slice(-this.maxLedger);
501
+ }
502
+ if (delta.append?.length) {
503
+ const byId = new Map(this.ledger.map((e) => [e.id, e]));
504
+ for (const entry of delta.append) byId.set(entry.id, entry);
505
+ this.ledger = [...byId.values()].sort((a, b) => b.ts - a.ts).slice(0, this.maxLedger);
506
+ }
507
+ if (delta.hasMore !== void 0) this.ledgerHasMore = delta.hasMore;
508
+ this.notify();
509
+ return true;
510
+ }
511
+ /* ── The passage of time ───────────────────────────────────────────────── */
512
+ /**
513
+ * Re-evaluate the balance's age.
514
+ *
515
+ * `getState` memoizes — it must, or `useSyncExternalStore` re-renders forever — which means the
516
+ * age would otherwise freeze at the moment of the last push, and a balance would sit on screen
517
+ * reading "as of 2s ago" for an hour. That is precisely the stale-as-fresh failure `asOf` exists
518
+ * to prevent, reintroduced by a caching bug.
519
+ *
520
+ * So time is an INPUT here, and the panel drives this on an interval. Notifying only when the
521
+ * rendered age actually changes keeps it off the re-render hot path: a one-second tick that
522
+ * re-renders once per second is honest; one that re-renders sixty times is waste.
523
+ *
524
+ * @returns Whether anything the view shows changed.
525
+ */
526
+ tick() {
527
+ if (!this.account.credits) return false;
528
+ const previous = this.snapshot;
529
+ if (!previous) return false;
530
+ const ageMs = creditsAge(this.account.credits, this.now()) ?? 0;
531
+ const stale = ageMs > this.staleAfterMs;
532
+ if (stale === previous.creditsStale && ageBucket(ageMs) === ageBucket(previous.creditsAgeMs ?? 0)) {
533
+ return false;
534
+ }
535
+ this.notify();
536
+ return true;
537
+ }
538
+ /** Expand or collapse the ledger. A view preference. */
539
+ setLedgerExpanded(expanded) {
540
+ this.ledgerExpanded = expanded;
541
+ this.notify();
542
+ }
543
+ /* ── Intents ───────────────────────────────────────────────────────────── */
544
+ /**
545
+ * Ask the host to do something.
546
+ *
547
+ * **The panel never renders the result.** It does not flip to `signedOut` because the user
548
+ * clicked Sign out, and it does not add credits because the user clicked Purchase. It emits, and
549
+ * waits to be told — the same intents-only doctrine the rest of the family follows, and the only
550
+ * shape compatible with `XENO AUTH - SPEC.md` L12, where a sensitive action may be REFUSED at the
551
+ * step-up prompt after the click.
552
+ *
553
+ * @param kind - What to ask for.
554
+ * @param extra - `seatId` or `productId`.
555
+ * @returns The emitted action.
556
+ */
557
+ request(kind, extra = {}) {
558
+ const action = { kind, ...extra };
559
+ if (requiresStepUp(kind)) action.stepUp = true;
560
+ this.host.emit("action", action);
561
+ return action;
562
+ }
563
+ /** Ask the host to re-read the balance. Cheap, idempotent, no step-up. */
564
+ refresh() {
565
+ return this.request("refresh");
566
+ }
567
+ /* ── Lifecycle ─────────────────────────────────────────────────────────── */
568
+ /**
569
+ * Serialize.
570
+ *
571
+ * **View preferences only.** No identity, no plan, no balance, no token.
572
+ *
573
+ * `XENO AUTH - SPEC.md` L9 forbids a refresh token in `localStorage` or `~/.xeno/*`, and this
574
+ * panel's one capability is `storage.local` — the very store L9 names. Persisting a balance would
575
+ * also reintroduce the stale-as-fresh problem `asOf` exists to solve: a number restored from disk
576
+ * has no honest timestamp.
577
+ */
578
+ serialize() {
579
+ return { ledgerExpanded: this.ledgerExpanded };
580
+ }
581
+ /** Restore view preferences. */
582
+ deserialize(state) {
583
+ if (!state || typeof state !== "object") return;
584
+ const s = state;
585
+ if (typeof s.ledgerExpanded === "boolean") this.ledgerExpanded = s.ledgerExpanded;
586
+ this.notify();
587
+ }
588
+ /** Tear down. */
589
+ dispose() {
590
+ this.listeners.clear();
591
+ this.account = EMPTY_STATE;
592
+ this.ledger = [];
593
+ }
594
+ };
595
+
596
+ // src/auth/account/manifest.ts
597
+ import { WELL_KNOWN_PORT_SCHEMAS } from "@xenosystem/block-sdk";
598
+ var ACCOUNT_PANEL_ID = "xeno.core.account";
599
+ var accountManifest = {
600
+ id: ACCOUNT_PANEL_ID,
601
+ version: "0.1.0",
602
+ title: "Account",
603
+ icon: "user-round",
604
+ description: "Identity, plan, credits, entitlements and ledger \u2014 all host-pushed, never fetched. Balances carry the moment they were true. Sensitive actions are emitted as step-up intents, never performed.",
605
+ defaultSlot: "inspector",
606
+ inputs: [
607
+ {
608
+ id: "account",
609
+ name: "Account",
610
+ type: "object",
611
+ schema: WELL_KNOWN_PORT_SCHEMAS.ACCOUNT,
612
+ description: "{status, identity?, plan?, credits?, entitlements?, seats?, message?}. The host is authoritative. Contains NO token \u2014 see AUTH L9.",
613
+ multiple: false
614
+ },
615
+ {
616
+ id: "ledger",
617
+ name: "Ledger",
618
+ type: "object",
619
+ schema: WELL_KNOWN_PORT_SCHEMAS.LEDGER,
620
+ description: "{rev?, append?, replace?, clear?, hasMore?}. Append-oriented; `rev` gates out-of-order deliveries.",
621
+ multiple: true
622
+ }
623
+ ],
624
+ outputs: [
625
+ {
626
+ id: "action",
627
+ name: "Action",
628
+ type: "object",
629
+ schema: WELL_KNOWN_PORT_SCHEMAS.ACCOUNT_ACTION,
630
+ description: "{kind, seatId?, productId?, stepUp?}. An INTENT. `stepUp: true` marks an action AUTH L12 requires a fresh interactive re-auth for; the host must not satisfy it silently from the broker."
631
+ }
632
+ ],
633
+ commands: [
634
+ {
635
+ id: "refresh",
636
+ title: "Refresh",
637
+ description: "Ask the host to re-read the balance. Idempotent; no step-up.",
638
+ parameters: {}
639
+ },
640
+ {
641
+ id: "get_account",
642
+ title: "Get Account",
643
+ description: "Return the rendered account state \u2014 status, plan, balance and its asOf. Never a token.",
644
+ parameters: {}
645
+ },
646
+ {
647
+ id: "get_ledger",
648
+ title: "Get Ledger",
649
+ description: "Return the held ledger entries, newest first.",
650
+ parameters: {}
651
+ }
652
+ // NOTE: `signOut`, `purchase` and `manageBilling` are deliberately NOT commands. They require a
653
+ // fresh interactive re-auth (AUTH L12), and an agent-invocable command is by definition not
654
+ // interactive. Exposing them would make this panel the bypass for the step-up requirement.
655
+ ],
656
+ config: [
657
+ {
658
+ key: "showLedger",
659
+ label: "Show ledger",
660
+ type: "boolean",
661
+ defaultValue: true,
662
+ description: "Show recent credit activity."
663
+ },
664
+ {
665
+ key: "showEntitlements",
666
+ label: "Show entitlements",
667
+ type: "boolean",
668
+ defaultValue: true,
669
+ description: "List what the plan includes. Display only \u2014 entitlement is enforced server-side (AUTH L10)."
670
+ },
671
+ {
672
+ key: "staleAfterMs",
673
+ label: "Stale after (ms)",
674
+ type: "number",
675
+ defaultValue: 6e4,
676
+ description: "How old a balance may be before it is labelled stale."
677
+ },
678
+ {
679
+ key: "maxLedger",
680
+ label: "Max ledger entries",
681
+ type: "number",
682
+ defaultValue: 500,
683
+ description: "Entries retained in memory."
684
+ },
685
+ {
686
+ key: "emptyHint",
687
+ label: "Empty hint",
688
+ type: "text",
689
+ description: "Shown when no account state has arrived.",
690
+ placeholder: "Waiting for account state\u2026"
691
+ }
692
+ ],
693
+ capabilities: ["storage.local"],
694
+ sdk: "^1.1.0"
695
+ };
696
+
697
+ // src/auth/account/react/AccountPanelView.tsx
698
+ import { useSyncExternalStore } from "react";
699
+ import {
700
+ Badge,
701
+ EmptyState,
702
+ Row,
703
+ RowList,
704
+ ScrollArea,
705
+ Section,
706
+ StatTile,
707
+ StatusBar,
708
+ TextButton,
709
+ Toolbar,
710
+ ToolbarGroup
711
+ } from "@xenosystem/workbench/primitives/react";
712
+ import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
713
+ var STATUS_LABEL = {
714
+ unknown: "Loading",
715
+ signedOut: "Signed out",
716
+ signedIn: "Signed in",
717
+ refreshing: "Refreshing",
718
+ expired: "Session expired",
719
+ offline: "Offline"
720
+ };
721
+ var STATUS_TONE = {
722
+ unknown: "neutral",
723
+ signedOut: "neutral",
724
+ signedIn: "success",
725
+ refreshing: "neutral",
726
+ expired: "warning",
727
+ offline: "warning"
728
+ };
729
+ function AccountPanelView({
730
+ controller,
731
+ showLedger = true,
732
+ showEntitlements = true,
733
+ emptyHint
734
+ }) {
735
+ const state = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
736
+ if (state.status === "unknown") {
737
+ return /* @__PURE__ */ jsx2(EmptyState, { title: "Account", hint: emptyHint ?? "Waiting for account state\u2026" });
738
+ }
739
+ if (!isSignedIn(state.status)) {
740
+ return /* @__PURE__ */ jsx2(
741
+ EmptyState,
742
+ {
743
+ title: STATUS_LABEL[state.status],
744
+ hint: state.message ?? "Sign in to see your plan and credits.",
745
+ action: /* @__PURE__ */ jsx2(TextButton, { strong: true, onClick: () => controller.request("signIn"), children: "Sign in" })
746
+ }
747
+ );
748
+ }
749
+ const { identity, plan, credits } = state;
750
+ const unit = credits?.unit ?? "credits";
751
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
752
+ /* @__PURE__ */ jsx2(
753
+ Toolbar,
754
+ {
755
+ left: /* @__PURE__ */ jsxs(Fragment2, { children: [
756
+ /* @__PURE__ */ jsx2(Badge, { tone: STATUS_TONE[state.status], children: STATUS_LABEL[state.status] }),
757
+ plan ? /* @__PURE__ */ jsx2(Badge, { title: `Plan: ${plan.label}`, children: plan.label }) : null
758
+ ] }),
759
+ right: /* @__PURE__ */ jsx2(ToolbarGroup, { end: true, children: /* @__PURE__ */ jsx2(TextButton, { onClick: () => controller.refresh(), children: "Refresh" }) })
760
+ }
761
+ ),
762
+ /* @__PURE__ */ jsxs(ScrollArea, { children: [
763
+ /* @__PURE__ */ jsxs("div", { style: { padding: 8, display: "flex", flexDirection: "column", gap: 2 }, children: [
764
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, fontWeight: 600 }, children: identity?.name ?? identity?.sub }),
765
+ identity?.email ? /* @__PURE__ */ jsx2("div", { style: { fontSize: 10, opacity: 0.65 }, children: identity.email }) : null,
766
+ state.message ? /* @__PURE__ */ jsx2("div", { style: { fontSize: 10, opacity: 0.75, marginTop: 4 }, children: state.message }) : null
767
+ ] }),
768
+ credits ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, padding: "0 8px 8px" }, children: [
769
+ /* @__PURE__ */ jsx2(
770
+ StatTile,
771
+ {
772
+ label: unit,
773
+ value: credits.balance.toLocaleString(),
774
+ sub: state.creditsAgeMs === null ? void 0 : state.creditsStale ? `as of ${formatAge(state.creditsAgeMs)} \u2014 may be out of date` : `as of ${formatAge(state.creditsAgeMs)}`,
775
+ icon: state.creditsStale ? /* @__PURE__ */ jsx2(Badge, { tone: "warning", children: "stale" }) : void 0,
776
+ title: "Pushed by the host, not fetched by this panel."
777
+ }
778
+ ),
779
+ credits.included !== void 0 ? /* @__PURE__ */ jsx2(
780
+ StatTile,
781
+ {
782
+ label: "included",
783
+ value: credits.included.toLocaleString(),
784
+ sub: credits.used === void 0 ? void 0 : `${credits.used.toLocaleString()} used`
785
+ }
786
+ ) : null
787
+ ] }) : null,
788
+ state.seats.length > 1 ? /* @__PURE__ */ jsx2(Section, { title: "Seats", flush: true, children: /* @__PURE__ */ jsx2(RowList, { children: state.seats.map((seat) => /* @__PURE__ */ jsx2(
789
+ Row,
790
+ {
791
+ noIcon: true,
792
+ label: seat.label,
793
+ trailing: seat.active ? /* @__PURE__ */ jsx2(Badge, { children: "active" }) : /* @__PURE__ */ jsx2(
794
+ TextButton,
795
+ {
796
+ onClick: () => controller.request("switchSeat", { seatId: seat.id }),
797
+ children: "Switch"
798
+ }
799
+ )
800
+ },
801
+ seat.id
802
+ )) }) }) : null,
803
+ showEntitlements && state.entitlements.length > 0 ? /* @__PURE__ */ jsx2(Section, { title: "Included", flush: true, children: /* @__PURE__ */ jsx2(RowList, { children: state.entitlements.map((ent) => /* @__PURE__ */ jsx2(
804
+ Row,
805
+ {
806
+ noIcon: true,
807
+ label: ent.label,
808
+ meta: ent.detail,
809
+ trailing: /* @__PURE__ */ jsx2(Badge, { tone: ent.included ? "success" : "neutral", children: ent.included ? "yes" : "no" })
810
+ },
811
+ ent.id
812
+ )) }) }) : null,
813
+ showLedger ? /* @__PURE__ */ jsxs(
814
+ Section,
815
+ {
816
+ title: "Recent activity",
817
+ flush: true,
818
+ collapsed: !state.ledgerExpanded,
819
+ onCollapsedChange: (collapsed) => controller.setLedgerExpanded(!collapsed),
820
+ badge: state.ledger.length > 0 ? /* @__PURE__ */ jsx2(Badge, { children: state.ledger.length }) : void 0,
821
+ children: [
822
+ state.ledger.length === 0 ? /* @__PURE__ */ jsx2(EmptyState, { title: "No activity", hint: "Credit spends and grants appear here." }) : /* @__PURE__ */ jsx2(RowList, { children: state.ledger.map((entry) => /* @__PURE__ */ jsx2(
823
+ Row,
824
+ {
825
+ noIcon: true,
826
+ label: entry.description,
827
+ meta: entry.source,
828
+ trailing: /* @__PURE__ */ jsx2("span", { style: { fontVariantNumeric: "tabular-nums", fontSize: 11 }, children: formatAmount(entry.amount) })
829
+ },
830
+ entry.id
831
+ )) }),
832
+ state.ledgerHasMore ? /* @__PURE__ */ jsx2("div", { style: { padding: "2px 8px", fontSize: 9, opacity: 0.6 }, children: "Older entries are not held by this panel." }) : null
833
+ ]
834
+ }
835
+ ) : null
836
+ ] }),
837
+ /* @__PURE__ */ jsx2("div", { style: { padding: "0 8px", fontSize: 9, opacity: 0.55 }, children: "Billing and sign-out ask you to confirm who you are." }),
838
+ /* @__PURE__ */ jsx2(
839
+ Toolbar,
840
+ {
841
+ divided: false,
842
+ right: /* @__PURE__ */ jsxs(ToolbarGroup, { end: true, children: [
843
+ /* @__PURE__ */ jsx2(TextButton, { onClick: () => controller.request("manageBilling"), children: "Billing\u2026" }),
844
+ /* @__PURE__ */ jsx2(TextButton, { onClick: () => controller.request("signOut"), children: "Sign out\u2026" })
845
+ ] })
846
+ }
847
+ ),
848
+ /* @__PURE__ */ jsx2(
849
+ StatusBar,
850
+ {
851
+ left: plan?.renewsAt ? `renews ${new Date(plan.renewsAt).toLocaleDateString()}` : void 0,
852
+ right: state.status === "offline" ? "offline \u2014 showing last known" : void 0
853
+ }
854
+ )
855
+ ] });
856
+ }
857
+
858
+ // src/auth/account/panel.ts
859
+ function createAccountPanel(options = {}) {
860
+ return {
861
+ manifest: accountManifest,
862
+ activate(host) {
863
+ const config = host.config ?? {};
864
+ const controller = new AccountController({
865
+ host: { emit: (portId, value) => host.emit(portId, value) },
866
+ staleAfterMs: typeof config.staleAfterMs === "number" ? config.staleAfterMs : void 0,
867
+ maxLedger: typeof config.maxLedger === "number" ? config.maxLedger : void 0
868
+ });
869
+ const resolve = (config2) => ({
870
+ showLedger: config2.showLedger !== false,
871
+ showEntitlements: config2.showEntitlements !== false,
872
+ emptyHint: typeof config2.emptyHint === "string" ? config2.emptyHint : void 0
873
+ });
874
+ let renderConfig = resolve(host.config ?? {});
875
+ let unrender = null;
876
+ let root = null;
877
+ let ticker = null;
878
+ let currentEl = null;
879
+ const draw = (el) => {
880
+ unrender?.();
881
+ if (ticker === null) ticker = setInterval(() => controller.tick(), 1e3);
882
+ if (options.render) {
883
+ unrender = options.render(el, { controller, config: renderConfig });
884
+ return;
885
+ }
886
+ root = createRoot3(el);
887
+ root.render(createElement3(AccountPanelView, { controller, ...renderConfig }));
888
+ unrender = () => {
889
+ root?.unmount();
890
+ root = null;
891
+ };
892
+ };
893
+ const unbindConfig = bindConfig2(host, (config2) => {
894
+ renderConfig = resolve(config2);
895
+ if (currentEl) draw(currentEl);
896
+ });
897
+ return {
898
+ render(el) {
899
+ currentEl = el;
900
+ draw(el);
901
+ },
902
+ onInput(portId, value) {
903
+ if (portId === "account") {
904
+ if (value && typeof value === "object" && "status" in value) {
905
+ controller.setAccount(value);
906
+ }
907
+ } else if (portId === "ledger") {
908
+ if (Array.isArray(value)) {
909
+ controller.applyLedger({ append: value });
910
+ } else if (value && typeof value === "object") {
911
+ controller.applyLedger(value);
912
+ }
913
+ }
914
+ },
915
+ async onCommand(commandId) {
916
+ switch (commandId) {
917
+ case "refresh":
918
+ return controller.refresh();
919
+ case "get_account": {
920
+ const s = controller.getState();
921
+ return {
922
+ status: s.status,
923
+ sub: s.identity?.sub,
924
+ plan: s.plan?.id,
925
+ balance: s.credits?.balance,
926
+ asOf: s.credits?.asOf,
927
+ stale: s.creditsStale
928
+ };
929
+ }
930
+ case "get_ledger":
931
+ return controller.getState().ledger;
932
+ default:
933
+ return;
934
+ }
935
+ },
936
+ serialize() {
937
+ return controller.serialize();
938
+ },
939
+ deserialize(state) {
940
+ controller.deserialize(state);
941
+ },
942
+ dispose() {
943
+ unbindConfig();
944
+ currentEl = null;
945
+ if (ticker !== null) {
946
+ clearInterval(ticker);
947
+ ticker = null;
948
+ }
949
+ unrender?.();
950
+ unrender = null;
951
+ controller.dispose();
952
+ }
953
+ };
954
+ }
955
+ };
956
+ }
957
+ var accountPanel = createAccountPanel();
353
958
  export {
959
+ ACCOUNT_PANEL_ID,
354
960
  AUTH_GATE_DECLARATION,
355
961
  AUTH_GATE_INITIAL,
356
962
  AUTH_GATE_PANEL_ID,
963
+ AccountController,
964
+ AccountPanelView,
357
965
  AuthGateController,
358
966
  AuthGateView,
359
967
  BEHAVIOURS,
360
968
  PHASE_INTENTS,
969
+ STEP_UP_ACTIONS,
970
+ accountManifest,
971
+ accountPanel,
361
972
  authGateManifest,
362
973
  authGatePanel,
363
974
  authGateView,
975
+ createAccountPanel,
364
976
  createAuthGatePanel,
977
+ creditsAge,
365
978
  deriveAuthGatePhase,
979
+ formatAge,
980
+ formatAmount,
366
981
  formatSince,
367
982
  hostContradiction,
368
983
  intentOffered,
984
+ isSignedIn,
369
985
  mountXenoAuthGate,
370
- plateCopy
986
+ plateCopy,
987
+ requiresStepUp
371
988
  };
@@ -1,4 +1,4 @@
1
- import { PanelModule, PanelManifest, LogSink } from '@xenosystem/panel-sdk';
1
+ import { PanelModule, PanelManifest, LogSink } from '@xenosystem/block-sdk';
2
2
  import * as _xenosystem_data_core from '@xenosystem/data-core';
3
3
  import { XenoColumn, XenoQuery, XenoResultSet, XenoValue, XenoRow, XenoAggFn, XenoValueFormat, XenoFilterGroup, XenoColumnRole, XenoSchemaCatalog, XenoFilter, XenoSourceSchema } from '@xenosystem/data-core';
4
4
  import { ReactNode } from 'react';