graphvault-studio 0.1.4 → 0.1.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6
4
+
5
+ - Remove the global KPI strip from every Studio tab; summary signals now stay in Overview and Operations instead of occupying the main workspace everywhere.
6
+
7
+ ## 0.1.5
8
+
9
+ - Restore the Object Hierarchy view as an expandable lazy tree with persistent root context instead of a drilldown-only list.
10
+
3
11
  ## 0.1.4
4
12
 
5
13
  - Add production safety status and score to the summary API, Overview, KPI bar, and Operations view.
package/dist/admin-ui.js CHANGED
@@ -25,7 +25,7 @@ export const ADMIN_HTML = `<!doctype html>
25
25
  }
26
26
  * { box-sizing: border-box; }
27
27
  body { margin: 0; min-height: 100vh; }
28
- strong, b, .status, .panel-title, .mutation summary, .object-id, .tag, .field-name, .kpi strong { text-shadow: none; }
28
+ strong, b, .status, .panel-title, .mutation summary, .object-id, .tag, .field-name { text-shadow: none; }
29
29
  button, input, select, textarea { font: inherit; }
30
30
  button { border: 1px solid var(--line); background: #fff; border-radius: 7px; cursor: pointer; }
31
31
  button:hover { border-color: #98aab3; background: #f6fafb; }
@@ -51,11 +51,7 @@ export const ADMIN_HTML = `<!doctype html>
51
51
  .topbar h1 { margin: 0; font-family: "Avenir Next", "SF Pro Display", "Aptos Display", "Inter", ui-sans-serif, system-ui, sans-serif; font-size: 24px; letter-spacing: 0; font-weight: 600; }
52
52
  .auth { display: none; grid-template-columns: minmax(160px, 280px) auto; gap: 8px; }
53
53
  .workspace { padding: 18px; display: grid; gap: 14px; }
54
- .kpis { display: grid; grid-template-columns: repeat(4, minmax(150px, 1fr)); gap: 12px; }
55
- .kpi, .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 10px 28px rgba(15, 35, 45, 0.05); }
56
- .kpi { padding: 14px; display: grid; gap: 8px; min-height: 94px; }
57
- .kpi span { color: var(--muted); font-size: 13px; }
58
- .kpi strong { font-size: 28px; line-height: 1; overflow-wrap: anywhere; font-weight: 600; }
54
+ .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 10px 28px rgba(15, 35, 45, 0.05); }
59
55
  .workgrid { display: grid; grid-template-columns: minmax(330px, 500px) minmax(560px, 1fr); gap: 14px; min-height: 0; }
60
56
  .panel { min-width: 0; overflow: hidden; }
61
57
  .panel-head { padding: 13px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--line); background: #fbfdfd; }
@@ -118,7 +114,7 @@ export const ADMIN_HTML = `<!doctype html>
118
114
  .field-actions { grid-area: actions; justify-content: flex-start; }
119
115
  }
120
116
  @media (max-width: 980px) {
121
- .shell, .kpis, .mutation-grid, .audit-grid, .topbar, .fields-head { grid-template-columns: 1fr; }
117
+ .shell, .mutation-grid, .audit-grid, .topbar, .fields-head { grid-template-columns: 1fr; }
122
118
  .gvql-actions, .gvql-parameter-row { grid-template-columns: 1fr; }
123
119
  nav { grid-template-rows: auto; gap: 10px; padding: 12px; }
124
120
  .brand { grid-template-columns: 42px 1fr; padding-bottom: 2px; }
@@ -167,7 +163,6 @@ export const ADMIN_HTML = `<!doctype html>
167
163
  <span class="status" id="status">loading</span>
168
164
  </header>
169
165
  <main class="workspace">
170
- <div class="kpis" id="kpis"></div>
171
166
  <div class="workgrid">
172
167
  <div class="panel">
173
168
  <div class="panel-head"><span class="panel-title" id="listTitle">Objects</span><span class="hint" id="listHint">Select a record</span></div>
@@ -220,7 +215,6 @@ OFFSET 0</textarea>
220
215
  const viz = document.getElementById('viz');
221
216
  const fields = document.getElementById('fields');
222
217
  const list = document.getElementById('list');
223
- const kpis = document.getElementById('kpis');
224
218
  const status = document.getElementById('status');
225
219
  const title = document.getElementById('title');
226
220
  const subtitle = document.getElementById('subtitle');
@@ -231,7 +225,11 @@ OFFSET 0</textarea>
231
225
  const authTokenInput = document.getElementById('authToken');
232
226
  const objectPageSize = 100;
233
227
  let objectPageOffset = 0;
234
- let hierarchyPath = [];
228
+ let hierarchyRootId = '';
229
+ let selectedHierarchyId = '';
230
+ const hierarchyExpanded = new Set();
231
+ const hierarchyRecords = new Map();
232
+ const hierarchyChildren = new Map();
235
233
  let gvqlEditorWired = false;
236
234
  const viewText = {
237
235
  hierarchy: ['Object Hierarchy', 'Root-first graph view with references you can follow.'],
@@ -296,7 +294,6 @@ OFFSET 0</textarea>
296
294
  return value;
297
295
  };
298
296
  const postJson = (url, body) => requestJson(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
299
- const kpi = (label, value) => '<div class="kpi"><span>' + esc(label) + '</span><strong>' + esc(value) + '</strong></div>';
300
297
  function setRows(rows, emptyText) {
301
298
  if (!rows.length) {
302
299
  const empty = document.createElement('div');
@@ -322,21 +319,12 @@ OFFSET 0</textarea>
322
319
  }));
323
320
  }
324
321
  async function refreshKpis() {
325
- const summary = await requestJson('/api/summary?verify=false');
326
- const hardening = summary.hardening || {};
327
- const ops = summary.operations || {};
328
- const library = summary.library || {};
329
- const safety = summary.productionSafety || {};
330
- const walLabel = (hardening.transactionLog || '-') + (typeof ops.pendingWalCommits === 'number' ? ' / ' + ops.pendingWalCommits + ' pending' : '');
331
- const libraryLabel = (library.installedVersion || '-') + (library.status === 'warning' ? ' warning' : '');
332
- const safetyLabel = (safety.status || '-') + (typeof safety.score === 'number' ? ' / ' + safety.score : '');
333
- kpis.innerHTML = kpi('Objects', summary.objectCount) + kpi('Transaction', summary.transactionId) + kpi('Safety', safetyLabel) + kpi('Library', libraryLabel) + kpi('WAL', walLabel) + kpi('Lock', hardening.writerLock || '-') + kpi('Ops', ops.status || '-') + kpi('Snapshot', summary.currentSnapshot || '-');
334
- return summary;
322
+ return apiJson('/api/summary?verify=false');
335
323
  }
336
324
  async function showHierarchy() {
337
325
  setView('hierarchy');
338
326
  listTitle.textContent = 'Object hierarchy';
339
- listHint.textContent = 'Lazy loaded';
327
+ listHint.textContent = 'Expandable lazy tree';
340
328
  await refreshKpis();
341
329
  const root = await apiJson('/api/root');
342
330
  if (!root.rootObjectId) {
@@ -344,29 +332,75 @@ OFFSET 0</textarea>
344
332
  show(root);
345
333
  return;
346
334
  }
347
- await showObjectWithChildren(root.rootObjectId, 'root', []);
348
- show({ root: root.rootObjectId, note: 'Only the visible branch is loaded. Click a row to drill into its children.' });
335
+ hierarchyRootId = root.rootObjectId;
336
+ selectedHierarchyId = selectedHierarchyId || hierarchyRootId;
337
+ hierarchyExpanded.add(hierarchyRootId);
338
+ await ensureHierarchyNode(hierarchyRootId);
339
+ await renderHierarchyTree();
340
+ await selectHierarchyObject(selectedHierarchyId, 'root');
341
+ }
342
+ async function ensureHierarchyNode(id) {
343
+ if (!hierarchyRecords.has(id)) {
344
+ hierarchyRecords.set(id, await apiJson('/api/objects/' + encodeURIComponent(id)));
345
+ }
346
+ if (!hierarchyChildren.has(id)) {
347
+ hierarchyChildren.set(id, await apiJson('/api/objects/' + encodeURIComponent(id) + '/children'));
348
+ }
349
+ return {
350
+ record: hierarchyRecords.get(id),
351
+ children: hierarchyChildren.get(id) || []
352
+ };
349
353
  }
350
- async function showObjectWithChildren(id, label, parentPath) {
354
+ async function selectHierarchyObject(id, label) {
351
355
  document.getElementById('graphRoot').value = id;
352
- const record = await apiJson('/api/objects/' + encodeURIComponent(id));
353
- hierarchyPath = parentPath.concat([{ id, label, record }]);
356
+ selectedHierarchyId = id;
357
+ const data = await ensureHierarchyNode(id);
358
+ const record = data.record;
354
359
  renderEditableFields(record);
355
360
  document.getElementById('mid').value = id;
356
361
  document.getElementById('detailHint').textContent = 'Object #' + id;
357
362
  out.textContent = JSON.stringify(record, null, 2);
358
- const children = await apiJson('/api/objects/' + encodeURIComponent(id) + '/children');
359
- const rows = hierarchyPath.map((entry, index) => ({
360
- depth: index,
361
- columns: ['#' + entry.record.objectId, entry.record.node.type || entry.record.node.kind, entry.label + ' -> ' + summarizeRecord(entry.record), index === hierarchyPath.length - 1 ? 'current' : 'parent'],
362
- onclick: () => showObjectWithChildren(entry.id, entry.label, hierarchyPath.slice(0, index))
363
- })).concat(children.map(child => ({
364
- depth: hierarchyPath.length,
365
- columns: ['#' + child.to, child.type || child.kind, child.path + ' -> ' + child.preview, 'open'],
366
- onclick: () => showObjectWithChildren(child.to, child.path, hierarchyPath)
367
- })));
363
+ }
364
+ async function toggleHierarchyObject(id, label) {
365
+ const data = await ensureHierarchyNode(id);
366
+ if (data.children.length) {
367
+ if (hierarchyExpanded.has(id)) hierarchyExpanded.delete(id);
368
+ else hierarchyExpanded.add(id);
369
+ }
370
+ await selectHierarchyObject(id, label);
371
+ await renderHierarchyTree();
372
+ }
373
+ async function renderHierarchyTree() {
374
+ if (!hierarchyRootId) return;
375
+ const rows = [];
376
+ await appendHierarchyRows(rows, hierarchyRootId, 'root', 0, []);
368
377
  setRows(rows, 'No children');
369
378
  }
379
+ async function appendHierarchyRows(rows, id, label, depth, path) {
380
+ const data = await ensureHierarchyNode(id);
381
+ const record = data.record;
382
+ const children = data.children;
383
+ const isExpanded = hierarchyExpanded.has(id);
384
+ const marker = children.length ? (isExpanded ? '[-] ' : '[+] ') : ' ';
385
+ rows.push({
386
+ depth,
387
+ columns: [marker + '#' + record.objectId, record.node.type || record.node.kind, label + ' -> ' + summarizeRecord(record), id === selectedHierarchyId ? 'selected' : children.length + ' children'],
388
+ onclick: () => toggleHierarchyObject(id, label)
389
+ });
390
+ if (!isExpanded) return;
391
+ const nextPath = path.concat(id);
392
+ for (const child of children) {
393
+ if (nextPath.includes(child.to)) {
394
+ rows.push({
395
+ depth: depth + 1,
396
+ columns: ['[ref] #' + child.to, child.type || child.kind, child.path + ' -> cycle/shared reference', 'linked'],
397
+ onclick: () => selectHierarchyObject(child.to, child.path)
398
+ });
399
+ } else {
400
+ await appendHierarchyRows(rows, child.to, child.path, depth + 1, nextPath);
401
+ }
402
+ }
403
+ }
370
404
  async function showObjectInHierarchyPath(id) {
371
405
  document.getElementById('graphRoot').value = id;
372
406
  setView('hierarchy');
@@ -1 +1 @@
1
- {"version":3,"file":"admin-ui.js","sourceRoot":"","sources":["../src/admin-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAmOC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgvBvD,CAAC"}
1
+ {"version":3,"file":"admin-ui.js","sourceRoot":"","sources":["../src/admin-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA6NC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwxBvD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphvault-studio",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Graphical admin client for GraphVault object graph stores.",
5
5
  "repository": {
6
6
  "type": "git",