@sukramj/fabric-ui 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sukramj/fabric-ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The web interface of go-fabric nodes: Svelte 5 pages, UI kit, design tokens with a Home Assistant theme bridge, and the generated API client.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SukramJ",
@@ -78,7 +78,7 @@
78
78
  "@tsconfig/svelte": "5.0.8",
79
79
  "@types/node": "26.6.5",
80
80
  "@types/qrcode": "1.5.6",
81
- "happy-dom": "20.14.6",
81
+ "happy-dom": "21.0.4",
82
82
  "js-yaml": "4.3.2",
83
83
  "openapi-typescript": "7.13.0",
84
84
  "svelte": "5.57.2",
@@ -497,6 +497,16 @@ export interface components {
497
497
  advertising: boolean;
498
498
  commissioning_window_open: boolean;
499
499
  commissioning_window_duration_seconds?: number;
500
+ /**
501
+ * @description Which window is open — the node's own on its configured credentials, a basic one on fresh credentials, an administrator's enhanced one; absent without a node.
502
+ * @enum {string}
503
+ */
504
+ window_kind?: "none" | "own" | "basic" | "enhanced";
505
+ /**
506
+ * Format: date-time
507
+ * @description When the open window's timer closes it
508
+ */
509
+ window_deadline?: string;
500
510
  /** @description Whether the host serves a selection. */
501
511
  exposure: boolean;
502
512
  /** @description The admin token of token mode; absent when the host decides admin rights. */
@@ -533,9 +543,9 @@ export interface components {
533
543
  fabrics: components["schemas"]["Fabric"][];
534
544
  };
535
545
  Pairing: {
536
- /** @description Only for a window this handler opened on fresh credentials; the codes carry it. */
546
+ /** @description The discriminator the codes encode: of the window this handler opened on fresh credentials, else the configured one. Absent from servers before 0.1.2 for the configured source. */
537
547
  discriminator?: number;
538
- /** @description Only for a window this handler opened on fresh credentials. */
548
+ /** @description The passcode the codes encode: of the window this handler opened on fresh credentials, else the configured one. Absent from servers before 0.1.2 for the configured source. */
539
549
  passcode?: number;
540
550
  qr_code: string;
541
551
  manual_code: string;
@@ -549,17 +559,20 @@ export interface components {
549
559
  duration_seconds?: number;
550
560
  };
551
561
  Window: {
552
- /** @description Absent for a window on the configured credentials. */
562
+ /** @description The window's discriminator; absent from servers before 0.1.2 for a window on the configured credentials. */
553
563
  discriminator?: number;
554
- /** @description Absent for a window on the configured credentials. */
564
+ /** @description The window's passcode; absent from servers before 0.1.2 for a window on the configured credentials. */
555
565
  passcode?: number;
556
566
  duration_seconds: number;
557
567
  qr_code: string;
558
568
  manual_code: string;
559
569
  };
570
+ /** @description One cluster server mounted on the endpoint, in its Descriptor's ServerList order. */
560
571
  Cluster: {
561
572
  id: number;
573
+ /** @description The schema's cluster name; absent for a cluster the schema does not know. */
562
574
  name?: string;
575
+ /** @description The schema's ClusterRevision; absent for a cluster the schema does not know. */
563
576
  revision?: number;
564
577
  };
565
578
  Endpoint: {
@@ -567,6 +580,8 @@ export interface components {
567
580
  parent_endpoint_id: number;
568
581
  device_type: number;
569
582
  device_type_name: string;
583
+ /** @description The revision the endpoint's Descriptor serves for its device type; absent for a type the schema does not know. */
584
+ device_type_revision?: number;
570
585
  reachable: boolean;
571
586
  name: string;
572
587
  /** @description Opaque identity of the host's source; `key` is the endpoint's stable key. */
@@ -0,0 +1,14 @@
1
+ // The commissioning window's countdown from the deadline the status
2
+ // reports (window_deadline, on the node's clock), so a reload or a page
3
+ // opened late counts down what is left instead of the full duration.
4
+
5
+ /**
6
+ * Whole seconds left until deadline at now (milliseconds since the epoch),
7
+ * never negative; null without a deadline or for one that does not parse.
8
+ */
9
+ export function secondsUntil(deadline: string | undefined, now: number = Date.now()): number | null {
10
+ if (!deadline) return null;
11
+ const t = Date.parse(deadline);
12
+ if (Number.isNaN(t)) return null;
13
+ return Math.max(0, Math.ceil((t - now) / 1000));
14
+ }
@@ -2,10 +2,12 @@
2
2
  advertised, its counts and the commissioning window. Ported from the
3
3
  status card of openccu-loom assets/ui/src/routes/matter/Matter.svelte:47-100
4
4
  at b40b289a, without the tabs (the shell or the host navigates). The
5
- host's configuration warnings come first, in their own card. -->
5
+ host's configuration warnings come first, in their own card. An open
6
+ window counts down from the deadline the status reports. -->
6
7
  <script lang="ts">
7
8
  import { onMount } from "svelte";
8
9
  import { useFabricUI } from "../../context";
10
+ import { secondsUntil } from "../../countdown";
9
11
  import Badge from "../../kit/Badge.svelte";
10
12
  import Card from "../../kit/Card.svelte";
11
13
  import ErrorState from "../../kit/ErrorState.svelte";
@@ -14,11 +16,22 @@
14
16
 
15
17
  const { t, node } = useFabricUI();
16
18
 
19
+ let now = $state(Date.now());
20
+
17
21
  onMount(() => {
18
22
  void node.loadStatus();
23
+ const tick = setInterval(() => (now = Date.now()), 1000);
24
+ return () => clearInterval(tick);
19
25
  });
20
26
 
21
27
  const s = $derived(node.status);
28
+ // Seconds left in the open window, from its deadline; the duration it
29
+ // was opened for when the server reports no deadline.
30
+ const windowLeft = $derived(
31
+ s?.commissioning_window_open
32
+ ? (secondsUntil(s.window_deadline, now) ?? ((s.commissioning_window_duration_seconds ?? 0) > 0 ? s.commissioning_window_duration_seconds! : null))
33
+ : null,
34
+ );
22
35
  </script>
23
36
 
24
37
  {#if node.statusLoading && !s}
@@ -54,8 +67,8 @@
54
67
  {#if s.commissioning_window_open}
55
68
  <Badge variant="warning">
56
69
  {t("node.pair.window_open")}
57
- {#if (s.commissioning_window_duration_seconds ?? 0) > 0}
58
- ({s.commissioning_window_duration_seconds}s)
70
+ {#if windowLeft !== null}
71
+ <span data-testid="window-left">({windowLeft}s)</span>
59
72
  {/if}
60
73
  </Badge>
61
74
  {/if}
@@ -7,6 +7,7 @@
7
7
  // host-readiness gate dropped.
8
8
  import { ApiError, type FabricClient, type FabricList, type ExposureSchema, type Item, type Status, type Update, type Window } from "./api/client";
9
9
  import { subscribeStream, type StreamEvent } from "./api/stream";
10
+ import { secondsUntil } from "./countdown";
10
11
  import type { Translate } from "./i18n";
11
12
 
12
13
  export interface Toast {
@@ -172,6 +173,9 @@ export function createNodeStore(client: FabricClient, deps: NodeStoreDeps) {
172
173
  if (!st.enabled || !st.commissioning_window_open) return;
173
174
  const p = await client.pairing();
174
175
  const duration = p.duration_seconds ?? st.commissioning_window_duration_seconds ?? 0;
176
+ // The deadline says what is left; without one (a server before 0.1.2)
177
+ // the full duration is the upper bound.
178
+ const left = secondsUntil(st.window_deadline) ?? (duration > 0 ? duration : null);
175
179
  commissioning = {
176
180
  phase: "open",
177
181
  window: {
@@ -181,11 +185,10 @@ export function createNodeStore(client: FabricClient, deps: NodeStoreDeps) {
181
185
  qr_code: p.qr_code,
182
186
  manual_code: p.manual_code,
183
187
  },
184
- // Without a server-side remaining time the full duration is the upper bound.
185
- remaining: duration > 0 ? duration : null,
188
+ remaining: left,
186
189
  addedFabricLabel: null,
187
190
  };
188
- if (duration > 0) startCountdown(duration);
191
+ if (left !== null && left > 0) startCountdown(left);
189
192
  }
190
193
 
191
194
  async function closeWindow() {