@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.
- package/README.md +33 -67
- package/index.ts +20 -83
- package/package.json +69 -72
- package/src/client/AnnotationProvider.tsx +218 -255
- package/src/client/AnnotationSidebar.tsx +393 -338
- package/src/client/FileIcon.tsx +105 -66
- package/src/client/PlanRenderer.tsx +607 -551
- package/src/client/RenderErrorContext.ts +7 -7
- package/src/client/ResponsePreview.tsx +192 -173
- package/src/client/annotationDom.ts +43 -97
- package/src/client/annotationDraft.ts +106 -96
- package/src/client/appContext.tsx +40 -40
- package/src/client/components/Callout.tsx +20 -20
- package/src/client/components/Checklist.tsx +27 -29
- package/src/client/components/Choice.tsx +178 -159
- package/src/client/components/CodeBlock.tsx +27 -26
- package/src/client/components/Diff.tsx +16 -17
- package/src/client/components/FilePreview.tsx +102 -74
- package/src/client/components/Image.tsx +41 -39
- package/src/client/components/Markdown.tsx +33 -32
- package/src/client/components/Mermaid.tsx +593 -542
- package/src/client/components/Note.tsx +8 -8
- package/src/client/components/Priority.tsx +10 -12
- package/src/client/components/ResponseNote.tsx +16 -15
- package/src/client/components/Section.tsx +28 -26
- package/src/client/components/Table.tsx +33 -30
- package/src/client/components/Task.tsx +58 -60
- package/src/client/components/UserInput.tsx +169 -152
- package/src/client/components/index.ts +16 -16
- package/src/client/components/renderMarkdownChildren.tsx +86 -68
- package/src/client/diffLines.ts +51 -39
- package/src/client/generateMarkdown.ts +197 -191
- package/src/client/hostContext.tsx +39 -34
- package/src/client/index.ts +44 -125
- package/src/client/pageGlobals.ts +25 -0
- package/src/client/persistenceSchemas.ts +41 -0
- package/src/client/preact-all.ts +4 -4
- package/src/client/runtime.ts +58 -111
- package/src/client/styles.css +1 -74
- package/src/engine/compiler.ts +411 -323
- package/src/engine/file-admission.ts +25 -0
- package/src/engine/paths.ts +44 -44
- package/src/engine/session.ts +515 -626
- package/src/engine/sessionSchemas.ts +71 -0
- package/src/engine/watcher.ts +70 -60
- package/src/engine/websocket.ts +134 -132
- package/annotate.ts +0 -94
- package/daemon.ts +0 -46
- package/src/client/AnnotationDraftFooter.tsx +0 -76
- package/src/client/AnnotationEditor.tsx +0 -320
- package/src/client/AnnotationList.tsx +0 -190
- package/src/client/Popover.tsx +0 -361
- package/src/client/SessionContext.tsx +0 -3
- package/src/client/annotationContext.ts +0 -130
- package/src/client/annotationMarkdown.ts +0 -102
- package/src/client/annotationTarget.ts +0 -73
- package/src/client/blockTarget.ts +0 -127
- package/src/client/highlightRange.ts +0 -262
- package/src/client/popoverPosition.ts +0 -46
- package/src/client/regionTarget.ts +0 -180
- package/src/client/testing/dom.ts +0 -74
- package/src/client/textTarget.ts +0 -31
- package/src/client/useRegionAnnotation.tsx +0 -188
- package/src/client/useTextAnnotation.tsx +0 -194
- package/src/client/utils.ts +0 -33
- package/src/engine/channel.ts +0 -278
- package/src/engine/http-utils.ts +0 -14
- package/src/engine/lifecycle.ts +0 -302
- package/src/engine/router.ts +0 -16
- package/src/engine/waiter.ts +0 -188
|
@@ -1,88 +1,116 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
113
|
+
</pre>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
88
116
|
}
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { SessionContext } from '#canvas/runtime'
|
|
2
|
+
import { useContext, useState } from 'react'
|
|
3
3
|
|
|
4
4
|
interface ImageProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
-
|
|
13
|
+
const sessionId = useContext(SessionContext)
|
|
14
|
+
const [error, setError] = useState(false)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const imgSrc = `/api/image?session=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(src)}`
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
2
|
-
import { marked } from
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
16
|
+
gfm: true,
|
|
17
|
+
breaks: false,
|
|
18
|
+
})
|
|
18
19
|
|
|
19
20
|
export function Markdown({ children, file, __content }: MarkdownProps) {
|
|
20
|
-
|
|
21
|
+
const source = __content ?? (typeof children === 'string' ? children : String(children ?? ''))
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const html = useMemo(() => {
|
|
24
|
+
if (!source) return ''
|
|
25
|
+
return marked.parse(source, { async: false }) as string
|
|
26
|
+
}, [source])
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
}
|