@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.
- package/README.md +33 -67
- package/index.ts +19 -83
- package/package.json +68 -72
- package/src/client/AnnotationProvider.tsx +227 -268
- package/src/client/AnnotationSidebar.tsx +392 -338
- package/src/client/FileIcon.tsx +106 -65
- 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 +98 -92
- package/src/client/appContext.tsx +40 -40
- package/src/client/components/Callout.tsx +20 -20
- package/src/client/components/Checklist.tsx +28 -28
- package/src/client/components/Choice.tsx +178 -159
- package/src/client/components/CodeBlock.tsx +26 -26
- package/src/client/components/Diff.tsx +17 -17
- package/src/client/components/FilePreview.tsx +99 -74
- package/src/client/components/Image.tsx +41 -39
- package/src/client/components/Markdown.tsx +33 -32
- package/src/client/components/Mermaid.tsx +592 -542
- package/src/client/components/Note.tsx +8 -8
- package/src/client/components/Priority.tsx +11 -11
- package/src/client/components/ResponseNote.tsx +17 -15
- package/src/client/components/Section.tsx +28 -26
- package/src/client/components/Table.tsx +34 -29
- 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 +34 -34
- package/src/client/index.ts +43 -125
- 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 +316 -313
- package/src/engine/paths.ts +44 -44
- package/src/engine/session.ts +623 -618
- package/src/engine/watcher.ts +63 -60
- package/src/engine/websocket.ts +123 -121
- 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 -58
- 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,130 +1,136 @@
|
|
|
1
1
|
interface RevisionState {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
revision: number
|
|
3
|
+
hasFeedback: boolean
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
interface AnnotationStorage {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
getItem(key: string): string | null
|
|
8
|
+
setItem(key: string, value: string): void
|
|
9
|
+
removeItem(key: string): void
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
interface RemovableAnnotationStorage {
|
|
13
|
-
|
|
13
|
+
removeItem(key: string): void
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export type AnnotationDraftPhase =
|
|
16
|
+
export type AnnotationDraftPhase = 'current' | 'next'
|
|
17
17
|
|
|
18
18
|
export function annotationDraftKey(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
sessionId: string,
|
|
20
|
+
revision: number,
|
|
21
|
+
phase: AnnotationDraftPhase = 'current',
|
|
22
22
|
): string {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const base = `canvas:${sessionId}:rev:${revision}`
|
|
24
|
+
return phase === 'next' ? `${base}:next-draft` : base
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function handledKey(sessionId: string, revision: number): string {
|
|
28
|
-
|
|
28
|
+
return `${annotationDraftKey(sessionId, revision)}:draft-handled`
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function clearPersistedDraft(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
storage: RemovableAnnotationStorage,
|
|
33
|
+
sessionId: string,
|
|
34
|
+
revision: number,
|
|
35
35
|
): void {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
try {
|
|
37
|
+
storage.removeItem(annotationDraftKey(sessionId, revision))
|
|
38
|
+
storage.removeItem(handledKey(sessionId, revision))
|
|
39
|
+
} catch {}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function parseState(raw: string | null): object | null {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
if (!raw) return null
|
|
44
|
+
try {
|
|
45
|
+
const value: unknown = JSON.parse(raw)
|
|
46
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
47
|
+
? value
|
|
48
|
+
: null
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function hasResponseValue(entry: unknown): boolean {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (!Array.isArray(entry) || entry.length < 2) return false
|
|
56
|
+
const response: unknown = entry[1]
|
|
57
|
+
if (typeof response !== 'object' || response === null || Array.isArray(response)) return false
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const note = Reflect.get(response, 'note')
|
|
60
|
+
if (typeof note === 'string' && note.trim().length > 0) return true
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const value = Reflect.get(response, 'value')
|
|
63
|
+
if (typeof value === 'string') return value.trim().length > 0
|
|
64
|
+
if (Array.isArray(value)) return value.length > 0
|
|
65
|
+
return value !== null && value !== undefined
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function hasDraftContent(state: object | null): boolean {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if (!state) return false
|
|
70
|
+
const annotations = Reflect.get(state, 'annotations')
|
|
71
|
+
if (Array.isArray(annotations) && annotations.length > 0) return true
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const generalNote = Reflect.get(state, 'generalNote')
|
|
74
|
+
if (typeof generalNote === 'string' && generalNote.trim().length > 0) return true
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const responses = Reflect.get(state, 'responses')
|
|
77
|
+
return Array.isArray(responses) && responses.some(hasResponseValue)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Carry an unsent draft forward without crossing submitted revisions. */
|
|
81
81
|
export function carryUnsubmittedDraft(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
storage: AnnotationStorage,
|
|
83
|
+
sessionId: string,
|
|
84
|
+
targetRevision: number,
|
|
85
|
+
revisions: RevisionState[],
|
|
86
86
|
): number | null {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
87
|
+
try {
|
|
88
|
+
const targetKey = annotationDraftKey(sessionId, targetRevision)
|
|
89
|
+
if (hasDraftContent(parseState(storage.getItem(targetKey)))) {
|
|
90
|
+
storage.setItem(handledKey(sessionId, targetRevision), '1')
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
if (storage.getItem(handledKey(sessionId, targetRevision)) !== null) return null
|
|
94
|
+
|
|
95
|
+
const previous = revisions
|
|
96
|
+
.filter((revision) => revision.revision < targetRevision)
|
|
97
|
+
.sort((left, right) => right.revision - left.revision)
|
|
98
|
+
|
|
99
|
+
for (const revision of previous) {
|
|
100
|
+
if (revision.hasFeedback) {
|
|
101
|
+
const sourceKey = annotationDraftKey(sessionId, revision.revision, 'next')
|
|
102
|
+
const source = parseState(storage.getItem(sourceKey))
|
|
103
|
+
if (!hasDraftContent(source)) return null
|
|
104
|
+
|
|
105
|
+
storage.setItem(
|
|
106
|
+
targetKey,
|
|
107
|
+
JSON.stringify({
|
|
108
|
+
...source,
|
|
109
|
+
feedbackEntries: [],
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
storage.setItem(handledKey(sessionId, targetRevision), '1')
|
|
113
|
+
storage.removeItem(sourceKey)
|
|
114
|
+
return revision.revision
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const source = parseState(storage.getItem(annotationDraftKey(sessionId, revision.revision)))
|
|
118
|
+
if (!hasDraftContent(source)) {
|
|
119
|
+
if (storage.getItem(handledKey(sessionId, revision.revision)) !== null) return null
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
storage.setItem(
|
|
124
|
+
targetKey,
|
|
125
|
+
JSON.stringify({
|
|
126
|
+
...source,
|
|
127
|
+
feedbackEntries: [],
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
storage.setItem(handledKey(sessionId, targetRevision), '1')
|
|
131
|
+
return revision.revision
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
|
|
135
|
+
return null
|
|
130
136
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { ActiveView } from '#canvas/runtime'
|
|
2
|
+
import { createContext } from 'react'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shell state the annotation surface reads but does not own.
|
|
@@ -9,53 +9,53 @@ import type { ActiveView } from "#canvas/runtime";
|
|
|
9
9
|
* and how to navigate. The host provides both contexts around the surface.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export type { ActiveView }
|
|
12
|
+
export type { ActiveView }
|
|
13
13
|
|
|
14
14
|
export interface CanvasFileInfo {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
filename: string
|
|
16
|
+
diffStats?: { added: number; removed: number }
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface RevisionInfo {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
revision: number
|
|
21
|
+
label?: string
|
|
22
|
+
canvasFiles: CanvasFileInfo[]
|
|
23
|
+
createdAt: string
|
|
24
|
+
hasFeedback: boolean
|
|
25
|
+
feedbackConsumed: boolean
|
|
26
|
+
response?: string
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export const ActiveViewContext = createContext<{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
activeView: ActiveView
|
|
31
|
+
setActiveView: (v: ActiveView) => void
|
|
32
|
+
openFiles: string[]
|
|
33
|
+
closeFile: (path: string) => void
|
|
34
|
+
canvasFiles: string[]
|
|
35
35
|
}>({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
36
|
+
activeView: { type: 'overview' },
|
|
37
|
+
setActiveView: () => {},
|
|
38
|
+
openFiles: [],
|
|
39
|
+
closeFile: () => {},
|
|
40
|
+
canvasFiles: [],
|
|
41
|
+
})
|
|
42
42
|
|
|
43
43
|
export const RevisionContext = createContext<{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
currentRevision: number
|
|
45
|
+
selectedRevision: number
|
|
46
|
+
revisions: RevisionInfo[]
|
|
47
|
+
setSelectedRevision: (rev: number) => void
|
|
48
|
+
isReadOnly: boolean
|
|
49
|
+
compareRevision: { left: number; right: number } | null
|
|
50
|
+
setCompareRevision: (rev: { left: number; right: number } | null) => void
|
|
51
|
+
agentWatching: boolean
|
|
52
52
|
}>({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
})
|
|
53
|
+
currentRevision: 1,
|
|
54
|
+
selectedRevision: 1,
|
|
55
|
+
revisions: [],
|
|
56
|
+
setSelectedRevision: () => {},
|
|
57
|
+
isReadOnly: false,
|
|
58
|
+
compareRevision: null,
|
|
59
|
+
setCompareRevision: () => {},
|
|
60
|
+
agentWatching: false,
|
|
61
|
+
})
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { renderMarkdownChildren } from
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { renderMarkdownChildren } from './renderMarkdownChildren'
|
|
3
3
|
|
|
4
4
|
interface CalloutProps {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type?: 'info' | 'warning' | 'danger' | 'tip'
|
|
6
|
+
children?: React.ReactNode
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const config = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
info: { bg: 'bg-accent-blue-muted', icon: 'i', iconColor: 'text-accent-blue' },
|
|
11
|
+
warning: { bg: 'bg-accent-amber-muted', icon: '!', iconColor: 'text-accent-amber' },
|
|
12
|
+
danger: { bg: 'bg-accent-red-muted', icon: '\u2715', iconColor: 'text-accent-red' },
|
|
13
|
+
tip: { bg: 'bg-accent-green-muted', icon: '\u2713', iconColor: 'text-accent-green' },
|
|
14
|
+
}
|
|
15
15
|
|
|
16
|
-
export function Callout({ type =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
export function Callout({ type = 'info', children }: CalloutProps) {
|
|
17
|
+
const cfg = config[type]
|
|
18
|
+
return (
|
|
19
|
+
<div className={`${cfg.bg} relative rounded-lg px-5 py-4 mt-3 flex gap-3 items-start`} data-md="callout" data-md-type={type}>
|
|
20
|
+
{/* Decorative: the type it stands for is already in data-md-type. */}
|
|
21
|
+
<span aria-hidden="true" className={`${cfg.iconColor} opacity-70 text-base leading-relaxed flex-shrink-0 mt-px`}>
|
|
22
|
+
{cfg.icon}
|
|
23
|
+
</span>
|
|
24
|
+
<div className="text-body text-text-secondary leading-relaxed">{renderMarkdownChildren(children)}</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
27
|
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
3
|
interface ChecklistItem {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
label: string
|
|
5
|
+
checked: boolean
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
interface ChecklistProps {
|
|
9
|
-
|
|
9
|
+
items: ChecklistItem[]
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function Checklist({ items }: ChecklistProps) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
13
|
+
return (
|
|
14
|
+
<ul className="space-y-1 mt-3" data-md="checklist">
|
|
15
|
+
{items.map((item, i) => (
|
|
16
|
+
<li key={i} data-md="checklist-item" data-md-label={item.label} className="relative flex items-start gap-2.5 py-1">
|
|
17
|
+
<span
|
|
18
|
+
className={`w-4 h-4 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 transition-all ${
|
|
19
|
+
item.checked
|
|
20
|
+
? 'bg-accent-green'
|
|
21
|
+
: 'border-[1.5px] border-border-hover'
|
|
22
|
+
}`}
|
|
23
|
+
>
|
|
24
|
+
{item.checked && (
|
|
25
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
26
|
+
<path d="M2.5 5L4.5 7L7.5 3" style={{ stroke: 'var(--color-text-inverse)' }} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
)}
|
|
29
|
+
</span>
|
|
30
|
+
<span className={`text-body font-body ${item.checked ? 'text-text-tertiary line-through decoration-border-hover' : 'text-text-secondary'}`}>
|
|
31
|
+
{item.label}
|
|
32
|
+
</span>
|
|
33
|
+
</li>
|
|
34
|
+
))}
|
|
35
|
+
</ul>
|
|
36
|
+
)
|
|
37
37
|
}
|