@camstack/ui-library 1.2.7 → 1.2.9
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/composites/format-state-value.d.ts +62 -0
- package/dist/index.cjs +168 -35
- package/dist/index.js +168 -35
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf formatting for the device State panel — in particular, turning epoch
|
|
3
|
+
* timestamps into readable local dates.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this needs a rule rather than a `typeof`
|
|
6
|
+
*
|
|
7
|
+
* `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
|
|
8
|
+
* required to carry one. But "large number" is not "timestamp": `bitrate`,
|
|
9
|
+
* `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
|
|
10
|
+
* bitrate as a date in 1970 is worse than leaving it raw.
|
|
11
|
+
*
|
|
12
|
+
* So a value is treated as a time point only when BOTH hold:
|
|
13
|
+
*
|
|
14
|
+
* 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
|
|
15
|
+
* `lastSeen`, `expires…`; and
|
|
16
|
+
* 2. the NUMBER falls in a plausible epoch range.
|
|
17
|
+
*
|
|
18
|
+
* Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
|
|
19
|
+
* "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
|
|
20
|
+
* Durations are deliberately NOT converted — this formats instants, not spans.
|
|
21
|
+
*
|
|
22
|
+
* ## The raw value is never lost
|
|
23
|
+
*
|
|
24
|
+
* This is a debugging panel, so the exact number still matters. The formatted text
|
|
25
|
+
* is what is shown; `raw` is returned alongside for a `title` tooltip, and search
|
|
26
|
+
* matches against both. Replacing the value outright would trade one kind of
|
|
27
|
+
* unreadability for another.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isTimeKey(key: string): boolean;
|
|
30
|
+
/** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
|
|
31
|
+
export declare function epochMsOf(value: number): number | null;
|
|
32
|
+
/**
|
|
33
|
+
* Locale-formatted instant. `undefined` locale means the viewer's own locale,
|
|
34
|
+
* which is the point — an operator in Rome should not read UTC.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatLocalDate(epochMs: number): string;
|
|
37
|
+
export interface FormattedLeaf {
|
|
38
|
+
/** What is displayed. */
|
|
39
|
+
readonly text: string;
|
|
40
|
+
/**
|
|
41
|
+
* The untouched original, present ONLY when `text` differs from it — so a
|
|
42
|
+
* caller can show a tooltip without one on every unremarkable value.
|
|
43
|
+
*/
|
|
44
|
+
readonly raw: string | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Format one leaf of the state tree, given the key it sits under.
|
|
48
|
+
*
|
|
49
|
+
* Handles two timestamp shapes:
|
|
50
|
+
* - a NUMBER under a time-shaped key → local date, raw kept
|
|
51
|
+
* - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
|
|
52
|
+
* string is unambiguous, and a Moleculer round-trip turns every `Date` into
|
|
53
|
+
* one, so these appear whether or not the key looks temporal.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatLeafValue(key: string, value: unknown): FormattedLeaf;
|
|
56
|
+
/**
|
|
57
|
+
* Strict ISO-8601 with a time part. Requires the `T` and at least
|
|
58
|
+
* `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
|
|
59
|
+
* label than a date, and `Date.parse` would silently read it as UTC midnight and
|
|
60
|
+
* shift it a day in a negative-offset locale.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isIsoDateString(value: string): boolean;
|
package/dist/index.cjs
CHANGED
|
@@ -44922,6 +44922,137 @@ function StatusBadge$1({ status }) {
|
|
|
44922
44922
|
}
|
|
44923
44923
|
}
|
|
44924
44924
|
//#endregion
|
|
44925
|
+
//#region src/composites/format-state-value.ts
|
|
44926
|
+
/**
|
|
44927
|
+
* Leaf formatting for the device State panel — in particular, turning epoch
|
|
44928
|
+
* timestamps into readable local dates.
|
|
44929
|
+
*
|
|
44930
|
+
* ## Why this needs a rule rather than a `typeof`
|
|
44931
|
+
*
|
|
44932
|
+
* `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
|
|
44933
|
+
* required to carry one. But "large number" is not "timestamp": `bitrate`,
|
|
44934
|
+
* `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
|
|
44935
|
+
* bitrate as a date in 1970 is worse than leaving it raw.
|
|
44936
|
+
*
|
|
44937
|
+
* So a value is treated as a time point only when BOTH hold:
|
|
44938
|
+
*
|
|
44939
|
+
* 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
|
|
44940
|
+
* `lastSeen`, `expires…`; and
|
|
44941
|
+
* 2. the NUMBER falls in a plausible epoch range.
|
|
44942
|
+
*
|
|
44943
|
+
* Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
|
|
44944
|
+
* "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
|
|
44945
|
+
* Durations are deliberately NOT converted — this formats instants, not spans.
|
|
44946
|
+
*
|
|
44947
|
+
* ## The raw value is never lost
|
|
44948
|
+
*
|
|
44949
|
+
* This is a debugging panel, so the exact number still matters. The formatted text
|
|
44950
|
+
* is what is shown; `raw` is returned alongside for a `title` tooltip, and search
|
|
44951
|
+
* matches against both. Replacing the value outright would trade one kind of
|
|
44952
|
+
* unreadability for another.
|
|
44953
|
+
*/
|
|
44954
|
+
/**
|
|
44955
|
+
* Keys that name an instant. Anchored to the END of the key so `updatedAt` and
|
|
44956
|
+
* `lastPushAtMs` match while `timeoutMs`, `batchIntervalMs`, `retentionDays` and
|
|
44957
|
+
* `uptimeSeconds` do not.
|
|
44958
|
+
*/
|
|
44959
|
+
var TIME_KEY_PATTERNS = [
|
|
44960
|
+
/at$/i,
|
|
44961
|
+
/atms$/i,
|
|
44962
|
+
/^timestamp$/i,
|
|
44963
|
+
/timestamp$/i,
|
|
44964
|
+
/since$/i,
|
|
44965
|
+
/^lastseen$/i,
|
|
44966
|
+
/lastseen$/i,
|
|
44967
|
+
/^expires?$/i,
|
|
44968
|
+
/expiry$/i
|
|
44969
|
+
];
|
|
44970
|
+
/**
|
|
44971
|
+
* Plausible epoch windows, roughly 2001 → 2096. Deliberately narrow: it is what
|
|
44972
|
+
* separates a timestamp from a byte count that happens to be large, and from `0`
|
|
44973
|
+
* (which in this codebase means "never", and must stay `0` rather than becoming
|
|
44974
|
+
* 1 January 1970).
|
|
44975
|
+
*/
|
|
44976
|
+
var MS_EPOCH_MIN = 0xe8d4a51000;
|
|
44977
|
+
var MS_EPOCH_MAX = 4e12;
|
|
44978
|
+
var SEC_EPOCH_MIN = 1e9;
|
|
44979
|
+
var SEC_EPOCH_MAX = 4e9;
|
|
44980
|
+
function isTimeKey(key) {
|
|
44981
|
+
return TIME_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
44982
|
+
}
|
|
44983
|
+
/** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
|
|
44984
|
+
function epochMsOf(value) {
|
|
44985
|
+
if (!Number.isFinite(value)) return null;
|
|
44986
|
+
if (value >= MS_EPOCH_MIN && value <= MS_EPOCH_MAX) return value;
|
|
44987
|
+
if (value >= SEC_EPOCH_MIN && value <= SEC_EPOCH_MAX) return value * 1e3;
|
|
44988
|
+
return null;
|
|
44989
|
+
}
|
|
44990
|
+
/**
|
|
44991
|
+
* Locale-formatted instant. `undefined` locale means the viewer's own locale,
|
|
44992
|
+
* which is the point — an operator in Rome should not read UTC.
|
|
44993
|
+
*/
|
|
44994
|
+
function formatLocalDate(epochMs) {
|
|
44995
|
+
const date = new Date(epochMs);
|
|
44996
|
+
if (Number.isNaN(date.getTime())) return String(epochMs);
|
|
44997
|
+
return date.toLocaleString(void 0, {
|
|
44998
|
+
year: "numeric",
|
|
44999
|
+
month: "2-digit",
|
|
45000
|
+
day: "2-digit",
|
|
45001
|
+
hour: "2-digit",
|
|
45002
|
+
minute: "2-digit",
|
|
45003
|
+
second: "2-digit"
|
|
45004
|
+
});
|
|
45005
|
+
}
|
|
45006
|
+
function plain(v) {
|
|
45007
|
+
if (v === null) return "null";
|
|
45008
|
+
if (v === void 0) return "undefined";
|
|
45009
|
+
if (typeof v === "string") return v;
|
|
45010
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
45011
|
+
try {
|
|
45012
|
+
return JSON.stringify(v);
|
|
45013
|
+
} catch {
|
|
45014
|
+
return String(v);
|
|
45015
|
+
}
|
|
45016
|
+
}
|
|
45017
|
+
/**
|
|
45018
|
+
* Format one leaf of the state tree, given the key it sits under.
|
|
45019
|
+
*
|
|
45020
|
+
* Handles two timestamp shapes:
|
|
45021
|
+
* - a NUMBER under a time-shaped key → local date, raw kept
|
|
45022
|
+
* - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
|
|
45023
|
+
* string is unambiguous, and a Moleculer round-trip turns every `Date` into
|
|
45024
|
+
* one, so these appear whether or not the key looks temporal.
|
|
45025
|
+
*/
|
|
45026
|
+
function formatLeafValue(key, value) {
|
|
45027
|
+
if (typeof value === "number" && isTimeKey(key)) {
|
|
45028
|
+
const epochMs = epochMsOf(value);
|
|
45029
|
+
if (epochMs !== null) return {
|
|
45030
|
+
text: formatLocalDate(epochMs),
|
|
45031
|
+
raw: String(value)
|
|
45032
|
+
};
|
|
45033
|
+
}
|
|
45034
|
+
if (typeof value === "string" && isIsoDateString(value)) {
|
|
45035
|
+
const parsed = Date.parse(value);
|
|
45036
|
+
if (!Number.isNaN(parsed)) return {
|
|
45037
|
+
text: formatLocalDate(parsed),
|
|
45038
|
+
raw: value
|
|
45039
|
+
};
|
|
45040
|
+
}
|
|
45041
|
+
return {
|
|
45042
|
+
text: plain(value),
|
|
45043
|
+
raw: null
|
|
45044
|
+
};
|
|
45045
|
+
}
|
|
45046
|
+
/**
|
|
45047
|
+
* Strict ISO-8601 with a time part. Requires the `T` and at least
|
|
45048
|
+
* `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
|
|
45049
|
+
* label than a date, and `Date.parse` would silently read it as UTC midnight and
|
|
45050
|
+
* shift it a day in a negative-offset locale.
|
|
45051
|
+
*/
|
|
45052
|
+
function isIsoDateString(value) {
|
|
45053
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?$/.test(value);
|
|
45054
|
+
}
|
|
45055
|
+
//#endregion
|
|
44925
45056
|
//#region src/composites/state-values-stream.tsx
|
|
44926
45057
|
/**
|
|
44927
45058
|
* StateValuesStream — live current-state tree for ui-library.
|
|
@@ -45443,24 +45574,28 @@ function CapNode({ capName, slice, updatedAt, searchNeedle, isPathOpen, onToggle
|
|
|
45443
45574
|
function TreeNode({ path, nodeKey, value, depth, searchNeedle, isPathOpen, onToggle }) {
|
|
45444
45575
|
const branch = asBranch(value);
|
|
45445
45576
|
const indentStyle = { paddingLeft: `${12 + depth * 14}px` };
|
|
45446
|
-
if (!branch)
|
|
45447
|
-
|
|
45448
|
-
|
|
45449
|
-
|
|
45450
|
-
|
|
45451
|
-
|
|
45452
|
-
|
|
45453
|
-
|
|
45454
|
-
|
|
45455
|
-
|
|
45456
|
-
|
|
45457
|
-
|
|
45458
|
-
|
|
45459
|
-
|
|
45460
|
-
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
|
|
45577
|
+
if (!branch) {
|
|
45578
|
+
const leaf = formatLeafValue(nodeKey, value);
|
|
45579
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
45580
|
+
className: "flex items-start gap-1 px-3 py-0.5",
|
|
45581
|
+
style: indentStyle,
|
|
45582
|
+
children: [
|
|
45583
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
45584
|
+
className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
|
|
45585
|
+
children: nodeKey
|
|
45586
|
+
}),
|
|
45587
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
45588
|
+
className: "text-foreground-subtle",
|
|
45589
|
+
children: ":"
|
|
45590
|
+
}),
|
|
45591
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
45592
|
+
className: cn("break-all", highlightCls(leaf.text, searchNeedle, valueCls(value))),
|
|
45593
|
+
...leaf.raw === null ? {} : { title: leaf.raw },
|
|
45594
|
+
children: leaf.text
|
|
45595
|
+
})
|
|
45596
|
+
]
|
|
45597
|
+
});
|
|
45598
|
+
}
|
|
45464
45599
|
const open = isPathOpen(path, depth);
|
|
45465
45600
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
45466
45601
|
className: "flex items-center gap-1 px-3 py-0.5 hover:bg-surface-hover/30 cursor-pointer select-none",
|
|
@@ -45525,17 +45660,6 @@ function asBranch(value) {
|
|
|
45525
45660
|
}
|
|
45526
45661
|
return null;
|
|
45527
45662
|
}
|
|
45528
|
-
function formatValue(v) {
|
|
45529
|
-
if (v === null) return "null";
|
|
45530
|
-
if (v === void 0) return "undefined";
|
|
45531
|
-
if (typeof v === "string") return v;
|
|
45532
|
-
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
45533
|
-
try {
|
|
45534
|
-
return JSON.stringify(v);
|
|
45535
|
-
} catch {
|
|
45536
|
-
return String(v);
|
|
45537
|
-
}
|
|
45538
|
-
}
|
|
45539
45663
|
/** Tailwind colour for a leaf value by JS type. */
|
|
45540
45664
|
function valueCls(v) {
|
|
45541
45665
|
if (v === null || v === void 0) return "text-foreground-subtle/60";
|
|
@@ -45549,18 +45673,27 @@ function highlightCls(text, needle, base) {
|
|
|
45549
45673
|
if (needle && text.toLowerCase().includes(needle)) return cn(base, "bg-violet-500/20 rounded px-0.5");
|
|
45550
45674
|
return base;
|
|
45551
45675
|
}
|
|
45552
|
-
/**
|
|
45553
|
-
*
|
|
45554
|
-
|
|
45555
|
-
|
|
45676
|
+
/**
|
|
45677
|
+
* Deep search a slice — true when `needle` appears in any key or any leaf value
|
|
45678
|
+
* anywhere in the tree.
|
|
45679
|
+
*
|
|
45680
|
+
* `key` is threaded down so a leaf is matched the way it is DISPLAYED: a timestamp
|
|
45681
|
+
* shown as a local date has to be findable by typing part of that date. Both forms
|
|
45682
|
+
* match, so the raw epoch still works too — the operator should not have to know
|
|
45683
|
+
* which one the panel chose to render.
|
|
45684
|
+
*/
|
|
45685
|
+
function sliceMatchesSearch(value, needle, key = "") {
|
|
45686
|
+
if (Array.isArray(value)) return value.some((v, i) => sliceMatchesSearch(v, needle, String(i)));
|
|
45556
45687
|
if (value && typeof value === "object") {
|
|
45557
45688
|
for (const [k, v] of Object.entries(value)) {
|
|
45558
45689
|
if (k.toLowerCase().includes(needle)) return true;
|
|
45559
|
-
if (sliceMatchesSearch(v, needle)) return true;
|
|
45690
|
+
if (sliceMatchesSearch(v, needle, k)) return true;
|
|
45560
45691
|
}
|
|
45561
45692
|
return false;
|
|
45562
45693
|
}
|
|
45563
|
-
|
|
45694
|
+
const leaf = formatLeafValue(key, value);
|
|
45695
|
+
if (leaf.text.toLowerCase().includes(needle)) return true;
|
|
45696
|
+
return leaf.raw !== null && leaf.raw.toLowerCase().includes(needle);
|
|
45564
45697
|
}
|
|
45565
45698
|
/** Human "Ns ago" / "Nm ago" age label for the per-cap freshness
|
|
45566
45699
|
* indicator. */
|
package/dist/index.js
CHANGED
|
@@ -44898,6 +44898,137 @@ function StatusBadge$1({ status }) {
|
|
|
44898
44898
|
}
|
|
44899
44899
|
}
|
|
44900
44900
|
//#endregion
|
|
44901
|
+
//#region src/composites/format-state-value.ts
|
|
44902
|
+
/**
|
|
44903
|
+
* Leaf formatting for the device State panel — in particular, turning epoch
|
|
44904
|
+
* timestamps into readable local dates.
|
|
44905
|
+
*
|
|
44906
|
+
* ## Why this needs a rule rather than a `typeof`
|
|
44907
|
+
*
|
|
44908
|
+
* `lastFetchedAt: 1785060000000` is unreadable, and every `runtimeState` slice is
|
|
44909
|
+
* required to carry one. But "large number" is not "timestamp": `bitrate`,
|
|
44910
|
+
* `memoryRss`, `diskFree` and `retentionDays` are all numbers, and rendering a
|
|
44911
|
+
* bitrate as a date in 1970 is worse than leaving it raw.
|
|
44912
|
+
*
|
|
44913
|
+
* So a value is treated as a time point only when BOTH hold:
|
|
44914
|
+
*
|
|
44915
|
+
* 1. the KEY names a time point — `…At`, `…AtMs`, `timestamp`, `…Since`,
|
|
44916
|
+
* `lastSeen`, `expires…`; and
|
|
44917
|
+
* 2. the NUMBER falls in a plausible epoch range.
|
|
44918
|
+
*
|
|
44919
|
+
* Requiring both is what keeps `uptimeSeconds` (a duration whose name contains
|
|
44920
|
+
* "time"), `timeoutMs` and `batchIntervalMs` (durations ending in "Ms") out of it.
|
|
44921
|
+
* Durations are deliberately NOT converted — this formats instants, not spans.
|
|
44922
|
+
*
|
|
44923
|
+
* ## The raw value is never lost
|
|
44924
|
+
*
|
|
44925
|
+
* This is a debugging panel, so the exact number still matters. The formatted text
|
|
44926
|
+
* is what is shown; `raw` is returned alongside for a `title` tooltip, and search
|
|
44927
|
+
* matches against both. Replacing the value outright would trade one kind of
|
|
44928
|
+
* unreadability for another.
|
|
44929
|
+
*/
|
|
44930
|
+
/**
|
|
44931
|
+
* Keys that name an instant. Anchored to the END of the key so `updatedAt` and
|
|
44932
|
+
* `lastPushAtMs` match while `timeoutMs`, `batchIntervalMs`, `retentionDays` and
|
|
44933
|
+
* `uptimeSeconds` do not.
|
|
44934
|
+
*/
|
|
44935
|
+
var TIME_KEY_PATTERNS = [
|
|
44936
|
+
/at$/i,
|
|
44937
|
+
/atms$/i,
|
|
44938
|
+
/^timestamp$/i,
|
|
44939
|
+
/timestamp$/i,
|
|
44940
|
+
/since$/i,
|
|
44941
|
+
/^lastseen$/i,
|
|
44942
|
+
/lastseen$/i,
|
|
44943
|
+
/^expires?$/i,
|
|
44944
|
+
/expiry$/i
|
|
44945
|
+
];
|
|
44946
|
+
/**
|
|
44947
|
+
* Plausible epoch windows, roughly 2001 → 2096. Deliberately narrow: it is what
|
|
44948
|
+
* separates a timestamp from a byte count that happens to be large, and from `0`
|
|
44949
|
+
* (which in this codebase means "never", and must stay `0` rather than becoming
|
|
44950
|
+
* 1 January 1970).
|
|
44951
|
+
*/
|
|
44952
|
+
var MS_EPOCH_MIN = 0xe8d4a51000;
|
|
44953
|
+
var MS_EPOCH_MAX = 4e12;
|
|
44954
|
+
var SEC_EPOCH_MIN = 1e9;
|
|
44955
|
+
var SEC_EPOCH_MAX = 4e9;
|
|
44956
|
+
function isTimeKey(key) {
|
|
44957
|
+
return TIME_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
44958
|
+
}
|
|
44959
|
+
/** Epoch milliseconds for a numeric value under a time-shaped key, else null. */
|
|
44960
|
+
function epochMsOf(value) {
|
|
44961
|
+
if (!Number.isFinite(value)) return null;
|
|
44962
|
+
if (value >= MS_EPOCH_MIN && value <= MS_EPOCH_MAX) return value;
|
|
44963
|
+
if (value >= SEC_EPOCH_MIN && value <= SEC_EPOCH_MAX) return value * 1e3;
|
|
44964
|
+
return null;
|
|
44965
|
+
}
|
|
44966
|
+
/**
|
|
44967
|
+
* Locale-formatted instant. `undefined` locale means the viewer's own locale,
|
|
44968
|
+
* which is the point — an operator in Rome should not read UTC.
|
|
44969
|
+
*/
|
|
44970
|
+
function formatLocalDate(epochMs) {
|
|
44971
|
+
const date = new Date(epochMs);
|
|
44972
|
+
if (Number.isNaN(date.getTime())) return String(epochMs);
|
|
44973
|
+
return date.toLocaleString(void 0, {
|
|
44974
|
+
year: "numeric",
|
|
44975
|
+
month: "2-digit",
|
|
44976
|
+
day: "2-digit",
|
|
44977
|
+
hour: "2-digit",
|
|
44978
|
+
minute: "2-digit",
|
|
44979
|
+
second: "2-digit"
|
|
44980
|
+
});
|
|
44981
|
+
}
|
|
44982
|
+
function plain(v) {
|
|
44983
|
+
if (v === null) return "null";
|
|
44984
|
+
if (v === void 0) return "undefined";
|
|
44985
|
+
if (typeof v === "string") return v;
|
|
44986
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
44987
|
+
try {
|
|
44988
|
+
return JSON.stringify(v);
|
|
44989
|
+
} catch {
|
|
44990
|
+
return String(v);
|
|
44991
|
+
}
|
|
44992
|
+
}
|
|
44993
|
+
/**
|
|
44994
|
+
* Format one leaf of the state tree, given the key it sits under.
|
|
44995
|
+
*
|
|
44996
|
+
* Handles two timestamp shapes:
|
|
44997
|
+
* - a NUMBER under a time-shaped key → local date, raw kept
|
|
44998
|
+
* - an ISO-8601 STRING → local date, raw kept. No key check needed: an ISO
|
|
44999
|
+
* string is unambiguous, and a Moleculer round-trip turns every `Date` into
|
|
45000
|
+
* one, so these appear whether or not the key looks temporal.
|
|
45001
|
+
*/
|
|
45002
|
+
function formatLeafValue(key, value) {
|
|
45003
|
+
if (typeof value === "number" && isTimeKey(key)) {
|
|
45004
|
+
const epochMs = epochMsOf(value);
|
|
45005
|
+
if (epochMs !== null) return {
|
|
45006
|
+
text: formatLocalDate(epochMs),
|
|
45007
|
+
raw: String(value)
|
|
45008
|
+
};
|
|
45009
|
+
}
|
|
45010
|
+
if (typeof value === "string" && isIsoDateString(value)) {
|
|
45011
|
+
const parsed = Date.parse(value);
|
|
45012
|
+
if (!Number.isNaN(parsed)) return {
|
|
45013
|
+
text: formatLocalDate(parsed),
|
|
45014
|
+
raw: value
|
|
45015
|
+
};
|
|
45016
|
+
}
|
|
45017
|
+
return {
|
|
45018
|
+
text: plain(value),
|
|
45019
|
+
raw: null
|
|
45020
|
+
};
|
|
45021
|
+
}
|
|
45022
|
+
/**
|
|
45023
|
+
* Strict ISO-8601 with a time part. Requires the `T` and at least
|
|
45024
|
+
* `hh:mm` — a bare `2026-07-26` is far more often a version, an id fragment or a
|
|
45025
|
+
* label than a date, and `Date.parse` would silently read it as UTC midnight and
|
|
45026
|
+
* shift it a day in a negative-offset locale.
|
|
45027
|
+
*/
|
|
45028
|
+
function isIsoDateString(value) {
|
|
45029
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?$/.test(value);
|
|
45030
|
+
}
|
|
45031
|
+
//#endregion
|
|
44901
45032
|
//#region src/composites/state-values-stream.tsx
|
|
44902
45033
|
/**
|
|
44903
45034
|
* StateValuesStream — live current-state tree for ui-library.
|
|
@@ -45419,24 +45550,28 @@ function CapNode({ capName, slice, updatedAt, searchNeedle, isPathOpen, onToggle
|
|
|
45419
45550
|
function TreeNode({ path, nodeKey, value, depth, searchNeedle, isPathOpen, onToggle }) {
|
|
45420
45551
|
const branch = asBranch(value);
|
|
45421
45552
|
const indentStyle = { paddingLeft: `${12 + depth * 14}px` };
|
|
45422
|
-
if (!branch)
|
|
45423
|
-
|
|
45424
|
-
|
|
45425
|
-
|
|
45426
|
-
|
|
45427
|
-
|
|
45428
|
-
|
|
45429
|
-
|
|
45430
|
-
|
|
45431
|
-
|
|
45432
|
-
|
|
45433
|
-
|
|
45434
|
-
|
|
45435
|
-
|
|
45436
|
-
|
|
45437
|
-
|
|
45438
|
-
|
|
45439
|
-
|
|
45553
|
+
if (!branch) {
|
|
45554
|
+
const leaf = formatLeafValue(nodeKey, value);
|
|
45555
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
45556
|
+
className: "flex items-start gap-1 px-3 py-0.5",
|
|
45557
|
+
style: indentStyle,
|
|
45558
|
+
children: [
|
|
45559
|
+
/* @__PURE__ */ jsx("span", {
|
|
45560
|
+
className: cn("shrink-0", highlightCls(nodeKey, searchNeedle, "text-primary/70")),
|
|
45561
|
+
children: nodeKey
|
|
45562
|
+
}),
|
|
45563
|
+
/* @__PURE__ */ jsx("span", {
|
|
45564
|
+
className: "text-foreground-subtle",
|
|
45565
|
+
children: ":"
|
|
45566
|
+
}),
|
|
45567
|
+
/* @__PURE__ */ jsx("span", {
|
|
45568
|
+
className: cn("break-all", highlightCls(leaf.text, searchNeedle, valueCls(value))),
|
|
45569
|
+
...leaf.raw === null ? {} : { title: leaf.raw },
|
|
45570
|
+
children: leaf.text
|
|
45571
|
+
})
|
|
45572
|
+
]
|
|
45573
|
+
});
|
|
45574
|
+
}
|
|
45440
45575
|
const open = isPathOpen(path, depth);
|
|
45441
45576
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
45442
45577
|
className: "flex items-center gap-1 px-3 py-0.5 hover:bg-surface-hover/30 cursor-pointer select-none",
|
|
@@ -45501,17 +45636,6 @@ function asBranch(value) {
|
|
|
45501
45636
|
}
|
|
45502
45637
|
return null;
|
|
45503
45638
|
}
|
|
45504
|
-
function formatValue(v) {
|
|
45505
|
-
if (v === null) return "null";
|
|
45506
|
-
if (v === void 0) return "undefined";
|
|
45507
|
-
if (typeof v === "string") return v;
|
|
45508
|
-
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
45509
|
-
try {
|
|
45510
|
-
return JSON.stringify(v);
|
|
45511
|
-
} catch {
|
|
45512
|
-
return String(v);
|
|
45513
|
-
}
|
|
45514
|
-
}
|
|
45515
45639
|
/** Tailwind colour for a leaf value by JS type. */
|
|
45516
45640
|
function valueCls(v) {
|
|
45517
45641
|
if (v === null || v === void 0) return "text-foreground-subtle/60";
|
|
@@ -45525,18 +45649,27 @@ function highlightCls(text, needle, base) {
|
|
|
45525
45649
|
if (needle && text.toLowerCase().includes(needle)) return cn(base, "bg-violet-500/20 rounded px-0.5");
|
|
45526
45650
|
return base;
|
|
45527
45651
|
}
|
|
45528
|
-
/**
|
|
45529
|
-
*
|
|
45530
|
-
|
|
45531
|
-
|
|
45652
|
+
/**
|
|
45653
|
+
* Deep search a slice — true when `needle` appears in any key or any leaf value
|
|
45654
|
+
* anywhere in the tree.
|
|
45655
|
+
*
|
|
45656
|
+
* `key` is threaded down so a leaf is matched the way it is DISPLAYED: a timestamp
|
|
45657
|
+
* shown as a local date has to be findable by typing part of that date. Both forms
|
|
45658
|
+
* match, so the raw epoch still works too — the operator should not have to know
|
|
45659
|
+
* which one the panel chose to render.
|
|
45660
|
+
*/
|
|
45661
|
+
function sliceMatchesSearch(value, needle, key = "") {
|
|
45662
|
+
if (Array.isArray(value)) return value.some((v, i) => sliceMatchesSearch(v, needle, String(i)));
|
|
45532
45663
|
if (value && typeof value === "object") {
|
|
45533
45664
|
for (const [k, v] of Object.entries(value)) {
|
|
45534
45665
|
if (k.toLowerCase().includes(needle)) return true;
|
|
45535
|
-
if (sliceMatchesSearch(v, needle)) return true;
|
|
45666
|
+
if (sliceMatchesSearch(v, needle, k)) return true;
|
|
45536
45667
|
}
|
|
45537
45668
|
return false;
|
|
45538
45669
|
}
|
|
45539
|
-
|
|
45670
|
+
const leaf = formatLeafValue(key, value);
|
|
45671
|
+
if (leaf.text.toLowerCase().includes(needle)) return true;
|
|
45672
|
+
return leaf.raw !== null && leaf.raw.toLowerCase().includes(needle);
|
|
45540
45673
|
}
|
|
45541
45674
|
/** Human "Ns ago" / "Nm ago" age label for the per-cap freshness
|
|
45542
45675
|
* indicator. */
|