@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
package/src/engine/watcher.ts
CHANGED
|
@@ -1,84 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from
|
|
3
|
-
import {
|
|
4
|
-
import type { SessionManager } from
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, watch } from 'fs'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { type CanvasComponents, compileJsx } from './compiler'
|
|
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
|
+
/** Scratch dir for recompiles, from `createCanvasPaths()`. */
|
|
10
|
+
tempDir?: string
|
|
11
|
+
/** Host components, same value the push path compiles with — a recompile
|
|
12
|
+
* that dropped them would fail on canvases the original push accepted. */
|
|
13
|
+
components?: CanvasComponents
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const watchers = new Map<string, ReturnType<typeof watch>[]>()
|
|
16
|
+
const watchers = new Map<string, ReturnType<typeof watch>[]>()
|
|
17
17
|
|
|
18
18
|
export function watchSession(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
sessionId: string,
|
|
20
|
+
sessionManager: SessionManager,
|
|
21
|
+
broadcast: BroadcastFn,
|
|
22
|
+
options: WatchOptions = {},
|
|
23
23
|
) {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Close existing watchers for this session
|
|
25
|
+
unwatchSession(sessionId)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const session = sessionManager.get(sessionId)
|
|
28
|
+
if (!session) return
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const rev = session.currentRevision
|
|
31
|
+
const canvasFiles = sessionManager.getRevisionCanvasFiles(sessionId, rev)
|
|
32
|
+
if (canvasFiles.length === 0) return
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const fileWatchers: ReturnType<typeof watch>[] = []
|
|
35
|
+
const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
for (const filename of canvasFiles) {
|
|
38
|
+
const filePath = sessionManager.getRevisionJsxPath(sessionId, rev, filename)
|
|
39
|
+
if (!existsSync(filePath)) continue
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const watcher = watch(filePath, async (event) => {
|
|
42
|
+
if (event !== 'change') return
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Debounce per-file
|
|
45
|
+
const existing = debounceTimers.get(filename)
|
|
46
|
+
if (existing) clearTimeout(existing)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
debounceTimers.set(
|
|
49
|
+
filename,
|
|
50
|
+
setTimeout(async () => {
|
|
51
|
+
debounceTimers.delete(filename)
|
|
52
|
+
try {
|
|
53
|
+
const jsx = readFileSync(filePath, 'utf-8')
|
|
54
|
+
const result = await compileJsx(jsx, {
|
|
55
|
+
projectRoot: session.projectRoot,
|
|
56
|
+
tempDir: options.tempDir,
|
|
57
|
+
...(options.components ? { components: options.components } : {}),
|
|
58
|
+
})
|
|
59
|
+
if (result.ok) {
|
|
60
|
+
sessionManager.saveCompiled(sessionId, filename, result.js, rev)
|
|
61
|
+
broadcast(sessionId)
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
}, 200),
|
|
65
|
+
)
|
|
66
|
+
})
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
fileWatchers.push(watcher)
|
|
69
|
+
}
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
watchers.set(sessionId, fileWatchers)
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export function unwatchSession(sessionId: string) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const sessionWatchers = watchers.get(sessionId)
|
|
76
|
+
if (sessionWatchers) {
|
|
77
|
+
for (const w of sessionWatchers) w.close()
|
|
78
|
+
watchers.delete(sessionId)
|
|
79
|
+
}
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
export function unwatchAll() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
for (const [, sessionWatchers] of watchers) {
|
|
84
|
+
for (const w of sessionWatchers) w.close()
|
|
85
|
+
}
|
|
86
|
+
watchers.clear()
|
|
84
87
|
}
|
package/src/engine/websocket.ts
CHANGED
|
@@ -4,156 +4,158 @@
|
|
|
4
4
|
* so a delivery closes it.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { browserFrame,
|
|
8
|
-
import type {
|
|
7
|
+
import { browserFrame, type ChannelSocket, createNotificationChannel, type TopicChannel } from '@fabrika/daemon-kit'
|
|
8
|
+
import type { RemoteFeedbackEntry, SessionManager } from './session'
|
|
9
9
|
|
|
10
|
-
export type WSData = { type:
|
|
10
|
+
export type WSData = { type: 'browser' | 'wait'; sessionId: string }
|
|
11
11
|
|
|
12
12
|
export interface CanvasSocket extends ChannelSocket {
|
|
13
|
-
|
|
13
|
+
data: WSData
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
interface CanvasRenderError {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
revision: number
|
|
18
|
+
filename: string
|
|
19
|
+
message: string
|
|
20
|
+
stack?: string
|
|
21
|
+
componentStack?: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
interface SubmitFrame {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
type: 'submit'
|
|
26
|
+
feedback: string
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
interface RenderErrorFrame {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
type: 'render-error'
|
|
31
|
+
error: CanvasRenderError
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
35
|
-
|
|
35
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function isCanvasRenderError(value: unknown): value is CanvasRenderError {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
return typeof value === 'object' && value !== null
|
|
40
|
+
&& 'revision' in value && Number.isInteger(value.revision)
|
|
41
|
+
&& 'filename' in value && typeof value.filename === 'string'
|
|
42
|
+
&& 'message' in value && typeof value.message === 'string'
|
|
43
|
+
&& (!('stack' in value) || value.stack === undefined || typeof value.stack === 'string')
|
|
44
|
+
&& (!('componentStack' in value) || value.componentStack === undefined || typeof value.componentStack === 'string')
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function isSubmitFrame(value: unknown): value is SubmitFrame {
|
|
48
|
-
|
|
48
|
+
return isRecord(value) && value['type'] === 'submit' && typeof value['feedback'] === 'string'
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function isRenderErrorFrame(value: unknown): value is RenderErrorFrame {
|
|
52
|
-
|
|
52
|
+
return isRecord(value) && value['type'] === 'render-error' && isCanvasRenderError(value['error'])
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface WebSocketManagerOptions {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Who marks feedback consumed once a waiting client has been handed it.
|
|
58
|
+
*
|
|
59
|
+
* `"on-delivery"` (default) clears it in the same step, so the same feedback
|
|
60
|
+
* can never be delivered twice — right when the waiting client *is* the
|
|
61
|
+
* consumer. `"external"` leaves it pending for a separate gate to claim
|
|
62
|
+
* through the consume endpoint, for hosts where delivery and consumption are
|
|
63
|
+
* different steps.
|
|
64
|
+
*/
|
|
65
|
+
feedbackConsumption?: 'on-delivery' | 'external'
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function createWebSocketManager(
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
sessionManager: SessionManager,
|
|
70
|
+
options: WebSocketManagerOptions = {},
|
|
71
71
|
) {
|
|
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
|
-
|
|
72
|
+
const consumeOnDelivery = (options.feedbackConsumption ?? 'on-delivery') === 'on-delivery'
|
|
73
|
+
const pendingRenderErrors = new Map<string, CanvasRenderError>()
|
|
74
|
+
|
|
75
|
+
/** Hands a held error to the waiters, if any showed up; the caller announces the departure. */
|
|
76
|
+
function deliverPendingRenderError(sessionId: string): boolean {
|
|
77
|
+
const error = pendingRenderErrors.get(sessionId)
|
|
78
|
+
if (!error) return false
|
|
79
|
+
if (!channel.relayToWaiters(sessionId, { type: 'render-error', error })) return false
|
|
80
|
+
pendingRenderErrors.delete(sessionId)
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function submit(frame: SubmitFrame, topic: TopicChannel) {
|
|
85
|
+
const sessionId = topic.topic
|
|
86
|
+
const session = sessionManager.get(sessionId)
|
|
87
|
+
if (!session) return
|
|
88
|
+
|
|
89
|
+
sessionManager.saveFeedback(sessionId, session.currentRevision, frame.feedback)
|
|
90
|
+
broadcastRevisionUpdate(sessionId)
|
|
91
|
+
if (!topic.hasWaiter()) return
|
|
92
|
+
|
|
93
|
+
if (consumeOnDelivery) {
|
|
94
|
+
sessionManager.consumeFeedback(sessionId, session.currentRevision)
|
|
95
|
+
broadcastRevisionUpdate(sessionId)
|
|
96
|
+
}
|
|
97
|
+
topic.relayToWaiters({ type: 'submit', feedback: frame.feedback })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderError(frame: RenderErrorFrame, topic: TopicChannel) {
|
|
101
|
+
const sessionId = topic.topic
|
|
102
|
+
const session = sessionManager.get(sessionId)
|
|
103
|
+
if (!session) return
|
|
104
|
+
if (frame.error.revision !== session.currentRevision) return
|
|
105
|
+
if (!session.canvasFiles.includes(frame.error.filename)) return
|
|
106
|
+
|
|
107
|
+
pendingRenderErrors.set(sessionId, frame.error)
|
|
108
|
+
if (deliverPendingRenderError(sessionId)) topic.broadcastPresence()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const channel = createNotificationChannel<CanvasSocket>({
|
|
112
|
+
roleOf: (socket) => socket.data.type,
|
|
113
|
+
topicOf: (socket) => socket.data.sessionId,
|
|
114
|
+
waiterPolicy: 'close-on-delivery',
|
|
115
|
+
presenceFrame: (watching) => ({ type: 'watcher-status', watching }),
|
|
116
|
+
// A render error raised before the CLI watcher connected is held for it.
|
|
117
|
+
onWaiterOpen: (topic) => {
|
|
118
|
+
deliverPendingRenderError(topic.topic)
|
|
119
|
+
},
|
|
120
|
+
frames: [
|
|
121
|
+
browserFrame(isSubmitFrame, submit),
|
|
122
|
+
browserFrame(isRenderErrorFrame, renderError),
|
|
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";
|
package/daemon.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// @fabrika/canvas-kernel/daemon — the host-agnostic daemon runtime.
|
|
2
|
-
//
|
|
3
|
-
// Ring 1: everything a local daemon needs that knows nothing about canvases —
|
|
4
|
-
// the WebSocket notification channel, the CLI waiter, process lifecycle, route
|
|
5
|
-
// dispatch, and the on-disk layout. A host whose domain is not canvases (Figma
|
|
6
|
-
// screens, say) takes this entry and none of the canvas engine behind `/server`.
|
|
7
|
-
|
|
8
|
-
// --- WebSocket notification channel ----------------------------------------
|
|
9
|
-
export { createNotificationChannel, browserFrame } from "./src/engine/channel";
|
|
10
|
-
export type {
|
|
11
|
-
ChannelRole,
|
|
12
|
-
ChannelSocket,
|
|
13
|
-
TopicChannel,
|
|
14
|
-
RelayOptions,
|
|
15
|
-
BrowserFrameHandler,
|
|
16
|
-
NotificationChannel,
|
|
17
|
-
NotificationChannelOptions,
|
|
18
|
-
} from "./src/engine/channel";
|
|
19
|
-
|
|
20
|
-
// --- CLI waiter ------------------------------------------------------------
|
|
21
|
-
export { waitForEvent, WaitError } from "./src/engine/waiter";
|
|
22
|
-
export type { WaiterSocket, WaitVerdict, WaitOptions, WaitMessages, WaitFailure } from "./src/engine/waiter";
|
|
23
|
-
|
|
24
|
-
// --- Daemon process lifecycle ----------------------------------------------
|
|
25
|
-
export { createDaemonLifecycle, DaemonStartError } from "./src/engine/lifecycle";
|
|
26
|
-
export type {
|
|
27
|
-
DaemonLifecycle,
|
|
28
|
-
DaemonLifecycleConfig,
|
|
29
|
-
DaemonHealth,
|
|
30
|
-
DaemonHealthUp,
|
|
31
|
-
DaemonHealthDown,
|
|
32
|
-
DaemonPid,
|
|
33
|
-
DaemonStopResult,
|
|
34
|
-
DaemonStopReason,
|
|
35
|
-
DaemonEvent,
|
|
36
|
-
DaemonTimeouts,
|
|
37
|
-
} from "./src/engine/lifecycle";
|
|
38
|
-
|
|
39
|
-
// --- Router / HTTP ---------------------------------------------------------
|
|
40
|
-
export { dispatch } from "./src/engine/router";
|
|
41
|
-
export type { Route, RouteHandler } from "./src/engine/router";
|
|
42
|
-
export { jsonResponse, corsHeaders } from "./src/engine/http-utils";
|
|
43
|
-
|
|
44
|
-
// --- On-disk layout --------------------------------------------------------
|
|
45
|
-
export { createCanvasPaths } from "./src/engine/paths";
|
|
46
|
-
export type { CanvasPaths, CanvasPathsConfig } from "./src/engine/paths";
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type React from "react";
|
|
2
|
-
import { useAnnotations } from "./AnnotationProvider";
|
|
3
|
-
import { autoResizeTextarea } from "./utils";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The foot of a draft: the note that belongs to no one annotation, and the
|
|
7
|
-
* action that sends the draft off.
|
|
8
|
-
*
|
|
9
|
-
* What counts as content, and what the payload looks like, is the host's —
|
|
10
|
-
* hence `hasContent` and a bare `onSubmit`. The two slots are where a host
|
|
11
|
-
* hangs whatever else its submit step needs (a preview button, a validation
|
|
12
|
-
* notice) without this component learning about it.
|
|
13
|
-
*/
|
|
14
|
-
export interface AnnotationDraftFooterProps {
|
|
15
|
-
/** Whether the draft says anything. Hosts count more than annotations and
|
|
16
|
-
* the general note, so they decide. */
|
|
17
|
-
hasContent: boolean;
|
|
18
|
-
onSubmit: () => void;
|
|
19
|
-
/** Submitting an empty draft — an explicit "nothing to say", not a no-op. */
|
|
20
|
-
onSubmitEmpty: () => void;
|
|
21
|
-
/** Sits between the note and the actions: validation, status, a warning. */
|
|
22
|
-
notice?: React.ReactNode;
|
|
23
|
-
/** Sits left of the submit button, sharing the row. */
|
|
24
|
-
secondaryAction?: React.ReactNode;
|
|
25
|
-
placeholder?: string;
|
|
26
|
-
submitLabel?: string;
|
|
27
|
-
emptyLabel?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function AnnotationDraftFooter({
|
|
31
|
-
hasContent, onSubmit, onSubmitEmpty, notice, secondaryAction,
|
|
32
|
-
placeholder = "General notes...",
|
|
33
|
-
submitLabel = "Submit",
|
|
34
|
-
emptyLabel = "Submit without feedback",
|
|
35
|
-
}: AnnotationDraftFooterProps) {
|
|
36
|
-
const { generalNote, setGeneralNote } = useAnnotations();
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<>
|
|
40
|
-
{/* General note — seamless */}
|
|
41
|
-
<div className="border-t border-border-subtle px-4 py-3 flex-shrink-0">
|
|
42
|
-
<textarea
|
|
43
|
-
value={generalNote}
|
|
44
|
-
onChange={(e) => setGeneralNote(e.currentTarget.value)}
|
|
45
|
-
className="w-full bg-transparent text-[13px] font-body text-text-primary resize-none leading-relaxed p-0 border-none ring-0 shadow-none outline-none focus:outline-none focus:ring-0 focus:border-none placeholder:text-text-disabled min-h-[40px]"
|
|
46
|
-
placeholder={placeholder}
|
|
47
|
-
onInput={(e) => autoResizeTextarea(e.currentTarget, 40)}
|
|
48
|
-
ref={(el) => { if (el) autoResizeTextarea(el, 40); }}
|
|
49
|
-
/>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
{notice}
|
|
53
|
-
|
|
54
|
-
<div className="px-4 py-3 border-t border-border-subtle flex-shrink-0">
|
|
55
|
-
{hasContent ? (
|
|
56
|
-
<div className="flex gap-2">
|
|
57
|
-
{secondaryAction}
|
|
58
|
-
<button
|
|
59
|
-
onClick={onSubmit}
|
|
60
|
-
className="flex-1 py-2 rounded-lg font-body text-[13px] font-medium transition-all bg-btn-primary text-btn-primary-text hover:opacity-90 hover:-translate-y-px shadow-sm"
|
|
61
|
-
>
|
|
62
|
-
{submitLabel}
|
|
63
|
-
</button>
|
|
64
|
-
</div>
|
|
65
|
-
) : (
|
|
66
|
-
<button
|
|
67
|
-
onClick={onSubmitEmpty}
|
|
68
|
-
className="w-full py-2 rounded-lg font-body text-[13px] font-medium border border-border-medium text-text-secondary hover:text-text-primary hover:border-border-hover hover:bg-bg-input transition-all"
|
|
69
|
-
>
|
|
70
|
-
{emptyLabel}
|
|
71
|
-
</button>
|
|
72
|
-
)}
|
|
73
|
-
</div>
|
|
74
|
-
</>
|
|
75
|
-
);
|
|
76
|
-
}
|