@nbt-dev/components 0.0.9 → 0.1.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.
Files changed (36) hide show
  1. package/dist/{chunk-WBZS7I6V.js → chunk-AI2LKTA4.js} +30 -2
  2. package/dist/chunk-AI2LKTA4.js.map +7 -0
  3. package/dist/{chunk-235TYE66.js → chunk-BNA2MLFE.js} +151 -17
  4. package/dist/chunk-BNA2MLFE.js.map +7 -0
  5. package/dist/{chunk-VDIHQ2NS.js → chunk-HTAPMIK6.js} +11 -3
  6. package/dist/chunk-HTAPMIK6.js.map +7 -0
  7. package/dist/{chunk-S7VBQE6Y.js → chunk-PI7ZMVVU.js} +162 -33
  8. package/dist/chunk-PI7ZMVVU.js.map +7 -0
  9. package/dist/core/auth.d.ts +2 -0
  10. package/dist/core/index.d.ts +1 -1
  11. package/dist/core/use-cartridge-info.d.ts +1 -0
  12. package/dist/editor/editor-themes.d.ts +8 -0
  13. package/dist/editor/index.d.ts +2 -0
  14. package/dist/editor/index.js +7 -3
  15. package/dist/editor/nbt-editor.d.ts +4 -0
  16. package/dist/graph/index.js +2 -2
  17. package/dist/index.js +20 -8
  18. package/dist/index.js.map +2 -2
  19. package/dist/styles.css +1 -1
  20. package/dist/table/column-widths.d.ts +2 -0
  21. package/dist/table/index.js +2 -2
  22. package/package.json +3 -2
  23. package/src/core/auth.ts +40 -0
  24. package/src/core/data-store.ts +5 -1
  25. package/src/core/index.ts +2 -0
  26. package/src/core/use-cartridge-info.ts +15 -4
  27. package/src/editor/editor-themes.ts +128 -0
  28. package/src/editor/index.ts +2 -0
  29. package/src/editor/nbt-editor.tsx +60 -26
  30. package/src/graph/diagram.tsx +16 -0
  31. package/src/table/column-widths.ts +34 -0
  32. package/src/table/data-table.tsx +141 -15
  33. package/dist/chunk-235TYE66.js.map +0 -7
  34. package/dist/chunk-S7VBQE6Y.js.map +0 -7
  35. package/dist/chunk-VDIHQ2NS.js.map +0 -7
  36. package/dist/chunk-WBZS7I6V.js.map +0 -7
@@ -1,8 +1,10 @@
1
1
  // CodeMirror 6 React wrapper for one NBT buffer. Recreated per uri (keyed by
2
2
  // the host); owns the EditorView lifecycle, Ctrl+S save, and the LSP wiring.
3
+ // Theme / font size / line-wrap / tab size are live-tunable via compartments so
4
+ // changing a setting doesn't blow away cursor + scroll.
3
5
 
4
6
  import React from "react";
5
- import { EditorState } from "@codemirror/state";
7
+ import { Compartment, EditorState, Prec } from "@codemirror/state";
6
8
  import {
7
9
  EditorView,
8
10
  keymap,
@@ -20,40 +22,34 @@ import {
20
22
  syntaxHighlighting,
21
23
  defaultHighlightStyle,
22
24
  bracketMatching,
25
+ indentUnit,
23
26
  } from "@codemirror/language";
24
27
  import { lintGutter } from "@codemirror/lint";
25
28
  import { searchKeymap } from "@codemirror/search";
26
29
  import { completionKeymap, closeBrackets } from "@codemirror/autocomplete";
27
30
  import { nbtLanguageSupport } from "./nbt-language";
31
+ import { themeExtension } from "./editor-themes";
28
32
  import { lspExtensions, type GotoDefHandler } from "./lsp-extensions";
29
33
  import { NbtLspClient } from "./lsp-client";
30
34
 
31
- // Match the devtools panel chrome via its CSS variables (we render inside the
32
- // .nimbit-devtools subtree, so these resolve to the scoped theme).
33
- const nbtTheme = EditorView.theme(
34
- {
35
- "&": {
36
- height: "100%",
37
- fontSize: "12px",
38
- backgroundColor: "var(--background)",
39
- color: "var(--foreground)",
40
- },
35
+ // Sizing only — the selected color theme owns the rest. Built per font-size so
36
+ // the theme compartment can carry both.
37
+ function sizingTheme(fontSize: number) {
38
+ return EditorView.theme({
39
+ "&": { height: "100%", fontSize: `${fontSize}px` },
41
40
  ".cm-content": { fontFamily: "ui-monospace, monospace" },
42
- ".cm-gutters": {
43
- backgroundColor: "var(--background)",
44
- color: "var(--muted-foreground)",
45
- borderRight: "1px solid var(--border)",
46
- },
47
- ".cm-activeLine": { backgroundColor: "color-mix(in srgb, var(--accent) 35%, transparent)" },
48
41
  "&.cm-focused": { outline: "none" },
49
- ".cm-tooltip": {
50
- backgroundColor: "var(--popover)",
51
- color: "var(--popover-foreground)",
52
- border: "1px solid var(--border)",
53
- },
54
- },
55
- { dark: true },
56
- );
42
+ });
43
+ }
44
+
45
+ // `Prec.high` so this wins the indentUnit/tabSize facets over the
46
+ // `indentUnit.of(" ")` baked into nbtLanguageSupport().
47
+ function indentConfig(tabSize: number) {
48
+ return Prec.high([
49
+ indentUnit.of(" ".repeat(tabSize)),
50
+ EditorState.tabSize.of(tabSize),
51
+ ]);
52
+ }
57
53
 
58
54
  export type NbtEditorProps = {
59
55
  uri: string;
@@ -63,6 +59,10 @@ export type NbtEditorProps = {
63
59
  onChange?: (text: string) => void;
64
60
  onSave?: (text: string) => void;
65
61
  onGotoDefinition?: GotoDefHandler;
62
+ theme?: string;
63
+ fontSize?: number;
64
+ lineWrap?: boolean;
65
+ tabSize?: number;
66
66
  };
67
67
 
68
68
  export const NbtEditor: React.FC<NbtEditorProps> = ({
@@ -73,6 +73,10 @@ export const NbtEditor: React.FC<NbtEditorProps> = ({
73
73
  onChange,
74
74
  onSave,
75
75
  onGotoDefinition,
76
+ theme = "default",
77
+ fontSize = 12,
78
+ lineWrap = false,
79
+ tabSize = 2,
76
80
  }) => {
77
81
  const hostRef = React.useRef<HTMLDivElement | null>(null);
78
82
  const viewRef = React.useRef<EditorView | null>(null);
@@ -85,10 +89,20 @@ export const NbtEditor: React.FC<NbtEditorProps> = ({
85
89
  const onGotoRef = React.useRef(onGotoDefinition);
86
90
  onGotoRef.current = onGotoDefinition;
87
91
 
92
+ // Live-tunable settings live in compartments. Keep the latest values in a ref
93
+ // so a view rebuild (new uri/mode) seeds the right config without listing the
94
+ // settings in the create-effect deps.
95
+ const themeComp = React.useRef(new Compartment());
96
+ const wrapComp = React.useRef(new Compartment());
97
+ const indentComp = React.useRef(new Compartment());
98
+ const settingsRef = React.useRef({ theme, fontSize, lineWrap, tabSize });
99
+ settingsRef.current = { theme, fontSize, lineWrap, tabSize };
100
+
88
101
  React.useEffect(() => {
89
102
  const host = hostRef.current;
90
103
  if (!host) return;
91
104
 
105
+ const s = settingsRef.current;
92
106
  const extensions = [
93
107
  lineNumbers(),
94
108
  history(),
@@ -99,7 +113,9 @@ export const NbtEditor: React.FC<NbtEditorProps> = ({
99
113
  lintGutter(),
100
114
  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
101
115
  nbtLanguageSupport(),
102
- nbtTheme,
116
+ indentComp.current.of(indentConfig(s.tabSize)),
117
+ themeComp.current.of([sizingTheme(s.fontSize), themeExtension(s.theme)]),
118
+ wrapComp.current.of(s.lineWrap ? EditorView.lineWrapping : []),
103
119
  keymap.of([
104
120
  {
105
121
  key: "Mod-s",
@@ -135,8 +151,26 @@ export const NbtEditor: React.FC<NbtEditorProps> = ({
135
151
  viewRef.current = null;
136
152
  };
137
153
  // Recreate per buffer identity / mode; `value` is only the initial doc.
154
+ // Settings changes are handled by the reconfigure effect below.
138
155
  // eslint-disable-next-line react-hooks/exhaustive-deps
139
156
  }, [uri, readOnly, lsp]);
140
157
 
158
+ // Retune the live view when a setting changes — no destroy, so cursor/scroll
159
+ // survive.
160
+ React.useEffect(() => {
161
+ const view = viewRef.current;
162
+ if (!view) return;
163
+ view.dispatch({
164
+ effects: [
165
+ themeComp.current.reconfigure([
166
+ sizingTheme(fontSize),
167
+ themeExtension(theme),
168
+ ]),
169
+ wrapComp.current.reconfigure(lineWrap ? EditorView.lineWrapping : []),
170
+ indentComp.current.reconfigure(indentConfig(tabSize)),
171
+ ],
172
+ });
173
+ }, [theme, fontSize, lineWrap, tabSize]);
174
+
141
175
  return <div ref={hostRef} className="h-full min-h-0 overflow-hidden" />;
142
176
  };
@@ -8,6 +8,7 @@ import {
8
8
  ReactFlowProvider,
9
9
  useNodesInitialized,
10
10
  useReactFlow,
11
+ useStore,
11
12
  type Edge,
12
13
  type Node,
13
14
  } from "@xyflow/react";
@@ -35,9 +36,24 @@ const nodeTypes = { entity: EntityNode };
35
36
  const FitOnReady: React.FC<{ fitKey: string }> = ({ fitKey }) => {
36
37
  const initialized = useNodesInitialized();
37
38
  const { fitView } = useReactFlow();
39
+ // Container dimensions tracked by ReactFlow's store; change when the canvas is
40
+ // resized (window, sidebar, or the selection rail opening on the right).
41
+ const width = useStore((s) => s.width);
42
+ const height = useStore((s) => s.height);
43
+
38
44
  React.useEffect(() => {
39
45
  if (initialized) fitView({ padding: 0.25, duration: 200 });
40
46
  }, [initialized, fitKey, fitView]);
47
+
48
+ // When the selection rail opens it shrinks the canvas; without a refit a node
49
+ // near the right edge ends up hidden behind the rail. Refit on any resize,
50
+ // debounced so dragging the rail's resize handle stays smooth.
51
+ React.useEffect(() => {
52
+ if (!initialized || width === 0 || height === 0) return;
53
+ const t = setTimeout(() => fitView({ padding: 0.25, duration: 200 }), 120);
54
+ return () => clearTimeout(t);
55
+ }, [width, height, initialized, fitView]);
56
+
41
57
  return null;
42
58
  };
43
59
 
@@ -0,0 +1,34 @@
1
+ // Per-table column-width overrides, persisted to localStorage. Keyed by
2
+ // `<cart>:<entity>` so each table keeps its own widths; the inner map is
3
+ // fieldName -> pixel width. A missing entry means "use the default heuristic".
4
+
5
+ const KEY = "nbt_devtools_colw:v1";
6
+
7
+ type Blob = Record<string, Record<string, number>>;
8
+
9
+ function readBlob(): Blob {
10
+ try {
11
+ const raw = localStorage.getItem(KEY);
12
+ if (!raw) return {};
13
+ const parsed = JSON.parse(raw);
14
+ return typeof parsed === "object" && parsed ? (parsed as Blob) : {};
15
+ } catch {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ export function loadColWidths(cart: string, entity: string): Record<string, number> {
21
+ const blob = readBlob();
22
+ return blob[`${cart}:${entity}`] ?? {};
23
+ }
24
+
25
+ export function saveColWidth(cart: string, entity: string, col: string, w: number): void {
26
+ try {
27
+ const blob = readBlob();
28
+ const k = `${cart}:${entity}`;
29
+ (blob[k] ??= {})[col] = w;
30
+ localStorage.setItem(KEY, JSON.stringify(blob));
31
+ } catch {
32
+ /* storage unavailable (private mode / SSR) — widths stay in-memory only */
33
+ }
34
+ }
@@ -3,10 +3,12 @@ import { useBulkSubscription } from "../core/use-bulk-stream";
3
3
  import { cn } from "../core/utils";
4
4
  import { TYPE_DATETIME, TYPE_DOCUMENT } from "../generated/bulk-protocol";
5
5
  import ValuePopover, { type ValuePopoverData } from "./value-popover";
6
+ import { loadColWidths, saveColWidth } from "./column-widths";
6
7
 
7
8
  const ROW_H = 22;
8
9
  const OVERSCAN = 6;
9
10
  const MIN_COL_W = 80;
11
+ const MAX_COL_W = 600;
10
12
  const GUTTER_W = 32;
11
13
 
12
14
  const READONLY_FIELDS = new Set(["id", "createdAt", "updatedAt"]);
@@ -19,7 +21,7 @@ type DataTableProps = {
19
21
  onSelectRow: (rowJson: Record<string, string>) => void;
20
22
  };
21
23
 
22
- function colWidth(name: string): number {
24
+ function defaultColWidth(name: string): number {
23
25
  if (name === "id") return 220;
24
26
  if (name === "createdAt" || name === "updatedAt") return 180;
25
27
  if (name === "email" || name === "phone") return 180;
@@ -27,6 +29,85 @@ function colWidth(name: string): number {
27
29
  return Math.max(MIN_COL_W, Math.min(240, name.length * 9 + 24));
28
30
  }
29
31
 
32
+ // Shared offscreen canvas for measuring text width during auto-fit (no DOM reflow).
33
+ let measureCanvas: HTMLCanvasElement | null = null;
34
+ function measureCtx(): CanvasRenderingContext2D | null {
35
+ if (!measureCanvas) measureCanvas = document.createElement("canvas");
36
+ return measureCanvas.getContext("2d");
37
+ }
38
+
39
+ // Drag handle pinned to a header cell's right edge. Resizes by mutating the
40
+ // column's CSS var on the scroll root each frame (no React churn across the
41
+ // virtualized rows), committing the final width on release. Double-click
42
+ // auto-fits the column to its loaded content.
43
+ const ColResizeHandle: React.FC<{
44
+ index: number;
45
+ startWidth: number;
46
+ scrollerRef: React.RefObject<HTMLDivElement | null>;
47
+ onCommit: (w: number) => void;
48
+ onAutoFit: () => void;
49
+ }> = ({ index, startWidth, scrollerRef, onCommit, onAutoFit }) => {
50
+ const onPointerDown = (e: React.PointerEvent) => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ const root = scrollerRef.current;
54
+ if (!root) return;
55
+ const handle = e.currentTarget as HTMLElement;
56
+ handle.setPointerCapture(e.pointerId);
57
+ const startX = e.clientX;
58
+ let last = startWidth;
59
+ let moved = false;
60
+ let raf = 0;
61
+ let pending: number | null = null;
62
+ const flush = () => {
63
+ raf = 0;
64
+ if (pending == null) return;
65
+ last = pending;
66
+ root.style.setProperty(`--cw-${index}`, `${pending}px`);
67
+ pending = null;
68
+ };
69
+ const onMove = (ev: PointerEvent) => {
70
+ ev.preventDefault();
71
+ moved = true;
72
+ pending = Math.max(MIN_COL_W, Math.min(MAX_COL_W, startWidth + (ev.clientX - startX)));
73
+ if (!raf) raf = requestAnimationFrame(flush);
74
+ };
75
+ const onUp = () => {
76
+ if (raf) cancelAnimationFrame(raf);
77
+ flush();
78
+ if (moved) onCommit(last);
79
+ handle.removeEventListener("pointermove", onMove);
80
+ handle.removeEventListener("pointerup", onUp);
81
+ handle.removeEventListener("pointercancel", onUp);
82
+ try {
83
+ handle.releasePointerCapture(e.pointerId);
84
+ } catch {
85
+ /* already released */
86
+ }
87
+ document.body.style.userSelect = "";
88
+ document.body.style.cursor = "";
89
+ };
90
+ document.body.style.userSelect = "none";
91
+ document.body.style.cursor = "ew-resize";
92
+ handle.addEventListener("pointermove", onMove);
93
+ handle.addEventListener("pointerup", onUp);
94
+ handle.addEventListener("pointercancel", onUp);
95
+ };
96
+ return (
97
+ <div
98
+ onPointerDown={onPointerDown}
99
+ onDoubleClick={(e) => {
100
+ e.preventDefault();
101
+ e.stopPropagation();
102
+ onAutoFit();
103
+ }}
104
+ title="Drag to resize · double-click to fit"
105
+ style={{ touchAction: "none" }}
106
+ className="absolute right-0 top-0 bottom-0 z-20 w-1.5 cursor-ew-resize hover:bg-accent/40"
107
+ />
108
+ );
109
+ };
110
+
30
111
  const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSelectRow }) => {
31
112
  const stream = useBulkSubscription(cart, entity);
32
113
  const scrollerRef = React.useRef<HTMLDivElement | null>(null);
@@ -37,6 +118,12 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
37
118
  const [popover, setPopover] = React.useState<ValuePopoverData | null>(null);
38
119
  // Bump on every chunk arrival without re-rendering each FRAME_DATA.
39
120
  const [tick, setTick] = React.useState(0);
121
+ // Per-column width overrides (px), keyed by field name. Seeded from
122
+ // localStorage; remounts on cart/entity change (keyed at the call site).
123
+ const [overrides, setOverrides] = React.useState<Record<string, number>>(
124
+ () => loadColWidths(cart, entity),
125
+ );
126
+ const widthFor = (name: string) => overrides[name] ?? defaultColWidth(name);
40
127
 
41
128
  React.useEffect(() => {
42
129
  stream.setOnRender(() => setTick((n) => n + 1));
@@ -84,9 +171,41 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
84
171
  };
85
172
 
86
173
  const searchDisabled = searchFields.length === 0;
87
- const totalColW = GUTTER_W + columns.reduce((s, c) => s + colWidth(c.name), 0);
88
174
  const idColIndex = columns.findIndex((c) => c.name === "id");
89
175
 
176
+ // Drive every width off CSS vars on the scroll root so a drag mutates one
177
+ // property and resizes the whole virtualized column with no React re-render.
178
+ const widthVars = React.useMemo(() => {
179
+ const style: React.CSSProperties & Record<string, string> = { ["--gw"]: `${GUTTER_W}px` };
180
+ const parts = ["var(--gw)"];
181
+ columns.forEach((c, i) => {
182
+ style[`--cw-${i}`] = `${widthFor(c.name)}px`;
183
+ parts.push(`var(--cw-${i})`);
184
+ });
185
+ style["--total-w"] = `calc(${parts.join(" + ")})`;
186
+ return style;
187
+ // eslint-disable-next-line react-hooks/exhaustive-deps
188
+ }, [columns, overrides]);
189
+
190
+ const commitWidth = (name: string, w: number) => {
191
+ setOverrides((o) => ({ ...o, [name]: w }));
192
+ saveColWidth(cart, entity, name, w);
193
+ };
194
+
195
+ const autoFit = (colIndex: number, name: string) => {
196
+ const ctx = measureCtx();
197
+ const root = scrollerRef.current;
198
+ if (!ctx || !root) return;
199
+ ctx.font = `12px ${getComputedStyle(root).fontFamily}`;
200
+ let w = ctx.measureText(name).width;
201
+ const cap = Math.min(rows.length, 1000);
202
+ for (let r = 0; r < cap; r++) w = Math.max(w, ctx.measureText(rows[r]?.[colIndex] ?? "").width);
203
+ // px-2 padding (16) + border slack (2).
204
+ const fitted = Math.max(MIN_COL_W, Math.min(MAX_COL_W, Math.ceil(w) + 18));
205
+ root.style.setProperty(`--cw-${colIndex}`, `${fitted}px`);
206
+ commitWidth(name, fitted);
207
+ };
208
+
90
209
  const scrollRowIntoView = (r: number) => {
91
210
  const el = scrollerRef.current;
92
211
  if (!el) return;
@@ -143,8 +262,8 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
143
262
  <div className="shrink-0 text-[11px] text-muted-foreground tabular-nums">
144
263
  {stream.error ? (
145
264
  <span className="text-red-400">{stream.error}</span>
146
- ) : !stream.connected ? (
147
- <span>connecting…</span>
265
+ ) : columns.length === 0 ? (
266
+ <span>{stream.connected ? "loading…" : "connecting…"}</span>
148
267
  ) : (
149
268
  <span>
150
269
  {stream.loadedRows.toLocaleString()} / {stream.totalRows.toLocaleString()} rows
@@ -169,29 +288,36 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
169
288
  }}
170
289
  className="group/table relative min-h-0 flex-1 overflow-auto font-mono outline-none"
171
290
  >
172
- <div style={{ width: totalColW }}>
291
+ <div style={{ ...widthVars, width: "var(--total-w)" }}>
173
292
  {/* Sticky header lives inside the scroller so it shares the native
174
293
  horizontal scroll with the rows — no JS transform sync, no jitter. */}
175
294
  <div
176
- className="sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] uppercase tracking-wider text-muted-foreground"
177
- style={{ width: totalColW }}
295
+ className="sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] text-muted-foreground"
296
+ style={{ width: "var(--total-w)" }}
178
297
  >
179
298
  <div
180
- style={{ width: GUTTER_W }}
299
+ style={{ width: "var(--gw)" }}
181
300
  className="shrink-0 border-r border-border"
182
301
  />
183
- {columns.map((c) => (
302
+ {columns.map((c, ci) => (
184
303
  <div
185
304
  key={c.name}
186
- style={{ width: colWidth(c.name) }}
187
- className="flex items-center overflow-hidden border-r border-border px-2"
305
+ style={{ width: `var(--cw-${ci})` }}
306
+ className="relative flex items-center overflow-hidden border-r border-border px-2"
188
307
  >
189
308
  <span className="truncate">{c.name}</span>
309
+ <ColResizeHandle
310
+ index={ci}
311
+ startWidth={widthFor(c.name)}
312
+ scrollerRef={scrollerRef}
313
+ onCommit={(w) => commitWidth(c.name, w)}
314
+ onAutoFit={() => autoFit(ci, c.name)}
315
+ />
190
316
  </div>
191
317
  ))}
192
318
  </div>
193
319
 
194
- <div style={{ height: totalH, width: totalColW, position: "relative" }}>
320
+ <div style={{ height: totalH, width: "var(--total-w)", position: "relative" }}>
195
321
  {rows.slice(start, end).map((row, i) => {
196
322
  const rowIdx = start + i;
197
323
  return (
@@ -200,7 +326,7 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
200
326
  style={{
201
327
  top: rowIdx * ROW_H,
202
328
  height: ROW_H,
203
- width: totalColW,
329
+ width: "var(--total-w)",
204
330
  }}
205
331
  className={cn(
206
332
  "absolute left-0 flex text-[12px] leading-none",
@@ -211,7 +337,7 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
211
337
  type="button"
212
338
  title="Open row detail"
213
339
  onClick={() => handleRowClick(rowIdx)}
214
- style={{ width: GUTTER_W }}
340
+ style={{ width: "var(--gw)" }}
215
341
  className={cn(
216
342
  "flex shrink-0 items-center justify-center border-r border-border/60",
217
343
  "cursor-pointer text-[10px] tabular-nums text-muted-foreground/60",
@@ -251,7 +377,7 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
251
377
  editable,
252
378
  });
253
379
  }}
254
- style={{ width: colWidth(c.name) }}
380
+ style={{ width: `var(--cw-${ci})` }}
255
381
  className={cn(
256
382
  "flex cursor-pointer items-center overflow-hidden border-r border-border/60 px-2",
257
383
  "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground",
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/table/data-table.tsx", "../src/core/utils.ts", "../src/table/value-popover.tsx"],
4
- "sourcesContent": ["import React from \"react\";\nimport { useBulkSubscription } from \"../core/use-bulk-stream\";\nimport { cn } from \"../core/utils\";\nimport { TYPE_DATETIME, TYPE_DOCUMENT } from \"../generated/bulk-protocol\";\nimport ValuePopover, { type ValuePopoverData } from \"./value-popover\";\n\nconst ROW_H = 22;\nconst OVERSCAN = 6;\nconst MIN_COL_W = 80;\nconst GUTTER_W = 32;\n\nconst READONLY_FIELDS = new Set([\"id\", \"createdAt\", \"updatedAt\"]);\nconst READONLY_TYPES = new Set([TYPE_DATETIME, TYPE_DOCUMENT]);\n\ntype DataTableProps = {\n cart: string;\n entity: string;\n searchFields: readonly string[];\n onSelectRow: (rowJson: Record<string, string>) => void;\n};\n\nfunction colWidth(name: string): number {\n if (name === \"id\") return 220;\n if (name === \"createdAt\" || name === \"updatedAt\") return 180;\n if (name === \"email\" || name === \"phone\") return 180;\n if (name.endsWith(\"At\")) return 180;\n return Math.max(MIN_COL_W, Math.min(240, name.length * 9 + 24));\n}\n\nconst DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSelectRow }) => {\n const stream = useBulkSubscription(cart, entity);\n const scrollerRef = React.useRef<HTMLDivElement | null>(null);\n const [scrollTop, setScrollTop] = React.useState(0);\n const [viewportH, setViewportH] = React.useState(0);\n const [query, setQuery] = React.useState(\"\");\n const [selected, setSelected] = React.useState<{ r: number; c: number } | null>(null);\n const [popover, setPopover] = React.useState<ValuePopoverData | null>(null);\n // Bump on every chunk arrival without re-rendering each FRAME_DATA.\n const [tick, setTick] = React.useState(0);\n\n React.useEffect(() => {\n stream.setOnRender(() => setTick((n) => n + 1));\n return () => stream.setOnRender(null);\n }, [stream]);\n\n // Re-run once the scroller actually mounts. On first load columns are empty,\n // so the \"Waiting for schema\u2026\" placeholder renders instead of the scroller \u2014\n // scrollerRef is null and the observer never attaches, leaving viewportH at 0\n // (only OVERSCAN rows windowed). Keying on columns-present re-attaches the\n // moment the schema arrives and the scroller appears.\n const hasScroller = stream.columns.length > 0;\n React.useEffect(() => {\n const el = scrollerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => setViewportH(el.clientHeight));\n ro.observe(el);\n setViewportH(el.clientHeight);\n return () => ro.disconnect();\n }, [hasScroller]);\n\n const rows = stream.store.rows;\n const columns = stream.columns;\n\n const totalH = rows.length * ROW_H;\n const start = Math.max(0, Math.floor(scrollTop / ROW_H) - OVERSCAN);\n const visibleCount = Math.max(0, Math.ceil(viewportH / ROW_H) + OVERSCAN * 2);\n const end = Math.min(rows.length, start + visibleCount);\n\n const onSearchKey = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n stream.search(query.trim());\n } else if (e.key === \"Escape\") {\n setQuery(\"\");\n stream.clearSearch();\n }\n };\n\n const handleRowClick = (rowIdx: number) => {\n const r = rows[rowIdx];\n if (!r) return;\n const out: Record<string, string> = {};\n for (let i = 0; i < columns.length; i++) out[columns[i]!.name] = r[i] ?? \"\";\n onSelectRow(out);\n };\n\n const searchDisabled = searchFields.length === 0;\n const totalColW = GUTTER_W + columns.reduce((s, c) => s + colWidth(c.name), 0);\n const idColIndex = columns.findIndex((c) => c.name === \"id\");\n\n const scrollRowIntoView = (r: number) => {\n const el = scrollerRef.current;\n if (!el) return;\n const top = r * ROW_H;\n if (top < el.scrollTop) el.scrollTop = top;\n else if (top + ROW_H > el.scrollTop + el.clientHeight)\n el.scrollTop = top + ROW_H - el.clientHeight;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (rows.length === 0 || columns.length === 0) return;\n if ((e.metaKey || e.ctrlKey) && (e.key === \"c\" || e.key === \"C\")) {\n if (!selected) return;\n const v = rows[selected.r]?.[selected.c] ?? \"\";\n void navigator.clipboard?.writeText(v);\n e.preventDefault();\n return;\n }\n const cur = selected ?? { r: start, c: 0 };\n let { r, c } = cur;\n if (e.key === \"ArrowUp\") r--;\n else if (e.key === \"ArrowDown\") r++;\n else if (e.key === \"ArrowLeft\") c--;\n else if (e.key === \"ArrowRight\") c++;\n else return;\n e.preventDefault();\n r = Math.max(0, Math.min(rows.length - 1, r));\n c = Math.max(0, Math.min(columns.length - 1, c));\n setSelected({ r, c });\n setPopover(null);\n scrollRowIntoView(r);\n };\n\n return (\n <div className=\"flex h-full flex-col bg-background\">\n <div className=\"flex h-7 shrink-0 items-center gap-2 border-b border-border px-2\">\n <input\n type=\"text\"\n value={query}\n disabled={searchDisabled}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onSearchKey}\n placeholder={\n searchDisabled\n ? \"Search disabled (no @@search declared)\"\n : `Search ${searchFields.join(\", \")} \u23CE`\n }\n className={cn(\n \"h-5 flex-1 rounded-sm border border-border bg-background px-2 text-[12px] outline-none\",\n \"placeholder:text-muted-foreground/70 focus:border-accent-foreground/30\",\n searchDisabled && \"opacity-50 cursor-not-allowed\",\n )}\n />\n <div className=\"shrink-0 text-[11px] text-muted-foreground tabular-nums\">\n {stream.error ? (\n <span className=\"text-red-400\">{stream.error}</span>\n ) : !stream.connected ? (\n <span>connecting\u2026</span>\n ) : (\n <span>\n {stream.loadedRows.toLocaleString()} / {stream.totalRows.toLocaleString()} rows\n {stream.streaming ? \" \u00B7 streaming\" : \"\"}\n </span>\n )}\n </div>\n </div>\n\n {columns.length === 0 ? (\n <div className=\"flex flex-1 items-center justify-center text-[12px] text-muted-foreground\">\n {stream.error ? stream.error : \"Waiting for schema\u2026\"}\n </div>\n ) : (\n <div\n ref={scrollerRef}\n tabIndex={0}\n onKeyDown={onKeyDown}\n onScroll={(e) => {\n setScrollTop((e.target as HTMLDivElement).scrollTop);\n if (popover) setPopover(null);\n }}\n className=\"group/table relative min-h-0 flex-1 overflow-auto font-mono outline-none\"\n >\n <div style={{ width: totalColW }}>\n {/* Sticky header lives inside the scroller so it shares the native\n horizontal scroll with the rows \u2014 no JS transform sync, no jitter. */}\n <div\n className=\"sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] uppercase tracking-wider text-muted-foreground\"\n style={{ width: totalColW }}\n >\n <div\n style={{ width: GUTTER_W }}\n className=\"shrink-0 border-r border-border\"\n />\n {columns.map((c) => (\n <div\n key={c.name}\n style={{ width: colWidth(c.name) }}\n className=\"flex items-center overflow-hidden border-r border-border px-2\"\n >\n <span className=\"truncate\">{c.name}</span>\n </div>\n ))}\n </div>\n\n <div style={{ height: totalH, width: totalColW, position: \"relative\" }}>\n {rows.slice(start, end).map((row, i) => {\n const rowIdx = start + i;\n return (\n <div\n key={rowIdx}\n style={{\n top: rowIdx * ROW_H,\n height: ROW_H,\n width: totalColW,\n }}\n className={cn(\n \"absolute left-0 flex text-[12px] leading-none\",\n rowIdx % 2 === 0 ? \"bg-background\" : \"bg-muted/20\",\n )}\n >\n <button\n type=\"button\"\n title=\"Open row detail\"\n onClick={() => handleRowClick(rowIdx)}\n style={{ width: GUTTER_W }}\n className={cn(\n \"flex shrink-0 items-center justify-center border-r border-border/60\",\n \"cursor-pointer text-[10px] tabular-nums text-muted-foreground/60\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {rowIdx + 1}\n </button>\n {columns.map((c, ci) => {\n const isSel = selected?.r === rowIdx && selected?.c === ci;\n return (\n <div\n key={c.name}\n data-selected={isSel}\n onClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n setPopover(null);\n }}\n onDoubleClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n const editable =\n idColIndex >= 0 &&\n !READONLY_FIELDS.has(c.name) &&\n !READONLY_TYPES.has(c.type);\n setPopover({\n r: rowIdx,\n c: ci,\n colName: c.name,\n value: row[ci] ?? \"\",\n rect: (e.currentTarget as HTMLElement).getBoundingClientRect(),\n cart,\n entity,\n rowId: idColIndex >= 0 ? row[idColIndex] ?? \"\" : \"\",\n colType: c.type,\n editable,\n });\n }}\n style={{ width: colWidth(c.name) }}\n className={cn(\n \"flex cursor-pointer items-center overflow-hidden border-r border-border/60 px-2\",\n \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground\",\n \"data-[selected=true]:[box-shadow:inset_0_0_0_1px_#60a5fa]\",\n )}\n title={row[ci]}\n >\n <span className=\"truncate\">{row[ci]}</span>\n </div>\n );\n })}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n {popover ? <ValuePopover data={popover} onClose={() => setPopover(null)} /> : null}\n {/* tick is bumped on each data chunk to force a re-render of the rows. */}\n {tick < 0 && <span />}\n </div>\n );\n};\n\nexport default DataTable;\n", "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", "import React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../core/utils\";\nimport { useDevToolsConfig } from \"../core/config\";\nimport {\n TYPE_U8,\n TYPE_U16,\n TYPE_U32,\n TYPE_U64,\n TYPE_S8,\n TYPE_S16,\n TYPE_S32,\n TYPE_S64,\n TYPE_BOOL,\n TYPE_FLOAT32,\n TYPE_FLOAT64,\n} from \"../generated/bulk-protocol\";\n\nconst WIDTH = 420;\n\nexport type ValuePopoverData = {\n r: number;\n c: number;\n colName: string;\n value: string;\n rect: DOMRect;\n cart: string;\n entity: string;\n rowId: string;\n colType: number;\n editable: boolean;\n};\n\nconst INT_TYPES = new Set([\n TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64,\n TYPE_S8, TYPE_S16, TYPE_S32, TYPE_S64,\n]);\nconst FLOAT_TYPES = new Set([TYPE_FLOAT32, TYPE_FLOAT64]);\n\nfunction position(rect: DOMRect): React.CSSProperties {\n const margin = 12;\n const left = Math.max(\n margin,\n Math.min(rect.left, window.innerWidth - WIDTH - margin),\n );\n const roomBelow = window.innerHeight - rect.bottom;\n const top = roomBelow >= 180 ? rect.bottom + 6 : Math.max(margin, rect.top - 188);\n return { left, top, width: WIDTH };\n}\n\n// Coerce the edited string to the JSON value the update handler expects for the\n// column's wire type. Returns the value or an error string.\nfunction coerce(draft: string, colType: number): { value: unknown } | { error: string } {\n if (colType === TYPE_BOOL) return { value: draft === \"true\" };\n if (INT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isInteger(n)) return { error: \"Expected an integer\" };\n return { value: n };\n }\n if (FLOAT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isFinite(n)) return { error: \"Expected a number\" };\n return { value: n };\n }\n return { value: draft };\n}\n\nconst ValuePopover: React.FC<{ data: ValuePopoverData; onClose: () => void }> = ({\n data,\n onClose,\n}) => {\n const { apiBaseUrl } = useDevToolsConfig();\n const [copied, setCopied] = React.useState(false);\n const [draft, setDraft] = React.useState(data.value);\n const [saving, setSaving] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n setDraft(data.value);\n setError(null);\n }, [data.value, data.r, data.c]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(data.value);\n setCopied(true);\n } catch {\n /* clipboard unavailable */\n }\n };\n\n const save = async () => {\n const result = coerce(draft, data.colType);\n if (\"error\" in result) {\n setError(result.error);\n return;\n }\n setSaving(true);\n setError(null);\n try {\n const r = await fetch(\n `${apiBaseUrl}/api/${data.cart}/${data.entity.toLowerCase()}/${encodeURIComponent(data.rowId)}`,\n {\n method: \"PUT\",\n credentials: \"include\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ [data.colName]: result.value }),\n },\n );\n if (!r.ok) {\n setError((await r.text()) || `HTTP ${r.status}`);\n setSaving(false);\n return;\n }\n // Success: the update handler broadcasts FRAME_DELTA_UPD, which refreshes\n // the row in place \u2014 just close.\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n setSaving(false);\n }\n };\n\n const isBool = data.colType === TYPE_BOOL;\n const isNumeric = INT_TYPES.has(data.colType) || FLOAT_TYPES.has(data.colType);\n\n return createPortal(\n <>\n <div className=\"fixed inset-0 z-[60]\" onClick={onClose} />\n <div\n className={cn(\n \"nimbit-devtools dark fixed z-[61] rounded-md border border-border bg-popover p-3\",\n \"text-[12px] text-popover-foreground shadow-lg\",\n )}\n style={position(data.rect)}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-2 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-foreground\">{data.colName}</div>\n <div className=\"text-[11px] text-muted-foreground\">Row {data.r + 1}</div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={copy}\n className={cn(\n \"rounded border border-border px-2 py-1 text-[11px] leading-none\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n {data.editable ? (\n <button\n type=\"button\"\n onClick={save}\n disabled={saving}\n className={cn(\n \"rounded border border-border bg-primary/90 px-2 py-1 text-[11px] leading-none text-primary-foreground\",\n \"hover:bg-primary disabled:opacity-50\",\n )}\n >\n {saving ? \"Saving\u2026\" : \"Save\"}\n </button>\n ) : null}\n </div>\n </div>\n\n {data.editable ? (\n <>\n {isBool ? (\n <select\n value={draft}\n disabled={saving}\n onChange={(e) => setDraft(e.target.value)}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 text-[12px] outline-none focus:border-accent-foreground/30\"\n >\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n ) : isNumeric ? (\n <input\n type=\"number\"\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") void save();\n }}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 font-mono text-[12px] outline-none focus:border-accent-foreground/30\"\n />\n ) : (\n <textarea\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n className=\"max-h-48 min-h-[3rem] w-full resize-y overflow-auto rounded-sm border border-border bg-background p-2 font-mono text-[11px] leading-5 outline-none focus:border-accent-foreground/30\"\n />\n )}\n {error ? (\n <div className=\"mt-2 break-words text-[11px] text-red-400\">{error}</div>\n ) : null}\n </>\n ) : (\n <div className=\"max-h-48 overflow-auto rounded-sm bg-muted/40 p-2 font-mono text-[11px] leading-5 whitespace-pre-wrap break-words\">\n {data.value.length > 0 ? (\n data.value\n ) : (\n <span className=\"font-sans italic text-muted-foreground\">(empty)</span>\n )}\n </div>\n )}\n </div>\n </>,\n document.body,\n );\n};\n\nexport default ValuePopover;\n"],
5
- "mappings": "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",
6
- "names": ["React", "jsx", "jsxs", "React"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/editor/nbt-editor.tsx", "../src/editor/nbt-language.ts", "../src/editor/lsp-extensions.ts", "../src/editor/lsp-client.ts"],
4
- "sourcesContent": ["// CodeMirror 6 React wrapper for one NBT buffer. Recreated per uri (keyed by\n// the host); owns the EditorView lifecycle, Ctrl+S save, and the LSP wiring.\n\nimport React from \"react\";\nimport { EditorState } from \"@codemirror/state\";\nimport {\n EditorView,\n keymap,\n lineNumbers,\n drawSelection,\n highlightActiveLine,\n} from \"@codemirror/view\";\nimport {\n defaultKeymap,\n history,\n historyKeymap,\n indentWithTab,\n} from \"@codemirror/commands\";\nimport {\n syntaxHighlighting,\n defaultHighlightStyle,\n bracketMatching,\n} from \"@codemirror/language\";\nimport { lintGutter } from \"@codemirror/lint\";\nimport { searchKeymap } from \"@codemirror/search\";\nimport { completionKeymap, closeBrackets } from \"@codemirror/autocomplete\";\nimport { nbtLanguageSupport } from \"./nbt-language\";\nimport { lspExtensions, type GotoDefHandler } from \"./lsp-extensions\";\nimport { NbtLspClient } from \"./lsp-client\";\n\n// Match the devtools panel chrome via its CSS variables (we render inside the\n// .nimbit-devtools subtree, so these resolve to the scoped theme).\nconst nbtTheme = EditorView.theme(\n {\n \"&\": {\n height: \"100%\",\n fontSize: \"12px\",\n backgroundColor: \"var(--background)\",\n color: \"var(--foreground)\",\n },\n \".cm-content\": { fontFamily: \"ui-monospace, monospace\" },\n \".cm-gutters\": {\n backgroundColor: \"var(--background)\",\n color: \"var(--muted-foreground)\",\n borderRight: \"1px solid var(--border)\",\n },\n \".cm-activeLine\": { backgroundColor: \"color-mix(in srgb, var(--accent) 35%, transparent)\" },\n \"&.cm-focused\": { outline: \"none\" },\n \".cm-tooltip\": {\n backgroundColor: \"var(--popover)\",\n color: \"var(--popover-foreground)\",\n border: \"1px solid var(--border)\",\n },\n },\n { dark: true },\n);\n\nexport type NbtEditorProps = {\n uri: string;\n value: string;\n readOnly?: boolean;\n lsp?: NbtLspClient | null;\n onChange?: (text: string) => void;\n onSave?: (text: string) => void;\n onGotoDefinition?: GotoDefHandler;\n};\n\nexport const NbtEditor: React.FC<NbtEditorProps> = ({\n uri,\n value,\n readOnly = false,\n lsp,\n onChange,\n onSave,\n onGotoDefinition,\n}) => {\n const hostRef = React.useRef<HTMLDivElement | null>(null);\n const viewRef = React.useRef<EditorView | null>(null);\n\n // Latest callbacks without recreating the view.\n const onChangeRef = React.useRef(onChange);\n onChangeRef.current = onChange;\n const onSaveRef = React.useRef(onSave);\n onSaveRef.current = onSave;\n const onGotoRef = React.useRef(onGotoDefinition);\n onGotoRef.current = onGotoDefinition;\n\n React.useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n\n const extensions = [\n lineNumbers(),\n history(),\n drawSelection(),\n highlightActiveLine(),\n bracketMatching(),\n closeBrackets(),\n lintGutter(),\n syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n nbtLanguageSupport(),\n nbtTheme,\n keymap.of([\n {\n key: \"Mod-s\",\n run: (view) => {\n onSaveRef.current?.(view.state.doc.toString());\n return true;\n },\n },\n ...defaultKeymap,\n ...historyKeymap,\n ...searchKeymap,\n ...completionKeymap,\n indentWithTab,\n ]),\n EditorView.updateListener.of((u) => {\n if (u.docChanged) onChangeRef.current?.(u.state.doc.toString());\n }),\n EditorState.readOnly.of(readOnly),\n ];\n if (lsp && !readOnly) {\n extensions.push(\n lspExtensions(lsp, uri, (defUri, line) => onGotoRef.current?.(defUri, line)),\n );\n }\n\n const view = new EditorView({\n state: EditorState.create({ doc: value, extensions }),\n parent: host,\n });\n viewRef.current = view;\n return () => {\n view.destroy();\n viewRef.current = null;\n };\n // Recreate per buffer identity / mode; `value` is only the initial doc.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uri, readOnly, lsp]);\n\n return <div ref={hostRef} className=\"h-full min-h-0 overflow-hidden\" />;\n};\n", "// NBT syntax highlighting for CodeMirror 6 \u2014 a StreamLanguage tokenizer ported\n// from the console tokenizer (modules/lang_common/scan.jai) and the VSCode\n// grammar (editors/vscode/nbt/syntaxes/nbt.tmLanguage.json). Highlighting is\n// local; everything semantic (diagnostics/completion/hover) comes via the LSP.\n\nimport {\n StreamLanguage,\n LanguageSupport,\n indentUnit,\n type StreamParser,\n} from \"@codemirror/language\";\n\nconst KEYWORDS = new Set([\n \"entity\", \"enum\", \"struct\", \"const\", \"export\", \"extends\",\n \"fn\", \"on\", \"action\", \"activity\", \"task\", \"variant\", \"workflow\", \"command\",\n \"middleware\", \"schedule\", \"every\", \"jai\",\n \"migration\", \"test\", \"mock\", \"assert\",\n \"cartridge\", \"service\", \"component\", \"app\", \"route\", \"layout\",\n \"import\", \"from\",\n \"if\", \"elif\", \"else\", \"while\", \"for\", \"in\", \"return\", \"break\", \"continue\",\n \"delete\", \"defer\", \"print\", \"sleep\", \"fail\",\n]);\n\nconst TYPES = new Set([\n \"string\", \"bool\", \"ulid\", \"document\", \"dict\", \"blob\", \"DateTime\",\n \"u8\", \"u16\", \"u32\", \"u64\", \"s8\", \"s16\", \"s32\", \"s64\",\n \"int\", \"integer\", \"float\", \"float64\", \"f32\", \"f64\", \"double\",\n \"time\", \"bytes\", \"any\", \"name\", \"ref\",\n]);\n\ntype NbtState = {\n tripleString: boolean;\n};\n\n// NBT strings carry NO escapes (scan.jai consumes to the bare closing quote),\n// so a backslash before a quote does not extend the string.\nfunction eatSingleLineString(stream: StringStream, quote: string): string {\n while (!stream.eol()) {\n if (stream.next() === quote) break;\n }\n return \"string\";\n}\n\ntype StringStream = Parameters<StreamParser<NbtState>[\"token\"]>[0];\n\nconst nbtParser: StreamParser<NbtState> = {\n name: \"nbt\",\n startState: () => ({ tripleString: false }),\n token(stream, state) {\n if (state.tripleString) {\n while (!stream.eol()) {\n if (stream.match('\"\"\"')) {\n state.tripleString = false;\n return \"string\";\n }\n stream.next();\n }\n return \"string\";\n }\n\n if (stream.eatSpace()) return null;\n\n const ch = stream.peek();\n if (ch == null) return null;\n\n if (ch === \"#\") {\n stream.skipToEnd();\n return \"comment\";\n }\n\n if (stream.match('\"\"\"')) {\n state.tripleString = true;\n // Rest of line is string body unless it closes on the same line.\n while (!stream.eol()) {\n if (stream.match('\"\"\"')) {\n state.tripleString = false;\n return \"string\";\n }\n stream.next();\n }\n return \"string\";\n }\n\n if (ch === '\"') {\n stream.next();\n return eatSingleLineString(stream, '\"');\n }\n if (ch === \"'\") {\n // Like the scanner: only a string opener when not glued to an identifier\n // (lets contractions inside docs pass).\n const before = stream.string.charAt(stream.pos - 1);\n if (!/[A-Za-z0-9_]/.test(before)) {\n stream.next();\n return eatSingleLineString(stream, \"'\");\n }\n stream.next();\n return null;\n }\n\n // f-strings: f\"...\" \u2014 body highlighted as a string (interpolation holes\n // are cosmetic only).\n if (ch === \"f\" && stream.string.charAt(stream.pos + 1) === '\"') {\n stream.next();\n stream.next();\n return eatSingleLineString(stream, '\"');\n }\n\n // @attr / @@attr\n if (ch === \"@\") {\n stream.next();\n stream.eat(\"@\");\n stream.eatWhile(/[A-Za-z0-9_]/);\n return \"meta\";\n }\n\n // Numbers. The optional fraction backtracks on `0..9` ranges (matches \"0\",\n // not \"0.\").\n if (/\\d/.test(ch)) {\n stream.match(/^\\d+(\\.\\d+)?/);\n return \"number\";\n }\n\n if (/[A-Za-z_]/.test(ch)) {\n stream.eatWhile(/[A-Za-z0-9_]/);\n const word = stream.current();\n if (word === \"true\" || word === \"false\") return \"atom\";\n if (KEYWORDS.has(word)) return \"keyword\";\n if (TYPES.has(word)) return \"typeName\";\n if (/^[A-Z]/.test(word)) return \"typeName\"; // entity/enum references\n return \"variableName\";\n }\n\n if (/[+\\-*/=<>!&|?.,:;]/.test(ch)) {\n stream.next();\n stream.eatWhile(/[+\\-*/=<>!&|.]/);\n return \"operator\";\n }\n\n stream.next();\n return null;\n },\n languageData: {\n commentTokens: { line: \"#\" },\n },\n};\n\nexport const nbtLanguage = StreamLanguage.define(nbtParser);\n\nexport function nbtLanguageSupport(): LanguageSupport {\n return new LanguageSupport(nbtLanguage, [indentUnit.of(\" \")]);\n}\n", "// CodeMirror extensions wiring an editor buffer to the NbtLspClient:\n// full-text didChange sync (debounced), pushed publishDiagnostics \u2192 lint,\n// completion, hover, and F12 go-to-definition.\n\nimport { type Extension, type Text } from \"@codemirror/state\";\nimport {\n EditorView,\n ViewPlugin,\n type ViewUpdate,\n hoverTooltip,\n keymap,\n} from \"@codemirror/view\";\nimport {\n autocompletion,\n type CompletionContext,\n type CompletionResult,\n} from \"@codemirror/autocomplete\";\nimport { setDiagnostics, type Diagnostic } from \"@codemirror/lint\";\nimport {\n NbtLspClient,\n type LspDiagnostic,\n type LspPosition,\n type LspCompletionItem,\n type LspLocation,\n} from \"./lsp-client\";\n\nfunction toLspPos(doc: Text, offset: number): LspPosition {\n const line = doc.lineAt(offset);\n return { line: line.number - 1, character: offset - line.from };\n}\n\nfunction fromLspPos(doc: Text, pos: LspPosition): number {\n const lineNo = Math.min(Math.max(pos.line + 1, 1), doc.lines);\n const line = doc.line(lineNo);\n return Math.min(line.from + Math.max(pos.character, 0), line.to);\n}\n\nfunction toCmDiagnostics(doc: Text, diags: LspDiagnostic[]): Diagnostic[] {\n return diags.map((d) => {\n const from = fromLspPos(doc, d.range.start);\n let to = fromLspPos(doc, d.range.end);\n if (to <= from) to = Math.min(from + 1, doc.length);\n const severity =\n d.severity === 2 ? \"warning\" : d.severity === 3 || d.severity === 4 ? \"info\" : \"error\";\n return { from, to, severity, message: d.message, source: d.source ?? \"nbt\" };\n });\n}\n\n// LSP CompletionItemKind \u2192 CM completion type (drives the icon).\nfunction completionType(kind?: number): string {\n switch (kind) {\n case 3: return \"function\";\n case 5: return \"property\";\n case 13: return \"enum\";\n case 14: return \"keyword\";\n case 22: return \"class\";\n default: return \"variable\";\n }\n}\n\nexport type GotoDefHandler = (uri: string, line: number) => void;\n\nexport function lspExtensions(\n client: NbtLspClient,\n uri: string,\n onGotoDefinition?: GotoDefHandler,\n): Extension[] {\n // Sync + diagnostics plugin. didOpen on create, debounced didChange,\n // diagnostics pushed by the server are dispatched into the lint state.\n const syncPlugin = ViewPlugin.fromClass(\n class {\n private timer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: () => void;\n\n constructor(readonly view: EditorView) {\n client.didOpen(uri, view.state.doc.toString());\n this.unsubscribe = client.onDiagnostics(uri, (diags) => {\n const cm = toCmDiagnostics(this.view.state.doc, diags);\n this.view.dispatch(setDiagnostics(this.view.state, cm));\n });\n }\n\n update(u: ViewUpdate) {\n if (!u.docChanged) return;\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => {\n this.timer = null;\n client.didChange(uri, this.view.state.doc.toString());\n }, 200);\n }\n\n destroy() {\n if (this.timer) {\n clearTimeout(this.timer);\n client.didChange(uri, this.view.state.doc.toString());\n }\n this.unsubscribe();\n }\n },\n );\n\n const completionSource = async (\n ctx: CompletionContext,\n ): Promise<CompletionResult | null> => {\n const word = ctx.matchBefore(/\\w*/);\n if (!word) return null;\n if (word.from === word.to && !ctx.explicit) {\n // Only fire automatically right after a trigger character.\n const before = ctx.state.doc.sliceString(Math.max(0, ctx.pos - 1), ctx.pos);\n if (before !== \".\" && before !== \":\" && before !== '\"') return null;\n }\n // Flush any pending edit so the server completes against the live buffer.\n client.didChange(uri, ctx.state.doc.toString());\n let result;\n try {\n result = await client.completion(uri, toLspPos(ctx.state.doc, ctx.pos));\n } catch {\n return null;\n }\n const items: LspCompletionItem[] = Array.isArray(result)\n ? result\n : (result?.items ?? []);\n if (items.length === 0) return null;\n return {\n from: word.from,\n options: items.map((it) => ({\n label: it.label,\n type: completionType(it.kind),\n detail: it.detail,\n })),\n };\n };\n\n const hover = hoverTooltip(async (view, pos) => {\n let result;\n try {\n result = await client.hover(uri, toLspPos(view.state.doc, pos));\n } catch {\n return null;\n }\n const value = result?.contents?.value;\n if (!value) return null;\n return {\n pos,\n create: () => {\n const dom = document.createElement(\"pre\");\n dom.className = \"nbt-hover-tooltip\";\n dom.style.cssText =\n \"margin:0;padding:6px 8px;max-width:480px;white-space:pre-wrap;font-size:11px;\";\n // Server emits fenced markdown code blocks; strip the fences, show text.\n dom.textContent = value.replace(/```\\w*\\n?/g, \"\");\n return { dom };\n },\n };\n });\n\n const gotoDef = keymap.of([\n {\n key: \"F12\",\n run: (view) => {\n const pos = toLspPos(view.state.doc, view.state.selection.main.head);\n client\n .definition(uri, pos)\n .then((result) => {\n const loc: LspLocation | null = Array.isArray(result)\n ? (result[0] ?? null)\n : result;\n if (!loc?.uri) return;\n if (loc.uri === uri) {\n const off = fromLspPos(view.state.doc, loc.range.start);\n view.dispatch({\n selection: { anchor: off },\n scrollIntoView: true,\n });\n } else {\n onGotoDefinition?.(loc.uri, loc.range.start.line);\n }\n })\n .catch(() => {});\n return true;\n },\n },\n ]);\n\n return [\n syncPlugin,\n autocompletion({ override: [completionSource] }),\n hover,\n gotoDef,\n ];\n}\n", "// Minimal LSP client over WebSocket for the console's dev-mode nbt_lsp bridge\n// (/_console/dev/lsp). One JSON-RPC message per WS text frame \u2014 no\n// Content-Length framing (the bridge's write_proc emits bare JSON).\n//\n// Hand-rolled on purpose: the server speaks ~10 methods with full-text sync,\n// and a library client (codemirror-languageserver) would drag transitive\n// runtime deps into every host app.\n\nexport type LspPosition = { line: number; character: number };\nexport type LspRange = { start: LspPosition; end: LspPosition };\nexport type LspDiagnostic = {\n range: LspRange;\n severity?: number; // 1=error 2=warning 3=info 4=hint\n message: string;\n source?: string;\n};\nexport type LspCompletionItem = {\n label: string;\n kind?: number;\n detail?: string;\n};\nexport type LspLocation = { uri: string; range: LspRange };\n\ntype Pending = {\n resolve: (v: unknown) => void;\n reject: (e: Error) => void;\n};\n\nexport class NbtLspClient {\n private url: string;\n private rootUri: string | null = null;\n private ws: WebSocket | null = null;\n private nextId = 1;\n private pending = new Map<number, Pending>();\n private diagHandlers = new Map<string, (d: LspDiagnostic[]) => void>();\n // Open buffers, kept for replay across reconnects.\n private docs = new Map<string, { text: string; version: number }>();\n private ready = false;\n private queue: string[] = [];\n private closed = false;\n private retryMs = 500;\n\n constructor(url: string) {\n this.url = url;\n this.connect();\n }\n\n dispose() {\n this.closed = true;\n this.ws?.close();\n this.pending.forEach((p) => p.reject(new Error(\"lsp client disposed\")));\n this.pending.clear();\n }\n\n private connect() {\n if (this.closed) return;\n const ws = new WebSocket(this.url);\n this.ws = ws;\n ws.onopen = () => {\n this.retryMs = 500;\n if (this.rootUri) this.sendInitialize(this.rootUri);\n };\n ws.onmessage = (ev) => {\n if (typeof ev.data !== \"string\") return;\n this.onMessage(ev.data);\n };\n ws.onclose = () => {\n this.ready = false;\n this.ws = null;\n for (const p of this.pending.values())\n p.reject(new Error(\"lsp connection closed\"));\n this.pending.clear();\n if (!this.closed) {\n setTimeout(() => this.connect(), this.retryMs);\n this.retryMs = Math.min(this.retryMs * 2, 10_000);\n }\n };\n }\n\n // Called once by the host with file://<projectRoot>; also re-sent after every\n // reconnect, followed by didOpen replays for tracked buffers.\n initialize(rootUri: string) {\n this.rootUri = rootUri;\n if (this.ws?.readyState === WebSocket.OPEN) this.sendInitialize(rootUri);\n }\n\n private sendInitialize(rootUri: string) {\n const id = this.nextId++;\n this.pending.set(id, {\n resolve: () => {\n this.sendRaw({ jsonrpc: \"2.0\", method: \"initialized\", params: {} });\n this.ready = true;\n for (const [uri, d] of this.docs) {\n this.sendRaw({\n jsonrpc: \"2.0\",\n method: \"textDocument/didOpen\",\n params: {\n textDocument: {\n uri,\n languageId: \"nbt\",\n version: d.version,\n text: d.text,\n },\n },\n });\n }\n const q = this.queue;\n this.queue = [];\n for (const m of q) this.ws?.send(m);\n },\n reject: () => {},\n });\n this.ws?.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n method: \"initialize\",\n params: { rootUri },\n }),\n );\n }\n\n private sendRaw(msg: object) {\n const s = JSON.stringify(msg);\n if (this.ready && this.ws?.readyState === WebSocket.OPEN) this.ws.send(s);\n else this.queue.push(s);\n }\n\n private onMessage(data: string) {\n let msg: any;\n try {\n msg = JSON.parse(data);\n } catch {\n return;\n }\n if (msg.id != null && (msg.result !== undefined || msg.error)) {\n const p = this.pending.get(msg.id);\n if (!p) return;\n this.pending.delete(msg.id);\n if (msg.error) p.reject(new Error(msg.error.message ?? \"lsp error\"));\n else p.resolve(msg.result);\n return;\n }\n if (msg.method === \"textDocument/publishDiagnostics\") {\n const uri = msg.params?.uri as string;\n const handler = this.diagHandlers.get(uri);\n if (handler) handler((msg.params?.diagnostics ?? []) as LspDiagnostic[]);\n }\n }\n\n request<T>(method: string, params: object): Promise<T> {\n const id = this.nextId++;\n const p = new Promise<T>((resolve, reject) => {\n this.pending.set(id, { resolve: resolve as Pending[\"resolve\"], reject });\n });\n this.sendRaw({ jsonrpc: \"2.0\", id, method, params });\n return p;\n }\n\n onDiagnostics(uri: string, handler: (d: LspDiagnostic[]) => void): () => void {\n this.diagHandlers.set(uri, handler);\n return () => {\n if (this.diagHandlers.get(uri) === handler) this.diagHandlers.delete(uri);\n };\n }\n\n didOpen(uri: string, text: string) {\n const existing = this.docs.get(uri);\n if (existing) {\n // Re-opening an already-tracked buffer (tab switch) \u2014 just resync.\n this.didChange(uri, text);\n return;\n }\n this.docs.set(uri, { text, version: 1 });\n this.sendRaw({\n jsonrpc: \"2.0\",\n method: \"textDocument/didOpen\",\n params: { textDocument: { uri, languageId: \"nbt\", version: 1, text } },\n });\n }\n\n didChange(uri: string, text: string) {\n const d = this.docs.get(uri);\n if (!d) return this.didOpen(uri, text);\n if (d.text === text) return;\n d.text = text;\n d.version += 1;\n this.sendRaw({\n jsonrpc: \"2.0\",\n method: \"textDocument/didChange\",\n params: {\n textDocument: { uri, version: d.version },\n contentChanges: [{ text }],\n },\n });\n }\n\n didClose(uri: string) {\n if (!this.docs.delete(uri)) return;\n this.sendRaw({\n jsonrpc: \"2.0\",\n method: \"textDocument/didClose\",\n params: { textDocument: { uri } },\n });\n }\n\n completion(uri: string, pos: LspPosition): Promise<{ items: LspCompletionItem[] } | LspCompletionItem[] | null> {\n return this.request(\"textDocument/completion\", {\n textDocument: { uri },\n position: pos,\n });\n }\n\n hover(uri: string, pos: LspPosition): Promise<{ contents?: { value?: string } } | null> {\n return this.request(\"textDocument/hover\", {\n textDocument: { uri },\n position: pos,\n });\n }\n\n definition(uri: string, pos: LspPosition): Promise<LspLocation | LspLocation[] | null> {\n return this.request(\"textDocument/definition\", {\n textDocument: { uri },\n position: pos,\n });\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["EditorView", "keymap", "EditorView", "keymap", "view"]
7
- }