@fabrika/canvas-kernel 0.1.44 → 0.2.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 (66) hide show
  1. package/README.md +33 -67
  2. package/index.ts +19 -83
  3. package/package.json +68 -72
  4. package/src/client/AnnotationProvider.tsx +227 -268
  5. package/src/client/AnnotationSidebar.tsx +392 -338
  6. package/src/client/FileIcon.tsx +106 -65
  7. package/src/client/PlanRenderer.tsx +607 -551
  8. package/src/client/RenderErrorContext.ts +7 -7
  9. package/src/client/ResponsePreview.tsx +192 -173
  10. package/src/client/annotationDom.ts +43 -97
  11. package/src/client/annotationDraft.ts +98 -92
  12. package/src/client/appContext.tsx +40 -40
  13. package/src/client/components/Callout.tsx +20 -20
  14. package/src/client/components/Checklist.tsx +28 -28
  15. package/src/client/components/Choice.tsx +178 -159
  16. package/src/client/components/CodeBlock.tsx +26 -26
  17. package/src/client/components/Diff.tsx +17 -17
  18. package/src/client/components/FilePreview.tsx +99 -74
  19. package/src/client/components/Image.tsx +41 -39
  20. package/src/client/components/Markdown.tsx +33 -32
  21. package/src/client/components/Mermaid.tsx +592 -542
  22. package/src/client/components/Note.tsx +8 -8
  23. package/src/client/components/Priority.tsx +11 -11
  24. package/src/client/components/ResponseNote.tsx +17 -15
  25. package/src/client/components/Section.tsx +28 -26
  26. package/src/client/components/Table.tsx +34 -29
  27. package/src/client/components/Task.tsx +58 -60
  28. package/src/client/components/UserInput.tsx +169 -152
  29. package/src/client/components/index.ts +16 -16
  30. package/src/client/components/renderMarkdownChildren.tsx +86 -68
  31. package/src/client/diffLines.ts +51 -39
  32. package/src/client/generateMarkdown.ts +197 -191
  33. package/src/client/hostContext.tsx +34 -34
  34. package/src/client/index.ts +43 -125
  35. package/src/client/preact-all.ts +4 -4
  36. package/src/client/runtime.ts +58 -111
  37. package/src/client/styles.css +1 -74
  38. package/src/engine/compiler.ts +316 -313
  39. package/src/engine/paths.ts +44 -44
  40. package/src/engine/session.ts +623 -618
  41. package/src/engine/watcher.ts +63 -60
  42. package/src/engine/websocket.ts +123 -121
  43. package/annotate.ts +0 -94
  44. package/daemon.ts +0 -46
  45. package/src/client/AnnotationDraftFooter.tsx +0 -76
  46. package/src/client/AnnotationEditor.tsx +0 -320
  47. package/src/client/AnnotationList.tsx +0 -190
  48. package/src/client/Popover.tsx +0 -361
  49. package/src/client/SessionContext.tsx +0 -3
  50. package/src/client/annotationContext.ts +0 -130
  51. package/src/client/annotationMarkdown.ts +0 -102
  52. package/src/client/annotationTarget.ts +0 -73
  53. package/src/client/blockTarget.ts +0 -127
  54. package/src/client/highlightRange.ts +0 -262
  55. package/src/client/popoverPosition.ts +0 -46
  56. package/src/client/regionTarget.ts +0 -180
  57. package/src/client/testing/dom.ts +0 -58
  58. package/src/client/textTarget.ts +0 -31
  59. package/src/client/useRegionAnnotation.tsx +0 -188
  60. package/src/client/useTextAnnotation.tsx +0 -194
  61. package/src/client/utils.ts +0 -33
  62. package/src/engine/channel.ts +0 -278
  63. package/src/engine/http-utils.ts +0 -14
  64. package/src/engine/lifecycle.ts +0 -302
  65. package/src/engine/router.ts +0 -16
  66. package/src/engine/waiter.ts +0 -188
@@ -1,88 +1,113 @@
1
- import React, { useEffect, useState, useContext, useRef } from "react";
2
- import { SessionContext, ActiveViewCtx } from "#canvas/runtime";
1
+ import { ActiveViewCtx, SessionContext } from '#canvas/runtime'
2
+ import React, { useContext, useEffect, useRef, useState } from 'react'
3
3
 
4
4
  interface FilePreviewProps {
5
- path: string;
6
- lines?: [number, number];
7
- /** Injected by compiler at build time — skip runtime fetch when present */
8
- __content?: string;
5
+ path: string
6
+ lines?: [number, number]
7
+ /** Injected by compiler at build time — skip runtime fetch when present */
8
+ __content?: string
9
9
  }
10
10
 
11
11
  const EXT_TO_LANG: Record<string, string> = {
12
- ts: "typescript", tsx: "typescript", js: "javascript", jsx: "javascript",
13
- py: "python", rs: "rust", go: "go", rb: "ruby", java: "java",
14
- json: "json", yaml: "yaml", yml: "yaml", md: "markdown",
15
- css: "css", html: "html", sh: "bash", bash: "bash", toml: "toml",
16
- sql: "sql", xml: "xml", c: "c", cpp: "cpp", h: "c",
17
- };
12
+ ts: 'typescript',
13
+ tsx: 'typescript',
14
+ js: 'javascript',
15
+ jsx: 'javascript',
16
+ py: 'python',
17
+ rs: 'rust',
18
+ go: 'go',
19
+ rb: 'ruby',
20
+ java: 'java',
21
+ json: 'json',
22
+ yaml: 'yaml',
23
+ yml: 'yaml',
24
+ md: 'markdown',
25
+ css: 'css',
26
+ html: 'html',
27
+ sh: 'bash',
28
+ bash: 'bash',
29
+ toml: 'toml',
30
+ sql: 'sql',
31
+ xml: 'xml',
32
+ c: 'c',
33
+ cpp: 'cpp',
34
+ h: 'c',
35
+ }
18
36
 
19
37
  export function FilePreview({ path, lines, __content }: FilePreviewProps) {
20
- const sessionId = useContext(SessionContext);
21
- const { setActiveView } = useContext(ActiveViewCtx);
22
- const [content, setContent] = useState<string | null>(__content ?? null);
23
- const [language, setLanguage] = useState(() => {
24
- const ext = path.split(".").pop() || "";
25
- return EXT_TO_LANG[ext] || "text";
26
- });
27
- const [error, setError] = useState<string | null>(null);
28
- const [loading, setLoading] = useState(!__content);
29
- const codeRef = useRef<HTMLElement>(null);
38
+ const sessionId = useContext(SessionContext)
39
+ const { setActiveView } = useContext(ActiveViewCtx)
40
+ const [content, setContent] = useState<string | null>(__content ?? null)
41
+ const [language, setLanguage] = useState(() => {
42
+ const ext = path.split('.').pop() || ''
43
+ return EXT_TO_LANG[ext] || 'text'
44
+ })
45
+ const [error, setError] = useState<string | null>(null)
46
+ const [loading, setLoading] = useState(!__content)
47
+ const codeRef = useRef<HTMLElement>(null)
30
48
 
31
- useEffect(() => {
32
- if (__content !== undefined) return; // already resolved at compile time
33
- setLoading(true);
34
- setError(null);
35
- fetch(`/api/file?session=${sessionId}&path=${encodeURIComponent(path)}`)
36
- .then((r) => r.json())
37
- .then((data: any) => {
38
- if (data.error) {
39
- setError(data.error);
40
- } else {
41
- let text = data.content;
42
- if (lines) {
43
- const allLines = text.split("\n");
44
- text = allLines.slice(lines[0] - 1, lines[1]).join("\n");
45
- }
46
- setContent(text);
47
- setLanguage(data.language || "text");
48
- }
49
- })
50
- .catch(() => setError("Failed to fetch file"))
51
- .finally(() => setLoading(false));
52
- }, [sessionId, path, lines?.[0], lines?.[1], __content]);
49
+ useEffect(() => {
50
+ if (__content !== undefined) return // already resolved at compile time
51
+ setLoading(true)
52
+ setError(null)
53
+ fetch(`/api/file?session=${sessionId}&path=${encodeURIComponent(path)}`)
54
+ .then((r) => r.json())
55
+ .then((data: any) => {
56
+ if (data.error) {
57
+ setError(data.error)
58
+ } else {
59
+ let text = data.content
60
+ if (lines) {
61
+ const allLines = text.split('\n')
62
+ text = allLines.slice(lines[0] - 1, lines[1]).join('\n')
63
+ }
64
+ setContent(text)
65
+ setLanguage(data.language || 'text')
66
+ }
67
+ })
68
+ .catch(() => setError('Failed to fetch file'))
69
+ .finally(() => setLoading(false))
70
+ }, [sessionId, path, lines?.[0], lines?.[1], __content])
53
71
 
54
- // Highlight after content loads
55
- useEffect(() => {
56
- const el = codeRef.current;
57
- const hljs = (window as any).hljs;
58
- if (!el || !hljs || !content) return;
59
- el.removeAttribute("data-highlighted");
60
- el.className = language ? `language-${language}` : "";
61
- el.textContent = content;
62
- hljs.highlightElement(el);
63
- }, [content, language]);
72
+ // Highlight after content loads
73
+ useEffect(() => {
74
+ const el = codeRef.current
75
+ const hljs = (window as any).hljs
76
+ if (!el || !hljs || !content) return
77
+ el.removeAttribute('data-highlighted')
78
+ el.className = language ? `language-${language}` : ''
79
+ el.textContent = content
80
+ hljs.highlightElement(el)
81
+ }, [content, language])
64
82
 
65
- if (loading) {
66
- return <div className="text-text-tertiary text-body mt-3 bg-bg-code rounded-md p-4">Loading {path}...</div>;
67
- }
83
+ if (loading) {
84
+ return <div className="text-text-tertiary text-body mt-3 bg-bg-code rounded-md p-4">Loading {path}...</div>
85
+ }
68
86
 
69
- if (error) {
70
- return <div className="text-accent-red text-body mt-3 bg-accent-red-muted rounded-md p-4">{path}: {error}</div>;
71
- }
87
+ if (error) {
88
+ return <div className="text-accent-red text-body mt-3 bg-accent-red-muted rounded-md p-4">{path}: {error}</div>
89
+ }
72
90
 
73
- return (
74
- <div className="mt-3 bg-bg-code rounded-md overflow-hidden" data-md="filepreview" data-md-path={path} {...(lines ? { "data-md-lines": `${lines[0]}-${lines[1]}` } : {})}>
75
- <div className="px-4 py-2 flex items-center justify-between">
76
- <button
77
- className="font-mono text-meta text-text-tertiary hover:text-text-secondary hover:underline cursor-pointer text-left"
78
- onClick={() => setActiveView({ type: "file", path })}
79
- title={`Open ${path}`}
80
- >{path}</button>
81
- {lines && <span className="font-mono text-meta text-text-tertiary opacity-60">L{lines[0]}–{lines[1]}</span>}
82
- </div>
83
- <pre className="px-4 pb-3 overflow-x-auto text-code font-mono !bg-transparent" style={{ background: "transparent" }}>
91
+ return (
92
+ <div
93
+ className="mt-3 bg-bg-code rounded-md overflow-hidden"
94
+ data-md="filepreview"
95
+ data-md-path={path}
96
+ {...(lines ? { 'data-md-lines': `${lines[0]}-${lines[1]}` } : {})}
97
+ >
98
+ <div className="px-4 py-2 flex items-center justify-between">
99
+ <button
100
+ className="font-mono text-meta text-text-tertiary hover:text-text-secondary hover:underline cursor-pointer text-left"
101
+ onClick={() => setActiveView({ type: 'file', path })}
102
+ title={`Open ${path}`}
103
+ >
104
+ {path}
105
+ </button>
106
+ {lines && <span className="font-mono text-meta text-text-tertiary opacity-60">L{lines[0]}–{lines[1]}</span>}
107
+ </div>
108
+ <pre className="px-4 pb-3 overflow-x-auto text-code font-mono !bg-transparent" style={{ background: 'transparent' }}>
84
109
  <code ref={codeRef} className={language ? `language-${language}` : ""}>{content}</code>
85
- </pre>
86
- </div>
87
- );
110
+ </pre>
111
+ </div>
112
+ )
88
113
  }
@@ -1,48 +1,50 @@
1
- import React, { useContext, useState } from "react";
2
- import { SessionContext } from "#canvas/runtime";
1
+ import { SessionContext } from '#canvas/runtime'
2
+ import React, { useContext, useState } from 'react'
3
3
 
4
4
  interface ImageProps {
5
- src: string;
6
- alt?: string;
7
- caption?: string;
8
- width?: number | string;
9
- height?: number | string;
5
+ src: string
6
+ alt?: string
7
+ caption?: string
8
+ width?: number | string
9
+ height?: number | string
10
10
  }
11
11
 
12
12
  export function ImageView({ src, alt, caption, width, height }: ImageProps) {
13
- const sessionId = useContext(SessionContext);
14
- const [error, setError] = useState(false);
13
+ const sessionId = useContext(SessionContext)
14
+ const [error, setError] = useState(false)
15
15
 
16
- const imgSrc = `/api/image?session=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(src)}`;
16
+ const imgSrc = `/api/image?session=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(src)}`
17
17
 
18
- if (error) {
19
- return (
20
- <div className="mt-3 text-accent-red text-body bg-accent-red-muted rounded-md p-4">
21
- {src}: Failed to load image
22
- </div>
23
- );
24
- }
18
+ if (error) {
19
+ return (
20
+ <div className="mt-3 text-accent-red text-body bg-accent-red-muted rounded-md p-4">
21
+ {src}: Failed to load image
22
+ </div>
23
+ )
24
+ }
25
25
 
26
- return (
27
- <figure className="mt-3 relative" data-md="image" data-md-src={src}>
28
- {/* The box a region annotation is measured against — the image alone, so
29
- the caption below cannot skew the percentages. */}
30
- <div data-annotation-image className="rounded-md overflow-hidden border border-border-subtle bg-bg-code inline-block">
31
- <img
32
- src={imgSrc}
33
- alt={alt || src}
34
- onError={() => setError(true)}
35
- draggable={false}
36
- className="block max-w-full h-auto"
37
- {...(width ? { width } : {})}
38
- {...(height ? { height } : {})}
39
- />
40
- </div>
41
- {caption && (
42
- <figcaption className="mt-1.5 text-meta text-text-tertiary font-body">
43
- {caption}
44
- </figcaption>
45
- )}
46
- </figure>
47
- );
26
+ return (
27
+ <figure className="mt-3 relative" data-md="image" data-md-src={src}>
28
+ {
29
+ /* The box a region annotation is measured against — the image alone, so
30
+ the caption below cannot skew the percentages. */
31
+ }
32
+ <div data-annotation-image className="rounded-md overflow-hidden border border-border-subtle bg-bg-code inline-block">
33
+ <img
34
+ src={imgSrc}
35
+ alt={alt || src}
36
+ onError={() => setError(true)}
37
+ draggable={false}
38
+ className="block max-w-full h-auto"
39
+ {...(width ? { width } : {})}
40
+ {...(height ? { height } : {})}
41
+ />
42
+ </div>
43
+ {caption && (
44
+ <figcaption className="mt-1.5 text-meta text-text-tertiary font-body">
45
+ {caption}
46
+ </figcaption>
47
+ )}
48
+ </figure>
49
+ )
48
50
  }
@@ -1,44 +1,45 @@
1
- import React, { useMemo } from "react";
2
- import { marked } from "marked";
1
+ // biome-ignore-all lint/security/noDangerouslySetInnerHtml: Canvas modules already execute as code in this document.
2
+ import { marked } from 'marked'
3
+ import React, { useMemo } from 'react'
3
4
 
4
5
  interface MarkdownProps {
5
- /** Inline markdown source (via children) */
6
- children?: React.ReactNode;
7
- /** File path (resolved at compile time) */
8
- file?: string;
9
- /** Injected by compiler when file prop is used */
10
- __content?: string;
6
+ /** Inline markdown source (via children) */
7
+ children?: React.ReactNode
8
+ /** File path (resolved at compile time) */
9
+ file?: string
10
+ /** Injected by compiler when file prop is used */
11
+ __content?: string
11
12
  }
12
13
 
13
14
  // Configure marked for safe rendering
14
15
  marked.setOptions({
15
- gfm: true,
16
- breaks: false,
17
- });
16
+ gfm: true,
17
+ breaks: false,
18
+ })
18
19
 
19
20
  export function Markdown({ children, file, __content }: MarkdownProps) {
20
- const source = __content ?? (typeof children === "string" ? children : String(children ?? ""));
21
+ const source = __content ?? (typeof children === 'string' ? children : String(children ?? ''))
21
22
 
22
- const html = useMemo(() => {
23
- if (!source) return "";
24
- return marked.parse(source, { async: false }) as string;
25
- }, [source]);
23
+ const html = useMemo(() => {
24
+ if (!source) return ''
25
+ return marked.parse(source, { async: false }) as string
26
+ }, [source])
26
27
 
27
- if (!source) {
28
- return (
29
- <div className="text-text-tertiary text-tiny mt-3">
30
- {file ? `Could not load: ${file}` : "No markdown content"}
31
- </div>
32
- );
33
- }
28
+ if (!source) {
29
+ return (
30
+ <div className="text-text-tertiary text-tiny mt-3">
31
+ {file ? `Could not load: ${file}` : 'No markdown content'}
32
+ </div>
33
+ )
34
+ }
34
35
 
35
- return (
36
- <div
37
- className="mt-3 prose-canvas"
38
- data-md="markdown"
39
- {...(file ? { "data-md-file": file } : {})}
40
- data-md-source={source}
41
- dangerouslySetInnerHTML={{ __html: html }}
42
- />
43
- );
36
+ return (
37
+ <div
38
+ className="mt-3 prose-canvas"
39
+ data-md="markdown"
40
+ {...(file ? { 'data-md-file': file } : {})}
41
+ data-md-source={source}
42
+ dangerouslySetInnerHTML={{ __html: html }}
43
+ />
44
+ )
44
45
  }