@fabrika/canvas-kernel 0.1.45 → 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.
Files changed (66) hide show
  1. package/README.md +33 -67
  2. package/index.ts +19 -83
  3. package/package.json +68 -72
  4. package/src/client/AnnotationProvider.tsx +227 -268
  5. package/src/client/AnnotationSidebar.tsx +392 -338
  6. package/src/client/FileIcon.tsx +106 -65
  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 +98 -92
  12. package/src/client/appContext.tsx +40 -40
  13. package/src/client/components/Callout.tsx +20 -20
  14. package/src/client/components/Checklist.tsx +28 -28
  15. package/src/client/components/Choice.tsx +178 -159
  16. package/src/client/components/CodeBlock.tsx +26 -26
  17. package/src/client/components/Diff.tsx +17 -17
  18. package/src/client/components/FilePreview.tsx +99 -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 +592 -542
  22. package/src/client/components/Note.tsx +8 -8
  23. package/src/client/components/Priority.tsx +11 -11
  24. package/src/client/components/ResponseNote.tsx +17 -15
  25. package/src/client/components/Section.tsx +28 -26
  26. package/src/client/components/Table.tsx +34 -29
  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 +34 -34
  34. package/src/client/index.ts +43 -125
  35. package/src/client/preact-all.ts +4 -4
  36. package/src/client/runtime.ts +58 -111
  37. package/src/client/styles.css +1 -74
  38. package/src/engine/compiler.ts +316 -313
  39. package/src/engine/paths.ts +44 -44
  40. package/src/engine/session.ts +623 -618
  41. package/src/engine/watcher.ts +63 -60
  42. package/src/engine/websocket.ts +123 -121
  43. package/annotate.ts +0 -94
  44. package/daemon.ts +0 -46
  45. package/src/client/AnnotationDraftFooter.tsx +0 -76
  46. package/src/client/AnnotationEditor.tsx +0 -320
  47. package/src/client/AnnotationList.tsx +0 -190
  48. package/src/client/Popover.tsx +0 -361
  49. package/src/client/SessionContext.tsx +0 -3
  50. package/src/client/annotationContext.ts +0 -130
  51. package/src/client/annotationMarkdown.ts +0 -102
  52. package/src/client/annotationTarget.ts +0 -73
  53. package/src/client/blockTarget.ts +0 -127
  54. package/src/client/highlightRange.ts +0 -262
  55. package/src/client/popoverPosition.ts +0 -46
  56. package/src/client/regionTarget.ts +0 -180
  57. package/src/client/testing/dom.ts +0 -74
  58. package/src/client/textTarget.ts +0 -31
  59. package/src/client/useRegionAnnotation.tsx +0 -188
  60. package/src/client/useTextAnnotation.tsx +0 -194
  61. package/src/client/utils.ts +0 -33
  62. package/src/engine/channel.ts +0 -278
  63. package/src/engine/http-utils.ts +0 -14
  64. package/src/engine/lifecycle.ts +0 -302
  65. package/src/engine/router.ts +0 -16
  66. package/src/engine/waiter.ts +0 -188
@@ -1,84 +1,87 @@
1
- import { watch, existsSync, readFileSync, readdirSync } from "fs";
2
- import { join } from "path";
3
- import { compileJsx, type CanvasComponents } from "./compiler";
4
- import type { SessionManager } from "./session";
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
- /** 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;
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
- sessionId: string,
20
- sessionManager: SessionManager,
21
- broadcast: BroadcastFn,
22
- options: WatchOptions = {},
19
+ sessionId: string,
20
+ sessionManager: SessionManager,
21
+ broadcast: BroadcastFn,
22
+ options: WatchOptions = {},
23
23
  ) {
24
- // Close existing watchers for this session
25
- unwatchSession(sessionId);
24
+ // Close existing watchers for this session
25
+ unwatchSession(sessionId)
26
26
 
27
- const session = sessionManager.get(sessionId);
28
- if (!session) return;
27
+ const session = sessionManager.get(sessionId)
28
+ if (!session) return
29
29
 
30
- const rev = session.currentRevision;
31
- const canvasFiles = sessionManager.getRevisionCanvasFiles(sessionId, rev);
32
- if (canvasFiles.length === 0) return;
30
+ const rev = session.currentRevision
31
+ const canvasFiles = sessionManager.getRevisionCanvasFiles(sessionId, rev)
32
+ if (canvasFiles.length === 0) return
33
33
 
34
- const fileWatchers: ReturnType<typeof watch>[] = [];
35
- const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
34
+ const fileWatchers: ReturnType<typeof watch>[] = []
35
+ const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
36
36
 
37
- for (const filename of canvasFiles) {
38
- const filePath = sessionManager.getRevisionJsxPath(sessionId, rev, filename);
39
- if (!existsSync(filePath)) continue;
37
+ for (const filename of canvasFiles) {
38
+ const filePath = sessionManager.getRevisionJsxPath(sessionId, rev, filename)
39
+ if (!existsSync(filePath)) continue
40
40
 
41
- const watcher = watch(filePath, async (event) => {
42
- if (event !== "change") return;
41
+ const watcher = watch(filePath, async (event) => {
42
+ if (event !== 'change') return
43
43
 
44
- // Debounce per-file
45
- const existing = debounceTimers.get(filename);
46
- if (existing) clearTimeout(existing);
44
+ // Debounce per-file
45
+ const existing = debounceTimers.get(filename)
46
+ if (existing) clearTimeout(existing)
47
47
 
48
- debounceTimers.set(filename, setTimeout(async () => {
49
- debounceTimers.delete(filename);
50
- try {
51
- const jsx = readFileSync(filePath, "utf-8");
52
- const result = await compileJsx(jsx, {
53
- projectRoot: session.projectRoot,
54
- tempDir: options.tempDir,
55
- ...(options.components ? { components: options.components } : {}),
56
- });
57
- if (result.ok) {
58
- sessionManager.saveCompiled(sessionId, filename, result.js, rev);
59
- broadcast(sessionId);
60
- }
61
- } catch {}
62
- }, 200));
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
- fileWatchers.push(watcher);
66
- }
68
+ fileWatchers.push(watcher)
69
+ }
67
70
 
68
- watchers.set(sessionId, fileWatchers);
71
+ watchers.set(sessionId, fileWatchers)
69
72
  }
70
73
 
71
74
  export function unwatchSession(sessionId: string) {
72
- const sessionWatchers = watchers.get(sessionId);
73
- if (sessionWatchers) {
74
- for (const w of sessionWatchers) w.close();
75
- watchers.delete(sessionId);
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
- for (const [, sessionWatchers] of watchers) {
81
- for (const w of sessionWatchers) w.close();
82
- }
83
- watchers.clear();
83
+ for (const [, sessionWatchers] of watchers) {
84
+ for (const w of sessionWatchers) w.close()
85
+ }
86
+ watchers.clear()
84
87
  }
@@ -4,156 +4,158 @@
4
4
  * so a delivery closes it.
5
5
  */
6
6
 
7
- import { browserFrame, createNotificationChannel, type ChannelSocket, type TopicChannel } from "./channel";
8
- import type { SessionManager, RemoteFeedbackEntry } from "./session";
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: "browser" | "wait"; sessionId: string };
10
+ export type WSData = { type: 'browser' | 'wait'; sessionId: string }
11
11
 
12
12
  export interface CanvasSocket extends ChannelSocket {
13
- data: WSData;
13
+ data: WSData
14
14
  }
15
15
 
16
16
  interface CanvasRenderError {
17
- revision: number;
18
- filename: string;
19
- message: string;
20
- stack?: string;
21
- componentStack?: string;
17
+ revision: number
18
+ filename: string
19
+ message: string
20
+ stack?: string
21
+ componentStack?: string
22
22
  }
23
23
 
24
24
  interface SubmitFrame {
25
- type: "submit";
26
- feedback: string;
25
+ type: 'submit'
26
+ feedback: string
27
27
  }
28
28
 
29
29
  interface RenderErrorFrame {
30
- type: "render-error";
31
- error: CanvasRenderError;
30
+ type: 'render-error'
31
+ error: CanvasRenderError
32
32
  }
33
33
 
34
34
  function isRecord(value: unknown): value is Record<string, unknown> {
35
- return typeof value === "object" && value !== null && !Array.isArray(value);
35
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
36
36
  }
37
37
 
38
38
  function isCanvasRenderError(value: unknown): value is CanvasRenderError {
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");
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
- return isRecord(value) && value.type === "submit" && typeof value.feedback === "string";
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
- return isRecord(value) && value.type === "render-error" && isCanvasRenderError(value.error);
52
+ return isRecord(value) && value['type'] === 'render-error' && isCanvasRenderError(value['error'])
53
53
  }
54
54
 
55
55
  export interface WebSocketManagerOptions {
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";
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
- sessionManager: SessionManager,
70
- options: WebSocketManagerOptions = {},
69
+ sessionManager: SessionManager,
70
+ options: WebSocketManagerOptions = {},
71
71
  ) {
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) => { deliverPendingRenderError(topic.topic); },
118
- frames: [
119
- browserFrame(isSubmitFrame, submit),
120
- browserFrame(isRenderErrorFrame, renderError),
121
- ],
122
- });
123
-
124
- function broadcastPlanUpdate(sessionId: string) {
125
- pendingRenderErrors.delete(sessionId);
126
- const session = sessionManager.get(sessionId);
127
- if (!session) return;
128
- channel.broadcast(sessionId, {
129
- type: "plan-updated",
130
- currentRevision: session.currentRevision,
131
- revisions: session.revisions,
132
- });
133
- }
134
-
135
- function broadcastRevisionUpdate(sessionId: string) {
136
- const session = sessionManager.get(sessionId);
137
- if (!session) return;
138
- channel.broadcast(sessionId, {
139
- type: "revision-updated",
140
- revisions: session.revisions,
141
- });
142
- }
143
-
144
- function broadcastRemoteFeedback(sessionId: string, revision: number, entries: RemoteFeedbackEntry[]) {
145
- channel.broadcast(sessionId, { type: "remote-feedback", revision, entries });
146
- }
147
-
148
- function broadcastWatcherStatus(sessionId: string) {
149
- channel.broadcastPresence(sessionId);
150
- }
151
-
152
- return {
153
- handlers: channel.handlers,
154
- broadcastPlanUpdate,
155
- broadcastRevisionUpdate,
156
- broadcastWatcherStatus,
157
- broadcastRemoteFeedback,
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
- }