@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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { annotationAuthorSchema, annotationSchema } from '@fabrika/annotations/schemas'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const diffStatsSchema = z.object({ added: z.number(), removed: z.number() }).passthrough()
|
|
5
|
+
const revisionFields = {
|
|
6
|
+
revision: z.number().int(),
|
|
7
|
+
label: z.string().optional(),
|
|
8
|
+
createdAt: z.string(),
|
|
9
|
+
hasFeedback: z.boolean(),
|
|
10
|
+
feedbackConsumed: z.boolean(),
|
|
11
|
+
response: z.string().optional(),
|
|
12
|
+
}
|
|
13
|
+
const revisionSchema = z.object({
|
|
14
|
+
...revisionFields,
|
|
15
|
+
canvasFiles: z.array(z.object({ filename: z.string(), diffStats: diffStatsSchema.optional() }).passthrough()),
|
|
16
|
+
canvasScope: z.discriminatedUnion('kind', [
|
|
17
|
+
z.object({ kind: z.literal('view'), filename: z.string() }).passthrough(),
|
|
18
|
+
z.object({ kind: z.literal('directory') }).passthrough(),
|
|
19
|
+
]).optional(),
|
|
20
|
+
}).passthrough()
|
|
21
|
+
const legacyRevisionSchema = z.object({
|
|
22
|
+
...revisionFields,
|
|
23
|
+
canvasFiles: z.never().optional(),
|
|
24
|
+
sourceFile: z.string().optional(),
|
|
25
|
+
diffStats: diffStatsSchema.optional(),
|
|
26
|
+
}).passthrough()
|
|
27
|
+
const shareSchema = z.object({
|
|
28
|
+
shareId: z.string(),
|
|
29
|
+
url: z.string(),
|
|
30
|
+
revision: z.number().int(),
|
|
31
|
+
createdAt: z.string(),
|
|
32
|
+
ownerToken: z.string().optional(),
|
|
33
|
+
expiresAt: z.string().optional(),
|
|
34
|
+
lastFeedbackAt: z.string().optional(),
|
|
35
|
+
encryptionKey: z.string().optional(),
|
|
36
|
+
}).passthrough()
|
|
37
|
+
const metadataFields = {
|
|
38
|
+
projectRoot: z.string(),
|
|
39
|
+
createdAt: z.string(),
|
|
40
|
+
updatedAt: z.string(),
|
|
41
|
+
}
|
|
42
|
+
export const savedSessionMetadataSchema = z.object({
|
|
43
|
+
...metadataFields,
|
|
44
|
+
currentRevision: z.number().int(),
|
|
45
|
+
revisions: z.array(z.union([revisionSchema, legacyRevisionSchema])),
|
|
46
|
+
shares: z.array(shareSchema).optional(),
|
|
47
|
+
}).passthrough().transform(meta => ({
|
|
48
|
+
migrated: meta.revisions.some(revision => revision.canvasFiles === undefined),
|
|
49
|
+
meta: {
|
|
50
|
+
...meta,
|
|
51
|
+
revisions: meta.revisions.map(revision => {
|
|
52
|
+
if (revision.canvasFiles !== undefined) return revision
|
|
53
|
+
const { sourceFile, diffStats, canvasFiles: _canvasFiles, ...fields } = revision
|
|
54
|
+
return { ...fields, canvasFiles: [{ filename: sourceFile || 'plan.jsx', diffStats }] }
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
export const remoteFeedbackEntrySchema = z.object({
|
|
60
|
+
id: z.string(),
|
|
61
|
+
shareId: z.string(),
|
|
62
|
+
revision: z.number().int(),
|
|
63
|
+
submittedAt: z.string(),
|
|
64
|
+
author: annotationAuthorSchema,
|
|
65
|
+
annotations: z.array(annotationSchema.omit({ images: true, source: true, author: true }).optional().catch(undefined))
|
|
66
|
+
.transform(annotations => annotations.filter(annotation => annotation !== undefined)),
|
|
67
|
+
generalNote: z.string().optional(),
|
|
68
|
+
}).passthrough()
|
|
69
|
+
|
|
70
|
+
export const remoteFeedbackSchema = z.array(remoteFeedbackEntrySchema.optional().catch(undefined))
|
|
71
|
+
.transform(entries => entries.filter(entry => entry !== undefined)).catch([])
|
package/src/engine/watcher.ts
CHANGED
|
@@ -1,84 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { SessionManager } from
|
|
1
|
+
import { existsSync, watch } from 'fs'
|
|
2
|
+
import { type CanvasComponents, compileJsx } from './compiler'
|
|
3
|
+
import { assertCanvasFileReadable, type AssertReadable, readCanvasFile } from './file-admission'
|
|
4
|
+
import type { SessionManager } from './session'
|
|
5
5
|
|
|
6
|
-
type BroadcastFn = (sessionId: string) => void
|
|
6
|
+
type BroadcastFn = (sessionId: string) => void
|
|
7
7
|
|
|
8
8
|
export interface WatchOptions {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
assertReadable?: AssertReadable
|
|
10
|
+
/** Scratch dir for recompiles, from `createCanvasPaths()`. */
|
|
11
|
+
tempDir?: string
|
|
12
|
+
/** Host components, same value the push path compiles with — a recompile
|
|
13
|
+
* that dropped them would fail on canvases the original push accepted. */
|
|
14
|
+
components?: CanvasComponents
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const watchers = new Map<string, ReturnType<typeof watch>[]>()
|
|
17
|
+
const watchers = new Map<string, ReturnType<typeof watch>[]>()
|
|
17
18
|
|
|
18
19
|
export function watchSession(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
sessionId: string,
|
|
21
|
+
sessionManager: SessionManager,
|
|
22
|
+
broadcast: BroadcastFn,
|
|
23
|
+
options: WatchOptions = {},
|
|
23
24
|
) {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Close existing watchers for this session
|
|
26
|
+
unwatchSession(sessionId)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const session = sessionManager.get(sessionId)
|
|
29
|
+
if (!session) return
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const rev = session.currentRevision
|
|
32
|
+
const canvasFiles = sessionManager.getRevisionCanvasFiles(sessionId, rev)
|
|
33
|
+
if (canvasFiles.length === 0) return
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const fileWatchers: ReturnType<typeof watch>[] = []
|
|
36
|
+
const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
37
|
+
const sources: { filename: string; filePath: string }[] = []
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
for (const filename of canvasFiles) {
|
|
40
|
+
const filePath = sessionManager.getRevisionJsxPath(sessionId, rev, filename)
|
|
41
|
+
if (!existsSync(filePath)) continue
|
|
42
|
+
assertCanvasFileReadable(filePath, options.assertReadable)
|
|
43
|
+
sources.push({ filename, filePath })
|
|
44
|
+
}
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
for (const { filename, filePath } of sources) {
|
|
47
|
+
const watcher = watch(filePath, async (event) => {
|
|
48
|
+
if (event !== 'change') return
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
// Debounce per-file
|
|
51
|
+
const existing = debounceTimers.get(filename)
|
|
52
|
+
if (existing) clearTimeout(existing)
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
debounceTimers.set(
|
|
55
|
+
filename,
|
|
56
|
+
setTimeout(async () => {
|
|
57
|
+
debounceTimers.delete(filename)
|
|
58
|
+
try {
|
|
59
|
+
const jsx = readCanvasFile(filePath, options.assertReadable)
|
|
60
|
+
const result = await compileJsx(jsx, {
|
|
61
|
+
projectRoot: session.projectRoot,
|
|
62
|
+
tempDir: options.tempDir,
|
|
63
|
+
assertReadable: options.assertReadable,
|
|
64
|
+
...(options.components ? { components: options.components } : {}),
|
|
65
|
+
})
|
|
66
|
+
if (result.ok) {
|
|
67
|
+
sessionManager.saveCompiled(sessionId, filename, result.js, rev)
|
|
68
|
+
broadcast(sessionId)
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
}, 200),
|
|
72
|
+
)
|
|
73
|
+
})
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
fileWatchers.push(watcher)
|
|
76
|
+
}
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
watchers.set(sessionId, fileWatchers)
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
export function unwatchSession(sessionId: string) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
const sessionWatchers = watchers.get(sessionId)
|
|
83
|
+
if (sessionWatchers) {
|
|
84
|
+
for (const w of sessionWatchers) w.close()
|
|
85
|
+
watchers.delete(sessionId)
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
export function unwatchAll() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
for (const [, sessionWatchers] of watchers) {
|
|
91
|
+
for (const w of sessionWatchers) w.close()
|
|
92
|
+
}
|
|
93
|
+
watchers.clear()
|
|
84
94
|
}
|
package/src/engine/websocket.ts
CHANGED
|
@@ -4,156 +4,158 @@
|
|
|
4
4
|
* so a delivery closes it.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
7
|
+
import { type ChannelSocket, createNotificationChannel, type TopicChannel } from '@fabrika/daemon-kit'
|
|
8
|
+
import { z } from 'zod'
|
|
9
|
+
import type { RemoteFeedbackEntry, SessionManager } from './session'
|
|
9
10
|
|
|
10
|
-
export type WSData = { type:
|
|
11
|
+
export type WSData = { type: 'browser' | 'wait'; sessionId: string }
|
|
11
12
|
|
|
12
13
|
export interface CanvasSocket extends ChannelSocket {
|
|
13
|
-
|
|
14
|
+
data: WSData
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
interface CanvasRenderError {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
revision: number
|
|
19
|
+
filename: string
|
|
20
|
+
message: string
|
|
21
|
+
stack?: string
|
|
22
|
+
componentStack?: string
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
interface SubmitFrame {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
type: 'submit'
|
|
27
|
+
feedback: string
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
interface RenderErrorFrame {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
type: 'render-error'
|
|
32
|
+
error: CanvasRenderError
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isSubmitFrame(value: unknown): value is SubmitFrame {
|
|
48
|
-
return isRecord(value) && value.type === "submit" && typeof value.feedback === "string";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isRenderErrorFrame(value: unknown): value is RenderErrorFrame {
|
|
52
|
-
return isRecord(value) && value.type === "render-error" && isCanvasRenderError(value.error);
|
|
53
|
-
}
|
|
35
|
+
const browserFrameSchema = z.discriminatedUnion('type', [
|
|
36
|
+
z.object({ type: z.literal('submit'), feedback: z.string() }).passthrough(),
|
|
37
|
+
z.object({
|
|
38
|
+
type: z.literal('render-error'),
|
|
39
|
+
error: z.object({
|
|
40
|
+
revision: z.number().int(),
|
|
41
|
+
filename: z.string(),
|
|
42
|
+
message: z.string(),
|
|
43
|
+
stack: z.string().optional(),
|
|
44
|
+
componentStack: z.string().optional(),
|
|
45
|
+
}).passthrough(),
|
|
46
|
+
}).passthrough(),
|
|
47
|
+
])
|
|
54
48
|
|
|
55
49
|
export interface WebSocketManagerOptions {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Who marks feedback consumed once a waiting client has been handed it.
|
|
52
|
+
*
|
|
53
|
+
* `"on-delivery"` (default) clears it in the same step, so the same feedback
|
|
54
|
+
* can never be delivered twice — right when the waiting client *is* the
|
|
55
|
+
* consumer. `"external"` leaves it pending for a separate gate to claim
|
|
56
|
+
* through the consume endpoint, for hosts where delivery and consumption are
|
|
57
|
+
* different steps.
|
|
58
|
+
*/
|
|
59
|
+
feedbackConsumption?: 'on-delivery' | 'external'
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
export function createWebSocketManager(
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
sessionManager: SessionManager,
|
|
64
|
+
options: WebSocketManagerOptions = {},
|
|
71
65
|
) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
66
|
+
const consumeOnDelivery = (options.feedbackConsumption ?? 'on-delivery') === 'on-delivery'
|
|
67
|
+
const pendingRenderErrors = new Map<string, CanvasRenderError>()
|
|
68
|
+
|
|
69
|
+
/** Hands a held error to the waiters, if any showed up; the caller announces the departure. */
|
|
70
|
+
function deliverPendingRenderError(sessionId: string): boolean {
|
|
71
|
+
const error = pendingRenderErrors.get(sessionId)
|
|
72
|
+
if (!error) return false
|
|
73
|
+
if (!channel.relayToWaiters(sessionId, { type: 'render-error', error })) return false
|
|
74
|
+
pendingRenderErrors.delete(sessionId)
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function submit(frame: SubmitFrame, topic: TopicChannel) {
|
|
79
|
+
const sessionId = topic.topic
|
|
80
|
+
const session = sessionManager.get(sessionId)
|
|
81
|
+
if (!session) return
|
|
82
|
+
|
|
83
|
+
sessionManager.saveFeedback(sessionId, session.currentRevision, frame.feedback)
|
|
84
|
+
broadcastRevisionUpdate(sessionId)
|
|
85
|
+
if (!topic.hasWaiter()) return
|
|
86
|
+
|
|
87
|
+
if (consumeOnDelivery) {
|
|
88
|
+
sessionManager.consumeFeedback(sessionId, session.currentRevision)
|
|
89
|
+
broadcastRevisionUpdate(sessionId)
|
|
90
|
+
}
|
|
91
|
+
topic.relayToWaiters({ type: 'submit', feedback: frame.feedback })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderError(frame: RenderErrorFrame, topic: TopicChannel) {
|
|
95
|
+
const sessionId = topic.topic
|
|
96
|
+
const session = sessionManager.get(sessionId)
|
|
97
|
+
if (!session) return
|
|
98
|
+
if (frame.error.revision !== session.currentRevision) return
|
|
99
|
+
if (!session.canvasFiles.includes(frame.error.filename)) return
|
|
100
|
+
|
|
101
|
+
pendingRenderErrors.set(sessionId, frame.error)
|
|
102
|
+
if (deliverPendingRenderError(sessionId)) topic.broadcastPresence()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const channel = createNotificationChannel<CanvasSocket>({
|
|
106
|
+
roleOf: (socket) => socket.data.type,
|
|
107
|
+
topicOf: (socket) => socket.data.sessionId,
|
|
108
|
+
waiterPolicy: 'close-on-delivery',
|
|
109
|
+
presenceFrame: (watching) => ({ type: 'watcher-status', watching }),
|
|
110
|
+
// A render error raised before the CLI watcher connected is held for it.
|
|
111
|
+
onWaiterOpen: (topic) => {
|
|
112
|
+
deliverPendingRenderError(topic.topic)
|
|
113
|
+
},
|
|
114
|
+
frames: [
|
|
115
|
+
{
|
|
116
|
+
bind(frame, topic) {
|
|
117
|
+
const parsed = browserFrameSchema.safeParse(frame)
|
|
118
|
+
if (!parsed.success) return undefined
|
|
119
|
+
const data = parsed.data
|
|
120
|
+
return () => data.type === 'submit' ? submit(data, topic) : renderError(data, topic)
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
function broadcastPlanUpdate(sessionId: string) {
|
|
127
|
+
pendingRenderErrors.delete(sessionId)
|
|
128
|
+
const session = sessionManager.get(sessionId)
|
|
129
|
+
if (!session) return
|
|
130
|
+
channel.broadcast(sessionId, {
|
|
131
|
+
type: 'plan-updated',
|
|
132
|
+
currentRevision: session.currentRevision,
|
|
133
|
+
revisions: session.revisions,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function broadcastRevisionUpdate(sessionId: string) {
|
|
138
|
+
const session = sessionManager.get(sessionId)
|
|
139
|
+
if (!session) return
|
|
140
|
+
channel.broadcast(sessionId, {
|
|
141
|
+
type: 'revision-updated',
|
|
142
|
+
revisions: session.revisions,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function broadcastRemoteFeedback(sessionId: string, revision: number, entries: RemoteFeedbackEntry[]) {
|
|
147
|
+
channel.broadcast(sessionId, { type: 'remote-feedback', revision, entries })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function broadcastWatcherStatus(sessionId: string) {
|
|
151
|
+
channel.broadcastPresence(sessionId)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
handlers: channel.handlers,
|
|
156
|
+
broadcastPlanUpdate,
|
|
157
|
+
broadcastRevisionUpdate,
|
|
158
|
+
broadcastWatcherStatus,
|
|
159
|
+
broadcastRemoteFeedback,
|
|
160
|
+
}
|
|
159
161
|
}
|
package/annotate.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// @fabrika/canvas-kernel/annotate — the annotation surface.
|
|
2
|
-
//
|
|
3
|
-
// Ring 2: collecting a reader's notes on something, and getting them back out
|
|
4
|
-
// as markdown an agent can act on. What a note points at is a locator strategy
|
|
5
|
-
// (text, block, image region), so a host annotates its own kind of thing by
|
|
6
|
-
// writing one target; the list, the editor, the draft and the markdown do not
|
|
7
|
-
// change. Grouping, headings and navigation stay with the host — this ring has
|
|
8
|
-
// no taxonomy of its own.
|
|
9
|
-
//
|
|
10
|
-
// Not yet a severed dependency: `AnnotationProvider` still imports the canvas
|
|
11
|
-
// response-pruning from `generateMarkdown`, so a bundle built off this entry
|
|
12
|
-
// carries that along. The export list is the API statement; the import graph
|
|
13
|
-
// has one edge left to cut.
|
|
14
|
-
|
|
15
|
-
// --- Annotation state ------------------------------------------------------
|
|
16
|
-
export { AnnotationProvider } from "./src/client/AnnotationProvider";
|
|
17
|
-
export type { PersistedState } from "./src/client/AnnotationProvider";
|
|
18
|
-
export { useAnnotations, AnnotationCtx, SessionContext } from "./src/client/runtime";
|
|
19
|
-
export type {
|
|
20
|
-
Annotation,
|
|
21
|
-
AnnotationAttachment,
|
|
22
|
-
AnnotationAuthor,
|
|
23
|
-
AnnotationContext,
|
|
24
|
-
AnnotationContextValue,
|
|
25
|
-
} from "./src/client/runtime";
|
|
26
|
-
export {
|
|
27
|
-
annotationDraftKey,
|
|
28
|
-
carryUnsubmittedDraft,
|
|
29
|
-
clearPersistedDraft,
|
|
30
|
-
} from "./src/client/annotationDraft";
|
|
31
|
-
export type { AnnotationDraftPhase } from "./src/client/annotationDraft";
|
|
32
|
-
|
|
33
|
-
// --- Surface ---------------------------------------------------------------
|
|
34
|
-
export { AnnotationList } from "./src/client/AnnotationList";
|
|
35
|
-
export type { AnnotationListProps } from "./src/client/AnnotationList";
|
|
36
|
-
export { AnnotationDraftFooter } from "./src/client/AnnotationDraftFooter";
|
|
37
|
-
export type { AnnotationDraftFooterProps } from "./src/client/AnnotationDraftFooter";
|
|
38
|
-
export { AnnotationEditor, imageToUrl } from "./src/client/AnnotationEditor";
|
|
39
|
-
export { Popover, AnnotationPopover, AnnotationCreatePopover, AnnotationEditPopover } from "./src/client/Popover";
|
|
40
|
-
|
|
41
|
-
// --- Drawing ---------------------------------------------------------------
|
|
42
|
-
export { useTextAnnotation } from "./src/client/useTextAnnotation";
|
|
43
|
-
export { useRegionAnnotation } from "./src/client/useRegionAnnotation";
|
|
44
|
-
|
|
45
|
-
// --- Locator strategies ----------------------------------------------------
|
|
46
|
-
export { sealTarget } from "./src/client/annotationTarget";
|
|
47
|
-
export type { AnnotationTarget, SealedAnnotationTarget, TargetAnnotation } from "./src/client/annotationTarget";
|
|
48
|
-
export { blockTarget } from "./src/client/blockTarget";
|
|
49
|
-
export type { BlockLocator } from "./src/client/blockTarget";
|
|
50
|
-
export { textTarget } from "./src/client/textTarget";
|
|
51
|
-
export type { TextLocator } from "./src/client/textTarget";
|
|
52
|
-
export {
|
|
53
|
-
createRegionOverlay,
|
|
54
|
-
describeRegion,
|
|
55
|
-
findRegionHost,
|
|
56
|
-
isDrawableRegion,
|
|
57
|
-
regionBetween,
|
|
58
|
-
regionPointIn,
|
|
59
|
-
regionStyle,
|
|
60
|
-
regionTarget,
|
|
61
|
-
REGION_HOST_ATTR,
|
|
62
|
-
REGION_UNITS,
|
|
63
|
-
} from "./src/client/regionTarget";
|
|
64
|
-
export type { RegionBox, RegionLocator, RegionPoint, RegionShape } from "./src/client/regionTarget";
|
|
65
|
-
export {
|
|
66
|
-
ANNOTATION_TARGETS,
|
|
67
|
-
ANNOTATABLE_SELECTOR,
|
|
68
|
-
BLOCK_SELECTOR,
|
|
69
|
-
describeSnippet,
|
|
70
|
-
findAnnotationElement,
|
|
71
|
-
findSnippetElement,
|
|
72
|
-
getBlockSnippet,
|
|
73
|
-
restoreAnnotationTargets,
|
|
74
|
-
} from "./src/client/annotationDom";
|
|
75
|
-
|
|
76
|
-
// --- Marks -----------------------------------------------------------------
|
|
77
|
-
export {
|
|
78
|
-
restoreMarks,
|
|
79
|
-
renameMarkId,
|
|
80
|
-
setMarkActive,
|
|
81
|
-
unwrapMarks,
|
|
82
|
-
updateAllMarkStates,
|
|
83
|
-
wrapRangeWithMark,
|
|
84
|
-
} from "./src/client/highlightRange";
|
|
85
|
-
|
|
86
|
-
// --- Feedback out ----------------------------------------------------------
|
|
87
|
-
export { renderAnnotation } from "./src/client/annotationMarkdown";
|
|
88
|
-
export type { RenderableAnnotation } from "./src/client/annotationMarkdown";
|
|
89
|
-
export { extractContext, formatSnippetInContext } from "./src/client/annotationContext";
|
|
90
|
-
|
|
91
|
-
// --- Host-provided context -------------------------------------------------
|
|
92
|
-
export { CanvasHostContext, localCanvasHost, useCanvasHost } from "./src/client/hostContext";
|
|
93
|
-
export type { CanvasHost } from "./src/client/hostContext";
|
|
94
|
-
export { autoResizeTextarea, fileAnnotationPath, generateAnnotationId, hostAcceptsUploads } from "./src/client/utils";
|