@fabrika/canvas-kernel 0.1.45 → 0.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.
Files changed (70) hide show
  1. package/README.md +33 -67
  2. package/index.ts +20 -83
  3. package/package.json +69 -72
  4. package/src/client/AnnotationProvider.tsx +218 -255
  5. package/src/client/AnnotationSidebar.tsx +393 -338
  6. package/src/client/FileIcon.tsx +105 -66
  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 +106 -96
  12. package/src/client/appContext.tsx +40 -40
  13. package/src/client/components/Callout.tsx +20 -20
  14. package/src/client/components/Checklist.tsx +27 -29
  15. package/src/client/components/Choice.tsx +178 -159
  16. package/src/client/components/CodeBlock.tsx +27 -26
  17. package/src/client/components/Diff.tsx +16 -17
  18. package/src/client/components/FilePreview.tsx +102 -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 +593 -542
  22. package/src/client/components/Note.tsx +8 -8
  23. package/src/client/components/Priority.tsx +10 -12
  24. package/src/client/components/ResponseNote.tsx +16 -15
  25. package/src/client/components/Section.tsx +28 -26
  26. package/src/client/components/Table.tsx +33 -30
  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 +39 -34
  34. package/src/client/index.ts +44 -125
  35. package/src/client/pageGlobals.ts +25 -0
  36. package/src/client/persistenceSchemas.ts +41 -0
  37. package/src/client/preact-all.ts +4 -4
  38. package/src/client/runtime.ts +58 -111
  39. package/src/client/styles.css +1 -74
  40. package/src/engine/compiler.ts +411 -323
  41. package/src/engine/file-admission.ts +25 -0
  42. package/src/engine/paths.ts +44 -44
  43. package/src/engine/session.ts +515 -626
  44. package/src/engine/sessionSchemas.ts +71 -0
  45. package/src/engine/watcher.ts +70 -60
  46. package/src/engine/websocket.ts +134 -132
  47. package/annotate.ts +0 -94
  48. package/daemon.ts +0 -46
  49. package/src/client/AnnotationDraftFooter.tsx +0 -76
  50. package/src/client/AnnotationEditor.tsx +0 -320
  51. package/src/client/AnnotationList.tsx +0 -190
  52. package/src/client/Popover.tsx +0 -361
  53. package/src/client/SessionContext.tsx +0 -3
  54. package/src/client/annotationContext.ts +0 -130
  55. package/src/client/annotationMarkdown.ts +0 -102
  56. package/src/client/annotationTarget.ts +0 -73
  57. package/src/client/blockTarget.ts +0 -127
  58. package/src/client/highlightRange.ts +0 -262
  59. package/src/client/popoverPosition.ts +0 -46
  60. package/src/client/regionTarget.ts +0 -180
  61. package/src/client/testing/dom.ts +0 -74
  62. package/src/client/textTarget.ts +0 -31
  63. package/src/client/useRegionAnnotation.tsx +0 -188
  64. package/src/client/useTextAnnotation.tsx +0 -194
  65. package/src/client/utils.ts +0 -33
  66. package/src/engine/channel.ts +0 -278
  67. package/src/engine/http-utils.ts +0 -14
  68. package/src/engine/lifecycle.ts +0 -302
  69. package/src/engine/router.ts +0 -16
  70. package/src/engine/waiter.ts +0 -188
@@ -1,88 +1,116 @@
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 { useContext, useEffect, useRef, useState } from 'react'
3
+ import { pageHighlighter } from '../pageGlobals'
4
+ import { filePreviewResponseSchema } from '../persistenceSchemas'
3
5
 
4
6
  interface FilePreviewProps {
5
- path: string;
6
- lines?: [number, number];
7
- /** Injected by compiler at build time — skip runtime fetch when present */
8
- __content?: string;
7
+ path: string
8
+ lines?: [number, number]
9
+ /** Injected by compiler at build time — skip runtime fetch when present */
10
+ __content?: string
9
11
  }
10
12
 
11
13
  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
- };
14
+ ts: 'typescript',
15
+ tsx: 'typescript',
16
+ js: 'javascript',
17
+ jsx: 'javascript',
18
+ py: 'python',
19
+ rs: 'rust',
20
+ go: 'go',
21
+ rb: 'ruby',
22
+ java: 'java',
23
+ json: 'json',
24
+ yaml: 'yaml',
25
+ yml: 'yaml',
26
+ md: 'markdown',
27
+ css: 'css',
28
+ html: 'html',
29
+ sh: 'bash',
30
+ bash: 'bash',
31
+ toml: 'toml',
32
+ sql: 'sql',
33
+ xml: 'xml',
34
+ c: 'c',
35
+ cpp: 'cpp',
36
+ h: 'c',
37
+ }
18
38
 
19
39
  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);
40
+ const sessionId = useContext(SessionContext)
41
+ const { setActiveView } = useContext(ActiveViewCtx)
42
+ const [content, setContent] = useState<string | null>(__content ?? null)
43
+ const [language, setLanguage] = useState(() => {
44
+ const ext = path.split('.').pop() || ''
45
+ return EXT_TO_LANG[ext] || 'text'
46
+ })
47
+ const [error, setError] = useState<string | null>(null)
48
+ const [loading, setLoading] = useState(!__content)
49
+ const codeRef = useRef<HTMLElement>(null)
30
50
 
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]);
51
+ useEffect(() => {
52
+ if (__content !== undefined) return // already resolved at compile time
53
+ setLoading(true)
54
+ setError(null)
55
+ fetch(`/api/file?session=${sessionId}&path=${encodeURIComponent(path)}`)
56
+ .then((r) => r.json())
57
+ .then((value: unknown) => {
58
+ const data = filePreviewResponseSchema.parse(value)
59
+ if ('error' in data) {
60
+ setError(data.error)
61
+ } else {
62
+ let text = data.content
63
+ if (lines) {
64
+ const allLines = text.split('\n')
65
+ text = allLines.slice(lines[0] - 1, lines[1]).join('\n')
66
+ }
67
+ setContent(text)
68
+ setLanguage(data.language || 'text')
69
+ }
70
+ })
71
+ .catch(() => setError('Failed to fetch file'))
72
+ .finally(() => setLoading(false))
73
+ }, [sessionId, path, lines?.[0], lines?.[1], __content])
53
74
 
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]);
75
+ // Highlight after content loads
76
+ useEffect(() => {
77
+ const el = codeRef.current
78
+ const hljs = pageHighlighter()
79
+ if (!el || !hljs || !content) return
80
+ el.removeAttribute('data-highlighted')
81
+ el.className = language ? `language-${language}` : ''
82
+ el.textContent = content
83
+ hljs.highlightElement(el)
84
+ }, [content, language])
64
85
 
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
- }
86
+ if (loading) {
87
+ return <div className="text-text-tertiary text-body mt-3 bg-bg-code rounded-md p-4">Loading {path}...</div>
88
+ }
68
89
 
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
- }
90
+ if (error) {
91
+ return <div className="text-accent-red text-body mt-3 bg-accent-red-muted rounded-md p-4">{path}: {error}</div>
92
+ }
72
93
 
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" }}>
94
+ return (
95
+ <div
96
+ className="mt-3 bg-bg-code rounded-md overflow-hidden"
97
+ data-md="filepreview"
98
+ data-md-path={path}
99
+ {...(lines ? { 'data-md-lines': `${lines[0]}-${lines[1]}` } : {})}
100
+ >
101
+ <div className="px-4 py-2 flex items-center justify-between">
102
+ <button
103
+ className="font-mono text-meta text-text-tertiary hover:text-text-secondary hover:underline cursor-pointer text-left"
104
+ onClick={() => setActiveView({ type: 'file', path })}
105
+ title={`Open ${path}`}
106
+ >
107
+ {path}
108
+ </button>
109
+ {lines && <span className="font-mono text-meta text-text-tertiary opacity-60">L{lines[0]}–{lines[1]}</span>}
110
+ </div>
111
+ <pre className="px-4 pb-3 overflow-x-auto text-code font-mono !bg-transparent" style={{ background: 'transparent' }}>
84
112
  <code ref={codeRef} className={language ? `language-${language}` : ""}>{content}</code>
85
- </pre>
86
- </div>
87
- );
113
+ </pre>
114
+ </div>
115
+ )
88
116
  }
@@ -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 { 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
  }