tjs-lang 0.2.7 → 0.3.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 (40) hide show
  1. package/demo/docs.json +32 -26
  2. package/demo/src/examples.ts +23 -83
  3. package/demo/src/playground-shared.ts +666 -0
  4. package/demo/src/tjs-playground.ts +65 -550
  5. package/demo/src/ts-examples.ts +5 -4
  6. package/demo/src/ts-playground.ts +50 -414
  7. package/dist/index.js +143 -160
  8. package/dist/index.js.map +12 -12
  9. package/dist/src/lang/emitters/js.d.ts +34 -2
  10. package/dist/src/lang/index.d.ts +1 -1
  11. package/dist/src/lang/types.d.ts +1 -1
  12. package/dist/src/types/Type.d.ts +3 -1
  13. package/dist/tjs-full.js +143 -160
  14. package/dist/tjs-full.js.map +12 -12
  15. package/dist/tjs-transpiler.js +122 -55
  16. package/dist/tjs-transpiler.js.map +9 -8
  17. package/dist/tjs-vm.js +14 -14
  18. package/dist/tjs-vm.js.map +5 -5
  19. package/docs/docs.json +792 -0
  20. package/docs/index.js +2652 -2835
  21. package/docs/index.js.map +11 -10
  22. package/editors/codemirror/ajs-language.ts +27 -1
  23. package/editors/codemirror/autocomplete.test.ts +3 -3
  24. package/package.json +1 -1
  25. package/src/lang/codegen.test.ts +11 -11
  26. package/src/lang/emitters/from-ts.ts +1 -1
  27. package/src/lang/emitters/js.ts +228 -4
  28. package/src/lang/index.ts +0 -3
  29. package/src/lang/inference.ts +40 -8
  30. package/src/lang/lang.test.ts +192 -35
  31. package/src/lang/roundtrip.test.ts +155 -0
  32. package/src/lang/runtime.ts +7 -0
  33. package/src/lang/types.ts +2 -0
  34. package/src/lang/typescript-syntax.test.ts +6 -4
  35. package/src/lang/wasm.test.ts +20 -0
  36. package/src/lang/wasm.ts +143 -0
  37. package/src/types/Type.test.ts +64 -0
  38. package/src/types/Type.ts +22 -1
  39. package/src/use-cases/transpiler-integration.test.ts +10 -10
  40. package/src/vm/atoms/batteries.ts +2 -0
@@ -0,0 +1,666 @@
1
+ /**
2
+ * Shared helpers for TJS and TS playgrounds
3
+ *
4
+ * Extracted from duplicated code in tjs-playground.ts and ts-playground.ts.
5
+ * Uses composition (exported functions), not inheritance.
6
+ */
7
+
8
+ import type { CodeMirror } from '../../editors/codemirror/component'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // TJS Runtime Stub (injected into iframe <script>)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /** The globalThis.__tjs runtime stub for iframe execution. Must stay in sync with src/lang/runtime.ts */
15
+ export const TJS_RUNTIME_STUB = `
16
+ globalThis.__tjs = {
17
+ version: '0.0.0',
18
+ pushStack: () => {},
19
+ popStack: () => {},
20
+ getStack: () => [],
21
+ typeError: (path, expected, value) => {
22
+ const actual = value === null ? 'null' : typeof value;
23
+ const err = new Error(\\\`Expected \\\${expected} for '\\\${path}', got \\\${actual}\\\`);
24
+ err.name = 'MonadicError';
25
+ err.path = path;
26
+ err.expected = expected;
27
+ err.actual = actual;
28
+ return err;
29
+ },
30
+ createRuntime: function() { return this; },
31
+ Is: (a, b) => {
32
+ if (a === b) return true;
33
+ if (a === null || b === null) return a === b;
34
+ if (typeof a !== typeof b) return false;
35
+ if (typeof a !== 'object') return false;
36
+ if (Array.isArray(a) && Array.isArray(b)) {
37
+ if (a.length !== b.length) return false;
38
+ return a.every((v, i) => globalThis.__tjs.Is(v, b[i]));
39
+ }
40
+ const keysA = Object.keys(a);
41
+ const keysB = Object.keys(b);
42
+ if (keysA.length !== keysB.length) return false;
43
+ return keysA.every(k => globalThis.__tjs.Is(a[k], b[k]));
44
+ },
45
+ IsNot: (a, b) => !globalThis.__tjs.Is(a, b),
46
+ };`
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Console capture script (injected into iframe <script>)
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /** Console.log capture that posts messages to parent. Handles objects with try/catch. */
53
+ export const CONSOLE_CAPTURE_SCRIPT = `
54
+ const _log = console.log;
55
+ console.log = (...args) => {
56
+ _log(...args);
57
+ parent.postMessage({ type: 'console', message: args.map(a => {
58
+ if (typeof a !== 'object' || a === null) return String(a);
59
+ try {
60
+ return JSON.stringify(a, null, 2);
61
+ } catch {
62
+ return String(a);
63
+ }
64
+ }).join(' ') }, '*');
65
+ };`
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Iframe document builder
69
+ // ---------------------------------------------------------------------------
70
+
71
+ export interface IframeDocOptions {
72
+ cssContent: string
73
+ htmlContent: string
74
+ importMapScript: string
75
+ jsCode: string
76
+ /** Import statements extracted from code (TJS separates these) */
77
+ importStatements?: string[]
78
+ /** Expose parent.run/runAgent/getIdToken in iframe */
79
+ parentBindings?: boolean
80
+ /** Auto-find and call TJS-annotated functions, append DOM results */
81
+ autoCallTjsFunction?: boolean
82
+ }
83
+
84
+ /**
85
+ * Build the iframe HTML document for code execution.
86
+ * Includes TJS runtime stub, console capture, DOM content detection,
87
+ * execution timing, and error boundary.
88
+ */
89
+ export function buildIframeDoc(options: IframeDocOptions): string {
90
+ const {
91
+ cssContent,
92
+ htmlContent,
93
+ importMapScript,
94
+ jsCode,
95
+ importStatements = [],
96
+ parentBindings = false,
97
+ autoCallTjsFunction = false,
98
+ } = options
99
+
100
+ const parentBindingsScript = parentBindings
101
+ ? `
102
+ if (parent.run) window.run = parent.run.bind(parent);
103
+ if (parent.runAgent) window.runAgent = parent.runAgent.bind(parent);
104
+ if (parent.getIdToken) window.getIdToken = parent.getIdToken.bind(parent);`
105
+ : ''
106
+
107
+ // When imports are separated, use two script blocks:
108
+ // 1. Regular script for runtime stub (must execute before module imports)
109
+ // 2. Module script for imports + code
110
+ const useSeparateScripts = importStatements.length > 0
111
+
112
+ const executionCode = autoCallTjsFunction
113
+ ? `
114
+ const __execStart = performance.now();
115
+ ${jsCode}
116
+
117
+ // Try to call the function if it exists and show result
118
+ const funcName = Object.keys(window).find(k => {
119
+ try { return typeof window[k] === 'function' && window[k].__tjs; }
120
+ catch { return false; }
121
+ });
122
+ if (funcName) {
123
+ const __callStart = performance.now();
124
+ const result = window[funcName]();
125
+ const __execTime = performance.now() - __callStart;
126
+ parent.postMessage({ type: 'timing', execTime: __execTime }, '*');
127
+ if (result !== undefined) {
128
+ if (result instanceof Node) {
129
+ document.body.append(result);
130
+ parent.postMessage({ type: 'hasPreviewContent' }, '*');
131
+ } else {
132
+ console.log('Result:', result);
133
+ }
134
+ }
135
+ } else {
136
+ const __execTime = performance.now() - __execStart;
137
+ parent.postMessage({ type: 'timing', execTime: __execTime }, '*');
138
+ }`
139
+ : `
140
+ const __execStart = performance.now();
141
+ ${jsCode}
142
+ const __execTime = performance.now() - __execStart;
143
+ parent.postMessage({ type: 'timing', execTime: __execTime }, '*');`
144
+
145
+ if (useSeparateScripts) {
146
+ return `<!DOCTYPE html>
147
+ <html>
148
+ <head>
149
+ <style>${cssContent}</style>
150
+ ${importMapScript}
151
+ </head>
152
+ <body>
153
+ ${htmlContent}
154
+ <script>${parentBindingsScript}
155
+ ${TJS_RUNTIME_STUB}
156
+ </script>
157
+ <script type="module">
158
+ ${importStatements.join('\n ')}
159
+ ${CONSOLE_CAPTURE_SCRIPT}
160
+
161
+ const __childrenBefore = document.body.children.length;
162
+ try {${executionCode}
163
+ if (document.body.children.length > __childrenBefore) {
164
+ parent.postMessage({ type: 'hasPreviewContent' }, '*');
165
+ }
166
+ } catch (e) {
167
+ parent.postMessage({ type: 'error', message: e.message }, '*');
168
+ }
169
+ </script>
170
+ </body>
171
+ </html>`
172
+ }
173
+
174
+ return `<!DOCTYPE html>
175
+ <html>
176
+ <head>
177
+ <style>${cssContent}</style>
178
+ ${importMapScript}
179
+ </head>
180
+ <body>
181
+ ${htmlContent}
182
+ <script type="module">${parentBindingsScript}
183
+ ${TJS_RUNTIME_STUB}
184
+ ${CONSOLE_CAPTURE_SCRIPT}
185
+
186
+ const __childrenBefore = document.body.children.length;
187
+ try {${executionCode}
188
+ if (document.body.children.length > __childrenBefore) {
189
+ parent.postMessage({ type: 'hasPreviewContent' }, '*');
190
+ }
191
+ } catch (e) {
192
+ parent.postMessage({ type: 'error', message: e.message }, '*');
193
+ }
194
+ </script>
195
+ </body>
196
+ </html>`
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Iframe message handler
201
+ // ---------------------------------------------------------------------------
202
+
203
+ export interface IframeMessageCallbacks {
204
+ onConsole: (message: string) => void
205
+ onTiming: (execTime: number) => void
206
+ onPreviewContent: () => void
207
+ onError: (message: string) => void
208
+ }
209
+
210
+ /**
211
+ * Create a message event handler for iframe postMessage communication.
212
+ * Returns the handler function (caller is responsible for addEventListener/removeEventListener).
213
+ */
214
+ export function createIframeMessageHandler(
215
+ callbacks: IframeMessageCallbacks
216
+ ): (event: MessageEvent) => void {
217
+ return (event: MessageEvent) => {
218
+ if (event.data?.type === 'console') {
219
+ callbacks.onConsole(event.data.message)
220
+ } else if (event.data?.type === 'timing') {
221
+ callbacks.onTiming(event.data.execTime)
222
+ } else if (event.data?.type === 'hasPreviewContent') {
223
+ callbacks.onPreviewContent()
224
+ } else if (event.data?.type === 'error') {
225
+ callbacks.onError(event.data.message)
226
+ }
227
+ }
228
+ }
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Console rendering
232
+ // ---------------------------------------------------------------------------
233
+
234
+ /**
235
+ * Render console messages with clickable line references.
236
+ * Parses patterns like "at line X", "line X:", "Line X", ":X:Y" (line:col).
237
+ */
238
+ export function renderConsoleMessages(
239
+ messages: string[],
240
+ consoleEl: HTMLElement,
241
+ goToLine: (line: number, col: number) => void
242
+ ): void {
243
+ const linePattern =
244
+ /(?:at line |line |Line )(\d+)(?:[:,]?\s*(?:column |col )?(\d+))?|:(\d+):(\d+)/g
245
+
246
+ const html = messages
247
+ .map((msg) => {
248
+ const escaped = msg
249
+ .replace(/&/g, '&amp;')
250
+ .replace(/</g, '&lt;')
251
+ .replace(/>/g, '&gt;')
252
+
253
+ return escaped.replace(linePattern, (match, l1, c1, l2, c2) => {
254
+ const line = l1 || l2
255
+ const col = c1 || c2 || '1'
256
+ return `<span class="clickable-line" data-line="${line}" data-col="${col}">${match}</span>`
257
+ })
258
+ })
259
+ .join('\n')
260
+
261
+ consoleEl.innerHTML = html
262
+ consoleEl.scrollTop = consoleEl.scrollHeight
263
+
264
+ consoleEl.querySelectorAll('.clickable-line').forEach((el) => {
265
+ el.addEventListener('click', (e) => {
266
+ const target = e.currentTarget as HTMLElement
267
+ const line = parseInt(target.dataset.line || '0', 10)
268
+ const col = parseInt(target.dataset.col || '1', 10)
269
+ if (line > 0) {
270
+ goToLine(line, col)
271
+ }
272
+ })
273
+ })
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Test results rendering
278
+ // ---------------------------------------------------------------------------
279
+
280
+ /**
281
+ * Render test results HTML with clickable error links and editor gutter markers.
282
+ * Returns pass/fail counts so callers can update tab indicators.
283
+ */
284
+ export function renderTestResults(
285
+ tests: any[],
286
+ outputEl: HTMLElement,
287
+ editor: CodeMirror,
288
+ goToLine: (line: number) => void
289
+ ): { passed: number; failed: number } {
290
+ if (!tests || tests.length === 0) {
291
+ outputEl.textContent = 'No tests defined'
292
+ editor.clearMarkers()
293
+ return { passed: 0, failed: 0 }
294
+ }
295
+
296
+ const passed = tests.filter((t: any) => t.passed).length
297
+ const failed = tests.filter((t: any) => !t.passed).length
298
+
299
+ // Set gutter markers for failed tests
300
+ const failedTests = tests.filter((t: any) => !t.passed && t.line)
301
+ if (failedTests.length > 0) {
302
+ editor.setMarkers(
303
+ failedTests.map((t: any) => ({
304
+ line: t.line,
305
+ message: t.error || t.description,
306
+ severity: 'error' as const,
307
+ }))
308
+ )
309
+ } else {
310
+ editor.clearMarkers()
311
+ }
312
+
313
+ let html = `<div class="test-summary">`
314
+ html += `<strong>${passed} passed</strong>`
315
+ if (failed > 0) {
316
+ html += `, <strong class="test-failed">${failed} failed</strong>`
317
+ }
318
+ html += `</div><ul class="test-list">`
319
+
320
+ for (const test of tests) {
321
+ const icon = test.passed ? '✓' : '✗'
322
+ const cls = test.passed ? 'test-pass' : 'test-fail'
323
+ const sigBadge = test.isSignatureTest
324
+ ? ' <span class="sig-badge">signature</span>'
325
+ : ''
326
+ const dataLine = test.line ? ` data-line="${test.line}"` : ''
327
+ html += `<li class="${cls}"${dataLine}>${icon} ${test.description}${sigBadge}`
328
+ if (!test.passed && test.error) {
329
+ html += `<div class="test-error${
330
+ test.line ? ' clickable-error' : ''
331
+ }"${dataLine}>${test.error}</div>`
332
+ }
333
+ html += `</li>`
334
+ }
335
+ html += `</ul>`
336
+
337
+ outputEl.innerHTML = html
338
+
339
+ // Add click handlers for clickable errors
340
+ outputEl.querySelectorAll('.clickable-error').forEach((el) => {
341
+ el.addEventListener('click', (e) => {
342
+ const line = parseInt(
343
+ (e.currentTarget as HTMLElement).dataset.line || '0',
344
+ 10
345
+ )
346
+ if (line > 0) {
347
+ goToLine(line)
348
+ }
349
+ })
350
+ })
351
+
352
+ return { passed, failed }
353
+ }
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // Formatting utilities
357
+ // ---------------------------------------------------------------------------
358
+
359
+ /** Format execution time as human-readable string (μs for <1ms, ms otherwise) */
360
+ export function formatExecTime(ms: number): string {
361
+ return ms < 1 ? `${(ms * 1000).toFixed(0)}μs` : `${ms.toFixed(2)}ms`
362
+ }
363
+
364
+ // ---------------------------------------------------------------------------
365
+ // Shared CSS styles
366
+ // ---------------------------------------------------------------------------
367
+
368
+ /**
369
+ * Shared playground CSS styles. Spread into each playground's styleSpec.
370
+ *
371
+ * Class names that differ between playgrounds (e.g. .tjs-toolbar vs .ts-toolbar,
372
+ * .tjs-main vs .ts-main) are NOT included here - those stay in each playground.
373
+ */
374
+ export const sharedPlaygroundStyles: Record<string, Record<string, string>> = {
375
+ ':host': {
376
+ display: 'flex',
377
+ flexDirection: 'column',
378
+ height: '100%',
379
+ flex: '1 1 auto',
380
+ background: 'var(--background, #fff)',
381
+ color: 'var(--text-color, #1f2937)',
382
+ fontFamily: 'system-ui, sans-serif',
383
+ },
384
+
385
+ ':host .run-btn': {
386
+ display: 'flex',
387
+ alignItems: 'center',
388
+ gap: '4px',
389
+ padding: '6px 12px',
390
+ background: 'var(--brand-color, #3d4a6b)',
391
+ color: 'var(--brand-text-color, white)',
392
+ border: 'none',
393
+ borderRadius: '6px',
394
+ cursor: 'pointer',
395
+ fontWeight: '500',
396
+ fontSize: '14px',
397
+ },
398
+
399
+ ':host .run-btn:hover:not(:disabled)': {
400
+ filter: 'brightness(1.1)',
401
+ },
402
+
403
+ ':host .run-btn:disabled': {
404
+ opacity: '0.6',
405
+ cursor: 'not-allowed',
406
+ },
407
+
408
+ ':host .toolbar-separator': {
409
+ width: '1px',
410
+ height: '20px',
411
+ background: 'var(--code-border, #d1d5db)',
412
+ },
413
+
414
+ ':host .build-flags': {
415
+ display: 'flex',
416
+ alignItems: 'center',
417
+ gap: '12px',
418
+ },
419
+
420
+ ':host .flag-label': {
421
+ display: 'flex',
422
+ alignItems: 'center',
423
+ gap: '4px',
424
+ fontSize: '13px',
425
+ color: 'var(--text-color, #6b7280)',
426
+ cursor: 'pointer',
427
+ userSelect: 'none',
428
+ },
429
+
430
+ ':host .flag-label:hover': {
431
+ color: 'var(--text-color, #374151)',
432
+ },
433
+
434
+ ':host .flag-label input[type="checkbox"]': {
435
+ margin: '0',
436
+ cursor: 'pointer',
437
+ accentColor: 'var(--brand-color, #3d4a6b)',
438
+ },
439
+
440
+ ':host .revert-btn': {
441
+ display: 'flex',
442
+ alignItems: 'center',
443
+ gap: '4px',
444
+ padding: '6px 12px',
445
+ background: 'var(--code-background, #e5e7eb)',
446
+ color: 'var(--text-color, #374151)',
447
+ border: '1px solid var(--code-border, #d1d5db)',
448
+ borderRadius: '6px',
449
+ cursor: 'pointer',
450
+ fontWeight: '500',
451
+ fontSize: '14px',
452
+ transition: 'opacity 0.2s',
453
+ },
454
+
455
+ ':host .revert-btn:hover:not(:disabled)': {
456
+ background: '#fef3c7',
457
+ borderColor: '#f59e0b',
458
+ color: '#92400e',
459
+ },
460
+
461
+ ':host .revert-btn:disabled': {
462
+ cursor: 'default',
463
+ },
464
+
465
+ ':host .elastic': {
466
+ flex: '1',
467
+ },
468
+
469
+ ':host .status-bar': {
470
+ fontSize: '13px',
471
+ color: 'var(--text-color, #6b7280)',
472
+ opacity: '0.7',
473
+ },
474
+
475
+ ':host .status-bar.error': {
476
+ color: '#dc2626',
477
+ opacity: '1',
478
+ },
479
+
480
+ ':host tosi-tabs > [name]': {
481
+ background: 'var(--background, #fff)',
482
+ color: 'var(--text-color, #1f2937)',
483
+ },
484
+
485
+ ':host .editor-wrapper': {
486
+ flex: '1 1 auto',
487
+ height: '100%',
488
+ minHeight: '300px',
489
+ position: 'relative',
490
+ overflow: 'hidden',
491
+ },
492
+
493
+ ':host .editor-wrapper code-mirror': {
494
+ display: 'block',
495
+ position: 'absolute',
496
+ top: '0',
497
+ left: '0',
498
+ right: '0',
499
+ bottom: '0',
500
+ },
501
+
502
+ ':host .preview-frame': {
503
+ width: '100%',
504
+ height: '100%',
505
+ border: 'none',
506
+ background: 'var(--background, #fff)',
507
+ },
508
+
509
+ ':host .docs-output': {
510
+ display: 'block',
511
+ padding: '12px 16px',
512
+ fontSize: '14px',
513
+ fontFamily: 'system-ui, sans-serif',
514
+ color: 'var(--text-color, inherit)',
515
+ background: 'var(--background, #fff)',
516
+ height: '100%',
517
+ overflow: 'auto',
518
+ },
519
+
520
+ ':host .docs-output h2': {
521
+ fontSize: '1.25em',
522
+ marginTop: '0',
523
+ marginBottom: '0.5em',
524
+ color: 'var(--text-color, #1f2937)',
525
+ },
526
+
527
+ ':host .docs-output pre': {
528
+ background: 'var(--code-background, #f3f4f6)',
529
+ padding: '8px 12px',
530
+ borderRadius: '6px',
531
+ overflow: 'auto',
532
+ fontSize: '13px',
533
+ },
534
+
535
+ ':host .docs-output code': {
536
+ fontFamily: 'ui-monospace, monospace',
537
+ fontSize: '0.9em',
538
+ },
539
+
540
+ ':host .docs-output p': {
541
+ margin: '0.75em 0',
542
+ lineHeight: '1.5',
543
+ },
544
+
545
+ ':host .docs-output h3': {
546
+ fontSize: '1em',
547
+ marginTop: '1em',
548
+ marginBottom: '0.5em',
549
+ },
550
+
551
+ ':host .docs-output ul': {
552
+ paddingLeft: '1.5em',
553
+ margin: '0.5em 0',
554
+ },
555
+
556
+ ':host .docs-output li': {
557
+ marginBottom: '0.25em',
558
+ },
559
+
560
+ ':host .docs-output hr': {
561
+ border: 'none',
562
+ borderTop: '1px solid var(--code-border, #e5e7eb)',
563
+ margin: '1.5em 0',
564
+ },
565
+
566
+ ':host .tests-output': {
567
+ padding: '12px',
568
+ fontSize: '14px',
569
+ fontFamily: 'system-ui, sans-serif',
570
+ color: 'var(--text-color, inherit)',
571
+ background: 'var(--background, #fff)',
572
+ height: '100%',
573
+ overflow: 'auto',
574
+ },
575
+
576
+ ':host .test-summary': {
577
+ marginBottom: '12px',
578
+ paddingBottom: '8px',
579
+ borderBottom: '1px solid var(--code-border, #e5e7eb)',
580
+ },
581
+
582
+ ':host .test-failed': {
583
+ color: '#dc2626',
584
+ },
585
+
586
+ ':host .test-list': {
587
+ listStyle: 'none',
588
+ padding: '0',
589
+ margin: '0',
590
+ },
591
+
592
+ ':host .test-list li': {
593
+ padding: '4px 0',
594
+ },
595
+
596
+ ':host .test-pass': {
597
+ color: '#16a34a',
598
+ },
599
+
600
+ ':host .test-fail': {
601
+ color: '#dc2626',
602
+ },
603
+
604
+ ':host .test-error': {
605
+ marginLeft: '20px',
606
+ marginTop: '4px',
607
+ padding: '8px',
608
+ background: 'rgba(220, 38, 38, 0.1)',
609
+ borderRadius: '4px',
610
+ fontSize: '13px',
611
+ fontFamily: 'var(--font-mono, monospace)',
612
+ },
613
+
614
+ ':host .clickable-error': {
615
+ cursor: 'pointer',
616
+ textDecoration: 'underline',
617
+ textDecorationStyle: 'dotted',
618
+ },
619
+
620
+ ':host .clickable-error:hover': {
621
+ background: 'rgba(220, 38, 38, 0.2)',
622
+ },
623
+
624
+ ':host .sig-badge': {
625
+ fontSize: '11px',
626
+ padding: '2px 6px',
627
+ marginLeft: '8px',
628
+ background: 'rgba(99, 102, 241, 0.1)',
629
+ color: '#6366f1',
630
+ borderRadius: '4px',
631
+ },
632
+
633
+ ':host .console-header': {
634
+ padding: '4px 12px',
635
+ background: 'var(--code-background, #f3f4f6)',
636
+ fontSize: '12px',
637
+ fontWeight: '500',
638
+ color: 'var(--text-color, #6b7280)',
639
+ opacity: '0.7',
640
+ borderBottom: '1px solid var(--code-border, #e5e7eb)',
641
+ },
642
+
643
+ ':host .console-output': {
644
+ flex: '1',
645
+ margin: '0',
646
+ padding: '8px 12px',
647
+ background: 'var(--code-background, #f3f4f6)',
648
+ color: 'var(--text-color, #1f2937)',
649
+ fontSize: '12px',
650
+ fontFamily: 'ui-monospace, monospace',
651
+ overflow: 'auto',
652
+ whiteSpace: 'pre-wrap',
653
+ },
654
+
655
+ ':host .clickable-line': {
656
+ cursor: 'pointer',
657
+ color: '#2563eb',
658
+ textDecoration: 'underline',
659
+ textDecorationStyle: 'dotted',
660
+ },
661
+
662
+ ':host .clickable-line:hover': {
663
+ color: '#1d4ed8',
664
+ background: 'rgba(37, 99, 235, 0.1)',
665
+ },
666
+ }