@xenosystem/blocks 0.21.0 → 0.23.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.
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -1
- package/dist/chunk-76V25B3E.js +435 -0
- package/dist/chunk-YSMDUREC.js +238 -0
- package/dist/chunk-YZ6J6A67.js +454 -0
- package/dist/corpus/index.d.ts +449 -0
- package/dist/corpus/index.js +991 -0
- package/dist/data/index.js +4 -2
- package/dist/edit/index.d.ts +162 -289
- package/dist/edit/index.js +30 -415
- package/dist/icons/index.js +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/ops/index.js +4 -2
- package/dist/scale-XiQwxiFA.d.ts +130 -0
- package/dist/time/index.js +2 -1
- package/package.json +6 -2
- package/dist/chunk-6ZIWULSK.js +0 -629
|
@@ -0,0 +1,991 @@
|
|
|
1
|
+
import {
|
|
2
|
+
XenoGraphScale,
|
|
3
|
+
edgePath,
|
|
4
|
+
fitToBounds,
|
|
5
|
+
zoomAboutCursor
|
|
6
|
+
} from "../chunk-76V25B3E.js";
|
|
7
|
+
import {
|
|
8
|
+
MinusGlyph,
|
|
9
|
+
PlusGlyph
|
|
10
|
+
} from "../chunk-YSMDUREC.js";
|
|
11
|
+
import "../chunk-2KG3PWR4.js";
|
|
12
|
+
|
|
13
|
+
// src/corpus/shape/panel.ts
|
|
14
|
+
import { createElement } from "react";
|
|
15
|
+
import { createRoot } from "react-dom/client";
|
|
16
|
+
import { bindConfig } from "@xenosystem/block-sdk";
|
|
17
|
+
|
|
18
|
+
// src/corpus/shape/logic.ts
|
|
19
|
+
var NODE_W = 210;
|
|
20
|
+
var NODE_H = 74;
|
|
21
|
+
var COLUMN_W = 250;
|
|
22
|
+
var COLUMN_GAP = 40;
|
|
23
|
+
var ROW_STEP = 92;
|
|
24
|
+
var FIRST_ROW = 160;
|
|
25
|
+
var LANE_TOP = 40;
|
|
26
|
+
var LANE_CARD_H = 88;
|
|
27
|
+
var LENSES = [
|
|
28
|
+
{ id: "all", label: "Everything", question: "The whole corpus", empty: "This corpus has no claims yet." },
|
|
29
|
+
{ id: "lapsed", label: "False now", question: "What is false right now?", empty: "Nothing is false right now \u2014 every checked claim holds." },
|
|
30
|
+
{ id: "unchecked", label: "Unchecked", question: "What has nothing checking it?", empty: "Everything here is checked." }
|
|
31
|
+
];
|
|
32
|
+
var isLens = (v) => LENSES.some((l) => l.id === v);
|
|
33
|
+
var DEPTHS = [0, 1, 2, 3];
|
|
34
|
+
var isDepth = (v) => typeof v === "number" && DEPTHS.includes(v);
|
|
35
|
+
function inLens(n, lens) {
|
|
36
|
+
if (lens === "all") return true;
|
|
37
|
+
if (lens === "lapsed") return n.kind === "tenet" && n.state === "lapsed";
|
|
38
|
+
if (n.kind === "tenet") return n.state === "unobserved" || n.state === "never-run";
|
|
39
|
+
return n.kind === "missing" || n.kind === "uncovered" || n.kind === "proposal";
|
|
40
|
+
}
|
|
41
|
+
function neighbourhood(edges, start, depth) {
|
|
42
|
+
if (!start || depth <= 0) return null;
|
|
43
|
+
const adj = /* @__PURE__ */ new Map();
|
|
44
|
+
const link = (a, b) => {
|
|
45
|
+
const l = adj.get(a);
|
|
46
|
+
if (l) l.push(b);
|
|
47
|
+
else adj.set(a, [b]);
|
|
48
|
+
};
|
|
49
|
+
for (const e of edges) {
|
|
50
|
+
link(e.from, e.to);
|
|
51
|
+
link(e.to, e.from);
|
|
52
|
+
}
|
|
53
|
+
const seen = /* @__PURE__ */ new Set([start]);
|
|
54
|
+
let frontier = [start];
|
|
55
|
+
for (let d = 0; d < depth && frontier.length; d++) {
|
|
56
|
+
const next = [];
|
|
57
|
+
for (const k of frontier) for (const m of adj.get(k) ?? []) if (!seen.has(m)) {
|
|
58
|
+
seen.add(m);
|
|
59
|
+
next.push(m);
|
|
60
|
+
}
|
|
61
|
+
frontier = next;
|
|
62
|
+
}
|
|
63
|
+
return seen;
|
|
64
|
+
}
|
|
65
|
+
function visibleKeys(nodes, edges, lens, selected, depth) {
|
|
66
|
+
const near = neighbourhood(edges, selected, depth);
|
|
67
|
+
const out = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const n of nodes) {
|
|
69
|
+
if (n.key === selected) {
|
|
70
|
+
out.add(n.key);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (near && !near.has(n.key)) continue;
|
|
74
|
+
if (!near && !inLens(n, lens)) continue;
|
|
75
|
+
out.add(n.key);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
var COMPACT_BELOW = 0.5;
|
|
80
|
+
var compactFont = (zoom) => Math.min(12 / Math.max(zoom, 0.01), 44);
|
|
81
|
+
function layout(lanes, nodes) {
|
|
82
|
+
const boxes = [];
|
|
83
|
+
const placed = [];
|
|
84
|
+
let col = 0;
|
|
85
|
+
for (const lane of lanes) {
|
|
86
|
+
const mine = nodes.filter((n) => n.lane === lane.id);
|
|
87
|
+
const span = lane.role === "org" ? 2 : 1;
|
|
88
|
+
const x = 40 + col * COLUMN_W;
|
|
89
|
+
const colA = span === 2 ? mine.filter((n) => n.kind === "tenet" || n.kind === "ref" || n.kind === "proposal") : mine;
|
|
90
|
+
const colB = span === 2 ? mine.filter((n) => !colA.includes(n)) : [];
|
|
91
|
+
colA.forEach((n, i) => placed.push({ ...n, x, y: FIRST_ROW + i * ROW_STEP }));
|
|
92
|
+
colB.forEach((n, i) => placed.push({ ...n, x: x + COLUMN_W, y: FIRST_ROW + i * ROW_STEP }));
|
|
93
|
+
const rows = Math.max(1, colA.length, colB.length);
|
|
94
|
+
boxes.push({ lane, x, w: NODE_W * span + (span - 1) * COLUMN_GAP, h: FIRST_ROW - 24 + rows * ROW_STEP + 10 });
|
|
95
|
+
col += span;
|
|
96
|
+
}
|
|
97
|
+
const width = 40 + col * COLUMN_W;
|
|
98
|
+
const height = Math.max(400, ...boxes.map((b) => b.h + 24));
|
|
99
|
+
return { boxes, placed, width, height };
|
|
100
|
+
}
|
|
101
|
+
function emphasisKey(n) {
|
|
102
|
+
if (n.kind === "tenet") return n.state === "lapsed" ? "lapsed" : n.state === "held" || n.state === "attested" ? "held" : "unobserved";
|
|
103
|
+
if (n.kind === "missing") return "unobserved";
|
|
104
|
+
if (n.kind === "proposal" || n.kind === "uncovered") return "none";
|
|
105
|
+
if (n.kind === "ref") return "held";
|
|
106
|
+
return "rule";
|
|
107
|
+
}
|
|
108
|
+
function chipFor(state, opts = {}) {
|
|
109
|
+
if (opts.attested && (state === "attested" || state === "held")) return { label: "ATTESTED", bg: "var(--cs-accent-soft)", ink: "var(--cs-accent)", ring: "none" };
|
|
110
|
+
switch (state) {
|
|
111
|
+
case "lapsed":
|
|
112
|
+
return { label: "LAPSED", bg: "var(--cs-danger)", ink: "var(--cs-on-danger)", ring: "none" };
|
|
113
|
+
case "held":
|
|
114
|
+
return { label: "HELD", bg: "var(--cs-ok-soft)", ink: "var(--cs-ok-ink)", ring: "none" };
|
|
115
|
+
case "attested":
|
|
116
|
+
return { label: "ATTESTED", bg: "var(--cs-accent-soft)", ink: "var(--cs-accent)", ring: "none" };
|
|
117
|
+
case "unobserved":
|
|
118
|
+
case "never-run":
|
|
119
|
+
return { label: "UNKNOWN", bg: "transparent", ink: "var(--cs-ink2)", ring: "inset 0 0 0 1px var(--cs-ink3)" };
|
|
120
|
+
case "none":
|
|
121
|
+
return { label: "NO CHECK", bg: "var(--cs-warn)", ink: "var(--cs-on-warn)", ring: "none" };
|
|
122
|
+
case "rule":
|
|
123
|
+
return { label: opts.binding ? "CHARTER" : "DOCTRINE", bg: "var(--cs-accent-soft)", ink: "var(--cs-accent)", ring: "none" };
|
|
124
|
+
case "ref":
|
|
125
|
+
return { label: "REFERENCE", bg: "var(--cs-field)", ink: "var(--cs-ink2)", ring: "none" };
|
|
126
|
+
case "doc":
|
|
127
|
+
return { label: "DOCUMENT", bg: "var(--cs-field)", ink: "var(--cs-ink2)", ring: "none" };
|
|
128
|
+
case "missing":
|
|
129
|
+
return { label: "INHERITED_UNAVAILABLE", bg: "var(--cs-warn)", ink: "var(--cs-on-warn)", ring: "none" };
|
|
130
|
+
default:
|
|
131
|
+
return { label: state.toUpperCase(), bg: "var(--cs-field)", ink: "var(--cs-ink2)", ring: "none" };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
var cardChip = (n) => chipFor(n.kind === "tenet" ? n.state : n.kind === "uncovered" ? "none" : n.state, { attested: n.attested, binding: n.binding });
|
|
135
|
+
function cellFill(state) {
|
|
136
|
+
if (state === "lapsed") return { bg: "var(--cs-danger)", ring: "none" };
|
|
137
|
+
if (state === "held") return { bg: "var(--cs-ok)", ring: "none" };
|
|
138
|
+
if (state === "attested") return { bg: "var(--cs-accent)", ring: "none" };
|
|
139
|
+
return { bg: "transparent", ring: "inset 0 0 0 1px var(--cs-ink3)" };
|
|
140
|
+
}
|
|
141
|
+
function cadenceWord(c) {
|
|
142
|
+
const m = /^(\d+)([hd])$/.exec(c);
|
|
143
|
+
if (!m) return c;
|
|
144
|
+
const h = Number(m[1]) * (m[2] === "d" ? 24 : 1);
|
|
145
|
+
return h === 24 ? "daily" : h === 1 ? "hourly" : h === 168 ? "weekly" : `every ${c}`;
|
|
146
|
+
}
|
|
147
|
+
function kindLabel(n) {
|
|
148
|
+
if (n.kind === "tenet") return `Tenet \xB7 ${n.attested ? "attested" : n.cadence ? cadenceWord(n.cadence) : "daily"}`;
|
|
149
|
+
return { ref: "Reference", rule: "Doctrine", doc: "Document", missing: "Chain", uncovered: "Repository", proposal: "Proposal" }[n.kind] ?? n.kind;
|
|
150
|
+
}
|
|
151
|
+
function subLine(n) {
|
|
152
|
+
if (n.kind !== "tenet") return n.sub ?? "";
|
|
153
|
+
if (n.state === "unobserved" || n.state === "never-run") return `expect ${n.expect ?? "\u2014"} \xB7 ${n.state === "never-run" ? "never run" : "unknown"}`;
|
|
154
|
+
if (n.state === "attested") return "a person vouches \xB7 not checked";
|
|
155
|
+
return `expect ${n.expect ?? "\u2014"} \xB7 ${n.state === "held" ? "holds" : `got ${n.observed ?? "\u2014"}`}`;
|
|
156
|
+
}
|
|
157
|
+
function laneCaption(lane) {
|
|
158
|
+
const role = lane.role === "org" ? "Org corpus" : lane.role === "uncovered" ? "Unmanaged" : lane.role === "orphan" ? "Product \xB7 orphan" : lane.role === "ancestor" ? "Ancestor" : "Product";
|
|
159
|
+
const sub = lane.role === "uncovered" ? `${lane.uncovered?.total ?? 0} repositories \xB7 no corpus` : `${(lane.root ?? "").replace(/\\/g, "/").split("/").slice(-3).join("/")}${lane.inherits ? ` \xB7 inherits ${lane.inherits}` : ""}`;
|
|
160
|
+
return { role, sub };
|
|
161
|
+
}
|
|
162
|
+
var EDGE_STYLE = {
|
|
163
|
+
ref: ["var(--cs-edge)", "4 4", 1.3, "Reference (fact owned below)"],
|
|
164
|
+
dep: ["var(--cs-cap)", "", 1.6, "Depends on another corpus"],
|
|
165
|
+
breach: ["var(--cs-danger)", "5 4", 1.5, "Ancestor asserts a child\u2019s fact"],
|
|
166
|
+
pointer: ["var(--cs-accent)", "2 3", 1.4, "Rule points to a document"],
|
|
167
|
+
super: ["var(--cs-ink3)", "2 3", 1.3, "Supersession (recorded)"]
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/corpus/shape/types.ts
|
|
171
|
+
function isCorpusModel(v) {
|
|
172
|
+
if (!v || typeof v !== "object") return false;
|
|
173
|
+
const m = v;
|
|
174
|
+
if (!Array.isArray(m.lanes) || !Array.isArray(m.nodes) || !Array.isArray(m.edges)) return false;
|
|
175
|
+
const str = (x) => typeof x === "string" && x.length > 0;
|
|
176
|
+
return m.lanes.every((l) => !!l && typeof l === "object" && str(l.id) && typeof l.name === "string") && m.nodes.every((n) => !!n && typeof n === "object" && str(n.key) && str(n.lane) && typeof n.label === "string" && typeof n.state === "string" && typeof n.kind === "string") && m.edges.every((e) => !!e && typeof e === "object" && str(e.from) && str(e.to));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/corpus/shape/controller.ts
|
|
180
|
+
var SHAPE_ZOOM = { min: 0.2, max: 2 };
|
|
181
|
+
var ALL_ON = { lapsed: true, held: true, unobserved: true, rule: true, none: true };
|
|
182
|
+
var ShapeController = class {
|
|
183
|
+
host;
|
|
184
|
+
listeners = /* @__PURE__ */ new Set();
|
|
185
|
+
s;
|
|
186
|
+
fitted = false;
|
|
187
|
+
constructor(options) {
|
|
188
|
+
this.host = options.host;
|
|
189
|
+
this.s = {
|
|
190
|
+
model: null,
|
|
191
|
+
boxes: [],
|
|
192
|
+
placed: [],
|
|
193
|
+
width: 0,
|
|
194
|
+
height: 0,
|
|
195
|
+
visible: /* @__PURE__ */ new Set(),
|
|
196
|
+
shown: [],
|
|
197
|
+
selected: null,
|
|
198
|
+
lens: isLens(options.lens) ? options.lens : "all",
|
|
199
|
+
depth: isDepth(options.depth) ? options.depth : 0,
|
|
200
|
+
emphasis: { ...ALL_ON },
|
|
201
|
+
camera: { zoom: 0.5, x: 0, y: 0 },
|
|
202
|
+
size: { w: 0, h: 0 },
|
|
203
|
+
compact: true,
|
|
204
|
+
animate: false,
|
|
205
|
+
edgeLabels: true,
|
|
206
|
+
emptyAnswer: null,
|
|
207
|
+
focusHint: false
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// ── reading ────────────────────────────────────────────────────────────────
|
|
211
|
+
/** Bound, so `useSyncExternalStore(controller.subscribe, controller.getState)` works as written. */
|
|
212
|
+
getState = () => this.s;
|
|
213
|
+
subscribe = (fn) => {
|
|
214
|
+
this.listeners.add(fn);
|
|
215
|
+
return () => {
|
|
216
|
+
this.listeners.delete(fn);
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
set(patch) {
|
|
220
|
+
this.s = { ...this.s, ...patch };
|
|
221
|
+
this.s = { ...this.s, ...this.derive() };
|
|
222
|
+
for (const l of this.listeners) l();
|
|
223
|
+
}
|
|
224
|
+
derive() {
|
|
225
|
+
const { model, placed, lens, depth, selected, camera } = this.s;
|
|
226
|
+
const visible = visibleKeys(placed, model?.edges ?? [], lens, selected && !selected.startsWith("lane:") ? selected : null, depth);
|
|
227
|
+
const shown = placed.filter((n) => visible.has(n.key));
|
|
228
|
+
const focusing = depth > 0 && !!selected && !selected.startsWith("lane:");
|
|
229
|
+
const answers = shown.some((n) => n.key !== selected);
|
|
230
|
+
const emptyAnswer = model && !focusing && lens !== "all" && !answers ? LENSES.find((l) => l.id === lens).empty : null;
|
|
231
|
+
return { visible, shown, compact: camera.zoom < COMPACT_BELOW, emptyAnswer, focusHint: depth > 0 && !focusing };
|
|
232
|
+
}
|
|
233
|
+
// ── inputs (the host's) ────────────────────────────────────────────────────
|
|
234
|
+
/** A new picture. Anything that is not a model is ignored: a mis-wire must not blank the canvas. */
|
|
235
|
+
setModel(value) {
|
|
236
|
+
if (!isCorpusModel(value)) return;
|
|
237
|
+
const L = layout(value.lanes, value.nodes);
|
|
238
|
+
const keys = new Set(L.placed.map((n) => n.key));
|
|
239
|
+
const sel = this.s.selected;
|
|
240
|
+
const keep = sel && (keys.has(sel) || sel.startsWith("lane:") && value.lanes.some((l) => `lane:${l.id}` === sel)) ? sel : null;
|
|
241
|
+
this.set({ model: value, boxes: L.boxes, placed: L.placed, width: L.width, height: L.height, selected: keep });
|
|
242
|
+
if (!this.fitted && this.s.size.w) {
|
|
243
|
+
this.fitted = true;
|
|
244
|
+
this.fit(false);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Selection driven from outside (an inspector, a search). Does not echo back on `selection`. */
|
|
248
|
+
setSelection(value) {
|
|
249
|
+
if (value !== null && typeof value !== "string") return;
|
|
250
|
+
if (value === this.s.selected) return;
|
|
251
|
+
this.set({ selected: value });
|
|
252
|
+
this.afterSelect();
|
|
253
|
+
}
|
|
254
|
+
/** Emphasis from a host's filter rail. Unknown keys are ignored; missing keys stay on. */
|
|
255
|
+
setEmphasis(value) {
|
|
256
|
+
if (!value || typeof value !== "object") return;
|
|
257
|
+
const next = { ...ALL_ON };
|
|
258
|
+
for (const k of Object.keys(ALL_ON)) {
|
|
259
|
+
const v = value[k];
|
|
260
|
+
if (typeof v === "boolean") next[k] = v;
|
|
261
|
+
}
|
|
262
|
+
this.set({ emphasis: next });
|
|
263
|
+
}
|
|
264
|
+
// ── the person's actions ───────────────────────────────────────────────────
|
|
265
|
+
select(key) {
|
|
266
|
+
if (key === this.s.selected) return;
|
|
267
|
+
this.set({ selected: key });
|
|
268
|
+
this.host.emit("selection", key);
|
|
269
|
+
this.afterSelect();
|
|
270
|
+
}
|
|
271
|
+
open(key) {
|
|
272
|
+
if (this.s.placed.some((n) => n.key === key)) this.host.emit("intent", { kind: "open", key });
|
|
273
|
+
}
|
|
274
|
+
setLens(lens) {
|
|
275
|
+
if (!isLens(lens) || lens === this.s.lens) return;
|
|
276
|
+
this.set({ lens });
|
|
277
|
+
this.refit();
|
|
278
|
+
}
|
|
279
|
+
setDepth(depth) {
|
|
280
|
+
if (!isDepth(depth) || depth === this.s.depth) return;
|
|
281
|
+
this.set({ depth });
|
|
282
|
+
this.refit();
|
|
283
|
+
}
|
|
284
|
+
toggleEdgeLabels() {
|
|
285
|
+
this.set({ edgeLabels: !this.s.edgeLabels });
|
|
286
|
+
}
|
|
287
|
+
/** Arrow keys move the selection spatially: nearest card in that direction, off-axis distance weighed double. */
|
|
288
|
+
moveSelection(dir) {
|
|
289
|
+
const [dx, dy] = { right: [1, 0], left: [-1, 0], down: [0, 1], up: [0, -1] }[dir];
|
|
290
|
+
const from = this.s.placed.find((n) => n.key === this.s.selected);
|
|
291
|
+
if (!from) {
|
|
292
|
+
const first = this.s.shown[0] ?? this.s.placed[0];
|
|
293
|
+
if (first) this.select(first.key);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
let best = null, bd = Infinity;
|
|
297
|
+
for (const n of this.s.shown) {
|
|
298
|
+
if (n.key === from.key) continue;
|
|
299
|
+
const ox = n.x - from.x, oy = n.y - from.y, prim = ox * dx + oy * dy;
|
|
300
|
+
if (prim <= 0) continue;
|
|
301
|
+
const d = prim + Math.abs(dx ? oy : ox) * 2;
|
|
302
|
+
if (d < bd) {
|
|
303
|
+
bd = d;
|
|
304
|
+
best = n;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (best) this.select(best.key);
|
|
308
|
+
}
|
|
309
|
+
// ── camera ─────────────────────────────────────────────────────────────────
|
|
310
|
+
resize(w, h) {
|
|
311
|
+
if (!(w > 0 && h > 0) || w === this.s.size.w && h === this.s.size.h) return;
|
|
312
|
+
this.set({ size: { w, h } });
|
|
313
|
+
if (!this.fitted && this.s.model) {
|
|
314
|
+
this.fitted = true;
|
|
315
|
+
this.fit(false);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
scale() {
|
|
319
|
+
return new XenoGraphScale(this.s.camera, { widthPx: this.s.size.w || 1, heightPx: this.s.size.h || 1 });
|
|
320
|
+
}
|
|
321
|
+
get viewport() {
|
|
322
|
+
return this.scale().viewport;
|
|
323
|
+
}
|
|
324
|
+
camera(next, animate) {
|
|
325
|
+
this.set({ camera: next, animate });
|
|
326
|
+
}
|
|
327
|
+
/** Zoom about a screen point. Never animated: it follows the wheel. A non-positive or non-finite factor is a no-op (zoomAboutCursor). */
|
|
328
|
+
zoomAt(px, py, factor) {
|
|
329
|
+
this.camera(zoomAboutCursor(this.scale(), px, py, factor, SHAPE_ZOOM), false);
|
|
330
|
+
}
|
|
331
|
+
zoomBy(factor) {
|
|
332
|
+
this.zoomAt(this.s.size.w / 2, this.s.size.h / 2, factor);
|
|
333
|
+
}
|
|
334
|
+
/** Pan by a screen-pixel delta. */
|
|
335
|
+
pan(dxPx, dyPx) {
|
|
336
|
+
const c = this.s.camera;
|
|
337
|
+
this.camera({ zoom: c.zoom, x: c.x - dxPx / c.zoom, y: c.y - dyPx / c.zoom }, false);
|
|
338
|
+
}
|
|
339
|
+
/** Centre the camera on a world point (the minimap). */
|
|
340
|
+
centreOn(wx, wy) {
|
|
341
|
+
const c = this.s.camera;
|
|
342
|
+
this.camera({ zoom: c.zoom, x: wx - this.s.size.w / 2 / c.zoom, y: wy - this.s.size.h / 2 / c.zoom }, true);
|
|
343
|
+
}
|
|
344
|
+
/** Fit everything. */
|
|
345
|
+
fit(animate = true) {
|
|
346
|
+
if (!this.s.size.w || !this.s.model) return;
|
|
347
|
+
this.camera(fitToBounds({ x: 20, y: 20, width: this.s.width, height: this.s.height }, this.s.size.w, this.s.size.h, { min: SHAPE_ZOOM.min, max: 1 }, 20), animate);
|
|
348
|
+
}
|
|
349
|
+
/** Fit what is SHOWN: a question or focus that leaves its answer off-screen is no answer. */
|
|
350
|
+
fitShown(animate = true) {
|
|
351
|
+
const shown = this.s.shown;
|
|
352
|
+
if (!this.s.size.w || !shown.length) return;
|
|
353
|
+
const x0 = Math.min(...shown.map((n) => n.x)), y0 = Math.min(...shown.map((n) => n.y));
|
|
354
|
+
const x1 = Math.max(...shown.map((n) => n.x + NODE_W)), y1 = Math.max(...shown.map((n) => n.y + NODE_H));
|
|
355
|
+
this.camera(fitToBounds({ x: x0 - 40, y: y0 - 40, width: x1 - x0 + 80, height: y1 - y0 + 80 }, this.s.size.w, this.s.size.h, { min: SHAPE_ZOOM.min, max: 1 }, 20), animate);
|
|
356
|
+
}
|
|
357
|
+
/** `F`: fit what answers when narrowed, everything otherwise. */
|
|
358
|
+
fitNow() {
|
|
359
|
+
if (this.narrowed()) this.fitShown();
|
|
360
|
+
else this.fit();
|
|
361
|
+
}
|
|
362
|
+
narrowed() {
|
|
363
|
+
return this.s.lens !== "all" || this.s.depth > 0 && !!this.s.selected && !this.s.selected.startsWith("lane:");
|
|
364
|
+
}
|
|
365
|
+
refit() {
|
|
366
|
+
if (!this.fitted) return;
|
|
367
|
+
if (this.narrowed()) this.fitShown();
|
|
368
|
+
else this.fit();
|
|
369
|
+
}
|
|
370
|
+
afterSelect() {
|
|
371
|
+
const sel = this.s.selected;
|
|
372
|
+
if (!sel || !this.s.size.w) return;
|
|
373
|
+
if (sel.startsWith("lane:")) {
|
|
374
|
+
const b = this.s.boxes.find((x) => `lane:${x.lane.id}` === sel);
|
|
375
|
+
if (b) this.centreOn(b.x + b.w / 2, LANE_TOP + LANE_CARD_H / 2);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (this.s.depth > 0) {
|
|
379
|
+
this.fitShown();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const n = this.s.placed.find((x) => x.key === sel);
|
|
383
|
+
if (!n) return;
|
|
384
|
+
const z = Math.max(this.s.camera.zoom, 0.8);
|
|
385
|
+
this.camera({ zoom: z, x: n.x + NODE_W / 2 - this.s.size.w / 2 / z, y: n.y + NODE_H / 2 - this.s.size.h / 2 / z }, true);
|
|
386
|
+
}
|
|
387
|
+
// ── persistence ────────────────────────────────────────────────────────────
|
|
388
|
+
/** The question and the depth are the viewer's: remembered so they do not reset on every open. */
|
|
389
|
+
serialize() {
|
|
390
|
+
return { lens: this.s.lens, depth: this.s.depth, edgeLabels: this.s.edgeLabels };
|
|
391
|
+
}
|
|
392
|
+
deserialize(state) {
|
|
393
|
+
if (!state || typeof state !== "object") return;
|
|
394
|
+
const o = state;
|
|
395
|
+
this.set({
|
|
396
|
+
lens: isLens(o.lens) ? o.lens : this.s.lens,
|
|
397
|
+
depth: isDepth(o.depth) ? o.depth : this.s.depth,
|
|
398
|
+
edgeLabels: typeof o.edgeLabels === "boolean" ? o.edgeLabels : this.s.edgeLabels
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
dispose() {
|
|
402
|
+
this.listeners.clear();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/corpus/shape/manifest.ts
|
|
407
|
+
var CORPUS_SHAPE_PANEL_ID = "xeno.corpus.shape";
|
|
408
|
+
var corpusShapeManifest = {
|
|
409
|
+
id: CORPUS_SHAPE_PANEL_ID,
|
|
410
|
+
version: "0.1.0",
|
|
411
|
+
title: "Corpus Shape",
|
|
412
|
+
icon: "waypoints",
|
|
413
|
+
description: "The shape of a project\u2019s verified truth: one lane per corpus, a card per claim wearing its last verdict, and the relations between them. Opens on a question (everything, false now, unchecked), focuses 1\u20133 links around a card, and stays readable zoomed out. Draws what the host hands it; never computes a verdict.",
|
|
414
|
+
defaultSlot: "center",
|
|
415
|
+
inputs: [
|
|
416
|
+
{
|
|
417
|
+
id: "model",
|
|
418
|
+
name: "Corpus model",
|
|
419
|
+
type: "object",
|
|
420
|
+
description: "XenoCorpusModel { lanes, nodes, edges } \u2014 XENO Corpus\u2019s /api/v1/model, or any host\u2019s equivalent. Read-only; a value that is not a model is ignored, so a mis-wire never blanks the canvas.",
|
|
421
|
+
multiple: false
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: "selection",
|
|
425
|
+
name: "Selection",
|
|
426
|
+
type: "string",
|
|
427
|
+
description: "A card key, `lane:<id>`, or null \u2014 externally driven, for syncing with an inspector or a search. Not echoed back.",
|
|
428
|
+
multiple: false
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: "emphasis",
|
|
432
|
+
name: "Emphasis",
|
|
433
|
+
type: "object",
|
|
434
|
+
description: "XenoCorpusEmphasis { lapsed, held, unobserved, rule, none } \u2014 booleans from a filter rail. Off DIMS a card, never hides it.",
|
|
435
|
+
multiple: false
|
|
436
|
+
}
|
|
437
|
+
],
|
|
438
|
+
outputs: [
|
|
439
|
+
{ id: "selection", name: "Selection", type: "string", description: "The card key or `lane:<id>` the person picked, or null." },
|
|
440
|
+
{
|
|
441
|
+
id: "intent",
|
|
442
|
+
name: "Intent",
|
|
443
|
+
type: "object",
|
|
444
|
+
description: '{ kind: "open", key } \u2014 the person asked to open a card (double-click, Enter). A request; the host decides what opening means.'
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
commands: [
|
|
448
|
+
{
|
|
449
|
+
id: "select",
|
|
450
|
+
title: "Select",
|
|
451
|
+
description: "Select a card or lane by key (`lane:<id>` for a lane), or clear with null.",
|
|
452
|
+
parameters: { key: { type: "string", description: "Card key, lane:<id>, or empty to clear" } }
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: "set_lens",
|
|
456
|
+
title: "Ask a question",
|
|
457
|
+
description: "Show everything, only what is false now, or only what nothing checks. Hides non-answers and fits the view to the answer.",
|
|
458
|
+
parameters: { lens: { type: "string", description: '"all" | "lapsed" | "unchecked"', required: true } }
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: "set_depth",
|
|
462
|
+
title: "Focus depth",
|
|
463
|
+
description: "Show only what lies within N links of the selection (0 = off, max 3). Outranks the question.",
|
|
464
|
+
parameters: { depth: { type: "number", description: "0, 1, 2 or 3", required: true } }
|
|
465
|
+
},
|
|
466
|
+
{ id: "fit", title: "Fit", description: "Fit the answer (or everything) in view.", parameters: {} },
|
|
467
|
+
{
|
|
468
|
+
id: "get_summary",
|
|
469
|
+
title: "Summary",
|
|
470
|
+
description: "What is on the canvas now: counts of shown cards by state, the question, the depth, the selection and the empty-answer sentence if any.",
|
|
471
|
+
parameters: {}
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
capabilities: ["storage.local"],
|
|
475
|
+
config: [
|
|
476
|
+
{ key: "minimap", label: "Minimap", type: "boolean", defaultValue: true, description: "A small overview in the corner that recentres the view on click." },
|
|
477
|
+
{ key: "legend", label: "Legend", type: "boolean", defaultValue: true, description: "What each relation line means, and the edge-label switch." }
|
|
478
|
+
],
|
|
479
|
+
sdk: "^1.0.0"
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// src/corpus/shape/react/CorpusShapePanelView.tsx
|
|
483
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
484
|
+
import { Column, DrawnSurface, EmptyState, IconButton, SegmentedControl, StatusBar, Text, TextButton, Toolbar, ToolbarGroup } from "@xenosystem/components/chrome";
|
|
485
|
+
|
|
486
|
+
// src/corpus/shape/declaration.ts
|
|
487
|
+
var LENS_OPTIONS = LENSES.map((l) => ({ value: l.id, label: l.label, title: l.question }));
|
|
488
|
+
var DEPTH_OPTIONS = DEPTHS.map((d) => ({ value: String(d), label: d ? String(d) : "Off", title: d ? `${d} link${d > 1 ? "s" : ""} around the selection` : "No focus" }));
|
|
489
|
+
function corpusShapeViewState(c, _options = {}) {
|
|
490
|
+
const s = c.getState();
|
|
491
|
+
const tenets = s.shown.filter((n) => n.kind === "tenet");
|
|
492
|
+
const lapsed = tenets.filter((n) => n.state === "lapsed").length;
|
|
493
|
+
const unknown = tenets.filter((n) => n.state === "unobserved" || n.state === "never-run").length;
|
|
494
|
+
return {
|
|
495
|
+
empty: !s.model,
|
|
496
|
+
drawn: !!s.model,
|
|
497
|
+
lens: s.lens,
|
|
498
|
+
lenses: LENS_OPTIONS,
|
|
499
|
+
depth: String(s.depth),
|
|
500
|
+
depths: DEPTH_OPTIONS,
|
|
501
|
+
zoomLabel: `${Math.round(s.camera.zoom * 100)}%`,
|
|
502
|
+
countLabel: s.model ? `${s.shown.length} of ${s.placed.length} cards \xB7 ${lapsed} lapsed \xB7 ${unknown} unknown` : ""
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function applyCorpusShapeUi(c, intent) {
|
|
506
|
+
switch (intent.kind) {
|
|
507
|
+
case "lens":
|
|
508
|
+
c.setLens(intent.lens);
|
|
509
|
+
break;
|
|
510
|
+
case "depth":
|
|
511
|
+
c.setDepth(Number(intent.depth));
|
|
512
|
+
break;
|
|
513
|
+
case "zoomIn":
|
|
514
|
+
c.zoomBy(1.2);
|
|
515
|
+
break;
|
|
516
|
+
case "zoomOut":
|
|
517
|
+
c.zoomBy(1 / 1.2);
|
|
518
|
+
break;
|
|
519
|
+
case "fit":
|
|
520
|
+
c.fitNow();
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
var corpusShapeViewManifest = {
|
|
525
|
+
...corpusShapeManifest,
|
|
526
|
+
capabilities: [],
|
|
527
|
+
inputs: [{ id: "view", name: "View state", type: "object", description: "The CorpusShapeViewModel the runtime derives from the controller on every draw." }],
|
|
528
|
+
outputs: [{ id: "ui", name: "UI intent", type: "object", description: "{ kind: lens | depth | zoomIn | zoomOut | fit, \u2026 } \u2014 applied by the runtime to the controller." }],
|
|
529
|
+
commands: []
|
|
530
|
+
};
|
|
531
|
+
var EMPTY = { empty: true, drawn: false, lens: "all", lenses: LENS_OPTIONS, depth: "0", depths: DEPTH_OPTIONS, zoomLabel: "50%", countLabel: "" };
|
|
532
|
+
var BEHAVIOURS = {
|
|
533
|
+
"corpus/shapePanelView": (inputs) => inputs.view ?? EMPTY
|
|
534
|
+
};
|
|
535
|
+
var CORPUS_SHAPE_RUNTIME_UNITS = ["corpus/ShapeSurface"];
|
|
536
|
+
var text = (b) => ({ component: "text", props: { text: b } });
|
|
537
|
+
var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
|
|
538
|
+
var CORPUS_SHAPE_DECLARATION = {
|
|
539
|
+
format: "xeno.block@1",
|
|
540
|
+
manifest: corpusShapeViewManifest,
|
|
541
|
+
behaviour: "corpus/shapePanelView",
|
|
542
|
+
composition: {
|
|
543
|
+
component: "chrome/Column",
|
|
544
|
+
children: [
|
|
545
|
+
{
|
|
546
|
+
component: "chrome/Toolbar",
|
|
547
|
+
props: {
|
|
548
|
+
left: {
|
|
549
|
+
node: {
|
|
550
|
+
component: "chrome/ToolbarGroup",
|
|
551
|
+
children: [
|
|
552
|
+
{ component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Show" }, options: { input: "view.lenses" }, value: { input: "view.lens" } }, on: { onChange: ui("lens", { lens: { arg: "" } }) } },
|
|
553
|
+
{ component: "chrome/Text", props: { role: { value: "meta" } }, children: [text({ value: "Around selection" })] },
|
|
554
|
+
{ component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Focus depth" }, options: { input: "view.depths" }, value: { input: "view.depth" } }, on: { onChange: ui("depth", { depth: { arg: "" } }) } }
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
right: {
|
|
559
|
+
node: {
|
|
560
|
+
component: "chrome/ToolbarGroup",
|
|
561
|
+
props: { end: { value: true } },
|
|
562
|
+
children: [
|
|
563
|
+
{ component: "chrome/IconButton", props: { icon: { node: { component: "icons/Minus" } }, label: { value: "Zoom out" } }, on: { onClick: ui("zoomOut") } },
|
|
564
|
+
{ component: "chrome/Text", props: { role: { value: "number" } }, children: [text({ input: "view.zoomLabel" })] },
|
|
565
|
+
{ component: "chrome/IconButton", props: { icon: { node: { component: "icons/Plus" } }, label: { value: "Zoom in" } }, on: { onClick: ui("zoomIn") } },
|
|
566
|
+
{ component: "chrome/TextButton", children: [text({ value: "Fit" })], on: { onClick: ui("fit") } }
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
{ component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No corpus yet" }, hint: { value: "Wire a XENO Corpus model into this panel\u2019s input." } } },
|
|
573
|
+
{ component: "corpus/ShapeSurface", when: { input: "view.drawn" } },
|
|
574
|
+
{ component: "chrome/StatusBar", props: { left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text({ input: "view.countLabel" })] } } } }
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/corpus/shape/react/surfaceTokens.ts
|
|
580
|
+
import { status, surface, text as text2 } from "@xenosystem/elements/tokens";
|
|
581
|
+
import { PANEL_COLORS } from "@xenosystem/components/chrome";
|
|
582
|
+
var soft = (c, pct) => `color-mix(in srgb, ${c} ${pct}%, transparent)`;
|
|
583
|
+
var SURFACE_TOKENS = {
|
|
584
|
+
"--cs-toolbar": `var(--xeno-viewport, ${surface.canvas})`,
|
|
585
|
+
"--cs-menu": `var(--xeno-panel-header, ${surface.panelHeader})`,
|
|
586
|
+
"--cs-field": `var(--xeno-control, ${surface.input})`,
|
|
587
|
+
"--cs-ink": `var(--xeno-text, ${text2.hover})`,
|
|
588
|
+
"--cs-ink2": `var(--xeno-muted-text, ${text2.active})`,
|
|
589
|
+
"--cs-ink3": `var(--xeno-muted-text, ${text2.active})`,
|
|
590
|
+
"--cs-line": PANEL_COLORS.border,
|
|
591
|
+
"--cs-edge": PANEL_COLORS.focus,
|
|
592
|
+
"--cs-dot": PANEL_COLORS.soft,
|
|
593
|
+
"--cs-lane": PANEL_COLORS.wash,
|
|
594
|
+
"--cs-accent": `var(--xeno-text, ${text2.hover})`,
|
|
595
|
+
"--cs-accent-soft": PANEL_COLORS.soft,
|
|
596
|
+
"--cs-cap": `var(--xeno-active, ${text2.active})`,
|
|
597
|
+
"--cs-ok": status.success,
|
|
598
|
+
"--cs-ok-soft": soft(status.success, 10),
|
|
599
|
+
"--cs-ok-ink": PANEL_COLORS.successText,
|
|
600
|
+
"--cs-warn": status.warning,
|
|
601
|
+
"--cs-on-warn": surface.page,
|
|
602
|
+
"--cs-danger": `var(--xeno-danger, ${status.danger})`,
|
|
603
|
+
"--cs-danger-text": PANEL_COLORS.errorText,
|
|
604
|
+
"--cs-on-danger": `var(--xeno-text, ${text2.hover})`,
|
|
605
|
+
"--cs-shadow": `0 0 0 1px ${PANEL_COLORS.soft}`,
|
|
606
|
+
"--cs-pop": `0 16px 40px ${PANEL_COLORS.well}, 0 0 0 1px ${PANEL_COLORS.border}`,
|
|
607
|
+
"--cs-mono": "var(--xeno-font-mono, ui-monospace, monospace)"
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/corpus/shape/react/CorpusShapePanelView.tsx
|
|
611
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
612
|
+
function CorpusShapePanelView({ controller, ...options }) {
|
|
613
|
+
useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
|
|
614
|
+
const view = corpusShapeViewState(controller, options);
|
|
615
|
+
const ui2 = (intent) => applyCorpusShapeUi(controller, intent);
|
|
616
|
+
return /* @__PURE__ */ jsxs(Column, { children: [
|
|
617
|
+
/* @__PURE__ */ jsx(
|
|
618
|
+
Toolbar,
|
|
619
|
+
{
|
|
620
|
+
left: /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
|
|
621
|
+
/* @__PURE__ */ jsx(SegmentedControl, { size: "sm", label: "Show", options: view.lenses, value: view.lens, onChange: (lens) => ui2({ kind: "lens", lens }) }),
|
|
622
|
+
/* @__PURE__ */ jsx(Text, { role: "meta", children: "Around selection" }),
|
|
623
|
+
/* @__PURE__ */ jsx(SegmentedControl, { size: "sm", label: "Focus depth", options: view.depths, value: view.depth, onChange: (depth) => ui2({ kind: "depth", depth }) })
|
|
624
|
+
] }),
|
|
625
|
+
right: /* @__PURE__ */ jsxs(ToolbarGroup, { end: true, children: [
|
|
626
|
+
/* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(MinusGlyph, {}), label: "Zoom out", onClick: () => ui2({ kind: "zoomOut" }) }),
|
|
627
|
+
/* @__PURE__ */ jsx(Text, { role: "number", children: view.zoomLabel }),
|
|
628
|
+
/* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(PlusGlyph, {}), label: "Zoom in", onClick: () => ui2({ kind: "zoomIn" }) }),
|
|
629
|
+
/* @__PURE__ */ jsx(TextButton, { onClick: () => ui2({ kind: "fit" }), children: "Fit" })
|
|
630
|
+
] })
|
|
631
|
+
}
|
|
632
|
+
),
|
|
633
|
+
view.empty ? /* @__PURE__ */ jsx(EmptyState, { title: "No corpus yet", hint: "Wire a XENO Corpus model into this panel\u2019s input." }) : null,
|
|
634
|
+
view.drawn ? /* @__PURE__ */ jsx(CorpusShapeSurface, { controller, ...options }) : null,
|
|
635
|
+
/* @__PURE__ */ jsx(StatusBar, { left: /* @__PURE__ */ jsx(Text, { role: "number", children: view.countLabel }) })
|
|
636
|
+
] });
|
|
637
|
+
}
|
|
638
|
+
function corpusShapeRuntimeComponents(controller, options = {}) {
|
|
639
|
+
return { "corpus/ShapeSurface": () => /* @__PURE__ */ jsx(CorpusShapeSurface, { controller, ...options }) };
|
|
640
|
+
}
|
|
641
|
+
var mono = { fontFamily: "var(--cs-mono)" };
|
|
642
|
+
var ellipsis = { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" };
|
|
643
|
+
var chip = { ...mono, fontSize: 9.5, lineHeight: "14px", padding: "0 4px", borderRadius: 2, whiteSpace: "nowrap", flex: "none", letterSpacing: ".02em" };
|
|
644
|
+
var plate = { position: "absolute", borderRadius: 6, background: "var(--cs-menu)", boxShadow: "var(--cs-shadow)" };
|
|
645
|
+
var MINIMAP_W = 180;
|
|
646
|
+
function CorpusShapeSurface({ controller, minimap = true, legend = true }) {
|
|
647
|
+
const s = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
|
|
648
|
+
const ref = useRef(null);
|
|
649
|
+
const drag = useRef(null);
|
|
650
|
+
useEffect(() => {
|
|
651
|
+
const el = ref.current;
|
|
652
|
+
if (!el) return;
|
|
653
|
+
const measure = () => {
|
|
654
|
+
const r = el.getBoundingClientRect();
|
|
655
|
+
if (r.width > 0 && r.height > 0) controller.resize(r.width, r.height);
|
|
656
|
+
};
|
|
657
|
+
measure();
|
|
658
|
+
const onWheel = (e) => {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
const r = el.getBoundingClientRect();
|
|
661
|
+
controller.zoomAt(e.clientX - r.left, e.clientY - r.top, Math.exp(-e.deltaY * 15e-4));
|
|
662
|
+
};
|
|
663
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
664
|
+
if (typeof ResizeObserver === "undefined") return () => el.removeEventListener("wheel", onWheel);
|
|
665
|
+
const ro = new ResizeObserver(measure);
|
|
666
|
+
ro.observe(el);
|
|
667
|
+
return () => {
|
|
668
|
+
ro.disconnect();
|
|
669
|
+
el.removeEventListener("wheel", onWheel);
|
|
670
|
+
};
|
|
671
|
+
}, [controller]);
|
|
672
|
+
const onKeyDown = (e) => {
|
|
673
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
674
|
+
const dir = { ArrowRight: "right", ArrowLeft: "left", ArrowDown: "down", ArrowUp: "up" }[e.key];
|
|
675
|
+
if (dir) {
|
|
676
|
+
e.preventDefault();
|
|
677
|
+
controller.moveSelection(dir);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (e.key === "f" || e.key === "F") {
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
controller.fitNow();
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (e.key === "Enter" && s.selected && !s.selected.startsWith("lane:")) {
|
|
686
|
+
e.preventDefault();
|
|
687
|
+
controller.open(s.selected);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (e.key === "Escape") controller.select(null);
|
|
691
|
+
};
|
|
692
|
+
const cam = s.camera;
|
|
693
|
+
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
694
|
+
const sel = s.selected;
|
|
695
|
+
const byKey = new Map(s.placed.map((n) => [n.key, n]));
|
|
696
|
+
const selNode = sel ? byKey.get(sel) ?? null : null;
|
|
697
|
+
const near = /* @__PURE__ */ new Set();
|
|
698
|
+
if (selNode) {
|
|
699
|
+
near.add(selNode.key);
|
|
700
|
+
for (const e of s.model?.edges ?? []) {
|
|
701
|
+
if (e.from === selNode.key) near.add(e.to);
|
|
702
|
+
if (e.to === selNode.key) near.add(e.from);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const stop = (e) => e.stopPropagation();
|
|
706
|
+
const mk = MINIMAP_W / Math.max(s.width, 1);
|
|
707
|
+
const vp = controller.viewport;
|
|
708
|
+
return /* @__PURE__ */ jsxs(
|
|
709
|
+
DrawnSurface,
|
|
710
|
+
{
|
|
711
|
+
ref,
|
|
712
|
+
label: "Corpus shape",
|
|
713
|
+
"data-canvas": "corpus-shape",
|
|
714
|
+
tabIndex: 0,
|
|
715
|
+
onKeyDown,
|
|
716
|
+
onPointerDown: (e) => {
|
|
717
|
+
if (e.button === 0) {
|
|
718
|
+
drag.current = { x: e.clientX, y: e.clientY };
|
|
719
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
onPointerMove: (e) => {
|
|
723
|
+
const d = drag.current;
|
|
724
|
+
if (!d) return;
|
|
725
|
+
controller.pan(e.clientX - d.x, e.clientY - d.y);
|
|
726
|
+
drag.current = { x: e.clientX, y: e.clientY };
|
|
727
|
+
},
|
|
728
|
+
onPointerUp: () => {
|
|
729
|
+
drag.current = null;
|
|
730
|
+
},
|
|
731
|
+
style: { ...SURFACE_TOKENS, backgroundColor: "var(--cs-toolbar)", backgroundImage: "radial-gradient(var(--cs-dot) 1px, transparent 1.3px)", backgroundSize: `${22 * cam.zoom}px ${22 * cam.zoom}px`, backgroundPosition: `${-cam.x * cam.zoom}px ${-cam.y * cam.zoom}px`, color: "var(--cs-ink)", cursor: drag.current ? "grabbing" : "grab" },
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "absolute", left: 0, top: 0, width: s.width, height: s.height, transformOrigin: "0 0", transform: `translate(${-cam.x * cam.zoom}px, ${-cam.y * cam.zoom}px) scale(${cam.zoom})`, transition: s.animate && !reduced ? "transform .38s cubic-bezier(.2,.7,.2,1)" : "none" }, children: [
|
|
734
|
+
s.boxes.map((b) => /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: b.x - 14, top: 24, width: b.w + 28, height: b.h, borderRadius: 10, background: b.lane.role === "uncovered" ? "transparent" : "var(--cs-lane)", border: b.lane.role === "uncovered" ? "1.5px dashed var(--cs-line)" : "0", boxSizing: "border-box" } }, `bg-${b.lane.id}`)),
|
|
735
|
+
/* @__PURE__ */ jsx("svg", { width: s.width, height: s.height, style: { position: "absolute", left: 0, top: 0, overflow: "visible", pointerEvents: "none" }, "aria-hidden": "true", children: (s.model?.edges ?? []).map((e, i) => {
|
|
736
|
+
const a = byKey.get(e.from), b = byKey.get(e.to);
|
|
737
|
+
if (!a || !b || !s.visible.has(a.key) || !s.visible.has(b.key)) return null;
|
|
738
|
+
const [c, dash, sw] = EDGE_STYLE[e.type] ?? EDGE_STYLE.ref;
|
|
739
|
+
const dim = !!selNode && !(e.from === selNode.key || e.to === selNode.key);
|
|
740
|
+
const same = a.x === b.x;
|
|
741
|
+
const from = same ? { x: a.x + NODE_W / 2, y: b.y > a.y ? a.y + NODE_H : a.y } : { x: b.x > a.x ? a.x + NODE_W : a.x, y: a.y + NODE_H / 2 };
|
|
742
|
+
const to = same ? { x: b.x + NODE_W / 2, y: b.y > a.y ? b.y : b.y + NODE_H } : { x: b.x > a.x ? b.x : b.x + NODE_W, y: b.y + NODE_H / 2 };
|
|
743
|
+
return /* @__PURE__ */ jsx("path", { d: edgePath(from, to), fill: "none", stroke: c, strokeWidth: dim ? sw : sw + 0.4, strokeDasharray: dash || void 0, opacity: dim ? 0.3 : 1 }, i);
|
|
744
|
+
}) }),
|
|
745
|
+
s.boxes.map((b) => {
|
|
746
|
+
const lane = b.lane, picked = sel === `lane:${lane.id}`, cap = laneCaption(lane);
|
|
747
|
+
const counts = lane.counts ?? {};
|
|
748
|
+
const odd = lane.role === "orphan" || lane.role === "uncovered";
|
|
749
|
+
const flagBg = odd ? "var(--cs-warn)" : counts.lapsed ? "var(--cs-danger)" : counts.unknown ? "transparent" : "var(--cs-ok-soft)";
|
|
750
|
+
const flagInk = odd ? "var(--cs-on-warn)" : counts.lapsed ? "var(--cs-on-danger)" : counts.unknown ? "var(--cs-ink2)" : "var(--cs-ok-ink)";
|
|
751
|
+
return /* @__PURE__ */ jsxs(
|
|
752
|
+
"button",
|
|
753
|
+
{
|
|
754
|
+
type: "button",
|
|
755
|
+
"aria-pressed": picked,
|
|
756
|
+
onClick: () => controller.select(`lane:${lane.id}`),
|
|
757
|
+
onPointerDown: stop,
|
|
758
|
+
style: { position: "absolute", left: b.x, top: LANE_TOP, width: b.w, height: LANE_CARD_H, display: "flex", flexDirection: "column", gap: 5, padding: "12px 14px", border: 0, borderRadius: 6, background: "var(--cs-menu)", boxShadow: picked ? "0 0 0 2px var(--cs-accent), var(--cs-pop)" : "var(--cs-shadow)", color: "var(--cs-ink)", textAlign: "left", cursor: "pointer", font: "inherit" },
|
|
759
|
+
children: [
|
|
760
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6, width: "100%" }, children: [
|
|
761
|
+
/* @__PURE__ */ jsx("span", { style: { ...mono, fontSize: 10, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--cs-ink3)" }, children: cap.role }),
|
|
762
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
|
|
763
|
+
lane.flag ? /* @__PURE__ */ jsx("span", { style: { ...chip, background: flagBg, color: flagInk, boxShadow: counts.unknown && !counts.lapsed && !odd ? "inset 0 0 0 1px var(--cs-ink3)" : "none" }, children: lane.flag }) : null
|
|
764
|
+
] }),
|
|
765
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 14, fontWeight: 600, letterSpacing: "-.01em" }, children: lane.name }),
|
|
766
|
+
/* @__PURE__ */ jsx("span", { style: { ...mono, ...ellipsis, fontSize: 10.5, color: "var(--cs-ink3)", maxWidth: "100%" }, children: cap.sub })
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
`lane-${lane.id}`
|
|
770
|
+
);
|
|
771
|
+
}),
|
|
772
|
+
s.shown.map((n) => {
|
|
773
|
+
const on = s.emphasis[emphasisKey(n)], picked = selNode?.key === n.key;
|
|
774
|
+
const dim = !on || !!selNode && !near.has(n.key);
|
|
775
|
+
const unknown = n.kind === "tenet" && (n.state === "unobserved" || n.state === "never-run");
|
|
776
|
+
const dashed = n.kind === "proposal" || n.kind === "missing" || n.kind === "uncovered";
|
|
777
|
+
const c = cardChip(n), sub = subLine(n);
|
|
778
|
+
const common = { type: "button", "aria-pressed": picked, "aria-label": `${n.label} \xB7 ${c.label.toLowerCase()} \xB7 ${sub}`, onClick: () => controller.select(n.key), onDoubleClick: () => controller.open(n.key), onPointerDown: stop };
|
|
779
|
+
if (s.compact) {
|
|
780
|
+
return /* @__PURE__ */ jsx(
|
|
781
|
+
"button",
|
|
782
|
+
{
|
|
783
|
+
...common,
|
|
784
|
+
"data-compact": "true",
|
|
785
|
+
title: n.label,
|
|
786
|
+
style: { position: "absolute", left: n.x, top: n.y, width: NODE_W, height: NODE_H, display: "flex", alignItems: "center", gap: 8, padding: "0 12px", border: dashed ? "3px dashed var(--cs-edge)" : 0, borderRadius: 8, background: c.bg, boxShadow: picked ? "0 0 0 4px var(--cs-accent)" : c.ring, opacity: dim ? on ? 0.45 : 0.22 : 1, color: c.ink, textAlign: "left", cursor: "pointer", font: "inherit" },
|
|
787
|
+
children: /* @__PURE__ */ jsx("span", { style: { ...ellipsis, fontSize: compactFont(cam.zoom), fontWeight: 600, lineHeight: 1.15, maxWidth: "100%" }, children: n.label })
|
|
788
|
+
},
|
|
789
|
+
n.key
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
return /* @__PURE__ */ jsxs(
|
|
793
|
+
"button",
|
|
794
|
+
{
|
|
795
|
+
...common,
|
|
796
|
+
style: { position: "absolute", left: n.x, top: n.y, width: NODE_W, height: NODE_H, display: "flex", flexDirection: "column", gap: 3, padding: "9px 11px", border: dashed ? "1.5px dashed var(--cs-edge)" : unknown ? "1px dashed var(--cs-ink3)" : 0, borderRadius: 6, background: dashed ? "var(--cs-toolbar)" : "var(--cs-menu)", boxShadow: picked ? "0 0 0 2px var(--cs-accent), var(--cs-pop)" : dashed ? "none" : "var(--cs-shadow)", opacity: dim ? on ? 0.45 : 0.22 : 1, color: "var(--cs-ink)", textAlign: "left", cursor: "pointer", font: "inherit" },
|
|
797
|
+
children: [
|
|
798
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6, width: "100%" }, children: [
|
|
799
|
+
/* @__PURE__ */ jsx("span", { style: { ...mono, ...ellipsis, flex: 1, fontSize: 10, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--cs-ink3)" }, children: kindLabel(n) }),
|
|
800
|
+
/* @__PURE__ */ jsx("span", { style: { ...chip, background: c.bg, color: c.ink, boxShadow: c.ring }, children: c.label })
|
|
801
|
+
] }),
|
|
802
|
+
/* @__PURE__ */ jsx("span", { style: { ...ellipsis, fontSize: 12.5, fontWeight: 500, lineHeight: 1.35, maxWidth: "100%" }, children: n.label }),
|
|
803
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%" }, children: [
|
|
804
|
+
/* @__PURE__ */ jsx("span", { style: { ...mono, ...ellipsis, flex: 1, fontSize: 10.5, color: n.state === "lapsed" ? "var(--cs-danger-text)" : "var(--cs-ink3)" }, children: sub }),
|
|
805
|
+
n.kind === "tenet" ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { flex: "none", display: "flex", gap: 1.5 }, children: (n.strip ?? []).map((cell, i) => {
|
|
806
|
+
const f = cellFill(cell.state);
|
|
807
|
+
return /* @__PURE__ */ jsx("span", { style: { width: 5, height: 9, borderRadius: 1, background: f.bg, boxShadow: f.ring } }, i);
|
|
808
|
+
}) }) : null
|
|
809
|
+
] })
|
|
810
|
+
]
|
|
811
|
+
},
|
|
812
|
+
n.key
|
|
813
|
+
);
|
|
814
|
+
}),
|
|
815
|
+
s.edgeLabels && selNode ? (s.model?.edges ?? []).filter((e) => e.from === selNode.key || e.to === selNode.key).map((e, i) => {
|
|
816
|
+
const a = byKey.get(e.from), b = byKey.get(e.to);
|
|
817
|
+
if (!a || !b || !s.visible.has(a.key) || !s.visible.has(b.key)) return null;
|
|
818
|
+
const up = b.y < a.y, mx = b.x + NODE_W / 2, my = a.x === b.x && up ? b.y + NODE_H + 9 : b.y - 9;
|
|
819
|
+
const c = EDGE_STYLE[e.type]?.[0] ?? "var(--cs-ink2)";
|
|
820
|
+
return /* @__PURE__ */ jsx("span", { style: { ...mono, position: "absolute", left: mx, top: my, transform: "translate(-50%,-50%)", fontSize: 9.5, lineHeight: "14px", padding: "0 5px", borderRadius: 2, background: "var(--cs-toolbar)", color: c === "var(--cs-edge)" ? "var(--cs-ink2)" : c, boxShadow: "0 0 0 1px var(--cs-line)", whiteSpace: "nowrap", pointerEvents: "none" }, children: e.label }, `l${i}`);
|
|
821
|
+
}) : null
|
|
822
|
+
] }),
|
|
823
|
+
s.emptyAnswer ? /* @__PURE__ */ jsx("div", { role: "status", style: { ...plate, left: "50%", top: "45%", transform: "translate(-50%,-50%)", padding: "14px 18px", fontSize: 13, color: "var(--cs-ink2)" }, children: s.emptyAnswer }) : null,
|
|
824
|
+
s.focusHint ? /* @__PURE__ */ jsx("div", { role: "status", style: { ...plate, left: 12, top: 12, padding: "6px 10px", fontSize: 11.5, color: "var(--cs-ink2)" }, children: "Select a card to focus around it" }) : null,
|
|
825
|
+
legend ? /* @__PURE__ */ jsxs("div", { onPointerDown: stop, style: { ...plate, left: 12, bottom: 12, display: "flex", flexDirection: "column", gap: 6, padding: "10px 12px" }, children: [
|
|
826
|
+
/* @__PURE__ */ jsx(TextButton, { onClick: () => controller.toggleEdgeLabels(), children: s.edgeLabels ? "Hide edge labels" : "Show edge labels" }),
|
|
827
|
+
Object.values(EDGE_STYLE).map(([c, dash, , label]) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
828
|
+
/* @__PURE__ */ jsx("svg", { width: "26", height: "6", "aria-hidden": "true", children: /* @__PURE__ */ jsx("line", { x1: "0", y1: "3", x2: "26", y2: "3", stroke: c, strokeWidth: "1.5", strokeDasharray: dash || void 0 }) }),
|
|
829
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 11.5, color: "var(--cs-ink2)" }, children: label })
|
|
830
|
+
] }, label))
|
|
831
|
+
] }) : null,
|
|
832
|
+
minimap && s.size.w >= 600 && s.size.h >= 360 ? /* @__PURE__ */ jsxs(
|
|
833
|
+
"div",
|
|
834
|
+
{
|
|
835
|
+
role: "img",
|
|
836
|
+
"aria-label": "Minimap",
|
|
837
|
+
onPointerDown: stop,
|
|
838
|
+
onClick: (e) => {
|
|
839
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
840
|
+
controller.centreOn((e.clientX - r.left) / mk, (e.clientY - r.top) / mk);
|
|
841
|
+
},
|
|
842
|
+
style: { ...plate, right: 12, bottom: 12, width: MINIMAP_W, height: 75, overflow: "hidden", cursor: "pointer" },
|
|
843
|
+
children: [
|
|
844
|
+
s.boxes.map((b) => /* @__PURE__ */ jsx("span", { style: { position: "absolute", left: b.x * mk, top: LANE_TOP * mk, width: b.w * mk, height: LANE_CARD_H * mk, background: "var(--cs-line)", borderRadius: 1 } }, b.lane.id)),
|
|
845
|
+
s.shown.map((n) => /* @__PURE__ */ jsx("span", { style: { position: "absolute", left: n.x * mk, top: n.y * mk, width: NODE_W * mk, height: NODE_H * mk, borderRadius: 1, background: n.state === "lapsed" ? "var(--cs-danger)" : n.state === "held" ? "var(--cs-ok)" : n.kind === "tenet" ? "var(--cs-ink3)" : n.kind === "proposal" || n.kind === "missing" || n.kind === "uncovered" ? "var(--cs-warn)" : "var(--cs-field)" } }, n.key)),
|
|
846
|
+
/* @__PURE__ */ jsx("span", { style: { position: "absolute", left: vp.x * mk, top: vp.y * mk, width: vp.width * mk, height: vp.height * mk, border: "1.5px solid var(--cs-accent)", borderRadius: 2, background: "var(--cs-accent-soft)", pointerEvents: "none" } })
|
|
847
|
+
]
|
|
848
|
+
}
|
|
849
|
+
) : null
|
|
850
|
+
]
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/corpus/shape/panel.ts
|
|
856
|
+
function createCorpusShapePanel(options = {}) {
|
|
857
|
+
return {
|
|
858
|
+
manifest: corpusShapeManifest,
|
|
859
|
+
activate(host) {
|
|
860
|
+
const controller = new ShapeController({ host: { emit: (portId, value) => host.emit(portId, value) } });
|
|
861
|
+
const resolve = (config) => ({
|
|
862
|
+
minimap: config.minimap !== false,
|
|
863
|
+
legend: config.legend !== false
|
|
864
|
+
});
|
|
865
|
+
let renderConfig = resolve(host.config ?? {});
|
|
866
|
+
let unrender = null;
|
|
867
|
+
let root = null;
|
|
868
|
+
let currentEl = null;
|
|
869
|
+
const draw = (el) => {
|
|
870
|
+
unrender?.();
|
|
871
|
+
if (options.render) {
|
|
872
|
+
unrender = options.render(el, { controller, config: renderConfig });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
root = createRoot(el);
|
|
876
|
+
root.render(createElement(CorpusShapePanelView, { controller, ...renderConfig }));
|
|
877
|
+
unrender = () => {
|
|
878
|
+
root?.unmount();
|
|
879
|
+
root = null;
|
|
880
|
+
};
|
|
881
|
+
};
|
|
882
|
+
const unbindConfig = bindConfig(host, (config) => {
|
|
883
|
+
renderConfig = resolve(config);
|
|
884
|
+
if (currentEl) draw(currentEl);
|
|
885
|
+
});
|
|
886
|
+
return {
|
|
887
|
+
render(el) {
|
|
888
|
+
currentEl = el;
|
|
889
|
+
draw(el);
|
|
890
|
+
},
|
|
891
|
+
// Every input is checked by the controller; a malformed payload is ignored, never thrown.
|
|
892
|
+
onInput(portId, value) {
|
|
893
|
+
if (portId === "model") controller.setModel(value);
|
|
894
|
+
else if (portId === "selection") controller.setSelection(value);
|
|
895
|
+
else if (portId === "emphasis") controller.setEmphasis(value);
|
|
896
|
+
},
|
|
897
|
+
async onCommand(commandId, params) {
|
|
898
|
+
switch (commandId) {
|
|
899
|
+
case "select": {
|
|
900
|
+
const key = typeof params.key === "string" && params.key ? params.key : null;
|
|
901
|
+
controller.select(key);
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
case "set_lens":
|
|
905
|
+
controller.setLens(params.lens);
|
|
906
|
+
return controller.getState().lens;
|
|
907
|
+
case "set_depth":
|
|
908
|
+
controller.setDepth(typeof params.depth === "string" ? Number(params.depth) : params.depth);
|
|
909
|
+
return controller.getState().depth;
|
|
910
|
+
case "fit":
|
|
911
|
+
controller.fitNow();
|
|
912
|
+
return true;
|
|
913
|
+
case "get_summary": {
|
|
914
|
+
const s = controller.getState();
|
|
915
|
+
const count = (pred) => s.shown.filter(pred).length;
|
|
916
|
+
return {
|
|
917
|
+
lens: s.lens,
|
|
918
|
+
depth: s.depth,
|
|
919
|
+
selected: s.selected,
|
|
920
|
+
shown: s.shown.length,
|
|
921
|
+
total: s.placed.length,
|
|
922
|
+
lapsed: count((n) => n.kind === "tenet" && n.state === "lapsed"),
|
|
923
|
+
held: count((n) => n.kind === "tenet" && n.state === "held"),
|
|
924
|
+
// Unknown is its own number: "not checked" is never folded into "fine".
|
|
925
|
+
unknown: count((n) => n.kind === "tenet" && (n.state === "unobserved" || n.state === "never-run")),
|
|
926
|
+
attested: count((n) => n.kind === "tenet" && n.state === "attested"),
|
|
927
|
+
dimmed: count((n) => !s.emphasis[emphasisKey(n)]),
|
|
928
|
+
emptyAnswer: s.emptyAnswer
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
default:
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
serialize() {
|
|
936
|
+
return controller.serialize();
|
|
937
|
+
},
|
|
938
|
+
deserialize(state) {
|
|
939
|
+
controller.deserialize(state);
|
|
940
|
+
},
|
|
941
|
+
dispose() {
|
|
942
|
+
unbindConfig();
|
|
943
|
+
currentEl = null;
|
|
944
|
+
unrender?.();
|
|
945
|
+
unrender = null;
|
|
946
|
+
controller.dispose();
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
var corpusShapePanel = createCorpusShapePanel();
|
|
953
|
+
export {
|
|
954
|
+
COMPACT_BELOW,
|
|
955
|
+
BEHAVIOURS as CORPUS_SHAPE_BEHAVIOURS,
|
|
956
|
+
CORPUS_SHAPE_DECLARATION,
|
|
957
|
+
CORPUS_SHAPE_PANEL_ID,
|
|
958
|
+
CORPUS_SHAPE_RUNTIME_UNITS,
|
|
959
|
+
CorpusShapePanelView,
|
|
960
|
+
CorpusShapeSurface,
|
|
961
|
+
DEPTHS,
|
|
962
|
+
EDGE_STYLE,
|
|
963
|
+
LENSES,
|
|
964
|
+
NODE_H,
|
|
965
|
+
NODE_W,
|
|
966
|
+
SHAPE_ZOOM,
|
|
967
|
+
SURFACE_TOKENS,
|
|
968
|
+
ShapeController,
|
|
969
|
+
applyCorpusShapeUi,
|
|
970
|
+
cardChip,
|
|
971
|
+
cellFill,
|
|
972
|
+
chipFor,
|
|
973
|
+
compactFont,
|
|
974
|
+
corpusShapeManifest,
|
|
975
|
+
corpusShapePanel,
|
|
976
|
+
corpusShapeRuntimeComponents,
|
|
977
|
+
corpusShapeViewManifest,
|
|
978
|
+
corpusShapeViewState,
|
|
979
|
+
createCorpusShapePanel,
|
|
980
|
+
emphasisKey,
|
|
981
|
+
inLens,
|
|
982
|
+
isCorpusModel,
|
|
983
|
+
isDepth,
|
|
984
|
+
isLens,
|
|
985
|
+
kindLabel,
|
|
986
|
+
laneCaption,
|
|
987
|
+
layout,
|
|
988
|
+
neighbourhood,
|
|
989
|
+
subLine,
|
|
990
|
+
visibleKeys
|
|
991
|
+
};
|