@nbt-dev/components 0.0.5

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.
Files changed (66) hide show
  1. package/LICENSE +177 -0
  2. package/README.md +10 -0
  3. package/TRADEMARKS.md +49 -0
  4. package/dist/chunk-3ZM6YOA4.js +704 -0
  5. package/dist/chunk-3ZM6YOA4.js.map +7 -0
  6. package/dist/chunk-7B2T5ZNG.js +467 -0
  7. package/dist/chunk-7B2T5ZNG.js.map +7 -0
  8. package/dist/chunk-S7VBQE6Y.js +636 -0
  9. package/dist/chunk-S7VBQE6Y.js.map +7 -0
  10. package/dist/chunk-UPEOXMLZ.js +625 -0
  11. package/dist/chunk-UPEOXMLZ.js.map +7 -0
  12. package/dist/core/auth.d.ts +13 -0
  13. package/dist/core/bulk-decoder.d.ts +13 -0
  14. package/dist/core/config.d.ts +10 -0
  15. package/dist/core/data-store.d.ts +20 -0
  16. package/dist/core/index.d.ts +9 -0
  17. package/dist/core/use-bulk-stream.d.ts +24 -0
  18. package/dist/core/use-cartridge-info.d.ts +14 -0
  19. package/dist/core/utils.d.ts +2 -0
  20. package/dist/editor/index.d.ts +7 -0
  21. package/dist/editor/index.js +16 -0
  22. package/dist/editor/index.js.map +7 -0
  23. package/dist/editor/lsp-client.d.ts +57 -0
  24. package/dist/editor/lsp-extensions.d.ts +4 -0
  25. package/dist/editor/nbt-editor.d.ts +13 -0
  26. package/dist/editor/nbt-language.d.ts +7 -0
  27. package/dist/generated/bulk-protocol.d.ts +36 -0
  28. package/dist/graph/diagram.d.ts +5 -0
  29. package/dist/graph/entity-graph-utils.d.ts +92 -0
  30. package/dist/graph/entity-node.d.ts +9 -0
  31. package/dist/graph/index.d.ts +5 -0
  32. package/dist/graph/index.js +19 -0
  33. package/dist/graph/index.js.map +7 -0
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.js +134 -0
  36. package/dist/index.js.map +7 -0
  37. package/dist/styles.css +2 -0
  38. package/dist/table/data-table.d.ts +9 -0
  39. package/dist/table/index.d.ts +3 -0
  40. package/dist/table/index.js +11 -0
  41. package/dist/table/index.js.map +7 -0
  42. package/dist/table/value-popover.d.ts +18 -0
  43. package/package.json +77 -0
  44. package/src/core/auth.ts +100 -0
  45. package/src/core/bulk-decoder.ts +178 -0
  46. package/src/core/config.tsx +39 -0
  47. package/src/core/data-store.ts +113 -0
  48. package/src/core/index.ts +34 -0
  49. package/src/core/use-bulk-stream.ts +412 -0
  50. package/src/core/use-cartridge-info.ts +100 -0
  51. package/src/core/utils.ts +6 -0
  52. package/src/editor/index.ts +13 -0
  53. package/src/editor/lsp-client.ts +227 -0
  54. package/src/editor/lsp-extensions.ts +191 -0
  55. package/src/editor/nbt-editor.tsx +142 -0
  56. package/src/editor/nbt-language.ts +151 -0
  57. package/src/generated/bulk-protocol.ts +63 -0
  58. package/src/graph/diagram.tsx +296 -0
  59. package/src/graph/entity-graph-utils.ts +423 -0
  60. package/src/graph/entity-node.tsx +122 -0
  61. package/src/graph/index.ts +19 -0
  62. package/src/index.ts +7 -0
  63. package/src/styles.css +94 -0
  64. package/src/table/data-table.tsx +274 -0
  65. package/src/table/index.ts +5 -0
  66. package/src/table/value-popover.tsx +230 -0
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ "use client";
2
+ import {
3
+ NbtEditor,
4
+ NbtLspClient,
5
+ lspExtensions,
6
+ nbtLanguage,
7
+ nbtLanguageSupport
8
+ } from "./chunk-S7VBQE6Y.js";
9
+ import {
10
+ DiagramView,
11
+ EntityNode,
12
+ buildEntityGraphModel,
13
+ cartsFromContracts,
14
+ entityGraphId,
15
+ filterEntityGraphModel
16
+ } from "./chunk-UPEOXMLZ.js";
17
+ import {
18
+ cn,
19
+ data_table_default,
20
+ value_popover_default
21
+ } from "./chunk-7B2T5ZNG.js";
22
+ import {
23
+ BulkStreamProvider,
24
+ DevToolsConfigProvider,
25
+ authHeaders,
26
+ clearDevToolsToken,
27
+ devToolsSignIn,
28
+ devToolsSignOut,
29
+ fetchWhoAmI,
30
+ getDevToolsToken,
31
+ setDevToolsToken,
32
+ useBulkRowCounts,
33
+ useBulkSubscription,
34
+ useDevToolsConfig,
35
+ wsAuthProtocols,
36
+ wsBaseFrom
37
+ } from "./chunk-3ZM6YOA4.js";
38
+
39
+ // src/core/use-cartridge-info.ts
40
+ import { useEffect, useState } from "react";
41
+ function buildRegistry(contracts) {
42
+ const reg = {};
43
+ const core = /* @__PURE__ */ new Set();
44
+ for (const c of contracts) {
45
+ const cart = c.cartridge;
46
+ if (!cart || !c.owns) continue;
47
+ const entities = [];
48
+ for (const [name, ent] of Object.entries(c.owns)) {
49
+ const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];
50
+ entities.push({
51
+ name,
52
+ route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,
53
+ searchFields: sf
54
+ });
55
+ }
56
+ if (entities.length > 0) {
57
+ reg[cart] = entities;
58
+ if (c.core) core.add(cart);
59
+ }
60
+ }
61
+ return { reg, core };
62
+ }
63
+ function useLiveBulkRegistry() {
64
+ const { apiBaseUrl } = useDevToolsConfig();
65
+ const [registry, setRegistry] = useState({});
66
+ const [coreCarts, setCoreCarts] = useState(/* @__PURE__ */ new Set());
67
+ const [loading, setLoading] = useState(true);
68
+ const [error, setError] = useState(null);
69
+ useEffect(() => {
70
+ const ac = new AbortController();
71
+ let cancelled = false;
72
+ setLoading(true);
73
+ setError(null);
74
+ (async () => {
75
+ try {
76
+ const r = await fetch(`${apiBaseUrl}/_console/contracts`, {
77
+ signal: ac.signal,
78
+ credentials: "include",
79
+ headers: authHeaders()
80
+ });
81
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
82
+ const data = await r.json();
83
+ if (!Array.isArray(data)) throw new Error("malformed contracts response");
84
+ if (cancelled) return;
85
+ const { reg, core } = buildRegistry(data);
86
+ setRegistry(reg);
87
+ setCoreCarts(core);
88
+ } catch (e) {
89
+ if (cancelled || e.name === "AbortError") return;
90
+ setError(e instanceof Error ? e.message : String(e));
91
+ } finally {
92
+ if (!cancelled) setLoading(false);
93
+ }
94
+ })();
95
+ return () => {
96
+ cancelled = true;
97
+ ac.abort();
98
+ };
99
+ }, [apiBaseUrl]);
100
+ const carts = Object.keys(registry).sort();
101
+ return { registry, carts, coreCarts, loading, error };
102
+ }
103
+ export {
104
+ BulkStreamProvider,
105
+ data_table_default as DataTable,
106
+ DevToolsConfigProvider,
107
+ DiagramView as EntityGraph,
108
+ EntityNode,
109
+ NbtEditor,
110
+ NbtLspClient,
111
+ value_popover_default as ValuePopover,
112
+ authHeaders,
113
+ buildEntityGraphModel,
114
+ cartsFromContracts,
115
+ clearDevToolsToken,
116
+ cn,
117
+ devToolsSignIn,
118
+ devToolsSignOut,
119
+ entityGraphId,
120
+ fetchWhoAmI,
121
+ filterEntityGraphModel,
122
+ getDevToolsToken,
123
+ lspExtensions,
124
+ nbtLanguage,
125
+ nbtLanguageSupport,
126
+ setDevToolsToken,
127
+ useBulkRowCounts,
128
+ useBulkSubscription,
129
+ useDevToolsConfig,
130
+ useLiveBulkRegistry,
131
+ wsAuthProtocols,
132
+ wsBaseFrom
133
+ };
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/use-cartridge-info.ts"],
4
+ "sourcesContent": ["import { useEffect, useState } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\n// Live cartridge/entity registry for the Data tab. Instead of the build-time\n// `BULK_REGISTRY` (every cart in the repo, generated by `nbt generate`), we\n// read the daemon's `/_console/contracts` \u2014 which only lists *running*\n// cartridges and carries each entity's `searchFields`. Bulk WS routes are\n// derived live (`/_ws/bulk/<cart>/<entity-lower>`) and fed straight into\n// useBulkStream. So the tab reflects what is actually installed right now.\n\nexport type BulkEntity = {\n name: string;\n route: string;\n searchFields: readonly string[];\n};\nexport type BulkRegistry = Record<string, BulkEntity[]>;\n\ntype Contract = {\n cartridge?: string;\n core?: boolean;\n owns?: Record<string, { searchFields?: string[] }>;\n};\n\nfunction buildRegistry(contracts: Contract[]): { reg: BulkRegistry; core: Set<string> } {\n const reg: BulkRegistry = {};\n const core = new Set<string>();\n for (const c of contracts) {\n const cart = c.cartridge;\n if (!cart || !c.owns) continue;\n const entities: BulkEntity[] = [];\n for (const [name, ent] of Object.entries(c.owns)) {\n const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];\n entities.push({\n name,\n route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,\n searchFields: sf,\n });\n }\n if (entities.length > 0) {\n reg[cart] = entities;\n if (c.core) core.add(cart);\n }\n }\n return { reg, core };\n}\n\nexport type LiveRegistryState = {\n registry: BulkRegistry;\n carts: string[];\n // Cartridge slugs flagged `core` by the daemon. The Data tab keeps `auth`\n // visible but tucks the rest of these behind a \"more cartridges\" menu so\n // deployed (user) cartridges are what's shown by default.\n coreCarts: Set<string>;\n loading: boolean;\n error: string | null;\n};\n\nexport function useLiveBulkRegistry(): LiveRegistryState {\n const { apiBaseUrl } = useDevToolsConfig();\n const [registry, setRegistry] = useState<BulkRegistry>({});\n const [coreCarts, setCoreCarts] = useState<Set<string>>(new Set());\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const ac = new AbortController();\n let cancelled = false;\n setLoading(true);\n setError(null);\n (async () => {\n try {\n const r = await fetch(`${apiBaseUrl}/_console/contracts`, {\n signal: ac.signal,\n credentials: \"include\",\n headers: authHeaders(),\n });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n const data = (await r.json()) as Contract[];\n if (!Array.isArray(data)) throw new Error(\"malformed contracts response\");\n if (cancelled) return;\n const { reg, core } = buildRegistry(data);\n setRegistry(reg);\n setCoreCarts(core);\n } catch (e) {\n if (cancelled || (e as { name?: string }).name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n ac.abort();\n };\n }, [apiBaseUrl]);\n\n const carts = Object.keys(registry).sort();\n return { registry, carts, coreCarts, loading, error };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,gBAAgB;AAwBpC,SAAS,cAAc,WAAiE;AACtF,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,WAAW;AACzB,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,CAAC,EAAE,KAAM;AACtB,UAAM,WAAyB,CAAC;AAChC,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,EAAE,IAAI,GAAG;AAChD,YAAM,KAAK,MAAM,QAAQ,KAAK,YAAY,IAAI,IAAI,eAAe,CAAC;AAClE,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,aAAa,IAAI,IAAI,KAAK,YAAY,CAAC;AAAA,QAC9C,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AACA,QAAI,SAAS,SAAS,GAAG;AACvB,UAAI,IAAI,IAAI;AACZ,UAAI,EAAE,KAAM,MAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,EAAE,KAAK,KAAK;AACrB;AAaO,SAAS,sBAAyC;AACvD,QAAM,EAAE,WAAW,IAAI,kBAAkB;AACzC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAsB,oBAAI,IAAI,CAAC;AACjE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AACb,KAAC,YAAY;AACX,UAAI;AACF,cAAM,IAAI,MAAM,MAAM,GAAG,UAAU,uBAAuB;AAAA,UACxD,QAAQ,GAAG;AAAA,UACX,aAAa;AAAA,UACb,SAAS,YAAY;AAAA,QACvB,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,QAAQ,EAAE,MAAM,EAAE;AAC7C,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACxE,YAAI,UAAW;AACf,cAAM,EAAE,KAAK,KAAK,IAAI,cAAc,IAAI;AACxC,oBAAY,GAAG;AACf,qBAAa,IAAI;AAAA,MACnB,SAAS,GAAG;AACV,YAAI,aAAc,EAAwB,SAAS,aAAc;AACjE,iBAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACrD,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,kBAAY;AACZ,SAAG,MAAM;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE,KAAK;AACzC,SAAO,EAAE,UAAU,OAAO,WAAW,SAAS,MAAM;AACtD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}.react-flow{--xy-edge-stroke-default:#b1b1b7;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#555;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#ffffff80;--xy-minimap-background-color-default:#fff;--xy-minimap-mask-background-color-default:#f0f0f099;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#e2e2e2;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:transparent;--xy-background-pattern-dots-color-default:#91919a;--xy-background-pattern-lines-color-default:#eee;--xy-background-pattern-cross-color-default:#e2e2e2;background-color:var(--xy-background-color,var(--xy-background-color-default));--xy-node-color-default:inherit;--xy-node-border-default:1px solid #1a192b;--xy-node-background-color-default:#fff;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #00000014;--xy-node-boxshadow-selected-default:0 0 0 .5px #1a192b;--xy-node-border-radius-default:3px;--xy-handle-background-color-default:#1a192b;--xy-handle-border-color-default:#fff;--xy-selection-background-color-default:#0059dc14;--xy-selection-border-default:1px dotted #0059dccc;--xy-controls-button-background-color-default:#fefefe;--xy-controls-button-background-color-hover-default:#f4f4f4;--xy-controls-button-color-default:inherit;--xy-controls-button-color-hover-default:inherit;--xy-controls-button-border-color-default:#eee;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#fff;--xy-edge-label-color-default:inherit;--xy-resize-background-color-default:#3367d9;direction:ltr}.react-flow.dark{--xy-edge-stroke-default:#3e3e3e;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#727272;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#96969640;--xy-minimap-background-color-default:#141414;--xy-minimap-mask-background-color-default:#3c3c3c99;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#2b2b2b;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:#141414;--xy-background-pattern-dots-color-default:#777;--xy-background-pattern-lines-color-default:#777;--xy-background-pattern-cross-color-default:#777;--xy-node-color-default:#f8f8f8;--xy-node-border-default:1px solid #3c3c3c;--xy-node-background-color-default:#1e1e1e;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #ffffff14;--xy-node-boxshadow-selected-default:0 0 0 .5px #999;--xy-handle-background-color-default:#bebebe;--xy-handle-border-color-default:#1e1e1e;--xy-selection-background-color-default:#c8c8dc14;--xy-selection-border-default:1px dotted #c8c8dccc;--xy-controls-button-background-color-default:#2b2b2b;--xy-controls-button-background-color-hover-default:#3e3e3e;--xy-controls-button-color-default:#f8f8f8;--xy-controls-button-color-hover-default:#fff;--xy-controls-button-border-color-default:#5b5b5b;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#141414;--xy-edge-label-color-default:#f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props,var(--xy-background-color,var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width,var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke,var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width,var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{pointer-events:none;position:absolute;overflow:visible}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected,var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}svg.react-flow__connectionline{z-index:1001;position:absolute;overflow:visible}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{-webkit-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default;position:absolute}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background-color:var(--xy-handle-background-color,var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color,var(--xy-handle-border-color-default));border-radius:100%;width:6px;min-width:5px;height:6px;min-height:5px;position:absolute}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:0;left:50%;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px)translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px)translateY(-50%)}.react-flow__attribution{background:var(--xy-attribution-background-color,var(--xy-attribution-background-color-default));margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__viewport-portal{-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__minimap{background:var(--xy-minimap-background-color-props,var(--xy-minimap-background-color,var(--xy-minimap-background-color-default)))}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var(--xy-minimap-mask-background-color-props,var(--xy-minimap-mask-background-color,var(--xy-minimap-mask-background-color-default)));stroke:var(--xy-minimap-mask-stroke-color-props,var(--xy-minimap-mask-stroke-color,var(--xy-minimap-mask-stroke-color-default)));stroke-width:var(--xy-minimap-mask-stroke-width-props,var(--xy-minimap-mask-stroke-width,var(--xy-minimap-mask-stroke-width-default)))}.react-flow__minimap-node{fill:var(--xy-minimap-node-background-color-props,var(--xy-minimap-node-background-color,var(--xy-minimap-node-background-color-default)));stroke:var(--xy-minimap-node-stroke-color-props,var(--xy-minimap-node-stroke-color,var(--xy-minimap-node-stroke-color-default)));stroke-width:var(--xy-minimap-node-stroke-width-props,var(--xy-minimap-node-stroke-width,var(--xy-minimap-node-stroke-width-default)))}.react-flow__background-pattern.dots{fill:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-dots-color-default)))}.react-flow__background-pattern.lines{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-lines-color-default)))}.react-flow__background-pattern.cross{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-cross-color-default)))}.react-flow__controls{box-shadow:var(--xy-controls-box-shadow,var(--xy-controls-box-shadow-default));flex-direction:column;display:flex}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{background:var(--xy-controls-button-background-color,var(--xy-controls-button-background-color-default));border:none;border-bottom:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)));width:26px;height:26px;color:var(--xy-controls-button-color-props,var(--xy-controls-button-color,var(--xy-controls-button-color-default)));cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;padding:4px;display:flex}.react-flow__controls-button svg{fill:currentColor;width:100%;max-width:12px;max-height:12px}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{border-radius:var(--xy-node-border-radius,var(--xy-node-border-radius-default));width:150px;color:var(--xy-node-color,var(--xy-node-color-default));text-align:center;border:var(--xy-node-border,var(--xy-node-border-default));background-color:var(--xy-node-background-color,var(--xy-node-background-color-default));padding:10px;font-size:12px}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover,var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected,var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color,var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color,var(--xy-selection-background-color-default));border:var(--xy-selection-border,var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var(--xy-controls-button-background-color-hover-props,var(--xy-controls-button-background-color-hover,var(--xy-controls-button-background-color-hover-default)));color:var(--xy-controls-button-color-hover-props,var(--xy-controls-button-color-hover,var(--xy-controls-button-color-hover-default)))}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)))}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border:1px solid #fff;border-radius:1px;width:5px;height:5px;translate:-50% -50%}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border-style:solid;border-width:0}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color,var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color,var(--xy-edge-label-color-default))}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-black:#000;--spacing:.25rem;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wider:.05em;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base,components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.\!right-0{right:0!important}.\!left-0{left:0!important}.left-0{left:0}.z-10{z-index:10}.z-\[60\]{z-index:60}.z-\[61\]{z-index:61}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.contents{display:contents}.flex{display:flex}.grid{display:grid}.table{display:table}.\!h-1{height:var(--spacing)!important}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-0{min-height:0}.min-h-\[3rem\]{min-height:3rem}.min-h-\[26px\]{min-height:26px}.\!w-1{width:var(--spacing)!important}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-\[260px\]{width:260px}.w-full{width:100%}.max-w-\[96px\]{max-width:96px}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.rounded{border-radius:.25rem}.rounded-md{border-radius:calc(var(--radius) * .8)}.rounded-sm{border-radius:calc(var(--radius) * .6)}.rounded-xl{border-radius:calc(var(--radius) * 1.4)}.rounded-t-xl{border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}.rounded-b-xl{border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.\!border-0{border-style:var(--tw-border-style)!important;border-width:0!important}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-blue-400\/60{border-color:#54a2ff99}@supports (color:color-mix(in lab, red, red)){.border-blue-400\/60{border-color:color-mix(in oklab, var(--color-blue-400) 60%, transparent)}}.border-blue-500{border-color:var(--color-blue-500)}.border-border,.border-border\/60{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/60{border-color:color-mix(in oklab, var(--border) 60%, transparent)}}.border-zinc-700{border-color:var(--color-zinc-700)}.\!bg-transparent{background-color:#0000!important}.bg-background{background-color:var(--background)}.bg-blue-500\/15{background-color:#3080ff26}@supports (color:color-mix(in lab, red, red)){.bg-blue-500\/15{background-color:color-mix(in oklab, var(--color-blue-500) 15%, transparent)}}.bg-muted\/20{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--muted) 20%, transparent)}}.bg-muted\/40{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--muted) 40%, transparent)}}.bg-popover{background-color:var(--popover)}.bg-primary\/90{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/90{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-none{--tw-leading:1;line-height:1}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-foreground{color:var(--foreground)}.text-muted-foreground,.text-muted-foreground\/60{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/60{color:color-mix(in oklab, var(--muted-foreground) 60%, transparent)}}.text-popover-foreground{color:var(--popover-foreground)}.text-primary-foreground{color:var(--primary-foreground)}.text-red-400{color:var(--color-red-400)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.\!opacity-0{opacity:0!important}.opacity-35{opacity:.35}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color:#0006}@supports (color:color-mix(in lab, red, red)){.shadow-black\/40{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 40%, transparent) var(--tw-shadow-alpha), transparent)}}.ring-blue-500\/10{--tw-ring-color:#3080ff1a}@supports (color:color-mix(in lab, red, red)){.ring-blue-500\/10{--tw-ring-color:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.ring-blue-500\/30{--tw-ring-color:#3080ff4d}@supports (color:color-mix(in lab, red, red)){.ring-blue-500\/30{--tw-ring-color:color-mix(in oklab, var(--color-blue-500) 30%, transparent)}}.ring-zinc-100\/10{--tw-ring-color:#f4f4f51a}@supports (color:color-mix(in lab, red, red)){.ring-zinc-100\/10{--tw-ring-color:color-mix(in oklab, var(--color-zinc-100) 10%, transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}.placeholder\:text-muted-foreground\/70::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.placeholder\:text-muted-foreground\/70::placeholder{color:color-mix(in oklab, var(--muted-foreground) 70%, transparent)}}@media (hover:hover){.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-primary:hover{background-color:var(--primary)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}}.focus\:border-accent-foreground\/30:focus{border-color:var(--accent-foreground)}@supports (color:color-mix(in lab, red, red)){.focus\:border-accent-foreground\/30:focus{border-color:color-mix(in oklab, var(--accent-foreground) 30%, transparent)}}.disabled\:opacity-50:disabled{opacity:.5}.data-\[selected\=true\]\:bg-accent[data-selected=true]{background-color:var(--accent)}.data-\[selected\=true\]\:text-accent-foreground[data-selected=true]{color:var(--accent-foreground)}.data-\[selected\=true\]\:\[box-shadow\:inset_0_0_0_1px_\#60a5fa\][data-selected=true]{box-shadow:inset 0 0 0 1px #60a5fa}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}.nbt-ui{color-scheme:dark;scrollbar-width:thin;scrollbar-color:oklch(40% 0 0) transparent;--radius:.625rem;--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--destructive-foreground:oklch(98.5% 0 0);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0)}.nbt-ui ::-webkit-scrollbar{width:10px;height:10px}.nbt-ui::-webkit-scrollbar{width:10px;height:10px}.nbt-ui ::-webkit-scrollbar-track{background:0 0}.nbt-ui::-webkit-scrollbar-track{background:0 0}.nbt-ui ::-webkit-scrollbar-corner{background:0 0}.nbt-ui::-webkit-scrollbar-corner{background:0 0}.nbt-ui ::-webkit-scrollbar-thumb{background:oklch(40% 0 0) padding-box padding-box;border:2px solid #0000;border-radius:5px}.nbt-ui::-webkit-scrollbar-thumb{background:oklch(40% 0 0) padding-box padding-box;border:2px solid #0000;border-radius:5px}.nbt-ui ::-webkit-scrollbar-thumb:hover{background:oklch(50% 0 0)}.nbt-ui::-webkit-scrollbar-thumb:hover{background:oklch(50% 0 0)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ type DataTableProps = {
3
+ cart: string;
4
+ entity: string;
5
+ searchFields: readonly string[];
6
+ onSelectRow: (rowJson: Record<string, string>) => void;
7
+ };
8
+ declare const DataTable: React.FC<DataTableProps>;
9
+ export default DataTable;
@@ -0,0 +1,3 @@
1
+ export { default as DataTable } from "./data-table";
2
+ export { default as ValuePopover } from "./value-popover";
3
+ export type { ValuePopoverData } from "./value-popover";
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import {
3
+ data_table_default,
4
+ value_popover_default
5
+ } from "../chunk-7B2T5ZNG.js";
6
+ import "../chunk-3ZM6YOA4.js";
7
+ export {
8
+ data_table_default as DataTable,
9
+ value_popover_default as ValuePopover
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ export type ValuePopoverData = {
3
+ r: number;
4
+ c: number;
5
+ colName: string;
6
+ value: string;
7
+ rect: DOMRect;
8
+ cart: string;
9
+ entity: string;
10
+ rowId: string;
11
+ colType: number;
12
+ editable: boolean;
13
+ };
14
+ declare const ValuePopover: React.FC<{
15
+ data: ValuePopoverData;
16
+ onClose: () => void;
17
+ }>;
18
+ export default ValuePopover;
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@nbt-dev/components",
3
+ "version": "0.0.5",
4
+ "description": "Reusable React building blocks for NBT-console apps: the CodeMirror NBT editor (+LSP), the entity graph, and the live data table.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "nbt-dev-source": "./src/index.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./editor": {
16
+ "types": "./dist/editor/index.d.ts",
17
+ "nbt-dev-source": "./src/editor/index.ts",
18
+ "import": "./dist/editor/index.js"
19
+ },
20
+ "./graph": {
21
+ "types": "./dist/graph/index.d.ts",
22
+ "nbt-dev-source": "./src/graph/index.ts",
23
+ "import": "./dist/graph/index.js"
24
+ },
25
+ "./table": {
26
+ "types": "./dist/table/index.d.ts",
27
+ "nbt-dev-source": "./src/table/index.ts",
28
+ "import": "./dist/table/index.js"
29
+ },
30
+ "./styles.css": "./dist/styles.css"
31
+ },
32
+ "files": [
33
+ "dist/",
34
+ "src/",
35
+ "README.md",
36
+ "LICENSE",
37
+ "TRADEMARKS.md"
38
+ ],
39
+ "scripts": {
40
+ "build": "node scripts/build.mjs",
41
+ "check:protocol": "node scripts/check-bulk-protocol.mjs",
42
+ "prepack": "node scripts/build.mjs"
43
+ },
44
+ "license": "CPAL-1.0",
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=18",
50
+ "react-dom": ">=18"
51
+ },
52
+ "dependencies": {
53
+ "@codemirror/autocomplete": "^6.20.0",
54
+ "@codemirror/commands": "^6.10.0",
55
+ "@codemirror/language": "^6.12.0",
56
+ "@codemirror/lint": "^6.9.1",
57
+ "@codemirror/search": "^6.5.11",
58
+ "@codemirror/state": "^6.5.4",
59
+ "@codemirror/view": "^6.39.5",
60
+ "@dagrejs/dagre": "^3.0.0",
61
+ "@xyflow/react": "^12.11.0",
62
+ "clsx": "^2.1.1",
63
+ "lucide-react": ">=0.5.0",
64
+ "tailwind-merge": "^3.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "@tailwindcss/cli": "^4.3.0",
68
+ "@types/react": "^19.2.15",
69
+ "@types/react-dom": "^19.2.3",
70
+ "esbuild": "^0.28.0",
71
+ "react": "^19.2.6",
72
+ "react-dom": "^19.2.6",
73
+ "tailwindcss": "^4.3.0",
74
+ "tw-animate-css": "^1.4.0",
75
+ "typescript": "^6.0.3"
76
+ }
77
+ }
@@ -0,0 +1,100 @@
1
+ // Devtools-managed auth. The panel can run embedded in a host page that has no
2
+ // session of its own, so it owns its credential rather than riding the host's
3
+ // `session_token` cookie: a token obtained via `signin` is stored in
4
+ // localStorage and attached as a Bearer header (HTTP) or in the `auth-<b64url>`
5
+ // WebSocket subprotocol. On an unconfigured/dev console the backend leaves
6
+ // devtools open, so a missing token is fine there.
7
+
8
+ const TOKEN_KEY = "nbt_devtools_token";
9
+
10
+ export function getDevToolsToken(): string | null {
11
+ try {
12
+ return localStorage.getItem(TOKEN_KEY);
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ export function setDevToolsToken(token: string): void {
19
+ try {
20
+ localStorage.setItem(TOKEN_KEY, token);
21
+ } catch {
22
+ /* storage unavailable (private mode / SSR) — token stays in-memory only */
23
+ }
24
+ }
25
+
26
+ export function clearDevToolsToken(): void {
27
+ try {
28
+ localStorage.removeItem(TOKEN_KEY);
29
+ } catch {
30
+ /* ignore */
31
+ }
32
+ }
33
+
34
+ // Merge the stored Bearer token into a header bag (if present).
35
+ export function authHeaders(base?: Record<string, string>): Record<string, string> {
36
+ const h: Record<string, string> = { ...(base ?? {}) };
37
+ const t = getDevToolsToken();
38
+ if (t) h["Authorization"] = `Bearer ${t}`;
39
+ return h;
40
+ }
41
+
42
+ export type WhoAmI = {
43
+ // node has real auth configured (else devtools is open / local dev)
44
+ configured: boolean;
45
+ // request carries a valid session
46
+ authenticated: boolean;
47
+ // caller may use devtools (open node, or a console operator)
48
+ isAdmin: boolean;
49
+ };
50
+
51
+ export async function fetchWhoAmI(apiBaseUrl: string, signal?: AbortSignal): Promise<WhoAmI> {
52
+ const r = await fetch(`${apiBaseUrl}/_console/whoami`, {
53
+ signal,
54
+ credentials: "include",
55
+ headers: authHeaders(),
56
+ });
57
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
58
+ return (await r.json()) as WhoAmI;
59
+ }
60
+
61
+ // Sign in against the console's auth cartridge and stash the returned token.
62
+ // Throws with a human-readable message on failure.
63
+ export async function devToolsSignIn(
64
+ apiBaseUrl: string,
65
+ email: string,
66
+ password: string,
67
+ ): Promise<void> {
68
+ const r = await fetch(`${apiBaseUrl}/api/auth/user/signin`, {
69
+ method: "POST",
70
+ credentials: "include",
71
+ headers: { "content-type": "application/json" },
72
+ body: JSON.stringify({ email, password }),
73
+ });
74
+ if (!r.ok) {
75
+ throw new Error(r.status === 401 ? "Invalid email or password" : `Sign-in failed (HTTP ${r.status})`);
76
+ }
77
+ const j = (await r.json()) as { token?: string };
78
+ if (!j.token) throw new Error("Sign-in returned no token");
79
+ setDevToolsToken(j.token);
80
+ }
81
+
82
+ export function devToolsSignOut(): void {
83
+ clearDevToolsToken();
84
+ }
85
+
86
+ function base64UrlEncode(value: string): string {
87
+ if (typeof btoa === "function") {
88
+ return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
89
+ }
90
+ throw new Error("No base64 encoder available for WS auth");
91
+ }
92
+
93
+ // WebSocket subprotocols for an authenticated devtools stream: a leading
94
+ // (non-auth) protocol name the server echoes, plus the `auth-<b64url>` token
95
+ // part it decodes. Without a stored token we offer only the name — fine on an
96
+ // open/dev console, rejected on a configured one.
97
+ export function wsAuthProtocols(name: string): string[] {
98
+ const t = getDevToolsToken();
99
+ return t ? [name, `auth-${base64UrlEncode(t)}`] : [name];
100
+ }
@@ -0,0 +1,178 @@
1
+ // Wire-format decoders + client→server command encoders for the multiplexed
2
+ // bulk WS protocol. Frame opcodes and column-type bytes live in
3
+ // @/generated/bulk-protocol (kept in sync with modules/cart/ws.jai).
4
+ //
5
+ // Every server→client binary frame is [op:u8][sid:u16 LE][payload]. The sid is
6
+ // the client-chosen subscription id; one socket multiplexes many entities, and
7
+ // the provider routes each frame to the matching view by sid (getFrameSid).
8
+ // Only the parse/encode bodies are hand-maintained here.
9
+
10
+ import {
11
+ FRAME_SCHEMA,
12
+ FRAME_DATA,
13
+ FRAME_DELTA_DEL,
14
+ FRAME_DELTA_INS,
15
+ FRAME_DELTA_UPD,
16
+ TYPE_U8,
17
+ TYPE_S8,
18
+ TYPE_BOOL,
19
+ TYPE_U16,
20
+ TYPE_S16,
21
+ TYPE_U32,
22
+ TYPE_S32,
23
+ TYPE_FLOAT32,
24
+ TYPE_U64,
25
+ TYPE_S64,
26
+ TYPE_FLOAT64,
27
+ TYPE_DATETIME,
28
+ TYPE_STRING,
29
+ TYPE_DOCUMENT,
30
+ fixedSizeForType,
31
+ type ColumnDef,
32
+ type SchemaFrame,
33
+ type DeltaFrame,
34
+ } from "../generated/bulk-protocol";
35
+
36
+ const textDecoder = new TextDecoder();
37
+
38
+ // Bytes between the op byte and the frame payload: the u16 LE subscription id.
39
+ const SID_BYTES = 2;
40
+ // Offset of the payload in every server→client frame (op + sid).
41
+ const HEAD = 1 + SID_BYTES;
42
+
43
+ function epochToDate(epoch: number): Date {
44
+ const abs = Math.abs(epoch);
45
+ if (abs < 10_000_000_000) return new Date(epoch * 1000);
46
+ if (abs < 10_000_000_000_000) return new Date(epoch);
47
+ if (abs < 10_000_000_000_000_000) return new Date(epoch / 1000);
48
+ return new Date(epoch / 1_000_000);
49
+ }
50
+
51
+ function formatCellValue(view: DataView, offset: number, col: ColumnDef): [string, number] {
52
+ switch (col.type) {
53
+ case TYPE_U8: return [String(view.getUint8(offset)), 1];
54
+ case TYPE_S8: return [String(view.getInt8(offset)), 1];
55
+ case TYPE_BOOL: return [view.getUint8(offset) ? "true" : "false", 1];
56
+ case TYPE_U16: return [String(view.getUint16(offset, true)), 2];
57
+ case TYPE_S16: return [String(view.getInt16(offset, true)), 2];
58
+ case TYPE_U32: return [String(view.getUint32(offset, true)), 4];
59
+ case TYPE_S32: return [String(view.getInt32(offset, true)), 4];
60
+ case TYPE_FLOAT32: return [String(view.getFloat32(offset, true)), 4];
61
+ case TYPE_U64: return [String(view.getBigUint64(offset, true)), 8];
62
+ case TYPE_S64: return [String(view.getBigInt64(offset, true)), 8];
63
+ case TYPE_FLOAT64: return [String(view.getFloat64(offset, true)), 8];
64
+ case TYPE_DATETIME: {
65
+ const epoch = Number(view.getBigInt64(offset, true));
66
+ if (epoch === 0) return ["", 8];
67
+ return [epochToDate(epoch).toISOString(), 8];
68
+ }
69
+ case TYPE_STRING:
70
+ case TYPE_DOCUMENT: {
71
+ const len = view.getUint32(offset, true);
72
+ const bytes = new Uint8Array(view.buffer, view.byteOffset + offset + 4, len);
73
+ return [textDecoder.decode(bytes), 4 + len];
74
+ }
75
+ default:
76
+ return ["", 0];
77
+ }
78
+ }
79
+
80
+ export function getFrameType(buf: ArrayBuffer): number {
81
+ return new DataView(buf).getUint8(0);
82
+ }
83
+
84
+ // The subscription id this frame belongs to (u16 LE right after the op byte).
85
+ export function getFrameSid(buf: ArrayBuffer): number {
86
+ return new DataView(buf).getUint16(1, true);
87
+ }
88
+
89
+ export function parseSchema(buf: ArrayBuffer): SchemaFrame {
90
+ const view = new DataView(buf);
91
+ let offset = 0;
92
+ const frameType = view.getUint8(offset); offset += 1;
93
+ if (frameType !== FRAME_SCHEMA) {
94
+ throw new Error(`Expected SCHEMA frame (0x01), got 0x${frameType.toString(16)}`);
95
+ }
96
+ offset += SID_BYTES;
97
+ const totalRows = view.getUint32(offset, true); offset += 4;
98
+ const columnCount = view.getUint16(offset, true); offset += 2;
99
+ const columns: ColumnDef[] = [];
100
+ for (let i = 0; i < columnCount; i++) {
101
+ const type = view.getUint8(offset); offset += 1;
102
+ const nameLen = view.getUint16(offset, true); offset += 2;
103
+ const nameBytes = new Uint8Array(buf, offset, nameLen);
104
+ const name = textDecoder.decode(nameBytes);
105
+ offset += nameLen;
106
+ columns.push({ name, type, fixedSize: fixedSizeForType(type) });
107
+ }
108
+ return { totalRows, columns };
109
+ }
110
+
111
+ export function parseDataChunk(buf: ArrayBuffer, columns: ColumnDef[]): string[][] {
112
+ const view = new DataView(buf);
113
+ let offset = 0;
114
+ const frameType = view.getUint8(offset); offset += 1;
115
+ if (frameType !== FRAME_DATA) {
116
+ throw new Error(`Expected DATA frame, got 0x${frameType.toString(16)}`);
117
+ }
118
+ offset += SID_BYTES;
119
+ const rowCount = view.getUint16(offset, true); offset += 2;
120
+ const rows: string[][] = [];
121
+ for (let r = 0; r < rowCount; r++) {
122
+ const row: string[] = [];
123
+ for (let c = 0; c < columns.length; c++) {
124
+ const [value, bytesRead] = formatCellValue(view, offset, columns[c]!);
125
+ row.push(value);
126
+ offset += bytesRead;
127
+ }
128
+ rows.push(row);
129
+ }
130
+ return rows;
131
+ }
132
+
133
+ export function parseDelta(buf: ArrayBuffer, columns: ColumnDef[]): DeltaFrame {
134
+ const view = new DataView(buf);
135
+ let offset = 0;
136
+ const op = view.getUint8(offset); offset += 1;
137
+ offset += SID_BYTES;
138
+ if (op === FRAME_DELTA_DEL) {
139
+ // [id_len:u16 LE][id_bytes] — the row id is a string (ULID), not an int.
140
+ const len = view.getUint16(offset, true); offset += 2;
141
+ const bytes = new Uint8Array(buf, offset, len);
142
+ return { op, id: textDecoder.decode(bytes) };
143
+ }
144
+ if (op === FRAME_DELTA_INS || op === FRAME_DELTA_UPD) {
145
+ const rowData: string[] = [];
146
+ for (let c = 0; c < columns.length; c++) {
147
+ const [value, bytesRead] = formatCellValue(view, offset, columns[c]!);
148
+ rowData.push(value);
149
+ offset += bytesRead;
150
+ }
151
+ return { op, rowData };
152
+ }
153
+ throw new Error(`Unknown delta op 0x${op.toString(16)}`);
154
+ }
155
+
156
+ export function parseError(buf: ArrayBuffer): string {
157
+ const view = new DataView(buf);
158
+ // [0xFF][sid:u16][msg_len:u16 LE][msg_bytes]
159
+ const len = view.getUint16(HEAD, true);
160
+ const bytes = new Uint8Array(buf, HEAD + 2, len);
161
+ return textDecoder.decode(bytes);
162
+ }
163
+
164
+ // --- client → server command encoders ---
165
+ // Each command carries the subscription id; sub/schema also name the target.
166
+
167
+ export function encodeStreamCmd(
168
+ sid: number,
169
+ cart: string,
170
+ entity: string,
171
+ opts?: { sort?: string; sort_desc?: boolean; filters?: Record<string, string> },
172
+ ): string {
173
+ return JSON.stringify({ cmd: "sub", sid, cart, entity, ...opts });
174
+ }
175
+
176
+ export function encodeUnsubCmd(sid: number): string {
177
+ return JSON.stringify({ cmd: "unsub", sid });
178
+ }