project-graph-mcp 2.2.6 → 2.3.1
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/ARCHITECTURE.md +81 -0
- package/CHANGELOG.md +57 -0
- package/README.md +9 -4
- package/package.json +4 -13
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/src/compact/expand.js +1 -1
- package/src/core/graph-builder.js +2 -2
- package/src/core/parser.js +2 -2
- package/src/network/server.js +1 -2
- package/src/network/web-server.js +1 -1
- package/vendor/symbiote-node/CHANGELOG.md +31 -0
- package/vendor/symbiote-node/LICENSE +21 -0
- package/vendor/symbiote-node/README.md +206 -0
- package/vendor/symbiote-node/canvas/AutoLayout.js +725 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +73 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +93 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +962 -0
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +1468 -0
- package/vendor/symbiote-node/canvas/FlowSimulator.js +323 -0
- package/vendor/symbiote-node/canvas/ForceLayout.js +189 -0
- package/vendor/symbiote-node/canvas/ForceWorker.js +1325 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +97 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +176 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/vendor/symbiote-node/canvas/LODManager.js +88 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +71 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +207 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +261 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +1840 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +132 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +21 -0
- package/vendor/symbiote-node/canvas/NodeViewManager.js +584 -0
- package/vendor/symbiote-node/canvas/PinExpansion.js +131 -0
- package/vendor/symbiote-node/canvas/PseudoConnection.js +80 -0
- package/vendor/symbiote-node/canvas/SubgraphManager.js +201 -0
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +443 -0
- package/vendor/symbiote-node/canvas/ViewportActions.js +446 -0
- package/vendor/symbiote-node/core/Connection.js +45 -0
- package/vendor/symbiote-node/core/Editor.js +451 -0
- package/vendor/symbiote-node/core/Frame.js +31 -0
- package/vendor/symbiote-node/core/GraphMermaid.js +348 -0
- package/vendor/symbiote-node/core/GraphText.js +210 -0
- package/vendor/symbiote-node/core/Node.js +143 -0
- package/vendor/symbiote-node/core/Portal.js +104 -0
- package/vendor/symbiote-node/core/Socket.js +185 -0
- package/vendor/symbiote-node/core/SubgraphNode.js +125 -0
- package/vendor/symbiote-node/index.js +103 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +361 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +332 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +133 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/vendor/symbiote-node/interactions/ConnectFlow.js +307 -0
- package/vendor/symbiote-node/interactions/Drag.js +102 -0
- package/vendor/symbiote-node/interactions/Selector.js +132 -0
- package/vendor/symbiote-node/interactions/SnapGrid.js +65 -0
- package/vendor/symbiote-node/interactions/Zoom.js +140 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +88 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +254 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +88 -0
- package/vendor/symbiote-node/layout/Layout/Layout.js +622 -0
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +25 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +293 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +467 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +33 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +156 -0
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +250 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +379 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +263 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +20 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +183 -0
- package/vendor/symbiote-node/layout/LayoutTree.js +246 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +89 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +14 -0
- package/vendor/symbiote-node/layout/index.js +16 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +79 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +41 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +24 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +16 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +65 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +29 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +683 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +92 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +17 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +25 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +90 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.js +87 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +10 -0
- package/vendor/symbiote-node/package.json +59 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +131 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +16 -0
- package/vendor/symbiote-node/plugins/History.js +384 -0
- package/vendor/symbiote-node/plugins/Readonly.js +59 -0
- package/vendor/symbiote-node/shapes/CircleShape.js +80 -0
- package/vendor/symbiote-node/shapes/CommentShape.js +35 -0
- package/vendor/symbiote-node/shapes/DiamondShape.js +115 -0
- package/vendor/symbiote-node/shapes/NodeShape.js +80 -0
- package/vendor/symbiote-node/shapes/PillShape.js +91 -0
- package/vendor/symbiote-node/shapes/RectShape.js +72 -0
- package/vendor/symbiote-node/shapes/SVGShape.js +494 -0
- package/vendor/symbiote-node/shapes/index.js +53 -0
- package/vendor/symbiote-node/themes/Palette.js +32 -0
- package/vendor/symbiote-node/themes/Skin.js +113 -0
- package/vendor/symbiote-node/themes/Theme.js +84 -0
- package/vendor/symbiote-node/themes/carbon.js +137 -0
- package/vendor/symbiote-node/themes/dark.js +137 -0
- package/vendor/symbiote-node/themes/ebook.js +138 -0
- package/vendor/symbiote-node/themes/grey.js +137 -0
- package/vendor/symbiote-node/themes/light.js +137 -0
- package/vendor/symbiote-node/themes/neon.js +138 -0
- package/vendor/symbiote-node/themes/pcb.js +273 -0
- package/vendor/symbiote-node/themes/synthwave.js +137 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +86 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +128 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +29 -0
- package/web/app.js +9 -5
- package/web/components/canvas-graph.js +1705 -0
- package/web/components/code-block.js +1 -1
- package/web/components/event-feed/CodeWidget.js +32 -0
- package/web/components/event-feed/EventWidget.js +97 -0
- package/web/components/event-feed/ListWidget.js +57 -0
- package/web/components/event-feed/MiniGraphWidget.js +159 -0
- package/web/components/follow-ribbon.js +134 -0
- package/web/dashboard.js +1 -1
- package/web/follow-controller.js +241 -0
- package/web/index.html +4 -0
- package/web/panels/ActionBoard/ActionBoard.js +1 -1
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -1
- package/web/panels/code-viewer.js +50 -15
- package/web/panels/dep-graph.js +2691 -7
- package/web/panels/file-tree.js +5 -2
- package/web/panels/live-monitor.js +75 -3
- package/web/style.css +39 -0
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/src/.contextignore +0 -22
- package/src/.project-graph-cache.json +0 -1
- package/src/compact/.project-graph-cache.json +0 -1
- package/web/.project-graph-cache.json +0 -1
- package/web/panels/SettingsPanel/.project-graph-cache.json +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// @ctx .context/web/follow-controller.ctx
|
|
2
|
+
/**
|
|
3
|
+
* FollowController — Central orchestrator for Follow Mode.
|
|
4
|
+
*
|
|
5
|
+
* Classifies incoming tool-events and dispatches debounced focus-change
|
|
6
|
+
* signals to subscribed panels (graph, code-viewer, monitor).
|
|
7
|
+
* Also manages the status ribbon text shown during active follow.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Does NOT import from app.js to avoid circular dependency.
|
|
10
|
+
* Call init(events, emit) before enable().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Debounce delay for heavy visual updates (camera, code loading) */
|
|
14
|
+
const HEAVY_DEBOUNCE = 800;
|
|
15
|
+
|
|
16
|
+
class FollowController {
|
|
17
|
+
/** @type {boolean} */
|
|
18
|
+
enabled = false;
|
|
19
|
+
/** @type {{type: string, target: any, action?: string, meta?: object}|null} */
|
|
20
|
+
currentFocus = null;
|
|
21
|
+
/** @type {string} */
|
|
22
|
+
statusText = '';
|
|
23
|
+
/** @type {number|null} */
|
|
24
|
+
_debounceTimer = null;
|
|
25
|
+
/** @type {string|null} Previous hash before entering follow mode */
|
|
26
|
+
_previousHash = null;
|
|
27
|
+
/** @type {Function|null} */
|
|
28
|
+
_boundHandler = null;
|
|
29
|
+
/** @type {EventTarget|null} */
|
|
30
|
+
_events = null;
|
|
31
|
+
/** @type {Function|null} */
|
|
32
|
+
_emit = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Late-bind events bus and emit function (breaks circular import).
|
|
36
|
+
* Must be called once before enable().
|
|
37
|
+
* @param {EventTarget} events
|
|
38
|
+
* @param {Function} emit
|
|
39
|
+
*/
|
|
40
|
+
init(events, emit) {
|
|
41
|
+
this._events = events;
|
|
42
|
+
this._emit = emit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
enable() {
|
|
46
|
+
if (this.enabled) return;
|
|
47
|
+
this.enabled = true;
|
|
48
|
+
|
|
49
|
+
// Save current location for restoring later
|
|
50
|
+
this._previousHash = location.hash;
|
|
51
|
+
|
|
52
|
+
// Bind tool-event listener
|
|
53
|
+
this._boundHandler = (e) => this._onToolEvent(e.detail);
|
|
54
|
+
this._events.addEventListener('tool-event', this._boundHandler);
|
|
55
|
+
|
|
56
|
+
this._emit('follow-state-changed', { enabled: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
disable() {
|
|
60
|
+
if (!this.enabled) return;
|
|
61
|
+
this.enabled = false;
|
|
62
|
+
|
|
63
|
+
// Clean up
|
|
64
|
+
if (this._boundHandler) {
|
|
65
|
+
this._events.removeEventListener('tool-event', this._boundHandler);
|
|
66
|
+
this._boundHandler = null;
|
|
67
|
+
}
|
|
68
|
+
if (this._debounceTimer) {
|
|
69
|
+
clearTimeout(this._debounceTimer);
|
|
70
|
+
this._debounceTimer = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.currentFocus = null;
|
|
74
|
+
this._emitStatus('');
|
|
75
|
+
|
|
76
|
+
this._emit('follow-state-changed', { enabled: false });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @returns {string|null} */
|
|
80
|
+
getPreviousHash() {
|
|
81
|
+
return this._previousHash;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Main tool-event dispatcher. Classifies the event and routes to appropriate action.
|
|
86
|
+
* @param {object} event - Tool event from WebSocket
|
|
87
|
+
*/
|
|
88
|
+
_onToolEvent(event) {
|
|
89
|
+
if (!this.enabled) return;
|
|
90
|
+
|
|
91
|
+
const toolName = event.tool || event.name || '';
|
|
92
|
+
const args = event.args || {};
|
|
93
|
+
const isCall = event.type === 'tool_call';
|
|
94
|
+
const isResult = event.type === 'tool_result';
|
|
95
|
+
|
|
96
|
+
// Extract short tool name (strip prefixes like 'default_api:', 'mcp_project-graph_')
|
|
97
|
+
const shortName = this._shortName(toolName);
|
|
98
|
+
|
|
99
|
+
// Status ribbon — update immediately on call
|
|
100
|
+
if (isCall) {
|
|
101
|
+
const statusText = this._buildStatusText(shortName, args);
|
|
102
|
+
if (statusText) this._emitStatus(statusText);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Visual focus — classify and dispatch (debounced for heavy ops)
|
|
106
|
+
const action = this._classify(shortName, args, isCall, isResult, event);
|
|
107
|
+
if (action) {
|
|
108
|
+
if (action.immediate) {
|
|
109
|
+
this._emitFocusNow(action.focus);
|
|
110
|
+
} else {
|
|
111
|
+
this._emitFocusDebounced(action.focus, action.debounce || HEAVY_DEBOUNCE);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Classify tool event into a visual action.
|
|
118
|
+
* Only handles tools emitted by our MCP server (navigate, get_skeleton, etc.).
|
|
119
|
+
* IDE-local tools (view_file, grep_search) never arrive over WebSocket.
|
|
120
|
+
* @returns {{focus: object, debounce?: number, immediate?: boolean}|null}
|
|
121
|
+
*/
|
|
122
|
+
_classify(tool, args, isCall, isResult, raw) {
|
|
123
|
+
if (!isCall) return null;
|
|
124
|
+
|
|
125
|
+
// === Graph navigation ===
|
|
126
|
+
if (tool === 'navigate') {
|
|
127
|
+
if (args.action === 'expand' && args.symbol) {
|
|
128
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'focus' }, debounce: HEAVY_DEBOUNCE };
|
|
129
|
+
}
|
|
130
|
+
if (args.action === 'deps' && args.symbol) {
|
|
131
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'deps' }, debounce: HEAVY_DEBOUNCE };
|
|
132
|
+
}
|
|
133
|
+
if (args.action === 'usages' && args.symbol) {
|
|
134
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'deps' }, debounce: HEAVY_DEBOUNCE };
|
|
135
|
+
}
|
|
136
|
+
if (args.action === 'call_chain' && args.from && args.to) {
|
|
137
|
+
return { focus: { type: 'graph', target: { from: args.from, to: args.to }, action: 'chain' }, immediate: true };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// === Skeleton / Overview ===
|
|
142
|
+
if (tool === 'get_skeleton' || tool === 'get_ai_context') {
|
|
143
|
+
return { focus: { type: 'graph', action: 'fit' }, immediate: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// === Code compaction (compact_file action has a file path) ===
|
|
147
|
+
if (tool === 'compact' && args.path) {
|
|
148
|
+
return { focus: { type: 'file', target: args.path }, debounce: HEAVY_DEBOUNCE };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// === Analysis ===
|
|
152
|
+
if (tool === 'analyze') {
|
|
153
|
+
return { focus: { type: 'analysis' }, immediate: true };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build human-readable status text for the ribbon.
|
|
161
|
+
* Only MCP-server tools arrive here (navigate, get_skeleton, compact, analyze, docs, etc.).
|
|
162
|
+
* @param {string} tool
|
|
163
|
+
* @param {object} args
|
|
164
|
+
* @returns {string}
|
|
165
|
+
*/
|
|
166
|
+
_buildStatusText(tool, args) {
|
|
167
|
+
const file = args.path || '';
|
|
168
|
+
const short = file ? file.split('/').slice(-2).join('/') : '';
|
|
169
|
+
|
|
170
|
+
switch (tool) {
|
|
171
|
+
case 'navigate': {
|
|
172
|
+
if (args.action === 'expand') return `Expanding ${args.symbol}`;
|
|
173
|
+
if (args.action === 'deps') return `Tracing deps of ${args.symbol}`;
|
|
174
|
+
if (args.action === 'usages') return `Finding usages of ${args.symbol}`;
|
|
175
|
+
if (args.action === 'call_chain') return `Tracing ${args.from} → ${args.to}`;
|
|
176
|
+
if (args.action === 'sub_projects') return `Scanning sub-projects`;
|
|
177
|
+
return `Navigating graph`;
|
|
178
|
+
}
|
|
179
|
+
case 'get_skeleton': return `Scanning project structure`;
|
|
180
|
+
case 'get_ai_context': return `Loading AI context`;
|
|
181
|
+
case 'compact': return `Compacting ${short}`;
|
|
182
|
+
case 'analyze': return `Analyzing: ${args.action || ''}`;
|
|
183
|
+
case 'docs': return `Documentation: ${args.action || ''}`;
|
|
184
|
+
case 'jsdoc': return `JSDoc: ${args.action || ''}`;
|
|
185
|
+
case 'db': return `Database: ${args.action || ''}`;
|
|
186
|
+
case 'testing': return `Tests: ${args.action || ''}`;
|
|
187
|
+
case 'filters': return `Filters: ${args.action || ''}`;
|
|
188
|
+
default: return tool ? `Running ${tool}` : '';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract short tool name from full prefixed name.
|
|
194
|
+
* 'default_api:view_file' → 'view_file'
|
|
195
|
+
* 'mcp_project-graph_navigate' → 'navigate'
|
|
196
|
+
*/
|
|
197
|
+
_shortName(full) {
|
|
198
|
+
// Strip 'default_api:' prefix
|
|
199
|
+
let name = full.replace(/^default_api:/, '');
|
|
200
|
+
// Strip 'mcp_project-graph_' prefix
|
|
201
|
+
name = name.replace(/^mcp_project-graph_/, '');
|
|
202
|
+
return name;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Emit focus change immediately (for urgent actions like call_chain).
|
|
207
|
+
*/
|
|
208
|
+
_emitFocusNow(focus) {
|
|
209
|
+
if (this._debounceTimer) {
|
|
210
|
+
clearTimeout(this._debounceTimer);
|
|
211
|
+
this._debounceTimer = null;
|
|
212
|
+
}
|
|
213
|
+
this.currentFocus = focus;
|
|
214
|
+
this._emit('follow-focus-changed', focus);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Emit focus change with debounce (for rapid file reads, etc.).
|
|
219
|
+
*/
|
|
220
|
+
_emitFocusDebounced(focus, delay) {
|
|
221
|
+
if (this._debounceTimer) {
|
|
222
|
+
clearTimeout(this._debounceTimer);
|
|
223
|
+
}
|
|
224
|
+
this._debounceTimer = setTimeout(() => {
|
|
225
|
+
this._debounceTimer = null;
|
|
226
|
+
this.currentFocus = focus;
|
|
227
|
+
this._emit('follow-focus-changed', focus);
|
|
228
|
+
}, delay);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Emit status text for the ribbon.
|
|
233
|
+
*/
|
|
234
|
+
_emitStatus(text) {
|
|
235
|
+
this.statusText = text;
|
|
236
|
+
this._emit('follow-status-changed', { text });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Singleton instance */
|
|
241
|
+
export const followController = new FollowController();
|
package/web/index.html
CHANGED
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
<span id="compression-stats" class="compression-stats" style="display:none"></span>
|
|
23
23
|
</div>
|
|
24
24
|
<div class="topbar-right">
|
|
25
|
+
<button id="follow-btn" class="follow-btn" title="Follow mode: UI follows agent actions">
|
|
26
|
+
<span class="material-symbols-outlined">smart_toy</span>
|
|
27
|
+
FOLLOW
|
|
28
|
+
</button>
|
|
25
29
|
<span id="agent-badge" class="agent-badge" style="display:none"></span>
|
|
26
30
|
<span id="status-indicator" class="status connected"></span>
|
|
27
31
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @ctx .context/web/panels/ActionBoard/ActionBoard.ctx
|
|
2
2
|
import t from"@symbiotejs/symbiote";import{state as e,events as o}from"../../dashboard-state.js";import s from"./ActionBoard.css.js";import n from"./ActionBoard.tpl.js";
|
|
3
3
|
import"../EventItem/EventItem.js";
|
|
4
|
-
export class ActionBoard extends t{init$={eventsItems:[]};initCallback(){
|
|
4
|
+
export class ActionBoard extends t{init$={eventsItems:[]};initCallback(){o.addEventListener("global-tool-event",t=>{const o=[...e.events].reverse();this.$.eventsItems=o}),this.$.eventsItems=[...e.events].reverse()}}
|
|
5
5
|
ActionBoard.template=n,ActionBoard.rootStyles=s,ActionBoard.reg("pg-action-board");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @ctx .context/web/panels/SettingsPanel/SettingsPanel.tpl.ctx
|
|
2
|
-
export default'\n<div class="pg-stg-title">
|
|
2
|
+
export default'\n<div class="pg-stg-title">Actions</div>\n<div style="display:flex;gap:8px;margin-bottom:16px">\n<button class="pg-stg-btn" ref="refreshBtn">↻ Refresh</button>\n<button class="pg-stg-btn pg-stg-btn-danger" ref="restartBtn">⟳ Restart</button>\n<button class="pg-stg-btn pg-stg-btn-danger" ref="stopBtn">⏹ Stop</button>\n</div>\n<div ref="restartStatus" style="margin-bottom:16px;font-size:11px;color:var(--sn-text-dim)"></div>\n\n<div class="pg-stg-title">Backend</div>\n<div class="pg-stg-card" ref="backendCard"></div>\n\n<div class="pg-stg-title">Active Instances</div>\n<div ref="instanceList"></div>\n\n<div class="pg-stg-title">Server Lifecycle</div>\n<div class="pg-stg-card" ref="lifecycleCard">\n <div class="pg-stg-metric"><span>Auto-shutdown</span><span class="pg-stg-val" ref="shutdownTimer">—</span></div>\n <div class="pg-stg-metric"><span>Uptime</span><span class="pg-stg-val" ref="uptimeVal">—</span></div>\n</div>\n';
|
|
@@ -16,7 +16,10 @@ function _getLang(path){if(!path)return'js';const i=path.lastIndexOf('.');if(i<0
|
|
|
16
16
|
// Toggle button label: "EXPAND" — beautifies via Terser + injects JSDoc from .ctx.
|
|
17
17
|
// _isReadable = false (compression saves <15% — already compact)
|
|
18
18
|
|
|
19
|
-
export class CodeViewer extends e{init$={filename:"Select a file",hasFile:!1,viewMode:"source",modeLabel:"source",statsText:"",showToggle:!1,toggleLabel:"",
|
|
19
|
+
export class CodeViewer extends e{init$={filename:"Select a file",hasFile:!1,viewMode:"source",modeLabel:"source",statsText:"",showToggle:!1,toggleLabel:"",onShowInGraph:()=>{
|
|
20
|
+
if(!this._currentPath)return;
|
|
21
|
+
window.location.hash = `#graph?focus=${encodeURIComponent(this._currentPath)}`;
|
|
22
|
+
},onToggleMode:()=>{
|
|
20
23
|
const lang=_getLang(this._currentPath);
|
|
21
24
|
if(lang==='md'){
|
|
22
25
|
this.$.viewMode=this.$.viewMode==="rendered"?"raw":"rendered";
|
|
@@ -26,7 +29,7 @@ export class CodeViewer extends e{init$={filename:"Select a file",hasFile:!1,vie
|
|
|
26
29
|
// Toggle between source and the transformation
|
|
27
30
|
this.$.viewMode=this.$.viewMode==="source"?"transformed":"source";
|
|
28
31
|
this._showCurrentMode();
|
|
29
|
-
}};_fileData=null;_isReadable=!1;_transformCache=null;_loadingTransform=!1;_currentPath=null;initCallback(){t.addEventListener("file-selected",e=>this._loadFile(e.detail.path))}renderCallback(){this.sub("hasFile",e=>{this.toggleAttribute("has-file",e)}),this.sub("viewMode",e=>{
|
|
32
|
+
}};_fileData=null;_isReadable=!1;_transformCache=null;_loadingTransform=!1;_currentPath=null;initCallback(){t.addEventListener("file-selected",e=>this._loadFile(e.detail.path));t.addEventListener("follow-focus-changed",e=>{const d=e.detail;if(d.type==="file"&&d.target){this._loadFile(d.target);if(d.meta?.startLine){setTimeout(()=>{const c=this._getCodeBlock();if(c&&c.scrollToLine)c.scrollToLine(d.meta.startLine)},200)}}});if(o.activeFile)requestAnimationFrame(()=>this._loadFile(o.activeFile))}renderCallback(){this.sub("hasFile",e=>{this.toggleAttribute("has-file",e)}),this.sub("viewMode",e=>{
|
|
30
33
|
const lang=_getLang(this._currentPath);
|
|
31
34
|
this.toggleAttribute("mode-raw","source"!==e);
|
|
32
35
|
if(lang==='md'){
|
|
@@ -50,28 +53,36 @@ if(lang==='md'){
|
|
|
50
53
|
e.$.lang=lang;
|
|
51
54
|
if("transformed"===this.$.viewMode){
|
|
52
55
|
// Show cached transform if available
|
|
53
|
-
if(this._transformCache){
|
|
56
|
+
if(this._transformCache){
|
|
57
|
+
e.$.code=this._transformCache;
|
|
58
|
+
if(this._transformStatsText) this.$.statsText=this._transformStatsText;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
54
61
|
if(this._loadingTransform)return;
|
|
55
62
|
this._loadingTransform=!0;
|
|
56
63
|
e.$.code=this._isReadable?"// Compressing...":"// Expanding...";
|
|
57
64
|
try{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
if(this._isReadable){
|
|
66
|
+
// MODE A: readable source → compress
|
|
67
|
+
const t=await n("/api/compact-file",{path:this._currentPath});
|
|
68
|
+
this._transformCache=t?.code||"// Compression unavailable";
|
|
69
|
+
this._transformStatsText=t?`Compressed: ${(t.compressed/1000).toFixed(1)}K chars (${t.savings})`:"";
|
|
70
|
+
}else{
|
|
71
|
+
// MODE B: compact source → expand (beautify + inject JSDoc from .ctx)
|
|
72
|
+
const t=await n("/api/expand-file",{path:this._currentPath});
|
|
73
|
+
this._transformCache=t?.code||"// Expand unavailable";
|
|
74
|
+
this._transformStatsText=t?`Expanded: ${(t.decompiled/1000).toFixed(1)}K chars | JSDocs injected: ${t.injected||0}`:"";
|
|
75
|
+
}
|
|
76
|
+
if(this._transformStatsText)this.$.statsText=this._transformStatsText;
|
|
67
77
|
e.$.code=this._transformCache;
|
|
68
78
|
}catch{e.$.code=this._isReadable?"// Compression failed":"// Expand failed"}
|
|
69
79
|
finally{this._loadingTransform=!1}
|
|
70
80
|
return;
|
|
71
81
|
}
|
|
72
82
|
// Source mode — raw file as-is
|
|
83
|
+
this.$.statsText=this._baseStatsText;
|
|
73
84
|
e.$.code=this._fileData.raw;
|
|
74
|
-
}async _loadFile(e){this.$.filename=e,this.$.hasFile=!1,this._fileData=null,this.$.statsText="",this._transformCache=null,this._currentPath=e;
|
|
85
|
+
}async _loadFile(e){this.$.filename=e,this.$.hasFile=!1,this._fileData=null,this.$.statsText="",this._baseStatsText="",this._transformStatsText="",this._transformCache=null,this._currentPath=e;
|
|
75
86
|
const lang=_getLang(e);
|
|
76
87
|
if(lang==='image'){
|
|
77
88
|
const i=this._getCodeBlock();
|
|
@@ -97,7 +108,10 @@ let s=_raw?.content||o;
|
|
|
97
108
|
// If no .ctx, source is readable → COMPACT available
|
|
98
109
|
const hasCtx=!!(t.ctxTok&&t.ctxTok>0);
|
|
99
110
|
this._isReadable=!hasCtx;
|
|
100
|
-
this._fileData={compact:o,raw:s,codeTok:t.codeTok||0,ctxTok:t.ctxTok||0,totalTok:t.totalTok||0,expanded:t.expanded||0,savings:t.savings||"0%"}
|
|
111
|
+
this._fileData={compact:o,raw:s,codeTok:t.codeTok||0,ctxTok:t.ctxTok||0,totalTok:t.totalTok||0,expanded:t.expanded||0,savings:t.savings||"0%"};
|
|
112
|
+
this._baseStatsText=t.codeTok&&t.expanded?formatStats(t):"";
|
|
113
|
+
this.$.statsText=this._baseStatsText;
|
|
114
|
+
const i=this._getCodeBlock();
|
|
101
115
|
if(lang==='md'){
|
|
102
116
|
this.$.viewMode="rendered";
|
|
103
117
|
this.$.modeLabel="rendered";
|
|
@@ -114,4 +128,25 @@ if(lang==='md'){
|
|
|
114
128
|
this.$.toggleLabel=this._isReadable?"compact":"expand";
|
|
115
129
|
i&&(i.$.code=s);
|
|
116
130
|
}
|
|
117
|
-
this.$.hasFile=!0}catch(e){const n=this._getCodeBlock();n&&(n.$.lang='plain',n.$.code=`// Error: ${e.message}`),this.$.showToggle=!1,this.$.hasFile=!0}}}
|
|
131
|
+
this.$.hasFile=!0}catch(e){const n=this._getCodeBlock();n&&(n.$.lang='plain',n.$.code=`// Error: ${e.message}`),this.$.showToggle=!1,this.$.hasFile=!0}}}
|
|
132
|
+
|
|
133
|
+
CodeViewer.template=`
|
|
134
|
+
<div class="pg-code-header">
|
|
135
|
+
<span class="pg-code-filename" bind="textContent: filename"></span>
|
|
136
|
+
<div class="pg-code-controls">
|
|
137
|
+
<span class="pg-code-stats" bind="textContent: statsText"></span>
|
|
138
|
+
<button class="pg-mode-toggle" bind="onclick: onShowInGraph" title="Show in Graph">
|
|
139
|
+
<span class="material-symbols-outlined" style="font-size:14px">account_tree</span>
|
|
140
|
+
<span class="pg-mode-label">graph</span>
|
|
141
|
+
</button>
|
|
142
|
+
<button class="pg-mode-toggle" bind="onclick: onToggleMode; hidden: !showToggle" title="Toggle view mode">
|
|
143
|
+
<span class="material-symbols-outlined" style="font-size:14px">compress</span>
|
|
144
|
+
<span class="pg-mode-label" bind="textContent: modeLabel"></span>
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
<code-block></code-block>
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
CodeViewer.rootStyles="\n pg-code-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n pg-code-viewer:not([has-file]) code-block {\n display: none;\n }\n .pg-code-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px;\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n border-bottom: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n background: var(--sn-node-header-bg, hsl(37, 25%, 93%));\n gap: 8px;\n }\n .pg-code-filename {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n .pg-code-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n }\n .pg-code-stats {\n font-size: 10px;\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n white-space: nowrap;\n }\n .pg-mode-toggle {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 8px;\n border: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n border-radius: 4px;\n background: var(--sn-bg, hsl(37, 30%, 91%));\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n font-family: inherit;\n font-size: 10px;\n cursor: pointer;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n transition: all 120ms ease;\n }\n .pg-mode-toggle:hover {\n background: var(--sn-node-hover, hsl(36, 22%, 88%));\n color: var(--sn-text, hsl(30, 15%, 18%));\n }\n pg-code-viewer[mode-raw] .pg-mode-toggle {\n background: hsla(210, 45%, 45%, 0.12);\n border-color: var(--sn-cat-server, hsl(210, 45%, 45%));\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n }\n .pg-mode-toggle[hidden] {\n display: none;\n }\n code-block {\n flex: 1;\n min-height: 0;\n }\n";
|
|
152
|
+
CodeViewer.reg("pg-code-viewer");
|