graphlin 0.1.2 → 0.1.3

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local architecture and activity viewer with passive, bounded event hooks.",
5
5
  "author": {
6
6
  "name": "Graphlin contributors"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local architecture and activity diagrams from observable coding-agent work.",
5
5
  "author": {
6
6
  "name": "Graphlin contributors"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Live architecture and activity diagrams from observable coding-agent work.",
5
5
  "private": false,
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "graphlin",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "description": "Local architecture and activity diagrams from observable coding-agent work.",
6
6
  "extensions": {
7
7
  "com.openai": {
@@ -157,10 +157,10 @@ export function liveNodeChanges(previous, next, eligible) {
157
157
  };
158
158
  }
159
159
 
160
- export function filterDiagram(graph, query) {
161
- if (!query) return graph;
160
+ export function filterDiagram(graph, query = '', kinds = null) {
161
+ if (!query && kinds === null) return graph;
162
162
  const needle = query.toLowerCase();
163
- const nodes = graph.nodes.filter(node => node.label.toLowerCase().includes(needle));
163
+ const nodes = graph.nodes.filter(node => (kinds === null || kinds.has(node.kind)) && node.label.toLowerCase().includes(needle));
164
164
  const visible = new Set(nodes.map(node => node.id));
165
165
  return { ...graph, nodes, edges: graph.edges.filter(edge => visible.has(edge.source) && visible.has(edge.target)) };
166
166
  }
@@ -842,6 +842,7 @@ export function startDashboardInfo({ load = signal => request('/api/about', { si
842
842
  throw new Error('invalid_dashboard_info');
843
843
  }
844
844
  $('project-path').textContent = safeText(info.projectRoot, 4096) || 'Path unavailable';
845
+ $('project-path').title = $('project-path').textContent;
845
846
  $('graphlin-version').textContent = info.version;
846
847
  $('version-update-steps').textContent = info.mode === 'demo'
847
848
  ? 'Press Ctrl+C in the demo terminal, then run this command to restart the updated offline demo.'
@@ -851,9 +852,14 @@ export function startDashboardInfo({ load = signal => request('/api/about', { si
851
852
  ? safeText(branch.name, 1024) || 'Branch unavailable'
852
853
  : branch?.status === 'detached' ? `Detached HEAD${branch.commit ? ` · ${safeText(branch.commit, 12)}` : ''}`
853
854
  : branch?.status === 'not_git' ? 'Not a Git repository' : 'Branch unavailable';
855
+ $('project-branch').title = $('project-branch').textContent;
854
856
  const latest = typeof info.update?.latest === 'string' &&
855
857
  /^\d+\.\d+\.\d+$/.test(info.update.latest) ? info.update.latest : null;
856
858
  const available = info.update?.status === 'available' && latest;
859
+ $('version-update-indicator').hidden = !available;
860
+ $('version-update-indicator').textContent = available ? `${latest} available` : 'Update available';
861
+ $('version-update-indicator').setAttribute('aria-label', available
862
+ ? `Graphlin ${latest} is available. View update instructions.` : 'View update instructions');
857
863
  $('version-update-status').textContent = available ? `Graphlin ${latest} is available`
858
864
  : info.update?.status === 'current' ? 'No newer release found'
859
865
  : 'Update check unavailable';
@@ -884,6 +890,7 @@ export function startDashboardInfo({ load = signal => request('/api/about', { si
884
890
  if ($('project-path').textContent === 'Checking…') $('project-path').textContent = 'Path unavailable';
885
891
  if ($('graphlin-version').textContent === 'Checking…') $('graphlin-version').textContent = 'Version unavailable';
886
892
  $('version-update-status').textContent = 'Update check unavailable';
893
+ $('version-update-indicator').hidden = true;
887
894
  $('version-update-guide').hidden = true;
888
895
  $('version-update-command').textContent = '';
889
896
  command = '';
@@ -1556,6 +1563,7 @@ export function startViewer() {
1556
1563
  viewport: null, fitBounds: null, zoom: 1, followFit: true, lastGraphSignature: '', inspectorSignature: '',
1557
1564
  nodeElements: new Map(), edgeElements: new Map(), activityElements: new Map(),
1558
1565
  views: new Map(), viewKey: null, view: null, displayGraph: null, searchQuery: '',
1566
+ nodeTypes: null, nodeTypeButtons: new Map(),
1559
1567
  effects: new Map(), liveReady: false, motionReady: false, movement: null, closed: false, projectName: '',
1560
1568
  };
1561
1569
  const motionPreference = window.matchMedia?.('(prefers-reduced-motion: reduce)');
@@ -1630,7 +1638,7 @@ export function startViewer() {
1630
1638
  $('onboarding-action').dataset.action = progress.next.action;
1631
1639
  // Preserve a manual text selection while live snapshots arrive.
1632
1640
  if ($('orientation-prompt').textContent !== ORIENTATION_PROMPT) $('orientation-prompt').textContent = ORIENTATION_PROMPT;
1633
- $('orientation').hidden = Boolean(state.searchQuery || state.replayFrame || state.snapshot?.mode === 'demo' || state.snapshot?.mode === 'replay');
1641
+ $('orientation').hidden = Boolean(state.searchQuery || state.nodeTypes !== null || state.replayFrame || state.snapshot?.mode === 'demo' || state.snapshot?.mode === 'replay');
1634
1642
  }
1635
1643
  function currentGraph() { return state.replayFrame?.graph || state.snapshot?.graph; }
1636
1644
  function applyTheme() {
@@ -1789,10 +1797,14 @@ export function startViewer() {
1789
1797
  // initial/session snapshots establish it without focusing an old arrival.
1790
1798
  const live = streamed && state.liveReady && !switched && !state.replayFrame &&
1791
1799
  snapshot.mode !== 'replay' && state.snapshot?.mode !== 'replay';
1792
- const focusNodeId = liveNodeChanges(state.snapshot?.graph, snapshot.graph, live).added.at(-1);
1800
+ const visible = new Set(filterDiagram(snapshot.graph, state.searchQuery, state.nodeTypes).nodes.map(node => node.id));
1801
+ const focusNodeId = liveNodeChanges(state.snapshot?.graph, snapshot.graph, live).added.filter(id => visible.has(id)).at(-1);
1793
1802
  const before = state.displayGraph;
1794
1803
  cancelMovement({ fit: !focusNodeId });
1795
- if (switched) resetView();
1804
+ if (switched) {
1805
+ resetView();
1806
+ state.nodeTypes = null;
1807
+ }
1796
1808
  state.snapshot = snapshot;
1797
1809
  state.epoch += 1;
1798
1810
  state.frames = historyFrames(snapshot);
@@ -1926,14 +1938,18 @@ export function startViewer() {
1926
1938
  }
1927
1939
  }
1928
1940
  function revealInspector() {
1941
+ revealDetailsSection($('inspector-body')?.parentElement);
1942
+ }
1943
+ function revealDetailsSection(panel) {
1944
+ setWorkspacePanel('details', true);
1929
1945
  const container = $('live-sidebar');
1930
- const panel = $('inspector-body')?.parentElement;
1931
1946
  if (!container?.scrollTo || !container.getBoundingClientRect || !panel?.getBoundingClientRect ||
1932
1947
  !container.contains(panel)) return;
1933
1948
  const region = container.getBoundingClientRect();
1934
1949
  const evidence = panel.getBoundingClientRect();
1935
1950
  if (!Number.isFinite(region.top) || !Number.isFinite(evidence.top) || !(region.height > 0)) return;
1936
- const offset = evidence.top - region.top - (container.clientTop || 0);
1951
+ const headingHeight = $('details-heading')?.getBoundingClientRect?.().height || 0;
1952
+ const offset = evidence.top - region.top - (container.clientTop || 0) - headingHeight;
1937
1953
  if (Math.abs(offset) < 1) return;
1938
1954
  container.scrollTo({
1939
1955
  top: Math.max(0, (container.scrollTop || 0) + offset),
@@ -2016,14 +2032,40 @@ export function startViewer() {
2016
2032
  state.followFit = false;
2017
2033
  setViewBox();
2018
2034
  }
2019
- function renderGraph({ forceFit = false, focusNodeId } = {}) {
2035
+ function renderNodeTypeFilters(canonical) {
2036
+ const container = $('node-type-filters');
2037
+ if (!container) return;
2038
+ // Discover kinds from the current canonical canvas, never search results.
2039
+ const kinds = [...new Set(canonical.nodes.map(node => node.kind))].sort();
2040
+ for (const [kind, button] of state.nodeTypeButtons) {
2041
+ if (!kinds.includes(kind)) {
2042
+ button.remove();
2043
+ state.nodeTypeButtons.delete(kind);
2044
+ }
2045
+ }
2046
+ kinds.forEach((kind, index) => {
2047
+ let button = state.nodeTypeButtons.get(kind);
2048
+ if (!button) {
2049
+ button = html('button', upperFirst(kind), 'node-type-filter');
2050
+ button.setAttribute('type', 'button');
2051
+ button.dataset.kind = kind;
2052
+ state.nodeTypeButtons.set(kind, button);
2053
+ }
2054
+ button.setAttribute('aria-pressed', String(state.nodeTypes === null || state.nodeTypes.has(kind)));
2055
+ if (container.children[index] !== button) container.insertBefore(button, container.children[index] || null);
2056
+ });
2057
+ $('node-types-all')?.setAttribute('aria-pressed', String(state.nodeTypes === null));
2058
+ $('node-types-none')?.setAttribute('aria-pressed', String(state.nodeTypes?.size === 0));
2059
+ }
2060
+ function renderGraph({ forceFit = false, arrange = false, focusNodeId } = {}) {
2020
2061
  const canonical = currentGraph();
2021
2062
  if (!canonical) return;
2022
2063
  const view = presentation();
2023
2064
  applyTheme();
2024
- // Search only projects visibility; layout, evidence and live arrival
2025
- // detection retain the complete source graph.
2026
- const graph = filterDiagram(projectPresentation(canonical, view), state.searchQuery);
2065
+ renderNodeTypeFilters(canonical);
2066
+ // Lay out only visible nodes so hidden components leave no empty slots.
2067
+ // Evidence, exports and live arrival detection retain the canonical graph.
2068
+ const graph = projectPresentation(filterDiagram(canonical, state.searchQuery, state.nodeTypes), view, { arrange });
2027
2069
  state.displayGraph = graph;
2028
2070
  const routes = graphEdgeRoutes(graph);
2029
2071
  const bounds = graphBounds(graph, routes);
@@ -2144,7 +2186,7 @@ export function startViewer() {
2144
2186
  $('diagram-title').textContent = `${state.replayFrame ? 'Historical' : 'Live'} architecture, revision ${graph.revision}`;
2145
2187
  $('diagram-desc').textContent = `${graph.nodes.length} components and ${graph.edges.length} relationships. Code interpretation does not establish runtime connectivity. Use Tab and Enter to inspect a component or relationship. With the diagram focused, use plus and minus to zoom, arrow keys to pan, and 0 to fit.`;
2146
2188
  $('graph-count').textContent = `${graph.nodes.length} components · ${graph.edges.length} relationships`;
2147
- $('diagram-search-status').textContent = state.searchQuery
2189
+ $('diagram-search-status').textContent = state.searchQuery || state.nodeTypes !== null
2148
2190
  ? `${graph.nodes.length} of ${canonical.nodes.length} components shown` : '';
2149
2191
  $('diagram-search-clear').hidden = !state.searchQuery;
2150
2192
  $('empty-canvas').hidden = graph.nodes.length > 0 || removalBounds().length > 0;
@@ -2156,8 +2198,12 @@ export function startViewer() {
2156
2198
  unavailable: ['Waiting for classification.', 'The classifier is unavailable. Safe activity continues below; supported architecture will appear when classification recovers.'],
2157
2199
  timeout: ['Evidence needs another moment.', 'Classification exceeded its deadline. Activity still appears below, and no unsupported components are added.'],
2158
2200
  };
2159
- const message = state.searchQuery
2160
- ? ['No matching components.', `No labels contain “${state.searchQuery}”. Try another search or press Esc to restore the diagram.`]
2201
+ const message = state.nodeTypes?.size === 0
2202
+ ? ['No component types selected.', 'Choose a type or All types to show components.']
2203
+ : state.searchQuery
2204
+ ? ['No matching components.', `No selected components match “${state.searchQuery}”. Try another search or press Esc to clear the search.`]
2205
+ : state.nodeTypes !== null && canonical.nodes.length
2206
+ ? ['No matching components.', 'Choose another type or All types to show components.']
2161
2207
  : state.replayFrame
2162
2208
  ? ['No components in this revision.', 'Move through the recent revisions or return to Live to follow the current map.']
2163
2209
  : emptyMessages[classifier] || ['Your architecture starts here.', 'Work in a connected agent session. Components appear when approved evidence supports them; activity can arrive first.'];
@@ -2227,8 +2273,7 @@ export function startViewer() {
2227
2273
  finishPan();
2228
2274
  cancelMovement();
2229
2275
  const before = state.displayGraph;
2230
- projectPresentation(graph, presentation(), { arrange: true });
2231
- renderGraph({ forceFit: true });
2276
+ renderGraph({ forceFit: true, arrange: true });
2232
2277
  moveLayout(before, state.displayGraph);
2233
2278
  announce(`Arranged using ${LAYOUT_NAMES[state.view.algorithm]}. Evidence and selection are unchanged.`);
2234
2279
  }
@@ -2371,6 +2416,7 @@ export function startViewer() {
2371
2416
  button.addEventListener('click', () => {
2372
2417
  const row = state.activityElements.get(event.id);
2373
2418
  if (row) {
2419
+ setWorkspacePanel('activity', true);
2374
2420
  row.querySelector('button').focus();
2375
2421
  row.scrollIntoView({ block: 'nearest' });
2376
2422
  }
@@ -2433,6 +2479,7 @@ export function startViewer() {
2433
2479
  updateSelection();
2434
2480
  renderInspector();
2435
2481
  renderActivity();
2482
+ revealInspector();
2436
2483
  announce(`Related evidence for ${(node || edge).label} is shown in the inspector.`);
2437
2484
  } else toast(state.replayFrame ? 'No evidence link for this event in the displayed revision. Return to Live to inspect current links.' : 'This captured event has no architecture evidence link. Tool activity alone does not establish an architectural claim.');
2438
2485
  });
@@ -2463,6 +2510,7 @@ export function startViewer() {
2463
2510
  function replayAt(index) {
2464
2511
  const frame = state.frames[index];
2465
2512
  if (!frame) return;
2513
+ setWorkspacePanel('history', true);
2466
2514
  resetMotionBaseline();
2467
2515
  state.replayFrame = frame;
2468
2516
  render();
@@ -2534,6 +2582,7 @@ export function startViewer() {
2534
2582
  if (!state.closed && attempt === state.connectEpoch) {
2535
2583
  state.projectName = friendlyProjectName(info.projectRoot);
2536
2584
  $('project-path').textContent = info.projectRoot;
2585
+ $('project-path').title = info.projectRoot;
2537
2586
  renderStatus();
2538
2587
  }
2539
2588
  } catch { /* The project ID remains a usable fallback. */ }
@@ -2588,24 +2637,85 @@ export function startViewer() {
2588
2637
  $('orientation-prompt').focus();
2589
2638
  }
2590
2639
  };
2591
- const onToggleActivity = () => {
2592
- const hidden = !$('activity-content').hidden;
2593
- $('activity-content').hidden = hidden;
2594
- $('activity-toggle').textContent = hidden ? 'Show activity log' : 'Hide activity log';
2595
- $('activity-toggle').setAttribute('aria-expanded', String(!hidden));
2640
+ const workspacePanels = {
2641
+ details: { panel: $('live-sidebar'), toggle: $('details-toggle') },
2642
+ history: { panel: $('history-panel'), toggle: $('history-toggle') },
2643
+ activity: { panel: $('activity-panel'), toggle: $('activity-toggle') },
2644
+ };
2645
+ function setWorkspacePanel(name, open) {
2646
+ const { panel, toggle } = workspacePanels[name];
2647
+ if (!open && panel.contains(document.activeElement)) toggle.focus();
2648
+ panel.hidden = !open;
2649
+ toggle.setAttribute('aria-expanded', String(open));
2650
+ if (name === 'details') $('workspace-body').dataset.detailsOpen = String(open);
2651
+ if (name === 'activity') $('activity-content').hidden = !open;
2652
+ }
2653
+ const onToggleDetails = () => setWorkspacePanel('details', $('live-sidebar').hidden);
2654
+ const onToggleHistory = () => setWorkspacePanel('history', $('history-panel').hidden);
2655
+ const onToggleActivity = () => setWorkspacePanel('activity', $('activity-panel').hidden);
2656
+ const onCloseDetails = () => {
2657
+ setWorkspacePanel('details', false);
2658
+ $('details-toggle').focus();
2659
+ };
2660
+ const onViewUpdate = () => {
2661
+ $('version-update-guide').open = !$('version-update-guide').hidden;
2662
+ revealDetailsSection($('version-update-status'));
2596
2663
  };
2664
+ const panelKeyHandlers = new Map();
2665
+ for (const [name, { panel, toggle }] of Object.entries(workspacePanels)) {
2666
+ setWorkspacePanel(name, false);
2667
+ const onKeyDown = event => {
2668
+ if (event.key !== 'Escape' || event.defaultPrevented || event.target?.tagName?.toLowerCase() === 'select') return;
2669
+ event.preventDefault();
2670
+ setWorkspacePanel(name, false);
2671
+ toggle.focus();
2672
+ };
2673
+ panel.addEventListener('keydown', onKeyDown);
2674
+ panelKeyHandlers.set(panel, onKeyDown);
2675
+ }
2597
2676
  $('onboarding-action').addEventListener('click', onOnboardingAction);
2598
2677
  $('orientation-copy').addEventListener('click', onCopyOrientation);
2678
+ $('details-toggle').addEventListener('click', onToggleDetails);
2679
+ $('details-close').addEventListener('click', onCloseDetails);
2680
+ $('version-update-indicator').addEventListener('click', onViewUpdate);
2681
+ $('history-toggle').addEventListener('click', onToggleHistory);
2599
2682
  $('activity-toggle').addEventListener('click', onToggleActivity);
2600
- const onSearchInput = () => {
2601
- if (state.closed || state.searchQuery === $('diagram-search').value) return;
2602
- state.searchQuery = $('diagram-search').value;
2683
+ const applyFilters = () => {
2603
2684
  // Filtering is not a source change: cancel existing decoration/movement
2604
2685
  // and render directly, without changing the snapshot arrival baseline.
2605
2686
  finishPan();
2606
2687
  clearMotion();
2607
2688
  renderOnboarding();
2608
- renderGraph({ forceFit: true });
2689
+ renderGraph({ forceFit: true, arrange: true });
2690
+ };
2691
+ const onSearchInput = () => {
2692
+ if (state.closed || state.searchQuery === $('diagram-search').value) return;
2693
+ state.searchQuery = $('diagram-search').value;
2694
+ applyFilters();
2695
+ };
2696
+ const onNodeTypeClick = event => {
2697
+ if (state.closed || !currentGraph()) return;
2698
+ const container = $('node-type-filters');
2699
+ let button = event.target;
2700
+ while (button && button.parentElement !== container) button = button.parentElement;
2701
+ const kind = button?.dataset.kind;
2702
+ if (!kind || !state.nodeTypeButtons.has(kind)) return;
2703
+ // null means all, including future kinds. Explicit selections retain their
2704
+ // choices when a kind disappears and returns; new kinds stay unselected.
2705
+ if (state.nodeTypes === null) state.nodeTypes = new Set(currentGraph().nodes.map(node => node.kind));
2706
+ if (state.nodeTypes.has(kind)) state.nodeTypes.delete(kind);
2707
+ else state.nodeTypes.add(kind);
2708
+ applyFilters();
2709
+ };
2710
+ const onAllNodeTypes = () => {
2711
+ if (state.closed) return;
2712
+ state.nodeTypes = null;
2713
+ applyFilters();
2714
+ };
2715
+ const onNoNodeTypes = () => {
2716
+ if (state.closed) return;
2717
+ state.nodeTypes = new Set();
2718
+ applyFilters();
2609
2719
  };
2610
2720
  const clearSearch = () => {
2611
2721
  $('diagram-search').value = '';
@@ -2627,6 +2737,9 @@ export function startViewer() {
2627
2737
  };
2628
2738
  $('diagram-search').addEventListener('input', onSearchInput);
2629
2739
  $('diagram-search-clear').addEventListener('click', clearSearch);
2740
+ $('node-type-filters')?.addEventListener('click', onNodeTypeClick);
2741
+ $('node-types-all')?.addEventListener('click', onAllNodeTypes);
2742
+ $('node-types-none')?.addEventListener('click', onNoNodeTypes);
2630
2743
  window.addEventListener('keydown', onSearchKeyDown);
2631
2744
  $('pause').addEventListener('click', () => control(state.snapshot?.paused ? 'resume' : 'pause'));
2632
2745
  $('session').addEventListener('change', () => control('session', $('session').value));
@@ -2771,9 +2884,17 @@ export function startViewer() {
2771
2884
  dashboardInfo.close();
2772
2885
  $('onboarding-action').removeEventListener('click', onOnboardingAction);
2773
2886
  $('orientation-copy').removeEventListener('click', onCopyOrientation);
2887
+ $('details-toggle').removeEventListener('click', onToggleDetails);
2888
+ $('details-close').removeEventListener('click', onCloseDetails);
2889
+ $('version-update-indicator').removeEventListener('click', onViewUpdate);
2890
+ $('history-toggle').removeEventListener('click', onToggleHistory);
2774
2891
  $('activity-toggle').removeEventListener('click', onToggleActivity);
2892
+ for (const [panel, handler] of panelKeyHandlers) panel.removeEventListener('keydown', handler);
2775
2893
  $('diagram-search').removeEventListener('input', onSearchInput);
2776
2894
  $('diagram-search-clear').removeEventListener('click', clearSearch);
2895
+ $('node-type-filters')?.removeEventListener('click', onNodeTypeClick);
2896
+ $('node-types-all')?.removeEventListener('click', onAllNodeTypes);
2897
+ $('node-types-none')?.removeEventListener('click', onNoNodeTypes);
2777
2898
  window.removeEventListener?.('keydown', onSearchKeyDown);
2778
2899
  $('architecture').removeEventListener('wheel', onDiagramWheel);
2779
2900
  connectionDialog.dispose();
@@ -16,6 +16,15 @@
16
16
  <div class="page">
17
17
  <header class="masthead">
18
18
  <a class="brand" href="/" aria-label="Graphlin live viewer"><span class="brand-mark" aria-hidden="true"></span>graphlin</a>
19
+ <section class="project-details" aria-label="Project and Graphlin version">
20
+ <p id="project-label" class="project-label">Local project</p>
21
+ <dl>
22
+ <div><dt>Branch</dt><dd id="project-branch">Checking…</dd></div>
23
+ <div class="project-path"><dt>Path</dt><dd id="project-path">Checking…</dd></div>
24
+ <div><dt>Graphlin</dt><dd id="graphlin-version">Checking…</dd></div>
25
+ </dl>
26
+ </section>
27
+ <button class="update-indicator" id="version-update-indicator" type="button" aria-controls="version-update-guide" hidden>Update available</button>
19
28
  <div class="connection" id="connection" data-state="connecting">
20
29
  <span class="connection-dot" aria-hidden="true"></span>
21
30
  <span id="connection-label" role="status">Connecting to local service</span>
@@ -24,29 +33,7 @@
24
33
  </header>
25
34
 
26
35
  <main id="main">
27
- <div class="intro">
28
- <div>
29
- <h1>Architecture, as it happens.</h1>
30
- <p>Follow the work. Inspect the evidence behind each connection.</p>
31
- </div>
32
- <p id="project-label" class="project-label">Local project</p>
33
- </div>
34
-
35
- <section class="project-details" aria-label="Project and Graphlin version">
36
- <dl>
37
- <div><dt>Branch</dt><dd id="project-branch">Checking…</dd></div>
38
- <div class="project-path"><dt>Path</dt><dd id="project-path">Checking…</dd></div>
39
- <div><dt>Graphlin</dt><dd id="graphlin-version">Checking…</dd></div>
40
- </dl>
41
- <p id="version-update-status" role="status">Checking for updates…</p>
42
- <details id="version-update-guide" hidden>
43
- <summary>How to update</summary>
44
- <p id="version-update-steps">Press Ctrl+C in the viewer terminal, then run this command. Start a new Claude Code or Codex session after setup finishes.</p>
45
- <pre id="version-update-command" tabindex="0"></pre>
46
- <button id="version-update-copy" type="button">Copy update command</button>
47
- <span id="version-update-copy-status" role="status"></span>
48
- </details>
49
- </section>
36
+ <h1 class="screen-reader">Live architecture workspace</h1>
50
37
 
51
38
  <div id="demo-banner" class="notice demo-notice" role="status" hidden>
52
39
  <strong>Demo session</strong>
@@ -61,30 +48,57 @@
61
48
  <span>Session</span>
62
49
  <select id="session" disabled><option value="">Waiting for a session</option></select>
63
50
  </label>
51
+ <div class="panel-controls" role="group" aria-label="Workspace panels">
52
+ <button type="button" id="details-toggle" aria-expanded="false" aria-controls="live-sidebar">Details</button>
53
+ <button type="button" id="history-toggle" aria-expanded="false" aria-controls="history-panel">History</button>
54
+ <button type="button" id="activity-toggle" aria-expanded="false" aria-controls="activity-panel">Activity</button>
55
+ </div>
64
56
  <div class="session-actions">
65
57
  <button type="button" id="classification-log" aria-haspopup="dialog" aria-controls="diagnostics-dialog">Classification log</button>
66
- <button type="button" id="how-to-connect" aria-haspopup="dialog" aria-controls="connection-dialog">How to connect</button>
58
+ <button type="button" id="how-to-connect" aria-haspopup="dialog" aria-controls="connection-dialog">Connect</button>
67
59
  <button type="button" id="pause" aria-describedby="capture-note" disabled>Pause classification</button>
68
60
  <button type="button" id="export" disabled>Export JSON</button>
69
61
  </div>
70
62
  </div>
71
63
 
72
- <div class="status-strip" aria-label="Capture and classification status">
73
- <div class="classification-status">
74
- <span class="status-dot" id="classifier-dot" data-state="waiting" aria-hidden="true"></span>
75
- <strong id="classifier-label">Waiting for local service</strong>
76
- <span id="capture-note">Capture status will appear with the first snapshot.</span>
77
- </div>
78
- <div class="coverage-status"><span id="coverage-label">Coverage not reported</span><span id="queue-label">No snapshot yet</span></div>
79
- </div>
80
-
81
- <div class="workspace-body">
64
+ <div class="workspace-body" id="workspace-body" data-details-open="false">
82
65
  <section class="drawing" id="drawing" data-theme="sketchbook" aria-labelledby="canvas-title">
83
66
  <div class="drawing-toolbar">
84
67
  <div class="drawing-heading">
85
68
  <h2 id="canvas-title">Live architecture</h2>
86
69
  <span id="revision">Revision —</span>
87
70
  </div>
71
+ <div class="layout-toolbar" role="group" aria-label="Diagram arrangement">
72
+ <label for="layout">Layout
73
+ <select id="layout" aria-describedby="layout-note" disabled>
74
+ <option value="hierarchy">Hierarchy top-down</option>
75
+ <option value="dependency">Dependency left-right</option>
76
+ <option value="grouped">Group by type</option>
77
+ <option value="circular">Circular</option>
78
+ <option value="grid">Grid</option>
79
+ <option value="original">Original</option>
80
+ </select>
81
+ </label>
82
+ <button type="button" id="arrange" disabled>Arrange</button>
83
+ <label class="auto-arrange" for="auto-arrange"><input id="auto-arrange" type="checkbox" checked disabled> Auto-arrange</label>
84
+ <div class="theme-picker">
85
+ <label for="theme">Theme
86
+ <select id="theme" aria-describedby="theme-note" disabled>
87
+ <option value="sketchbook">Sketchbook</option>
88
+ <option value="ocean">Ocean</option>
89
+ <option value="forest">Forest</option>
90
+ <option value="sunset">Sunset</option>
91
+ <option value="berry">Berry</option>
92
+ <option value="sepia">Sepia</option>
93
+ <option value="blueprint">Blueprint dark</option>
94
+ <option value="midnight">Midnight dark</option>
95
+ </select>
96
+ </label>
97
+ <span class="theme-preview" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
98
+ </div>
99
+ <span class="screen-reader" id="theme-note">Colors follow component types. Themes change appearance only.</span>
100
+ <span class="screen-reader" id="layout-note">Arranges when components or connections change.</span>
101
+ </div>
88
102
  <div class="zoom-controls" role="group" aria-label="Diagram zoom">
89
103
  <button type="button" id="zoom-out" aria-label="Zoom out" disabled>−</button>
90
104
  <output id="zoom-level" aria-label="Zoom level">100%</output>
@@ -92,43 +106,19 @@
92
106
  <button type="button" id="fit" disabled>Fit</button>
93
107
  </div>
94
108
  </div>
95
- <div class="layout-toolbar" role="group" aria-label="Diagram arrangement">
96
- <label for="layout">Layout
97
- <select id="layout" disabled>
98
- <option value="hierarchy">Hierarchy top-down</option>
99
- <option value="dependency">Dependency left-right</option>
100
- <option value="grouped">Group by type</option>
101
- <option value="circular">Circular</option>
102
- <option value="grid">Grid</option>
103
- <option value="original">Original</option>
104
- </select>
105
- </label>
106
- <button type="button" id="arrange" disabled>Arrange</button>
107
- <label class="auto-arrange" for="auto-arrange"><input id="auto-arrange" type="checkbox" checked disabled> Auto-arrange</label>
108
- <div class="theme-picker">
109
- <label for="theme">Theme
110
- <select id="theme" aria-describedby="theme-note" disabled>
111
- <option value="sketchbook">Sketchbook</option>
112
- <option value="ocean">Ocean</option>
113
- <option value="forest">Forest</option>
114
- <option value="sunset">Sunset</option>
115
- <option value="berry">Berry</option>
116
- <option value="sepia">Sepia</option>
117
- <option value="blueprint">Blueprint dark</option>
118
- <option value="midnight">Midnight dark</option>
119
- </select>
120
- </label>
121
- <span class="theme-preview" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
109
+ <div class="filter-toolbar">
110
+ <div class="diagram-search-bar" role="search" aria-label="Diagram search">
111
+ <label class="screen-reader" for="diagram-search">Find components</label>
112
+ <input id="diagram-search" type="search" maxlength="200" autocomplete="off" spellcheck="false" placeholder="Find components…" aria-controls="architecture" aria-describedby="diagram-search-hint" aria-keyshortcuts="/">
113
+ <button id="diagram-search-clear" type="button" hidden>Clear search</button>
114
+ <span class="screen-reader" id="diagram-search-hint">/ to search · Esc to clear</span>
115
+ <span id="diagram-search-status" role="status" aria-live="polite" aria-atomic="true"></span>
116
+ </div>
117
+ <div class="node-type-toolbar" role="group" aria-label="Component type filters">
118
+ <button type="button" id="node-types-all">All types</button>
119
+ <button type="button" id="node-types-none">Clear all</button>
120
+ <div id="node-type-filters" role="group" aria-label="Visible component types"></div>
122
121
  </div>
123
- <span class="screen-reader" id="theme-note">Colors follow component types. Themes change appearance only.</span>
124
- <span id="layout-note">Arranges when components or connections change.</span>
125
- </div>
126
- <div class="diagram-search-bar" role="search" aria-label="Diagram search">
127
- <label for="diagram-search">Find components</label>
128
- <input id="diagram-search" type="search" maxlength="200" autocomplete="off" spellcheck="false" placeholder="Search labels…" aria-controls="architecture" aria-describedby="diagram-search-hint" aria-keyshortcuts="/">
129
- <button id="diagram-search-clear" type="button" hidden>Clear search</button>
130
- <span id="diagram-search-hint">/ to search · Esc to clear</span>
131
- <span id="diagram-search-status" role="status" aria-live="polite" aria-atomic="true"></span>
132
122
  </div>
133
123
  <div class="diagram-stage" id="diagram-stage">
134
124
  <svg id="architecture" viewBox="0 0 920 510" role="group" tabindex="0" aria-labelledby="diagram-title diagram-desc">
@@ -164,15 +154,29 @@
164
154
  </div>
165
155
  <span id="graph-count">No components yet</span>
166
156
  </div>
167
- <details class="shape-key">
168
- <summary>Shape key</summary>
169
- <div id="shape-key-items"></div>
170
- <p>Shapes describe component types. Display overrides change appearance only.</p>
171
- </details>
172
157
  <p class="diagram-note" id="diagram-note">Code classification does not establish runtime connectivity.</p>
173
158
  </section>
174
159
 
175
- <aside class="live-sidebar" id="live-sidebar" aria-label="Live activity and evidence">
160
+ <aside class="live-sidebar" id="live-sidebar" aria-label="Live activity and evidence" hidden>
161
+ <div class="details-heading" id="details-heading"><h2>Details</h2><button type="button" id="details-close" aria-label="Close details">Close</button></div>
162
+ <div class="status-strip" aria-label="Capture and classification status">
163
+ <div class="classification-status">
164
+ <span class="status-dot" id="classifier-dot" data-state="waiting" aria-hidden="true"></span>
165
+ <strong id="classifier-label">Waiting for local service</strong>
166
+ <span id="capture-note">Capture status will appear with the first snapshot.</span>
167
+ </div>
168
+ <div class="coverage-status"><span id="coverage-label">Coverage not reported</span><span id="queue-label">No snapshot yet</span></div>
169
+ </div>
170
+ <section class="version-details" aria-label="Graphlin updates">
171
+ <p id="version-update-status" role="status">Checking for updates…</p>
172
+ <details id="version-update-guide" hidden>
173
+ <summary>How to update</summary>
174
+ <p id="version-update-steps">Press Ctrl+C in the viewer terminal, then run this command. Start a new Claude Code or Codex session after setup finishes.</p>
175
+ <pre id="version-update-command" tabindex="0"></pre>
176
+ <button id="version-update-copy" type="button">Copy update command</button>
177
+ <span id="version-update-copy-status" role="status"></span>
178
+ </details>
179
+ </section>
176
180
  <section class="onboarding" aria-labelledby="onboarding-title">
177
181
  <h2 id="onboarding-title">Getting your first shape</h2>
178
182
  <ol class="onboarding-progress" aria-label="Observed setup progress">
@@ -193,6 +197,11 @@
193
197
  <ol id="sidebar-hook-list" class="hook-receipts" aria-label="Received hooks across this project, newest first"></ol>
194
198
  </details>
195
199
  <section class="sidebar-history sidebar-theme" id="sidebar-history" data-theme="sketchbook" aria-labelledby="sidebar-change-heading">
200
+ <details class="shape-key">
201
+ <summary>Shape key</summary>
202
+ <div id="shape-key-items"></div>
203
+ <p>Shapes describe component types. Display overrides change appearance only.</p>
204
+ </details>
196
205
  <div class="sidebar-heading"><h2 id="sidebar-change-heading">Diagram changes</h2><span class="sidebar-count" id="sidebar-change-count">0</span></div>
197
206
  <p class="sidebar-note" id="sidebar-change-note">Newest changes first. Select a shape to inspect it, or a revision to replay.</p>
198
207
  <p class="sidebar-empty" id="sidebar-change-empty">Shape changes will stack here as this session discovers or changes the architecture.</p>
@@ -212,7 +221,7 @@
212
221
  </aside>
213
222
  </div>
214
223
 
215
- <div class="playback">
224
+ <div class="playback" id="history-panel" role="region" aria-label="Architecture history" hidden>
216
225
  <div class="mode-buttons" role="group" aria-label="Architecture view">
217
226
  <button id="live" type="button" aria-pressed="true">Live</button>
218
227
  <button id="replay" type="button" aria-pressed="false" disabled>Replay</button>
@@ -226,13 +235,12 @@
226
235
  </div>
227
236
  </section>
228
237
 
229
- <section class="activity-section" aria-labelledby="activity-heading">
238
+ <section class="activity-section" id="activity-panel" aria-labelledby="activity-heading" hidden>
230
239
  <div class="activity-heading">
231
240
  <div><h2 id="activity-heading">Activity stream</h2><p id="activity-note">Observable work, independent of classification.</p></div>
232
241
  <span id="activity-count">0 events</span>
233
- <button id="activity-toggle" type="button" aria-expanded="true" aria-controls="activity-content">Hide activity log</button>
234
242
  </div>
235
- <div id="activity-content">
243
+ <div id="activity-content" hidden>
236
244
  <p class="activity-empty" id="activity-empty">No activity received. Start work in a connected agent session; its captured events will appear here.</p>
237
245
  <ol id="activity-list" class="activity-list" aria-label="Recent activity, newest first"></ol>
238
246
  </div>
@@ -24,39 +24,42 @@ button:disabled { opacity: .5; cursor: default; }
24
24
  button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }
25
25
  a { color: var(--blue); }
26
26
  p, h1, h2, h3 { margin: 0; }
27
- .page { max-width: 1720px; margin: auto; padding: 0 38px; }
27
+ .page { width: 100%; height: 100vh; height: 100dvh; min-height: 620px; display: flex; flex-direction: column; }
28
+ main { flex: 1; min-height: 0; display: flex; flex-direction: column; }
28
29
  .skip-link { position: fixed; top: -100px; left: 20px; z-index: 20; padding: 12px 18px; background: var(--white); border: 2px solid var(--blue); border-radius: 6px; }
29
30
  .skip-link:focus { top: 12px; }
30
- .masthead { display: flex; align-items: center; justify-content: space-between; min-height: 85px; border-bottom: 1px solid var(--line); gap: 20px; }
31
- .brand { display: inline-flex; align-items: center; gap: 12px; color: var(--ink); font-size: 29px; font-weight: 750; letter-spacing: -1.4px; text-decoration: none; }
32
- .brand-mark { width: 28px; height: 28px; border: 3px solid var(--blue); border-radius: 7px; transform: rotate(-9deg); position: relative; }
31
+ .masthead { display: flex; align-items: center; min-height: 54px; padding: 7px 16px; border-bottom: 1px solid var(--line); gap: 22px; background: var(--white); flex-shrink: 0; }
32
+ .brand { display: inline-flex; align-items: center; gap: 9px; color: var(--ink); font-size: 23px; font-weight: 750; letter-spacing: -1.1px; text-decoration: none; flex-shrink: 0; }
33
+ .brand-mark { width: 22px; height: 22px; border: 2px solid var(--blue); border-radius: 6px; transform: rotate(-9deg); position: relative; }
33
34
  .brand-mark::after { content: ""; position: absolute; right: -8px; bottom: -6px; width: 12px; height: 12px; border: 3px solid var(--paper); border-radius: 50%; background: var(--teal); }
34
- .connection { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
35
+ .connection { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 11px; margin-left: auto; max-width: 250px; }
36
+ .update-indicator { flex-shrink: 0; min-height: 28px; padding: 3px 8px; font-size: 10px; color: var(--teal); border-color: var(--teal); }
35
37
  .connection-dot, .status-dot { display: inline-block; flex: 0 0 7px; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); }
36
38
  .connection[data-state="connected"] .connection-dot, .status-dot[data-state="ready"] { background: var(--teal); }
37
39
  .connection[data-state="reconnecting"] .connection-dot, .status-dot[data-state="waiting"] { background: var(--amber); }
38
40
  .connection[data-state="error"] .connection-dot, .connection[data-state="auth"] .connection-dot, .status-dot[data-state="error"] { background: var(--red); }
39
41
  .text-button { background: transparent; border-color: transparent; padding: 4px 6px; min-height: 32px; color: var(--blue); font-size: 12px; }
40
- .intro { padding: 25px 0 24px; display: flex; align-items: end; justify-content: space-between; gap: 22px; }
41
42
  h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650; line-height: 1.2; }
42
- .intro p { color: var(--muted); margin-top: 7px; }
43
- .intro .project-label { font-size: 12px; max-width: 250px; text-align: right; overflow-wrap: anywhere; }
44
- .notice { padding: 11px 16px; margin-bottom: 14px; border-radius: 7px; font-size: 13px; }
43
+ .notice { flex-shrink: 0; padding: 6px 16px; font-size: 11px; max-height: 20dvh; overflow: auto; }
45
44
  .demo-notice { display: flex; gap: 12px; color: #744807; background: #fff7e6; border: 1px solid #e6cb96; }
46
45
  .demo-notice strong { white-space: nowrap; }
47
46
  .error-notice { color: #85223a; background: #fff1f4; border: 1px solid #e8b8c3; }
48
- .workspace { border: 1px solid var(--line); border-radius: 13px; background: var(--white); overflow: hidden; box-shadow: 0 8px 30px #16324e05; }
49
- .workspace-toolbar { min-height: 71px; padding: 14px 21px; display: flex; flex-wrap: wrap; gap: 20px; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
50
- .session-picker { display: flex; align-items: center; gap: 11px; font-size: 12px; color: var(--muted); min-width: 0; }
51
- .session-picker select { color: var(--ink); font-size: 14px; font-weight: 600; border: 1px solid var(--line); border-radius: 6px; padding: 8px 30px 8px 11px; background: var(--paper); max-width: min(320px, 35vw); min-width: 150px; }
52
- .session-actions { display: flex; flex-wrap: wrap; gap: 8px; flex-shrink: 0; max-width: 100%; font-size: 12px; }
53
- .status-strip { display: flex; justify-content: space-between; gap: 15px; align-items: center; padding: 11px 21px; font-size: 11px; border-bottom: 1px solid var(--line); }
47
+ .workspace { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--white); }
48
+ .workspace-toolbar { padding: 6px 12px; display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; border-bottom: 1px solid var(--line); flex-shrink: 0; }
49
+ .session-picker { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--muted); min-width: 0; }
50
+ .session-picker select { color: var(--ink); font-size: 11px; font-weight: 600; border: 1px solid var(--line); border-radius: 5px; padding: 5px 22px 5px 8px; background: var(--paper); max-width: min(240px, 30vw); min-width: 100px; min-height: 32px; }
51
+ .session-actions { display: flex; flex-wrap: wrap; gap: 5px; margin-left: auto; font-size: 11px; }
52
+ .session-actions button, .panel-controls button { min-height: 32px; padding: 5px 9px; font-size: 11px; }
53
+ .panel-controls { display: flex; gap: 4px; }
54
+ .panel-controls button[aria-expanded="true"] { background: var(--soft); border-color: var(--blue); color: var(--blue); }
55
+ .status-strip { display: flex; flex-direction: column; gap: 9px; padding: 14px 17px; font-size: 11px; border-bottom: 1px solid var(--line); }
54
56
  .classification-status { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
55
57
  .classification-status strong { font-weight: 650; }
56
58
  #capture-note { color: var(--muted); }
57
- .coverage-status { display: flex; gap: 4px 16px; justify-content: flex-end; color: var(--muted); text-align: right; flex-wrap: wrap; max-width: 52%; }
59
+ .coverage-status { display: flex; gap: 4px 12px; color: var(--muted); flex-wrap: wrap; }
58
60
  #coverage-label { max-width: 67ch; }
59
- .workspace-body { display: grid; grid-template-columns: minmax(0, 1fr) 315px; align-items: start; }
61
+ .workspace-body { position: relative; flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-items: stretch; }
62
+ .workspace-body[data-details-open="true"] { grid-template-columns: minmax(0, 1fr) 340px; }
60
63
  .sidebar-theme,
61
64
  .drawing {
62
65
  --canvas: #fbfaf7; --ink: #293340; --muted: #4b5765; --node-ink: #343d49;
@@ -67,7 +70,7 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
67
70
  --role-queue: #fae8bd; --role-external: #f4dbe3; --role-module: #e3e7ed;
68
71
  --role-function: #d6edf2; --role-class: #dedaf3; --role-interface: #dde3fa;
69
72
  --role-event: #f8ddc7; --role-configuration: #e6edce; --role-package: #eee0d1;
70
- color-scheme: light; color: var(--ink); min-width: 0; display: flex; flex-direction: column;
73
+ color-scheme: light; color: var(--ink); min-width: 0; min-height: 0; display: flex; flex-direction: column;
71
74
  background-color: var(--canvas); background-image: radial-gradient(var(--diagram-grid) .65px, transparent .65px); background-size: 18px 18px;
72
75
  }
73
76
  .sidebar-theme[data-theme="ocean"],
@@ -163,33 +166,43 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
163
166
  .drawing [data-kind="package"], .sidebar-theme [data-kind="package"] { --node-fill: var(--role-package); }
164
167
  .drawing button:hover:not(:disabled) { background: var(--soft); border-color: var(--diagram-focus); }
165
168
  .drawing :is(button, input, select, summary):focus-visible { outline-color: var(--diagram-focus); }
166
- .drawing-toolbar { display: flex; justify-content: space-between; align-items: center; padding: 17px 21px 0; gap: 15px; z-index: 1; }
167
- .drawing-heading { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
169
+ .drawing-toolbar { display: flex; flex-wrap: wrap; align-items: center; padding: 6px 12px; gap: 6px 18px; background: var(--canvas); border-bottom: 1px solid var(--line); flex-shrink: 0; }
170
+ .drawing-heading { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
168
171
  .drawing-heading h2 { font-size: 13px; font-weight: 600; }
169
172
  #revision { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
170
- .zoom-controls { display: flex; gap: 3px; align-items: center; background: var(--canvas); flex-shrink: 0; }
173
+ .zoom-controls { display: flex; gap: 3px; align-items: center; background: var(--canvas); flex-shrink: 0; margin-left: auto; }
171
174
  .zoom-controls button { border: 0; background: transparent; min-width: 31px; min-height: 32px; padding: 2px 8px; font-size: 17px; }
172
175
  .zoom-controls #fit { border: 1px solid var(--line); font-size: 11px; margin-left: 5px; min-width: 39px; }
173
- .layout-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 12px 21px 4px; font-size: 11px; }
174
- .project-details { margin: 0 0 18px; padding: 12px 16px; border: 1px solid var(--line); border-radius: 8px; background: var(--white); font-size: 12px; }
175
- .project-details dl { display: flex; flex-wrap: wrap; gap: 12px 24px; margin: 0; }
176
- .project-details dl > div { min-width: 0; }
177
- .project-details dt { color: var(--muted); font-size: 10px; margin-bottom: 4px; }
176
+ .layout-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; font-size: 11px; }
177
+ .project-details { min-width: 0; flex: 1; font-size: 11px; }
178
+ .project-details dl { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 16px; margin: 0; }
179
+ .project-details dl > div { display: flex; align-items: baseline; gap: 5px; min-width: 0; }
180
+ .project-details dt { color: var(--muted); font-size: 10px; }
178
181
  .project-details dd { margin: 0; overflow-wrap: anywhere; }
179
- .project-details .project-path { flex: 1 1 240px; }
180
- .project-details > p { margin: 10px 0 0; color: var(--muted); font-size: 11px; }
181
- .project-details details { margin-top: 10px; }
182
- .project-details summary { cursor: pointer; font-weight: 600; }
183
- .project-details pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 10px; background: var(--paper); }
184
- .project-details button { min-height: 36px; font-size: 11px; }
185
- .diagram-search-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 12px 21px 4px; font-size: 11px; color: var(--muted); }
186
- .diagram-search-bar input { flex: 1 1 160px; min-width: 0; min-height: 40px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 5px; background: var(--white); color: var(--ink); }
182
+ .project-details .project-path { flex: 1 1 160px; }
183
+ #project-path { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
184
+ #project-branch { max-width: 22ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
185
+ .project-details .project-label { font-size: 11px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
186
+ .version-details { padding: 12px 17px; font-size: 11px; color: var(--muted); border-bottom: 1px solid var(--line); }
187
+ .version-details details { margin-top: 8px; }
188
+ .version-details summary { cursor: pointer; font-weight: 600; }
189
+ .version-details pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 10px; background: var(--paper); }
190
+ .version-details button { min-height: 32px; font-size: 11px; }
191
+ .diagram-search-bar { display: flex; align-items: center; gap: 6px; flex: 0 1 360px; min-width: 200px; font-size: 11px; color: var(--muted); }
192
+ .diagram-search-bar input { flex: 1 1 130px; width: 100%; min-width: 80px; min-height: 32px; padding: 5px 9px; border: 1px solid var(--line); border-radius: 5px; background: var(--white); color: var(--ink); }
187
193
  .diagram-search-bar input::placeholder { color: var(--muted); opacity: 1; }
188
- .diagram-search-bar button { min-height: 40px; font-size: 11px; }
194
+ .diagram-search-bar button { min-height: 32px; font-size: 11px; padding: 5px 7px; white-space: nowrap; }
195
+ #diagram-search-status { font-size: 10px; min-width: 72px; max-width: 140px; line-height: 1.3; }
196
+ .filter-toolbar { display: flex; align-items: center; gap: 12px; padding: 5px 12px; background: var(--canvas); border-bottom: 1px solid var(--line); flex-shrink: 0; }
197
+ .node-type-toolbar { display: flex; align-items: center; gap: 5px; min-width: 0; flex: 1; }
198
+ .node-type-toolbar button { font-size: 10px; min-height: 30px; padding: 4px 8px; white-space: nowrap; background: var(--white); flex-shrink: 0; }
199
+ #node-type-filters { display: flex; align-items: center; gap: 5px; min-width: 0; overflow-x: auto; padding: 4px; scrollbar-width: thin; }
200
+ #node-type-filters button[aria-pressed="true"] { background: var(--node-fill, var(--soft)); border-color: var(--blue); color: var(--ink); }
201
+ #node-type-filters button[aria-pressed="false"] { border-style: dashed; color: var(--muted); }
189
202
  #diagram-search-status:empty { display: none; }
190
203
  .layout-toolbar label { display: flex; align-items: center; gap: 8px; color: var(--muted); }
191
- .layout-toolbar select, .shape-picker select { color: var(--ink); background: var(--white); border: 1px solid var(--line); border-radius: 5px; padding: 7px 25px 7px 9px; font: inherit; min-height: 34px; }
192
- .layout-toolbar button { font-size: 11px; min-height: 34px; padding: 5px 12px; }
204
+ .layout-toolbar select, .shape-picker select { color: var(--ink); background: var(--white); border: 1px solid var(--line); border-radius: 5px; padding: 5px 23px 5px 8px; font: inherit; min-height: 32px; }
205
+ .layout-toolbar button { font-size: 11px; min-height: 32px; padding: 5px 9px; }
193
206
  .layout-toolbar .auto-arrange { color: var(--ink); white-space: nowrap; }
194
207
  .theme-picker { display: flex; align-items: center; gap: 8px; }
195
208
  .theme-preview { display: inline-flex; align-items: center; padding: 2px; }
@@ -198,14 +211,14 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
198
211
  .theme-preview i:nth-child(3) { transform: rotate(-3deg); background: var(--role-queue); }
199
212
  .theme-preview i:nth-child(4) { transform: rotate(7deg); background: var(--role-datastore); }
200
213
  .auto-arrange input { width: 15px; height: 15px; margin: 0; accent-color: var(--blue); }
201
- #layout-note { flex-basis: 100%; color: var(--muted); font-size: 10px; }
214
+
202
215
  #zoom-level { font-size: 11px; color: var(--muted); min-width: 39px; text-align: center; font-variant-numeric: tabular-nums; }
203
- .diagram-stage { position: relative; min-height: 410px; height: clamp(420px, 49vh, 650px); flex: 0 0 auto; overflow: hidden; }
216
+ .diagram-stage { position: relative; min-height: 180px; flex: 1 1 0; overflow: hidden; }
204
217
  #architecture { display: block; height: 100%; width: 100%; touch-action: pan-y; }
205
218
  #architecture:focus-visible { outline: 2px solid var(--diagram-focus); outline-offset: -5px; }
206
219
  #architecture.is-pannable { cursor: grab; touch-action: none; }
207
220
  #architecture.is-panning { cursor: grabbing; }
208
- .empty-canvas { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px; pointer-events: none; }
221
+ .empty-canvas { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: safe center; text-align: center; padding: 24px; overflow: auto; pointer-events: none; }
209
222
  .empty-canvas h3 { font-size: 24px; letter-spacing: -.6px; font-weight: 600; margin: 20px 0 9px; }
210
223
  .empty-canvas p { color: var(--muted); max-width: 46ch; font-size: 13px; }
211
224
  .orientation { pointer-events: auto; max-width: 46ch; margin-top: 18px; }
@@ -225,14 +238,14 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
225
238
  .empty-map i { width: 41px; height: 30px; border: 1.5px dashed #7999c3; border-radius: 6px; background: var(--paper); }
226
239
  .empty-map i:last-child { height: 38px; width: 31px; border-radius: 50% / 14%; }
227
240
  .empty-map span { width: 40px; border-top: 1.5px dashed #7999c3; }
228
- .drawing-footer { display: flex; justify-content: space-between; gap: 16px; padding: 10px 21px 4px; font-size: 10px; color: var(--muted); flex-wrap: wrap; }
241
+ .drawing-footer { display: flex; justify-content: space-between; gap: 5px 16px; padding: 6px 12px 2px; font-size: 10px; color: var(--muted); flex-wrap: wrap; flex-shrink: 0; background: var(--canvas); }
229
242
  .legend { display: flex; flex-wrap: wrap; gap: 17px; }
230
243
  .legend span { display: flex; align-items: center; gap: 6px; }
231
244
  .legend i { width: 15px; height: 10px; display: inline-block; border: 1.5px solid var(--blue); border-radius: 3px; background: var(--white); }
232
245
  .legend .proposed { border-color: var(--amber); border-style: dashed; background: var(--paper); }
233
246
  .legend .stale { border-color: var(--diagram-stale); border-style: dotted; background: var(--paper); }
234
- .diagram-note { color: var(--muted); font-size: 10px; padding: 3px 21px 14px; }
235
- .shape-key { color: var(--muted); font-size: 10px; padding: 6px 21px; }
247
+ .diagram-note { color: var(--muted); font-size: 10px; padding: 0 12px 5px; background: var(--canvas); flex-shrink: 0; }
248
+ .shape-key { color: var(--muted); font-size: 11px; padding: 10px 17px; border-bottom: 1px solid var(--line); }
236
249
  .shape-key summary { cursor: pointer; width: fit-content; padding: 3px 0; }
237
250
  #shape-key-items { display: grid; grid-template-columns: repeat(auto-fit, minmax(115px, 1fr)); gap: 7px 12px; margin: 9px 0; }
238
251
  .shape-key-item { display: flex; align-items: center; gap: 7px; }
@@ -307,7 +320,10 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
307
320
  .arrow-observed { fill: var(--diagram-edge); }
308
321
  .arrow-proposed { fill: var(--amber); }
309
322
  .arrow-stale { fill: var(--diagram-stale); }
310
- .live-sidebar { min-width: 0; max-height: min(920px, 90vh); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; border-left: 1px solid var(--line); }
323
+ .live-sidebar { min-width: 0; min-height: 0; max-height: 100%; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; border-left: 1px solid var(--line); background: var(--white); }
324
+ .details-heading { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 17px; background: var(--white); border-bottom: 1px solid var(--line); }
325
+ .details-heading h2 { font-size: 13px; }
326
+ .details-heading button { font-size: 11px; min-height: 30px; padding: 4px 8px; }
311
327
  .sidebar-hooks { padding: 14px 17px 12px; border-bottom: 1px solid var(--line); }
312
328
  .sidebar-hooks > summary { display: flex; align-items: center; gap: 10px; min-height: 30px; cursor: pointer; list-style: none; }
313
329
  .sidebar-hooks > summary::-webkit-details-marker { display: none; }
@@ -393,7 +409,7 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
393
409
  .source-reference summary { color: var(--blue); cursor: pointer; font-size: 11px; margin-top: 8px; }
394
410
  .source-reference pre { font: 11px/1.6 ui-monospace, "SFMono-Regular", Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; margin: 9px 0 0; border-radius: 4px; padding: 9px; background: var(--paper); max-height: 220px; overflow: auto; tab-size: 2; }
395
411
  .related-event { width: 100%; padding: 6px 0; text-align: left; font-size: 11px; color: var(--blue); background: none; border: 0; font-weight: 500; }
396
- .playback { border-top: 1px solid var(--line); display: grid; grid-template-columns: auto minmax(100px, 1fr) minmax(190px, 290px); align-items: center; gap: 22px; padding: 14px 21px; }
412
+ .playback { border-top: 1px solid var(--line); display: grid; grid-template-columns: auto minmax(100px, 1fr) minmax(190px, 290px); align-items: center; gap: 14px; padding: 8px 12px; flex-shrink: 0; max-height: 25dvh; overflow: auto; }
397
413
  .mode-buttons { display: flex; background: var(--paper); border: 1px solid var(--line); border-radius: 7px; padding: 2px; }
398
414
  .mode-buttons button { border: 0; background: transparent; color: var(--muted); min-height: 31px; font-size: 12px; border-radius: 4px; }
399
415
  .mode-buttons button[aria-pressed="true"] { color: var(--blue); background: var(--white); box-shadow: 0 1px 4px #16324e12; }
@@ -401,9 +417,10 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
401
417
  .scrubber input { accent-color: var(--blue); min-width: 20px; width: 100%; cursor: pointer; }
402
418
  .scrubber output { font-size: 11px; white-space: nowrap; min-width: 60px; color: var(--muted); font-variant-numeric: tabular-nums; }
403
419
  #replay-note { font-size: 10px; color: var(--muted); text-align: right; }
404
- .activity-section { padding-top: 24px; }
405
- .activity-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 13px; }
406
- .activity-heading h2 { font-size: 17px; letter-spacing: -.3px; font-weight: 600; }
420
+ .activity-section { height: min(220px, 28dvh); min-height: 110px; flex-shrink: 0; display: flex; flex-direction: column; padding: 10px 16px 0; border-top: 1px solid var(--line); background: var(--white); }
421
+ #activity-content { min-height: 0; overflow: auto; overscroll-behavior: contain; }
422
+ .activity-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 6px 12px; margin-bottom: 7px; flex-shrink: 0; }
423
+ .activity-heading h2 { font-size: 13px; font-weight: 600; }
407
424
  .activity-heading p { font-size: 11px; color: var(--muted); margin-top: 3px; }
408
425
  #activity-count { color: var(--muted); font-size: 11px; }
409
426
  .activity-list { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); max-height: 330px; overflow-y: auto; scrollbar-width: thin; }
@@ -419,7 +436,7 @@ h1 { font-size: clamp(27px, 2.3vw, 36px); letter-spacing: -1px; font-weight: 650
419
436
  .event-state { color: var(--muted); text-align: right; font-size: 11px; }
420
437
  .activity-empty { padding: 25px 22px; color: var(--muted); font-size: 12px; border: 1px dashed #bbcee6; border-radius: 8px; }
421
438
  .activity-item.is-focused { background: #eaf0fc; }
422
- footer { border-top: 1px solid var(--line); margin-top: 28px; padding: 18px 0 24px; display: flex; justify-content: space-between; gap: 18px; color: var(--muted); font-size: 10px; }
439
+ footer { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px 12px; padding: 3px 12px; border-top: 1px solid var(--line); font-size: 9px; color: var(--muted); flex-shrink: 0; }
423
440
  footer span { margin: 0 8px; color: #91a7c3; }
424
441
  .toast { position: fixed; bottom: 25px; left: 50%; transform: translateX(-50%); background: var(--ink); color: var(--white); padding: 12px 20px; border-radius: 8px; font-size: 12px; z-index: 10; max-width: calc(100vw - 32px); box-shadow: 0 6px 30px #16324e20; }
425
442
  .connection-dialog { width: min(760px, calc(100% - 32px)); max-height: calc(100vh - 48px); max-height: calc(100dvh - 48px); margin: auto; padding: 25px 28px; border: 1px solid var(--line); border-radius: 13px; background: var(--white); color: var(--ink); box-shadow: 0 24px 100px #16324e30; overflow-y: auto; }
@@ -491,54 +508,41 @@ footer span { margin: 0 8px; color: #91a7c3; }
491
508
  .diagnostics-log-path { color: var(--muted); font-size: 11px; line-height: 1.6; overflow-wrap: anywhere; margin: 8px 0 0; }
492
509
  .screen-reader { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
493
510
 
494
- @media (min-width: 1450px) {
495
- .workspace-body { grid-template-columns: minmax(0, 1fr) 345px; }
496
- .inspector { padding: 19px 25px; }
497
- }
498
511
  @media (max-width: 1100px) {
499
- .page { padding: 0 24px; }
500
- .workspace-body { grid-template-columns: minmax(0, 1fr) 280px; }
501
- .status-strip { align-items: start; }
502
- .classification-status { max-width: 62%; }
503
- .coverage-status { gap: 4px; flex-direction: column; }
504
- .inspector { padding: 17px; }
505
- .playback { gap: 16px; grid-template-columns: auto 1fr; }
506
- #replay-note { grid-column: 2; margin-top: -9px; text-align: left; }
507
- .drawing-toolbar { padding: 15px 15px 0; }
508
- .layout-toolbar { padding-right: 15px; padding-left: 15px; }
509
- .diagram-search-bar { padding-right: 15px; padding-left: 15px; }
510
- .drawing-heading { gap: 3px 10px; }
511
- .drawing-footer { padding-right: 15px; padding-left: 15px; }
512
- .diagram-note { padding-left: 15px; }
513
- }
514
- @media (max-width: 1000px) {
515
- .workspace-body { grid-template-columns: 1fr; }
516
- .live-sidebar { border-left: 0; border-top: 1px solid var(--line); max-height: 640px; }
517
- .inspector { padding: 17px 21px; }
518
- .change-tiles { grid-template-columns: repeat(8, minmax(0, 1fr)); }
519
- .inspector-header { margin-bottom: 8px; }
520
- .inspector-empty { padding-top: 5px; }
521
- .inspector-glyph { display: none; }
522
- .inspector-empty h3 { font-size: 18px; margin-bottom: 6px; }
523
- .inspector-empty p { max-width: 65ch; margin-bottom: 7px; }
524
- .inspector-empty .inspector-hint { margin-top: 5px; }
512
+ .drawing-heading { flex: 1; }
513
+ .layout-toolbar { order: 1; width: 100%; }
514
+ .theme-preview { display: none; }
515
+ .session-actions { gap: 4px; }
516
+ .workspace-body[data-details-open="true"] { grid-template-columns: minmax(0, 1fr) 310px; }
525
517
  }
526
518
  @media (max-width: 800px) {
527
- .page { padding: 0 18px; }
528
- .masthead { min-height: 74px; }
529
- .intro { padding: 22px 0; align-items: start; }
530
- .intro .project-label { max-width: 130px; padding-top: 4px; }
531
- .diagram-stage { min-height: 360px; height: 46vh; }
532
- .drawing-footer { padding-top: 7px; }
533
- .status-strip { padding: 11px 16px; }
534
- .workspace-toolbar { padding: 13px 16px; }
535
- .session-picker select { max-width: 32vw; min-width: 130px; }
536
- .session-actions button { padding: 8px 10px; }
537
- .activity-row { grid-template-columns: 64px 6px minmax(100px, 1fr) 80px; gap: 10px; }
519
+ .page { height: auto; min-height: 100dvh; }
520
+ .masthead { flex-wrap: wrap; gap: 6px 16px; padding: 8px 12px; }
521
+ .project-details { flex-basis: 100%; order: 1; }
522
+ .project-details .project-label { display: inline; margin-right: 12px; }
523
+ .project-details dl { gap: 3px 12px; }
524
+ .connection { font-size: 10px; max-width: 60%; }
525
+ .session-picker { flex: 1; }
526
+ .session-picker select { max-width: 100%; width: 100%; min-width: 0; }
527
+ .session-actions { width: 100%; margin-left: 0; }
528
+ .workspace-body[data-details-open="true"] { grid-template-columns: minmax(0, 1fr); }
529
+ .live-sidebar { position: absolute; top: 0; bottom: 0; right: 0; width: min(360px, 100%); z-index: 4; box-shadow: -6px 0 24px #16324e18; }
530
+ .layout-toolbar { flex-wrap: nowrap; overflow-x: auto; padding: 4px; scrollbar-width: thin; }
531
+ .layout-toolbar > * { flex-shrink: 0; }
532
+ .filter-toolbar { flex-wrap: wrap; gap: 2px; }
533
+ .diagram-search-bar { flex: 1 1 100%; }
534
+ .node-type-toolbar { flex-basis: 100%; }
535
+ .diagram-stage { min-height: max(320px, calc(100dvh - 335px)); }
536
+ .empty-canvas { padding: 20px; }
537
+ .empty-canvas h3 { font-size: 21px; }
538
+ .empty-canvas p { font-size: 12px; }
539
+ .playback { gap: 5px 12px; grid-template-columns: auto minmax(0, 1fr); }
540
+ #replay-note { grid-column: 1 / -1; text-align: left; }
538
541
  .event-detail { display: none; }
542
+ .activity-row { grid-template-columns: 57px 5px minmax(60px, 1fr) 68px; gap: 7px; padding: 12px 3px; font-size: 11px; }
543
+ .legend { gap: 5px 12px; }
539
544
  }
540
545
  @media (max-width: 550px) {
541
- .change-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
542
546
  .diagnostics-dialog { padding: 19px 15px; width: calc(100% - 20px); max-height: calc(100dvh - 24px); }
543
547
  .diagnostics-header h2 { font-size: 21px; }
544
548
  .diagnostics-filters { grid-template-columns: minmax(0, 1fr) auto; gap: 10px; }
@@ -552,59 +556,16 @@ footer span { margin: 0 8px; color: #91a7c3; }
552
556
  .connection-steps { padding-left: 18px; }
553
557
  .connection-steps pre { padding: 10px; font-size: 11px; }
554
558
  .connection-step-heading button { min-height: 40px; }
555
- .page { padding: 0 12px; }
556
- .brand { font-size: 25px; gap: 10px; }
557
- .brand-mark { height: 25px; width: 25px; }
558
- .connection { font-size: 10px; gap: 5px; max-width: 54%; flex-wrap: wrap; justify-content: flex-end; }
559
- #connection-label { max-width: 145px; text-align: right; }
560
- .connection .text-button { min-height: 26px; padding: 0 2px; font-size: 10px; }
561
- .intro { display: block; padding: 22px 2px 18px; }
562
- h1 { font-size: 27px; letter-spacing: -.8px; }
563
- .intro p { font-size: 12px; max-width: 38ch; }
564
- .intro .project-label { max-width: none; text-align: left; font-size: 10px; padding-top: 3px; }
565
- .demo-notice { flex-direction: column; gap: 3px; font-size: 11px; padding: 11px 13px; }
566
- .workspace { border-radius: 9px; }
567
- .workspace-toolbar { flex-wrap: wrap; gap: 10px; padding: 12px; }
568
- .session-picker { width: 100%; }
569
- .session-picker select { flex: 1; max-width: none; min-width: 0; min-height: 43px; }
570
- .session-actions { width: 100%; }
571
- .session-actions button { flex: 1; min-height: 43px; }
572
- .status-strip { flex-direction: column; align-items: start; gap: 8px; padding: 11px 13px; }
573
- .classification-status { max-width: none; font-size: 10px; gap: 6px; }
574
- #capture-note { flex-basis: 100%; padding-left: 13px; }
575
- .coverage-status { flex-direction: row; text-align: left; gap: 5px 14px; font-size: 10px; max-width: none; }
576
- .drawing-toolbar { padding: 10px 10px 0; gap: 7px; align-items: start; }
577
- .layout-toolbar { padding: 10px 12px 3px; gap: 9px; }
578
- .diagram-search-bar { padding: 10px 12px 3px; gap: 8px; }
579
- .layout-toolbar select, .layout-toolbar button { min-height: 40px; }
580
- .auto-arrange { min-height: 30px; }
581
- .shape-key { padding-right: 12px; padding-left: 12px; }
582
- .drawing-heading { display: flex; flex-direction: column; padding-top: 8px; gap: 3px; }
583
- .drawing-heading h2 { font-size: 11px; }
559
+ .brand { font-size: 22px; }
560
+ .drawing-toolbar { gap: 3px 7px; }
561
+ .drawing-heading { gap: 3px 8px; }
562
+ .drawing-heading h2 { font-size: 12px; }
584
563
  #revision { font-size: 10px; }
585
- .zoom-controls { gap: 0; }
586
- .zoom-controls button { min-width: 30px; min-height: 44px; }
587
- .zoom-controls #fit { min-width: 36px; min-height: 36px; margin-left: 2px; }
588
- #zoom-level { min-width: 31px; font-size: 9px; }
589
- .diagram-stage { height: 350px; min-height: 350px; }
590
- .empty-canvas { padding: 24px; }
591
- .empty-canvas h3 { font-size: 21px; }
592
- .empty-canvas p { font-size: 12px; }
593
- .legend { gap: 7px 13px; }
594
- .drawing-footer { padding: 9px 12px 5px; gap: 8px; font-size: 9px; }
595
- .diagram-note { padding: 0 12px 12px; font-size: 9px; }
596
- .inspector { padding: 13px 16px; }
597
- .playback { padding: 12px; gap: 9px; grid-template-columns: 1fr; }
598
- .mode-buttons { justify-self: start; }
599
- .mode-buttons button { min-height: 40px; min-width: 63px; }
600
- .scrubber input { min-height: 35px; }
601
- #replay-note { grid-column: 1; margin-top: -3px; }
602
- .activity-heading h2 { font-size: 16px; }
603
- .activity-heading p { font-size: 10px; max-width: 30ch; }
604
- .activity-row { grid-template-columns: 57px 5px minmax(60px, 1fr) 68px; gap: 7px; padding: 12px 3px; font-size: 11px; }
564
+ .layout-toolbar label { gap: 5px; }
565
+ .drawing-footer { font-size: 9px; }
566
+ .diagram-note { font-size: 9px; }
567
+ .activity-heading p { font-size: 10px; }
605
568
  .activity-row time, .event-state { font-size: 9px; }
606
- .activity-empty { padding: 18px; font-size: 11px; }
607
- footer { flex-direction: column; gap: 5px; margin-top: 22px; }
608
569
  }
609
570
  @media (prefers-reduced-motion: reduce) {
610
571
  *, *::before, *::after { scroll-behavior: auto !important; animation: none !important; transition: none !important; }
@@ -39,7 +39,7 @@ async function handle(line) {
39
39
  const supported = ['2024-11-05', '2025-03-26', '2025-06-18'];
40
40
  return result({ protocolVersion: supported.includes(message.params?.protocolVersion) ? message.params.protocolVersion : '2025-06-18',
41
41
  capabilities: { tools: { listChanged: false } },
42
- serverInfo: { name: 'graphlin', version: '0.1.2' },
42
+ serverInfo: { name: 'graphlin', version: '0.1.3' },
43
43
  instructions: 'Controls only. Passive host hooks provide observations when separately activated. No drawing calls after each action.' });
44
44
  }
45
45
  if (message.method === 'ping') return result({});