dialog-ide 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +201 -0
  3. package/README.md +211 -0
  4. package/THIRD_PARTY_LICENSES.md +157 -0
  5. package/bin/darwin-arm64/aambundle +0 -0
  6. package/bin/darwin-arm64/dgdebug +0 -0
  7. package/bin/darwin-arm64/dialogc +0 -0
  8. package/bin/dialog-lib/stddebug.dg +112 -0
  9. package/bin/dialog-lib/stdlib.dg +6730 -0
  10. package/bin/dialog-lib/unit.dg +187 -0
  11. package/dist/cli/commands/run-skein.d.ts +17 -0
  12. package/dist/cli/commands/run-skein.d.ts.map +1 -0
  13. package/dist/cli/commands/run-skein.js +105 -0
  14. package/dist/cli/commands/run-skein.js.map +1 -0
  15. package/dist/cli/commands/sources.d.ts +12 -0
  16. package/dist/cli/commands/sources.d.ts.map +1 -0
  17. package/dist/cli/commands/sources.js +42 -0
  18. package/dist/cli/commands/sources.js.map +1 -0
  19. package/dist/cli/commands/test.d.ts +9 -0
  20. package/dist/cli/commands/test.d.ts.map +1 -0
  21. package/dist/cli/commands/test.js +41 -0
  22. package/dist/cli/commands/test.js.map +1 -0
  23. package/dist/cli/context.d.ts +9 -0
  24. package/dist/cli/context.d.ts.map +1 -0
  25. package/dist/cli/context.js +78 -0
  26. package/dist/cli/context.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +32 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/dialog-cover-resize.d.ts +2 -0
  32. package/dist/dialog-cover-resize.d.ts.map +1 -0
  33. package/dist/dialog-cover-resize.js +46 -0
  34. package/dist/dialog-cover-resize.js.map +1 -0
  35. package/dist/dialog-export.d.ts +23 -0
  36. package/dist/dialog-export.d.ts.map +1 -0
  37. package/dist/dialog-export.js +133 -0
  38. package/dist/dialog-export.js.map +1 -0
  39. package/dist/dialog-feelies.d.ts +4 -0
  40. package/dist/dialog-feelies.d.ts.map +1 -0
  41. package/dist/dialog-feelies.js +62 -0
  42. package/dist/dialog-feelies.js.map +1 -0
  43. package/dist/dialog-outline.d.ts +19 -0
  44. package/dist/dialog-outline.d.ts.map +1 -0
  45. package/dist/dialog-outline.js +122 -0
  46. package/dist/dialog-outline.js.map +1 -0
  47. package/dist/dialog-project-init.d.ts +5 -0
  48. package/dist/dialog-project-init.d.ts.map +1 -0
  49. package/dist/dialog-project-init.js +107 -0
  50. package/dist/dialog-project-init.js.map +1 -0
  51. package/dist/dialog-source-coverage.d.ts +9 -0
  52. package/dist/dialog-source-coverage.d.ts.map +1 -0
  53. package/dist/dialog-source-coverage.js +61 -0
  54. package/dist/dialog-source-coverage.js.map +1 -0
  55. package/dist/dialog-source-decoration-provider.d.ts +11 -0
  56. package/dist/dialog-source-decoration-provider.d.ts.map +1 -0
  57. package/dist/dialog-source-decoration-provider.js +79 -0
  58. package/dist/dialog-source-decoration-provider.js.map +1 -0
  59. package/dist/dialog-symbol-index.d.ts +19 -0
  60. package/dist/dialog-symbol-index.d.ts.map +1 -0
  61. package/dist/dialog-symbol-index.js +107 -0
  62. package/dist/dialog-symbol-index.js.map +1 -0
  63. package/dist/dialog-symbol-provider.d.ts +5 -0
  64. package/dist/dialog-symbol-provider.d.ts.map +1 -0
  65. package/dist/dialog-symbol-provider.js +56 -0
  66. package/dist/dialog-symbol-provider.js.map +1 -0
  67. package/dist/dialog-web-export.d.ts +25 -0
  68. package/dist/dialog-web-export.d.ts.map +1 -0
  69. package/dist/dialog-web-export.js +283 -0
  70. package/dist/dialog-web-export.js.map +1 -0
  71. package/dist/dialog-workspace-symbol-provider.d.ts +16 -0
  72. package/dist/dialog-workspace-symbol-provider.d.ts.map +1 -0
  73. package/dist/dialog-workspace-symbol-provider.js +107 -0
  74. package/dist/dialog-workspace-symbol-provider.js.map +1 -0
  75. package/dist/dialog-workspace-watcher.d.ts +22 -0
  76. package/dist/dialog-workspace-watcher.d.ts.map +1 -0
  77. package/dist/dialog-workspace-watcher.js +102 -0
  78. package/dist/dialog-workspace-watcher.js.map +1 -0
  79. package/dist/dialoged/skein/compile-error.d.ts +10 -0
  80. package/dist/dialoged/skein/compile-error.d.ts.map +1 -0
  81. package/dist/dialoged/skein/compile-error.js +28 -0
  82. package/dist/dialoged/skein/compile-error.js.map +1 -0
  83. package/dist/dialoged/skein/dynamic.d.ts +14 -0
  84. package/dist/dialoged/skein/dynamic.d.ts.map +1 -0
  85. package/dist/dialoged/skein/dynamic.js +176 -0
  86. package/dist/dialoged/skein/dynamic.js.map +1 -0
  87. package/dist/dialoged/skein/frotz-build.d.ts +10 -0
  88. package/dist/dialoged/skein/frotz-build.d.ts.map +1 -0
  89. package/dist/dialoged/skein/frotz-build.js +70 -0
  90. package/dist/dialoged/skein/frotz-build.js.map +1 -0
  91. package/dist/dialoged/skein/index.d.ts +15 -0
  92. package/dist/dialoged/skein/index.d.ts.map +1 -0
  93. package/dist/dialoged/skein/index.js +31 -0
  94. package/dist/dialoged/skein/index.js.map +1 -0
  95. package/dist/dialoged/skein/io.d.ts +15 -0
  96. package/dist/dialoged/skein/io.d.ts.map +1 -0
  97. package/dist/dialoged/skein/io.js +53 -0
  98. package/dist/dialoged/skein/io.js.map +1 -0
  99. package/dist/dialoged/skein/persistence.d.ts +13 -0
  100. package/dist/dialoged/skein/persistence.d.ts.map +1 -0
  101. package/dist/dialoged/skein/persistence.js +228 -0
  102. package/dist/dialoged/skein/persistence.js.map +1 -0
  103. package/dist/dialoged/skein/process.d.ts +38 -0
  104. package/dist/dialoged/skein/process.d.ts.map +1 -0
  105. package/dist/dialoged/skein/process.js +189 -0
  106. package/dist/dialoged/skein/process.js.map +1 -0
  107. package/dist/dialoged/skein/progress.d.ts +17 -0
  108. package/dist/dialoged/skein/progress.d.ts.map +1 -0
  109. package/dist/dialoged/skein/progress.js +9 -0
  110. package/dist/dialoged/skein/progress.js.map +1 -0
  111. package/dist/dialoged/skein/project.d.ts +44 -0
  112. package/dist/dialoged/skein/project.d.ts.map +1 -0
  113. package/dist/dialoged/skein/project.js +223 -0
  114. package/dist/dialoged/skein/project.js.map +1 -0
  115. package/dist/dialoged/skein/search.d.ts +15 -0
  116. package/dist/dialoged/skein/search.d.ts.map +1 -0
  117. package/dist/dialoged/skein/search.js +99 -0
  118. package/dist/dialoged/skein/search.js.map +1 -0
  119. package/dist/dialoged/skein/service.d.ts +91 -0
  120. package/dist/dialoged/skein/service.d.ts.map +1 -0
  121. package/dist/dialoged/skein/service.js +1097 -0
  122. package/dist/dialoged/skein/service.js.map +1 -0
  123. package/dist/dialoged/skein/session.d.ts +96 -0
  124. package/dist/dialoged/skein/session.d.ts.map +1 -0
  125. package/dist/dialoged/skein/session.js +690 -0
  126. package/dist/dialoged/skein/session.js.map +1 -0
  127. package/dist/dialoged/skein/syntax.d.ts +5 -0
  128. package/dist/dialoged/skein/syntax.d.ts.map +1 -0
  129. package/dist/dialoged/skein/syntax.js +150 -0
  130. package/dist/dialoged/skein/syntax.js.map +1 -0
  131. package/dist/dialoged/skein/trace.d.ts +31 -0
  132. package/dist/dialoged/skein/trace.d.ts.map +1 -0
  133. package/dist/dialoged/skein/trace.js +172 -0
  134. package/dist/dialoged/skein/trace.js.map +1 -0
  135. package/dist/dialoged/skein/tree.d.ts +114 -0
  136. package/dist/dialoged/skein/tree.d.ts.map +1 -0
  137. package/dist/dialoged/skein/tree.js +701 -0
  138. package/dist/dialoged/skein/tree.js.map +1 -0
  139. package/dist/dialoged/skein/ui/ansi.d.ts +4 -0
  140. package/dist/dialoged/skein/ui/ansi.d.ts.map +1 -0
  141. package/dist/dialoged/skein/ui/ansi.js +215 -0
  142. package/dist/dialoged/skein/ui/ansi.js.map +1 -0
  143. package/dist/dialoged/skein/ui/diff.d.ts +7 -0
  144. package/dist/dialoged/skein/ui/diff.d.ts.map +1 -0
  145. package/dist/dialoged/skein/ui/diff.js +17 -0
  146. package/dist/dialoged/skein/ui/diff.js.map +1 -0
  147. package/dist/dialoged/skein/ui/knot-menu.d.ts +5 -0
  148. package/dist/dialoged/skein/ui/knot-menu.d.ts.map +1 -0
  149. package/dist/dialoged/skein/ui/knot-menu.js +89 -0
  150. package/dist/dialoged/skein/ui/knot-menu.js.map +1 -0
  151. package/dist/dialoged/skein/ui/marker-colors.d.ts +5 -0
  152. package/dist/dialoged/skein/ui/marker-colors.d.ts.map +1 -0
  153. package/dist/dialoged/skein/ui/marker-colors.js +18 -0
  154. package/dist/dialoged/skein/ui/marker-colors.js.map +1 -0
  155. package/dist/dialoged/skein/ui/render.d.ts +13 -0
  156. package/dist/dialoged/skein/ui/render.d.ts.map +1 -0
  157. package/dist/dialoged/skein/ui/render.js +332 -0
  158. package/dist/dialoged/skein/ui/render.js.map +1 -0
  159. package/dist/dialoged/skein/ui/traceRender.d.ts +11 -0
  160. package/dist/dialoged/skein/ui/traceRender.d.ts.map +1 -0
  161. package/dist/dialoged/skein/ui/traceRender.js +139 -0
  162. package/dist/dialoged/skein/ui/traceRender.js.map +1 -0
  163. package/dist/dialoged/skein/ui/tree-pane.d.ts +3 -0
  164. package/dist/dialoged/skein/ui/tree-pane.d.ts.map +1 -0
  165. package/dist/dialoged/skein/ui/tree-pane.js +135 -0
  166. package/dist/dialoged/skein/ui/tree-pane.js.map +1 -0
  167. package/dist/dialoged/skein/ui/webExportPage.d.ts +29 -0
  168. package/dist/dialoged/skein/ui/webExportPage.d.ts.map +1 -0
  169. package/dist/dialoged/skein/ui/webExportPage.js +72 -0
  170. package/dist/dialoged/skein/ui/webExportPage.js.map +1 -0
  171. package/dist/extension.d.ts +4 -0
  172. package/dist/extension.d.ts.map +1 -0
  173. package/dist/extension.js +885 -0
  174. package/dist/extension.js.map +1 -0
  175. package/dist/session-runner.d.ts +23 -0
  176. package/dist/session-runner.d.ts.map +1 -0
  177. package/dist/session-runner.js +92 -0
  178. package/dist/session-runner.js.map +1 -0
  179. package/language-configuration.json +43 -0
  180. package/media/icon.png +0 -0
  181. package/media/icons/arrow-down-circle-solid.svg +3 -0
  182. package/media/icons/arrow-down-solid.svg +3 -0
  183. package/media/icons/arrow-path-solid.svg +3 -0
  184. package/media/icons/arrow-right-solid.svg +3 -0
  185. package/media/icons/arrow-up-on-square-stack-solid.svg +5 -0
  186. package/media/icons/arrow-up-solid.svg +3 -0
  187. package/media/icons/arrow-uturn-left.svg +3 -0
  188. package/media/icons/arrow-uturn-right.svg +3 -0
  189. package/media/icons/bookmark-solid.svg +3 -0
  190. package/media/icons/bug-ant-solid.svg +3 -0
  191. package/media/icons/check-badge-solid.svg +3 -0
  192. package/media/icons/check-circle-solid.svg +3 -0
  193. package/media/icons/chevron-double-down-solid.svg +4 -0
  194. package/media/icons/chevron-double-up-solid.svg +4 -0
  195. package/media/icons/command-line-solid.svg +3 -0
  196. package/media/icons/cpu-chip-solid.svg +4 -0
  197. package/media/icons/dg-file-dark.svg +6 -0
  198. package/media/icons/dg-file-light.svg +6 -0
  199. package/media/icons/ellipsis-vertical-solid.svg +3 -0
  200. package/media/icons/exclamation-triangle.svg +3 -0
  201. package/media/icons/folder-arrow-down-solid.svg +3 -0
  202. package/media/icons/globe-alt.svg +3 -0
  203. package/media/icons/lock-closed-solid.svg +3 -0
  204. package/media/icons/magnifying-glass-solid.svg +3 -0
  205. package/media/icons/pencil-solid.svg +4 -0
  206. package/media/icons/play-solid.svg +3 -0
  207. package/media/icons/plus-circle-solid.svg +3 -0
  208. package/media/icons/scissors-solid.svg +5 -0
  209. package/media/icons/tag-solid.svg +3 -0
  210. package/media/icons/trash-solid.svg +3 -0
  211. package/media/icons/x-circle-solid.svg +3 -0
  212. package/media/js/datastar.js +9 -0
  213. package/media/js/main.js +1109 -0
  214. package/media/js/skein-loader.js +10 -0
  215. package/media/js/trace-loader.js +4 -0
  216. package/media/js/trace.js +147 -0
  217. package/media/style.css +5783 -0
  218. package/package.json +231 -0
  219. package/resources/bundle/default-cover.png +0 -0
  220. package/resources/bundle/introduction-to-if.pdf +0 -0
  221. package/resources/bundle/play-if-card.pdf +1990 -1
  222. package/resources/bundle/play.css +432 -0
  223. package/resources/bundle/style.css +450 -0
  224. package/resources/dfrotz-skein-patch.dg +4 -0
  225. package/syntaxes/dialog.tmLanguage.json +490 -0
@@ -0,0 +1,1097 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SkeinService = void 0;
37
+ const http = __importStar(require("http"));
38
+ const fs = __importStar(require("fs/promises"));
39
+ const path = __importStar(require("path"));
40
+ const session_1 = require("./session");
41
+ const compile_error_1 = require("./compile-error");
42
+ const tree_1 = require("./tree");
43
+ const render_1 = require("./ui/render");
44
+ const search_1 = require("./search");
45
+ const traceRender_1 = require("./ui/traceRender");
46
+ const trace_1 = require("./trace");
47
+ const syntax_1 = require("./syntax");
48
+ const STATIC_CONTENT_TYPES = {
49
+ '.css': 'text/css; charset=utf-8',
50
+ '.js': 'text/javascript; charset=utf-8',
51
+ '.svg': 'image/svg+xml'
52
+ };
53
+ const NO_ACTIVE_SESSION_FRAGMENT = '<div id="skein-app" class="p-4">No skein session running.</div>';
54
+ function readRequestBody(req) {
55
+ return new Promise((resolve, reject) => {
56
+ const chunks = [];
57
+ req.on('data', (chunk) => chunks.push(chunk));
58
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
59
+ req.on('error', reject);
60
+ });
61
+ }
62
+ const SPECIAL_KEYSTROKES = new Set(['enter', 'space', 'backspace']);
63
+ function normalizeKeystroke(text) {
64
+ if (text === ' ') {
65
+ return 'space';
66
+ }
67
+ if (text.length === 1 || SPECIAL_KEYSTROKES.has(text)) {
68
+ return text;
69
+ }
70
+ return null;
71
+ }
72
+ const SPINE_DIRECTIONS = new Set(['up', 'down', 'left', 'right', 'first', 'last']);
73
+ function parseSpineDirection(payload) {
74
+ const { direction } = payload;
75
+ return typeof direction === 'string' && SPINE_DIRECTIONS.has(direction)
76
+ ? direction
77
+ : null;
78
+ }
79
+ const SEEKABLE_STATUSES = new Set(['new', 'error']);
80
+ function parseSeekableStatus(payload) {
81
+ const { seekStatus } = payload;
82
+ return typeof seekStatus === 'string' && SEEKABLE_STATUSES.has(seekStatus)
83
+ ? seekStatus
84
+ : null;
85
+ }
86
+ function parseJsonBody(body) {
87
+ try {
88
+ const parsed = JSON.parse(body || '{}');
89
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
90
+ }
91
+ catch {
92
+ return {};
93
+ }
94
+ }
95
+ function parseKnotId(payload) {
96
+ const { knotId } = payload;
97
+ return typeof knotId === 'number' && Number.isInteger(knotId) ? knotId : null;
98
+ }
99
+ function parseNodeId(payload) {
100
+ const { nodeId } = payload;
101
+ return typeof nodeId === 'number' && Number.isInteger(nodeId) ? nodeId : null;
102
+ }
103
+ class SkeinService {
104
+ constructor(config) {
105
+ this.sessions = new Map();
106
+ this.isRunning = false;
107
+ this.sseClients = new Set();
108
+ this.onActiveSessionChange = () => this.broadcast();
109
+ this.cancelCurrentReplay = null;
110
+ this.currentProgress = null;
111
+ this.currentTrace = null;
112
+ this.searchQuery = '';
113
+ this.traceLoading = false;
114
+ this.traceSseClients = new Set();
115
+ this.grammarPromise = null;
116
+ this.config = config;
117
+ }
118
+ async start() {
119
+ if (this.isRunning) {
120
+ throw new Error('Service already running');
121
+ }
122
+ this.server = http.createServer((req, res) => {
123
+ this.handleRequest(req, res).catch((error) => {
124
+ console.error('Skein service request failed:', error);
125
+ if (!res.headersSent) {
126
+ res.writeHead(500);
127
+ }
128
+ res.end();
129
+ });
130
+ });
131
+ await new Promise((resolve, reject) => {
132
+ this.server.once('error', reject);
133
+ this.server.listen(this.config.port, this.config.host, resolve);
134
+ });
135
+ const address = this.server.address();
136
+ this.port = typeof address === 'object' && address !== null ? address.port : this.config.port;
137
+ this.isRunning = true;
138
+ }
139
+ async stop() {
140
+ if (!this.isRunning) {
141
+ return;
142
+ }
143
+ for (const session of this.sessions.values()) {
144
+ await session.stop();
145
+ }
146
+ this.sessions.clear();
147
+ for (const client of this.sseClients) {
148
+ client.end();
149
+ }
150
+ this.sseClients.clear();
151
+ for (const client of this.traceSseClients) {
152
+ client.end();
153
+ }
154
+ this.traceSseClients.clear();
155
+ if (this.activeSession) {
156
+ this.activeSession.offChange(this.onActiveSessionChange);
157
+ this.activeSession = undefined;
158
+ this.activeSessionId = undefined;
159
+ }
160
+ await new Promise((resolve, reject) => {
161
+ this.server.close((error) => (error ? reject(error) : resolve()));
162
+ });
163
+ this.server = undefined;
164
+ this.port = undefined;
165
+ this.isRunning = false;
166
+ }
167
+ getPort() {
168
+ if (this.port === undefined) {
169
+ throw new Error('Service is not running');
170
+ }
171
+ return this.port;
172
+ }
173
+ setActiveSession(session, sessionId, onSave) {
174
+ if (this.activeSession) {
175
+ this.activeSession.offChange(this.onActiveSessionChange);
176
+ }
177
+ this.activeSession = session;
178
+ this.activeSessionId = sessionId;
179
+ this.saveHandler = onSave;
180
+ if (this.activeSession) {
181
+ this.activeSession.onChange(this.onActiveSessionChange);
182
+ }
183
+ this.currentTrace = null;
184
+ this.traceLoading = false;
185
+ this.broadcastTrace();
186
+ this.searchQuery = '';
187
+ this.broadcast();
188
+ }
189
+ currentDisplayInfo() {
190
+ if (!this.activeSession || !this.activeSessionId) {
191
+ return undefined;
192
+ }
193
+ const tree = this.activeSession.getTree();
194
+ return { sessionId: this.activeSessionId, engine: tree.getEngine(), seed: tree.getSeed() };
195
+ }
196
+ currentSearchResults() {
197
+ const tree = this.activeSession?.getTree();
198
+ return tree ? (0, search_1.searchKnots)(tree, this.searchQuery) : { results: [], totalMatches: 0 };
199
+ }
200
+ async handleRequest(req, res) {
201
+ const url = new URL(req.url ?? '/', 'http://localhost');
202
+ if (url.pathname === '/') {
203
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
204
+ res.end((0, render_1.renderPage)(this.currentDisplayInfo(), this.activeSession?.getTree(), this.activeSession?.getGraphMenuId() ?? null, this.activeSession?.getTranscriptMenuId() ?? null, this.activeSession?.getShowDynamicState() ?? false, this.searchQuery, this.currentSearchResults(), this.activeSession?.getMarkerFilter() ?? null));
205
+ return;
206
+ }
207
+ if (url.pathname === '/events') {
208
+ this.handleSseConnect(res);
209
+ return;
210
+ }
211
+ if (url.pathname === '/trace') {
212
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
213
+ res.end((0, traceRender_1.renderTracePage)(this.currentTrace, this.traceLoading));
214
+ return;
215
+ }
216
+ if (url.pathname === '/trace/events') {
217
+ this.handleTraceSseConnect(res);
218
+ return;
219
+ }
220
+ if (url.pathname === '/trace/source-preview') {
221
+ await this.handleTraceSourcePreview(url, res);
222
+ return;
223
+ }
224
+ if (req.method === 'POST' && url.pathname === '/actions/send-command') {
225
+ await this.handleSendCommand(req, res);
226
+ return;
227
+ }
228
+ if (req.method === 'POST' && url.pathname === '/actions/send-keystroke') {
229
+ await this.handleSendKeystroke(req, res);
230
+ return;
231
+ }
232
+ if (req.method === 'POST' && url.pathname === '/actions/select-knot') {
233
+ await this.handleKnotAction(req, res, (session, knotId) => session.setActiveKnot(knotId), {
234
+ focusAfter: true
235
+ });
236
+ return;
237
+ }
238
+ if (req.method === 'POST' && url.pathname === '/actions/navigate-spine') {
239
+ await this.handleNavigateSpine(req, res);
240
+ return;
241
+ }
242
+ if (req.method === 'POST' && url.pathname === '/actions/seek-status') {
243
+ await this.handleSeekStatus(req, res);
244
+ return;
245
+ }
246
+ if (req.method === 'POST' && url.pathname === '/actions/new-child') {
247
+ await this.handleKnotAction(req, res, (session, knotId) => session.newChild(knotId), { focusAfter: true });
248
+ return;
249
+ }
250
+ if (req.method === 'POST' && url.pathname === '/actions/open-graph-menu') {
251
+ await this.handleKnotAction(req, res, (session, knotId) => session.openGraphMenu(knotId));
252
+ return;
253
+ }
254
+ if (req.method === 'POST' && url.pathname === '/actions/open-transcript-menu') {
255
+ await this.handleKnotAction(req, res, (session, knotId) => session.openTranscriptMenu(knotId));
256
+ return;
257
+ }
258
+ if (req.method === 'POST' && url.pathname === '/actions/bless-knot') {
259
+ await this.handleKnotAction(req, res, (session, knotId) => session.blessKnot(knotId), { flashMessage: 'Knot blessed' });
260
+ return;
261
+ }
262
+ if (req.method === 'POST' && url.pathname === '/actions/bless-changes') {
263
+ await this.handleKnotAction(req, res, (session, knotId) => session.blessChanges(knotId), {
264
+ flashMessage: 'Transcript blessed'
265
+ });
266
+ return;
267
+ }
268
+ if (req.method === 'POST' && url.pathname === '/actions/toggle-lock') {
269
+ await this.handleKnotAction(req, res, (session, knotId) => session.toggleLock(knotId));
270
+ return;
271
+ }
272
+ if (req.method === 'POST' && url.pathname === '/actions/toggle-tree-node') {
273
+ await this.handleKnotAction(req, res, (session, knotId) => session.toggleTreeNode(knotId));
274
+ return;
275
+ }
276
+ if (req.method === 'POST' && url.pathname === '/actions/set-label') {
277
+ await this.handleSetLabel(req, res);
278
+ return;
279
+ }
280
+ if (req.method === 'POST' && url.pathname === '/actions/set-marker') {
281
+ await this.handleSetMarker(req, res);
282
+ return;
283
+ }
284
+ if (req.method === 'POST' && url.pathname === '/actions/set-marker-filter') {
285
+ await this.handleSetMarkerFilter(req, res);
286
+ return;
287
+ }
288
+ if (req.method === 'POST' && url.pathname === '/actions/set-command') {
289
+ await this.handleSetCommand(req, res);
290
+ return;
291
+ }
292
+ if (req.method === 'POST' && url.pathname === '/actions/insert-parent') {
293
+ await this.handleInsertParent(req, res);
294
+ return;
295
+ }
296
+ if (req.method === 'POST' && url.pathname === '/actions/search') {
297
+ await this.handleSearch(req, res);
298
+ return;
299
+ }
300
+ if (req.method === 'POST' && url.pathname === '/actions/delete-knot') {
301
+ await this.handleKnotAction(req, res, (session, knotId) => session.deleteKnot(knotId));
302
+ return;
303
+ }
304
+ if (req.method === 'POST' && url.pathname === '/actions/splice-knot') {
305
+ await this.handleKnotAction(req, res, (session, knotId) => session.spliceKnot(knotId));
306
+ return;
307
+ }
308
+ if (req.method === 'POST' && url.pathname === '/actions/replay-to') {
309
+ await this.handleKnotAction(req, res, (session, knotId) => session.replayToKnot(knotId), {
310
+ flashMessage: 'Replayed to knot'
311
+ });
312
+ return;
313
+ }
314
+ if (req.method === 'POST' && url.pathname === '/actions/cancel-replay') {
315
+ await this.handleCancelReplay(req, res);
316
+ return;
317
+ }
318
+ if (req.method === 'POST' && url.pathname === '/actions/replay-all') {
319
+ await this.handleReplayAll(req, res);
320
+ return;
321
+ }
322
+ if (req.method === 'POST' && url.pathname === '/actions/undo') {
323
+ await this.handleUndoRedo(req, res, (session) => session.undo());
324
+ return;
325
+ }
326
+ if (req.method === 'POST' && url.pathname === '/actions/redo') {
327
+ await this.handleUndoRedo(req, res, (session) => session.redo());
328
+ return;
329
+ }
330
+ if (req.method === 'POST' && url.pathname === '/actions/save') {
331
+ await this.handleSave(req, res);
332
+ return;
333
+ }
334
+ if (req.method === 'POST' && url.pathname === '/actions/close-menus') {
335
+ await this.handleCloseMenus(req, res);
336
+ return;
337
+ }
338
+ if (req.method === 'POST' && url.pathname === '/actions/toggle-dynamic-state') {
339
+ await this.handleToggleDynamicState(req, res);
340
+ return;
341
+ }
342
+ if (req.method === 'POST' && url.pathname === '/actions/trace-knot') {
343
+ await this.handleTraceKnot(req, res);
344
+ return;
345
+ }
346
+ if (req.method === 'POST' && url.pathname === '/actions/trace-startup') {
347
+ await this.handleTraceStartup(req, res);
348
+ return;
349
+ }
350
+ if (req.method === 'POST' && url.pathname === '/actions/trace-search') {
351
+ await this.handleTraceSearch(req, res);
352
+ return;
353
+ }
354
+ if (req.method === 'POST' && url.pathname === '/actions/trace-toggle-node') {
355
+ await this.handleTraceToggleNode(req, res);
356
+ return;
357
+ }
358
+ if (req.method === 'POST' && url.pathname === '/actions/trace-expand-all') {
359
+ await this.handleTraceExpandCollapseAll(req, res, trace_1.expandAll);
360
+ return;
361
+ }
362
+ if (req.method === 'POST' && url.pathname === '/actions/trace-collapse-all') {
363
+ await this.handleTraceExpandCollapseAll(req, res, trace_1.collapseAll);
364
+ return;
365
+ }
366
+ if (url.pathname === '/style.css') {
367
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'style.css'));
368
+ return;
369
+ }
370
+ if (url.pathname === '/js/datastar.js') {
371
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'js', 'datastar.js'));
372
+ return;
373
+ }
374
+ if (url.pathname === '/js/main.js') {
375
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'js', 'main.js'));
376
+ return;
377
+ }
378
+ if (url.pathname === '/js/trace.js') {
379
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'js', 'trace.js'));
380
+ return;
381
+ }
382
+ if (url.pathname === '/js/skein-loader.js') {
383
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'js', 'skein-loader.js'));
384
+ return;
385
+ }
386
+ if (url.pathname === '/js/trace-loader.js') {
387
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'js', 'trace-loader.js'));
388
+ return;
389
+ }
390
+ if (url.pathname.startsWith('/icons/')) {
391
+ const name = path.basename(url.pathname.slice('/icons/'.length));
392
+ await this.serveStaticFile(res, path.join(this.config.mediaRoot, 'icons', name));
393
+ return;
394
+ }
395
+ res.writeHead(404);
396
+ res.end();
397
+ }
398
+ reportIfCompileError(error) {
399
+ if (error instanceof compile_error_1.DialogCompileError) {
400
+ this.config.onCompileError?.(error);
401
+ }
402
+ }
403
+ async handleSendCommand(req, res) {
404
+ if (!this.activeSession) {
405
+ res.writeHead(400);
406
+ res.end();
407
+ return;
408
+ }
409
+ const signals = parseJsonBody(await readRequestBody(req));
410
+ const rawCommand = signals.newCommand;
411
+ const command = (0, session_1.normalizeCommand)(typeof rawCommand === 'string' ? rawCommand : '');
412
+ if (command === '') {
413
+ res.writeHead(204);
414
+ res.end();
415
+ return;
416
+ }
417
+ try {
418
+ await this.activeSession.runCommand(command);
419
+ }
420
+ catch (error) {
421
+ console.error('Failed to run command:', error);
422
+ this.reportIfCompileError(error);
423
+ res.writeHead(500);
424
+ res.end();
425
+ return;
426
+ }
427
+ this.broadcastScript('sk.resetAndFocusCommandInput()');
428
+ res.writeHead(204);
429
+ res.end();
430
+ }
431
+ async handleSendKeystroke(req, res) {
432
+ if (!this.activeSession) {
433
+ res.writeHead(400);
434
+ res.end();
435
+ return;
436
+ }
437
+ const signals = parseJsonBody(await readRequestBody(req));
438
+ const rawKeystroke = signals.newKeystroke;
439
+ const keystroke = normalizeKeystroke(typeof rawKeystroke === 'string' ? rawKeystroke : '');
440
+ if (keystroke === null) {
441
+ res.writeHead(204);
442
+ res.end();
443
+ return;
444
+ }
445
+ try {
446
+ await this.activeSession.runCommand(keystroke);
447
+ }
448
+ catch (error) {
449
+ console.error('Failed to send keystroke:', error);
450
+ this.reportIfCompileError(error);
451
+ res.writeHead(500);
452
+ res.end();
453
+ return;
454
+ }
455
+ this.broadcastScript('sk.resetAndFocusCommandInput()');
456
+ res.writeHead(204);
457
+ res.end();
458
+ }
459
+ async handleNavigateSpine(req, res) {
460
+ if (!this.activeSession) {
461
+ res.writeHead(400);
462
+ res.end();
463
+ return;
464
+ }
465
+ const direction = parseSpineDirection(parseJsonBody(await readRequestBody(req)));
466
+ if (direction === null) {
467
+ res.writeHead(400);
468
+ res.end();
469
+ return;
470
+ }
471
+ try {
472
+ this.activeSession.navigateSpine(direction);
473
+ }
474
+ catch (error) {
475
+ console.error('Failed to navigate spine:', error);
476
+ this.reportIfCompileError(error);
477
+ res.writeHead(500);
478
+ res.end();
479
+ return;
480
+ }
481
+ const activeKnotId = this.activeSession.getTree().getActiveKnotId();
482
+ if (activeKnotId !== null) {
483
+ this.broadcastScript(`sk.resetAndFocusCommandInput(${activeKnotId})`);
484
+ }
485
+ res.writeHead(204);
486
+ res.end();
487
+ }
488
+ async handleSeekStatus(req, res) {
489
+ if (!this.activeSession) {
490
+ res.writeHead(400);
491
+ res.end();
492
+ return;
493
+ }
494
+ const status = parseSeekableStatus(parseJsonBody(await readRequestBody(req)));
495
+ if (status === null) {
496
+ res.writeHead(400);
497
+ res.end();
498
+ return;
499
+ }
500
+ try {
501
+ this.activeSession.seekStatus(status);
502
+ }
503
+ catch (error) {
504
+ console.error('Failed to seek status:', error);
505
+ this.reportIfCompileError(error);
506
+ res.writeHead(500);
507
+ res.end();
508
+ return;
509
+ }
510
+ const activeKnotId = this.activeSession.getTree().getActiveKnotId();
511
+ if (activeKnotId !== null) {
512
+ this.broadcastScript(`sk.resetAndFocusCommandInput(${activeKnotId})`);
513
+ }
514
+ res.writeHead(204);
515
+ res.end();
516
+ }
517
+ async handleKnotAction(req, res, fn, options = {}) {
518
+ if (!this.activeSession) {
519
+ res.writeHead(400);
520
+ res.end();
521
+ return;
522
+ }
523
+ const knotId = parseKnotId(parseJsonBody(await readRequestBody(req)));
524
+ if (knotId === null) {
525
+ res.writeHead(400);
526
+ res.end();
527
+ return;
528
+ }
529
+ try {
530
+ await fn(this.activeSession, knotId);
531
+ }
532
+ catch (error) {
533
+ this.activeSession.closeAllMenus();
534
+ if (error instanceof tree_1.KnotLockedError) {
535
+ this.broadcastFlash(error.message, 'error');
536
+ res.writeHead(409);
537
+ res.end();
538
+ return;
539
+ }
540
+ console.error('Knot action failed:', error);
541
+ this.reportIfCompileError(error);
542
+ res.writeHead(500);
543
+ res.end();
544
+ return;
545
+ }
546
+ if (options.flashMessage) {
547
+ this.broadcastFlash(options.flashMessage);
548
+ }
549
+ if (options.focusAfter) {
550
+ this.broadcastScript(`sk.resetAndFocusCommandInput(${knotId})`);
551
+ }
552
+ res.writeHead(204);
553
+ res.end();
554
+ }
555
+ async handleSetLabel(req, res) {
556
+ if (!this.activeSession) {
557
+ res.writeHead(400);
558
+ res.end();
559
+ return;
560
+ }
561
+ const payload = parseJsonBody(await readRequestBody(req));
562
+ const knotId = parseKnotId(payload);
563
+ if (knotId === null) {
564
+ res.writeHead(400);
565
+ res.end();
566
+ return;
567
+ }
568
+ const rawLabel = payload.label;
569
+ const label = typeof rawLabel === 'string' && rawLabel.trim() !== '' ? rawLabel.trim() : null;
570
+ try {
571
+ this.activeSession.setLabel(knotId, label);
572
+ }
573
+ catch (error) {
574
+ if (error instanceof tree_1.LabelConflictError) {
575
+ res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });
576
+ res.end(JSON.stringify({ error: error.message }));
577
+ return;
578
+ }
579
+ console.error('Failed to set label:', error);
580
+ res.writeHead(500);
581
+ res.end();
582
+ return;
583
+ }
584
+ res.writeHead(204);
585
+ res.end();
586
+ }
587
+ async handleSetMarker(req, res) {
588
+ if (!this.activeSession) {
589
+ res.writeHead(400);
590
+ res.end();
591
+ return;
592
+ }
593
+ const payload = parseJsonBody(await readRequestBody(req));
594
+ const knotId = parseKnotId(payload);
595
+ if (knotId === null) {
596
+ res.writeHead(400);
597
+ res.end();
598
+ return;
599
+ }
600
+ const marker = (0, tree_1.isValidMarker)(payload.marker) ? payload.marker : null;
601
+ try {
602
+ this.activeSession.setMarker(knotId, marker);
603
+ }
604
+ catch (error) {
605
+ console.error('Failed to set marker:', error);
606
+ res.writeHead(500);
607
+ res.end();
608
+ return;
609
+ }
610
+ res.writeHead(204);
611
+ res.end();
612
+ }
613
+ async handleSetMarkerFilter(req, res) {
614
+ if (!this.activeSession) {
615
+ res.writeHead(400);
616
+ res.end();
617
+ return;
618
+ }
619
+ const payload = parseJsonBody(await readRequestBody(req));
620
+ if (!(0, tree_1.isValidMarker)(payload.marker)) {
621
+ res.writeHead(400);
622
+ res.end();
623
+ return;
624
+ }
625
+ this.activeSession.setMarkerFilter(payload.marker);
626
+ res.writeHead(204);
627
+ res.end();
628
+ }
629
+ async handleInsertParent(req, res) {
630
+ if (!this.activeSession) {
631
+ res.writeHead(400);
632
+ res.end();
633
+ return;
634
+ }
635
+ const payload = parseJsonBody(await readRequestBody(req));
636
+ const knotId = parseKnotId(payload);
637
+ if (knotId === null) {
638
+ res.writeHead(400);
639
+ res.end();
640
+ return;
641
+ }
642
+ const command = typeof payload.command === 'string' ? payload.command : '';
643
+ try {
644
+ await this.activeSession.insertParent(knotId, command);
645
+ }
646
+ catch (error) {
647
+ console.error('Failed to insert parent:', error);
648
+ this.reportIfCompileError(error);
649
+ res.writeHead(500);
650
+ res.end();
651
+ return;
652
+ }
653
+ const activeKnotId = this.activeSession.getTree().getActiveKnotId();
654
+ if (activeKnotId !== null) {
655
+ this.broadcastScript(`sk.resetAndFocusCommandInput(${activeKnotId})`);
656
+ }
657
+ res.writeHead(204);
658
+ res.end();
659
+ }
660
+ async handleSetCommand(req, res) {
661
+ if (!this.activeSession) {
662
+ res.writeHead(400);
663
+ res.end();
664
+ return;
665
+ }
666
+ const payload = parseJsonBody(await readRequestBody(req));
667
+ const knotId = parseKnotId(payload);
668
+ if (knotId === null) {
669
+ res.writeHead(400);
670
+ res.end();
671
+ return;
672
+ }
673
+ const command = typeof payload.command === 'string' ? payload.command : '';
674
+ try {
675
+ await this.activeSession.setCommand(knotId, command);
676
+ }
677
+ catch (error) {
678
+ console.error('Failed to set command:', error);
679
+ this.reportIfCompileError(error);
680
+ res.writeHead(500);
681
+ res.end();
682
+ return;
683
+ }
684
+ res.writeHead(204);
685
+ res.end();
686
+ }
687
+ async handleSearch(req, res) {
688
+ if (!this.activeSession) {
689
+ res.writeHead(400);
690
+ res.end();
691
+ return;
692
+ }
693
+ const payload = parseJsonBody(await readRequestBody(req));
694
+ this.searchQuery = typeof payload.searchQuery === 'string' ? payload.searchQuery : '';
695
+ this.broadcast();
696
+ res.writeHead(204);
697
+ res.end();
698
+ }
699
+ async handleReplayAll(req, res) {
700
+ if (!this.activeSession) {
701
+ res.writeHead(400);
702
+ res.end();
703
+ return;
704
+ }
705
+ await readRequestBody(req);
706
+ try {
707
+ await this.activeSession.replayAll();
708
+ }
709
+ catch (error) {
710
+ console.error('Replay All failed:', error);
711
+ this.reportIfCompileError(error);
712
+ res.writeHead(500);
713
+ res.end();
714
+ return;
715
+ }
716
+ this.broadcastFlash('Replayed all paths');
717
+ res.writeHead(204);
718
+ res.end();
719
+ }
720
+ async handleCancelReplay(req, res) {
721
+ await readRequestBody(req);
722
+ this.cancelCurrentReplay?.();
723
+ res.writeHead(204);
724
+ res.end();
725
+ }
726
+ async handleSave(req, res) {
727
+ if (!this.activeSession || !this.saveHandler) {
728
+ res.writeHead(400);
729
+ res.end();
730
+ return;
731
+ }
732
+ await readRequestBody(req);
733
+ try {
734
+ await this.saveHandler();
735
+ }
736
+ catch (error) {
737
+ console.error('Failed to save session:', error);
738
+ res.writeHead(500);
739
+ res.end();
740
+ return;
741
+ }
742
+ res.writeHead(204);
743
+ res.end();
744
+ }
745
+ async handleCloseMenus(req, res) {
746
+ if (!this.activeSession) {
747
+ res.writeHead(400);
748
+ res.end();
749
+ return;
750
+ }
751
+ await readRequestBody(req);
752
+ this.activeSession.closeAllMenus();
753
+ res.writeHead(204);
754
+ res.end();
755
+ }
756
+ async handleToggleDynamicState(req, res) {
757
+ if (!this.activeSession) {
758
+ res.writeHead(400);
759
+ res.end();
760
+ return;
761
+ }
762
+ await readRequestBody(req);
763
+ this.activeSession.toggleShowDynamicState();
764
+ res.writeHead(204);
765
+ res.end();
766
+ }
767
+ async handleTraceKnot(req, res) {
768
+ if (!this.activeSession) {
769
+ res.writeHead(400);
770
+ res.end();
771
+ return;
772
+ }
773
+ const knotId = parseKnotId(parseJsonBody(await readRequestBody(req)));
774
+ if (knotId === null) {
775
+ res.writeHead(400);
776
+ res.end();
777
+ return;
778
+ }
779
+ this.traceLoading = true;
780
+ this.config.onTraceRequested?.();
781
+ this.broadcastTrace();
782
+ let raw;
783
+ try {
784
+ raw = await this.activeSession.traceKnot(knotId);
785
+ }
786
+ catch (error) {
787
+ console.error('Trace knot failed:', error);
788
+ this.reportIfCompileError(error);
789
+ this.traceLoading = false;
790
+ this.broadcastTrace();
791
+ res.writeHead(500);
792
+ res.end();
793
+ return;
794
+ }
795
+ this.traceLoading = false;
796
+ if (raw === null) {
797
+ this.broadcastTrace();
798
+ res.writeHead(400);
799
+ res.end();
800
+ return;
801
+ }
802
+ const knot = this.activeSession.getTree().getKnot(knotId);
803
+ this.currentTrace = {
804
+ tree: (0, trace_1.buildTraceTree)(raw),
805
+ commandLabel: knot?.command ?? '',
806
+ sourceKnotId: knotId,
807
+ searchTerm: '',
808
+ projectRoot: this.activeSession.getProjectRoot()
809
+ };
810
+ this.broadcastTrace();
811
+ res.writeHead(204);
812
+ res.end();
813
+ }
814
+ async handleTraceStartup(req, res) {
815
+ if (!this.activeSession) {
816
+ res.writeHead(400);
817
+ res.end();
818
+ return;
819
+ }
820
+ await readRequestBody(req);
821
+ this.traceLoading = true;
822
+ this.config.onTraceRequested?.();
823
+ this.broadcastTrace();
824
+ let raw;
825
+ try {
826
+ raw = await this.activeSession.traceStartup();
827
+ }
828
+ catch (error) {
829
+ console.error('Trace startup failed:', error);
830
+ this.reportIfCompileError(error);
831
+ this.traceLoading = false;
832
+ this.broadcastTrace();
833
+ res.writeHead(500);
834
+ res.end();
835
+ return;
836
+ }
837
+ this.traceLoading = false;
838
+ if (raw === null) {
839
+ this.broadcastTrace();
840
+ res.writeHead(400);
841
+ res.end();
842
+ return;
843
+ }
844
+ this.currentTrace = {
845
+ tree: (0, trace_1.buildTraceTree)(raw),
846
+ commandLabel: 'startup',
847
+ sourceKnotId: null,
848
+ searchTerm: '',
849
+ projectRoot: this.activeSession.getProjectRoot()
850
+ };
851
+ this.broadcastTrace();
852
+ res.writeHead(204);
853
+ res.end();
854
+ }
855
+ async handleTraceSearch(req, res) {
856
+ if (!this.currentTrace) {
857
+ res.writeHead(400);
858
+ res.end();
859
+ return;
860
+ }
861
+ const payload = parseJsonBody(await readRequestBody(req));
862
+ const searchTerm = typeof payload.searchTerm === 'string' ? payload.searchTerm : '';
863
+ const searched = (0, trace_1.searchTree)(this.currentTrace.tree, searchTerm);
864
+ this.currentTrace = { ...this.currentTrace, tree: (0, trace_1.expandToMatches)(searched), searchTerm };
865
+ this.broadcastTrace();
866
+ res.writeHead(204);
867
+ res.end();
868
+ }
869
+ async handleTraceToggleNode(req, res) {
870
+ if (!this.currentTrace) {
871
+ res.writeHead(400);
872
+ res.end();
873
+ return;
874
+ }
875
+ const nodeId = parseNodeId(parseJsonBody(await readRequestBody(req)));
876
+ if (nodeId === null) {
877
+ res.writeHead(400);
878
+ res.end();
879
+ return;
880
+ }
881
+ this.currentTrace = { ...this.currentTrace, tree: (0, trace_1.toggleExpanded)(this.currentTrace.tree, nodeId) };
882
+ this.broadcastTrace();
883
+ res.writeHead(204);
884
+ res.end();
885
+ }
886
+ async handleTraceExpandCollapseAll(req, res, fn) {
887
+ if (!this.currentTrace) {
888
+ res.writeHead(400);
889
+ res.end();
890
+ return;
891
+ }
892
+ await readRequestBody(req);
893
+ this.currentTrace = { ...this.currentTrace, tree: fn(this.currentTrace.tree) };
894
+ this.broadcastTrace();
895
+ res.writeHead(204);
896
+ res.end();
897
+ }
898
+ async handleTraceSourcePreview(url, res) {
899
+ const nodeIdParam = url.searchParams.get('nodeId');
900
+ const nodeId = nodeIdParam !== null ? Number(nodeIdParam) : NaN;
901
+ if (!this.currentTrace || !this.activeSession || Number.isNaN(nodeId)) {
902
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
903
+ res.end('Source not found');
904
+ return;
905
+ }
906
+ const node = (0, trace_1.getNode)(this.currentTrace.tree, nodeId);
907
+ const parsed = node?.source ? (0, trace_1.parseSource)(node.source) : null;
908
+ if (!parsed) {
909
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
910
+ res.end('Source not found');
911
+ return;
912
+ }
913
+ const [filePath, line] = parsed;
914
+ const resolvedPath = path.resolve(this.activeSession.getProjectRoot(), filePath);
915
+ let content;
916
+ try {
917
+ content = await fs.readFile(resolvedPath, 'utf8');
918
+ }
919
+ catch {
920
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
921
+ res.end('Source not found');
922
+ return;
923
+ }
924
+ const allLines = content.split('\n');
925
+ const context = 4;
926
+ const start = Math.max(0, line - context - 1);
927
+ const end = Math.min(allLines.length, line + context);
928
+ const html = this.config.grammarPath
929
+ ? (0, syntax_1.renderHighlightedSnippet)(await this.getGrammar(), allLines, start, end, line)
930
+ : (0, syntax_1.renderPlainSnippet)(allLines, start, end, line);
931
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
932
+ res.end(html);
933
+ }
934
+ getGrammar() {
935
+ if (!this.grammarPromise) {
936
+ this.grammarPromise = (0, syntax_1.loadDialogGrammar)(this.config.grammarPath);
937
+ }
938
+ return this.grammarPromise;
939
+ }
940
+ async handleUndoRedo(req, res, fn) {
941
+ if (!this.activeSession) {
942
+ res.writeHead(400);
943
+ res.end();
944
+ return;
945
+ }
946
+ await readRequestBody(req);
947
+ fn(this.activeSession);
948
+ res.writeHead(204);
949
+ res.end();
950
+ }
951
+ handleSseConnect(res) {
952
+ res.writeHead(200, {
953
+ 'Content-Type': 'text/event-stream',
954
+ 'Cache-Control': 'no-cache',
955
+ Connection: 'keep-alive'
956
+ });
957
+ this.sseClients.add(res);
958
+ res.on('close', () => this.sseClients.delete(res));
959
+ this.sendPatch(res);
960
+ this.sendScript(res, 'sk.resetAndFocusCommandInput()');
961
+ if (this.currentProgress) {
962
+ const { title, cancellable, percent, message } = this.currentProgress;
963
+ this.sendScript(res, `sk.showProgress(${JSON.stringify(title)}, ${JSON.stringify(cancellable)})`);
964
+ this.sendScript(res, `sk.updateProgress(${percent}, ${JSON.stringify(message)})`);
965
+ }
966
+ }
967
+ broadcast() {
968
+ for (const client of this.sseClients) {
969
+ this.sendPatch(client);
970
+ }
971
+ }
972
+ handleTraceSseConnect(res) {
973
+ res.writeHead(200, {
974
+ 'Content-Type': 'text/event-stream',
975
+ 'Cache-Control': 'no-cache',
976
+ Connection: 'keep-alive'
977
+ });
978
+ this.traceSseClients.add(res);
979
+ res.on('close', () => this.traceSseClients.delete(res));
980
+ this.sendTracePatch(res);
981
+ }
982
+ broadcastTrace() {
983
+ for (const client of this.traceSseClients) {
984
+ this.sendTracePatch(client);
985
+ }
986
+ }
987
+ sendTracePatch(res) {
988
+ if (res.writableEnded) {
989
+ return;
990
+ }
991
+ const html = (0, traceRender_1.renderTraceApp)(this.currentTrace, this.traceLoading);
992
+ const dataLines = html
993
+ .split('\n')
994
+ .map((line) => `data: elements ${line}`)
995
+ .join('\n');
996
+ res.write(`event: datastar-patch-elements\n${dataLines}\n\n`);
997
+ }
998
+ sendPatch(res) {
999
+ if (res.writableEnded) {
1000
+ return;
1001
+ }
1002
+ const info = this.currentDisplayInfo();
1003
+ const tree = this.activeSession?.getTree();
1004
+ const html = info && tree
1005
+ ? (0, render_1.renderApp)(info, tree, this.activeSession.getGraphMenuId(), this.activeSession.getTranscriptMenuId(), this.activeSession.getShowDynamicState(), this.searchQuery, this.currentSearchResults(), this.activeSession.getMarkerFilter())
1006
+ : NO_ACTIVE_SESSION_FRAGMENT;
1007
+ const dataLines = html
1008
+ .split('\n')
1009
+ .map((line) => `data: elements ${line}`)
1010
+ .join('\n');
1011
+ res.write(`event: datastar-patch-elements\n${dataLines}\n\n`);
1012
+ }
1013
+ broadcastScript(js) {
1014
+ for (const client of this.sseClients) {
1015
+ this.sendScript(client, js);
1016
+ }
1017
+ }
1018
+ broadcastFlash(message, type = 'info') {
1019
+ const args = type === 'info' ? JSON.stringify(message) : `${JSON.stringify(message)}, ${JSON.stringify(type)}`;
1020
+ this.broadcastScript(`sk.showFlash(${args})`);
1021
+ }
1022
+ async withProgress(options, task) {
1023
+ const token = { isCancellationRequested: false };
1024
+ this.cancelCurrentReplay = () => {
1025
+ token.isCancellationRequested = true;
1026
+ };
1027
+ this.currentProgress = { title: options.title, cancellable: options.cancellable, percent: 0, message: null };
1028
+ this.broadcastScript(`sk.showProgress(${JSON.stringify(options.title)}, ${JSON.stringify(options.cancellable)})`);
1029
+ const progress = {
1030
+ report: (update) => {
1031
+ const percent = Math.min(100, this.currentProgress.percent + (update.increment ?? 0));
1032
+ const message = update.message ?? null;
1033
+ this.currentProgress = { ...this.currentProgress, percent, message };
1034
+ this.broadcastScript(`sk.updateProgress(${percent}, ${JSON.stringify(message)})`);
1035
+ }
1036
+ };
1037
+ try {
1038
+ return await task(progress, token);
1039
+ }
1040
+ finally {
1041
+ this.cancelCurrentReplay = null;
1042
+ this.currentProgress = null;
1043
+ this.broadcastScript('sk.hideProgress()');
1044
+ }
1045
+ }
1046
+ sendScript(res, js) {
1047
+ if (res.writableEnded) {
1048
+ return;
1049
+ }
1050
+ res.write('event: datastar-patch-elements\n' +
1051
+ 'data: mode append\n' +
1052
+ 'data: selector body\n' +
1053
+ `data: elements <script data-effect="el.remove()">${js}</script>\n\n`);
1054
+ }
1055
+ async serveStaticFile(res, filePath) {
1056
+ try {
1057
+ const content = await fs.readFile(filePath);
1058
+ const contentType = STATIC_CONTENT_TYPES[path.extname(filePath)] ?? 'application/octet-stream';
1059
+ res.writeHead(200, { 'Content-Type': contentType });
1060
+ res.end(content);
1061
+ }
1062
+ catch {
1063
+ res.writeHead(404);
1064
+ res.end();
1065
+ }
1066
+ }
1067
+ createSession(config) {
1068
+ const session = session_1.SkeinSession.createNew(config);
1069
+ this.sessions.set(session.getId(), session);
1070
+ return session;
1071
+ }
1072
+ loadSession(tree, config) {
1073
+ const session = session_1.SkeinSession.createLoaded(tree, config);
1074
+ this.sessions.set(session.getId(), session);
1075
+ return session;
1076
+ }
1077
+ getSession(id) {
1078
+ return this.sessions.get(id) || null;
1079
+ }
1080
+ getAllSessions() {
1081
+ return Array.from(this.sessions.values());
1082
+ }
1083
+ async handleSkeinEndpoints() {
1084
+ console.log('Handling skein endpoints...');
1085
+ }
1086
+ serveUiComponents() {
1087
+ console.log('Serving UI components...');
1088
+ }
1089
+ sendSseUpdate(event) {
1090
+ console.log(`Sending SSE update: ${event.type}`, event.data);
1091
+ }
1092
+ isRunningService() {
1093
+ return this.isRunning;
1094
+ }
1095
+ }
1096
+ exports.SkeinService = SkeinService;
1097
+ //# sourceMappingURL=service.js.map