@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,128 +1,46 @@
1
- // @fabrika/canvas-kernel/client — the reusable annotation surface.
2
- //
3
- // Everything a host needs to render a compiled canvas and collect feedback on
4
- // it, minus the app shell (navigation, file browser, revision chrome), which
5
- // each host builds itself.
6
-
7
- // --- Annotation state ------------------------------------------------------
8
- // Everything that lives in runtime.ts is re-exported through `#canvas/runtime`,
9
- // never through a relative path. The runtime holds the React contexts and ships
10
- // as its own bundle shared with the compiled canvas; importing it relatively
11
- // would bundle a second copy, and the provider and its consumers would then be
12
- // writing to and reading from different contexts.
13
- export { AnnotationProvider } from "./AnnotationProvider";
14
- export type { PersistedState } from "./AnnotationProvider";
1
+ export { AnnotationProvider } from './AnnotationProvider'
2
+ export type { PersistedState } from './AnnotationProvider'
3
+ // Canvas documents and the shell must read the same external context instance.
15
4
  export {
16
- useAnnotations,
17
- useFeedback,
18
- useCanvasFile,
19
- useResponseRegistration,
20
- AnnotationCtx,
21
- SessionContext,
22
- ActiveViewCtx,
23
- CanvasFileCtx,
24
- } from "#canvas/runtime";
5
+ ActiveViewCtx,
6
+ AnnotationCtx,
7
+ CanvasFileCtx,
8
+ SessionContext,
9
+ useAnnotations,
10
+ useCanvasFile,
11
+ useFeedback,
12
+ useResponseRegistration,
13
+ } from '#canvas/runtime'
25
14
  export type {
26
- Annotation,
27
- AnnotationAttachment,
28
- AnnotationAuthor,
29
- AnnotationContext,
30
- AnnotationContextValue,
31
- ActiveView,
32
- FeedbackEntry,
33
- PlanResponse,
34
- } from "#canvas/runtime";
35
-
36
- // --- Surface ---------------------------------------------------------------
37
- export { AnnotationSidebar } from "./AnnotationSidebar";
38
- // The sidebar's reusable halves. Grouping stayed with the sidebar — what an
39
- // annotation belongs to is the host's taxonomy, so the host slices the list.
40
- export { AnnotationList } from "./AnnotationList";
41
- export type { AnnotationListProps } from "./AnnotationList";
42
- export { AnnotationDraftFooter } from "./AnnotationDraftFooter";
43
- export type { AnnotationDraftFooterProps } from "./AnnotationDraftFooter";
44
- export { AnnotationEditor, imageToUrl } from "./AnnotationEditor";
45
- export { PlanRenderer } from "./PlanRenderer";
46
- export { Popover, AnnotationPopover, AnnotationCreatePopover, AnnotationEditPopover } from "./Popover";
47
- export { ResponsePreview, MarkdownPreview } from "./ResponsePreview";
48
- export { FileIcon } from "./FileIcon";
49
-
50
- /** The kernel component set, as a namespace for hosts that inject it wholesale. */
51
- export * as components from "./components";
52
- export { useTextAnnotation } from "./useTextAnnotation";
53
- export { useRegionAnnotation } from "./useRegionAnnotation";
54
-
55
- // --- Host-provided context -------------------------------------------------
56
- export { CanvasHostContext, localCanvasHost, useCanvasHost } from "./hostContext";
57
- export type { CanvasHost } from "./hostContext";
58
- export { ActiveViewContext, RevisionContext } from "./appContext";
59
- export type { CanvasFileInfo, RevisionInfo } from "./appContext";
60
- export { RenderErrorContext } from "./RenderErrorContext";
61
- export type { CanvasRenderError } from "./RenderErrorContext";
62
-
63
- // --- Annotation plumbing ---------------------------------------------------
64
- export { extractContext, formatSnippetInContext } from "./annotationContext";
15
+ ActiveView,
16
+ Annotation,
17
+ AnnotationAttachment,
18
+ AnnotationAuthor,
19
+ AnnotationContext,
20
+ AnnotationContextValue,
21
+ FeedbackEntry,
22
+ PlanResponse,
23
+ } from '#canvas/runtime'
24
+ export { findAnnotationElement, scrollToAnnotation } from './annotationDom'
25
+ export { annotationDraftKey, carryUnsubmittedDraft, clearPersistedDraft } from './annotationDraft'
26
+ export type { AnnotationDraftPhase } from './annotationDraft'
27
+ export { AnnotationSidebar } from './AnnotationSidebar'
28
+ export { ActiveViewContext, RevisionContext } from './appContext'
29
+ export type { CanvasFileInfo, RevisionInfo } from './appContext'
30
+ export * as components from './components'
31
+ export { FileIcon } from './FileIcon'
65
32
  export {
66
- ANNOTATION_TARGETS,
67
- ANNOTATABLE_SELECTOR,
68
- BLOCK_SELECTOR,
69
- describeSnippet,
70
- findAnnotationElement,
71
- findSnippetElement,
72
- getBlockSnippet,
73
- restoreAnnotationTargets,
74
- scrollToAnnotation,
75
- } from "./annotationDom";
76
-
77
- // --- Locator strategies ----------------------------------------------------
78
- // What a snippet points at is the one thing in an annotation that is domain
79
- // specific. A host that needs to annotate something the kernel has no target
80
- // for writes one of these; nothing else has to change.
81
- export { sealTarget } from "./annotationTarget";
82
- export type { AnnotationTarget, SealedAnnotationTarget, TargetAnnotation } from "./annotationTarget";
83
- export { blockTarget } from "./blockTarget";
84
- export type { BlockLocator } from "./blockTarget";
85
- export { textTarget } from "./textTarget";
86
- export type { TextLocator } from "./textTarget";
87
- export {
88
- createRegionOverlay,
89
- describeRegion,
90
- findRegionHost,
91
- isDrawableRegion,
92
- regionBetween,
93
- regionPointIn,
94
- regionStyle,
95
- regionTarget,
96
- REGION_HOST_ATTR,
97
- REGION_UNITS,
98
- } from "./regionTarget";
99
- export type { RegionBox, RegionLocator, RegionPoint, RegionShape } from "./regionTarget";
100
- export {
101
- annotationDraftKey,
102
- carryUnsubmittedDraft,
103
- clearPersistedDraft,
104
- } from "./annotationDraft";
105
- export type { AnnotationDraftPhase } from "./annotationDraft";
106
- export {
107
- restoreMarks,
108
- renameMarkId,
109
- setMarkActive,
110
- unwrapMarks,
111
- updateAllMarkStates,
112
- wrapRangeWithMark,
113
- } from "./highlightRange";
114
- // One annotation as markdown — the half of feedback that belongs to no
115
- // particular document format. `generateMarkdown` below is the canvas format
116
- // built on it; a host with its own headings calls this directly.
117
- export { renderAnnotation } from "./annotationMarkdown";
118
- export type { RenderableAnnotation } from "./annotationMarkdown";
119
- export {
120
- canPruneResponses,
121
- generateMarkdown,
122
- getMissingRequired,
123
- getMissingRequiredFeedback,
124
- getMissingRequiredLabels,
125
- hasValue,
126
- pruneStaleResponses,
127
- } from "./generateMarkdown";
128
- export { autoResizeTextarea, fileAnnotationPath, generateAnnotationId, hostAcceptsUploads, RESPONSE_ANNOTATION_PATH } from "./utils";
33
+ canPruneResponses,
34
+ generateMarkdown,
35
+ getMissingRequired,
36
+ getMissingRequiredFeedback,
37
+ getMissingRequiredLabels,
38
+ hasValue,
39
+ pruneStaleResponses,
40
+ } from './generateMarkdown'
41
+ export { CanvasHostContext, localCanvasHost, useCanvasHost } from './hostContext'
42
+ export type { CanvasHost } from './hostContext'
43
+ export { PlanRenderer } from './PlanRenderer'
44
+ export { RenderErrorContext } from './RenderErrorContext'
45
+ export type { CanvasRenderError } from './RenderErrorContext'
46
+ export { MarkdownPreview, ResponsePreview } from './ResponsePreview'
@@ -1,4 +1,4 @@
1
- export * from "preact/compat";
2
- export { default } from "preact/compat";
3
- export { jsx, jsxs } from "preact/jsx-runtime";
4
- export { createRoot, hydrateRoot } from "preact/compat/client";
1
+ export * from 'preact/compat'
2
+ export { default } from 'preact/compat'
3
+ export { createRoot, hydrateRoot } from 'preact/compat/client'
4
+ export { jsx, jsxs } from 'preact/jsx-runtime'
@@ -2,105 +2,52 @@
2
2
  // The React.createContext call MUST live here (not in AnnotationProvider)
3
3
  // so that both bundles reference the same context object.
4
4
 
5
- import { createContext, useContext, useEffect } from "react";
5
+ import { createContext, useContext, useEffect } from 'react'
6
6
 
7
- export interface AnnotationContext {
8
- before: string;
9
- after: string;
10
- hierarchy: string[];
11
- lineStart?: number;
12
- lineEnd?: number;
13
- }
14
-
15
- /** Identifies the person who created an annotation. For local annotations
16
- * this is the daemon owner (unset). For remote annotations it carries the
17
- * reviewer's self-reported name + stable anonymous id assigned by the worker. */
18
- export interface AnnotationAuthor {
19
- id: string;
20
- name: string;
21
- }
22
-
23
- /** An image or other file attached to an annotation. The richer sibling of
24
- * `images: string[]`, carrying a mime type alongside a URL that can point at
25
- * either a local upload (daemon) or a remote blob (CF Worker R2). */
26
- export interface AnnotationAttachment {
27
- url: string;
28
- mime?: string;
29
- }
30
-
31
- export interface Annotation {
32
- id: string;
33
- snippet: string;
34
- note: string;
35
- createdAt: string;
36
- filePath?: string;
37
- canvasFile?: string;
38
- context?: AnnotationContext;
39
- /** Attachments written by the local editor, as bare upload paths/URLs. Still
40
- * the only field it writes, and what persisted drafts hold. */
41
- images?: string[];
42
- /** Attachments that arrived over the wire, from a reviewer on a shared
43
- * canvas. Both fields render — see `renderAnnotation`. */
44
- attachments?: AnnotationAttachment[];
45
- /** "remote" annotations come from a shared view and are read-only locally. */
46
- source?: "local" | "remote";
47
- /** Only set for source === "remote". */
48
- author?: AnnotationAuthor;
49
- }
7
+ import type { AnnotationContextValue as BaseAnnotationContextValue } from '@fabrika/annotations/runtime'
8
+ export type { Annotation, AnnotationAttachment, AnnotationAuthor, AnnotationContext } from '@fabrika/annotations/runtime'
50
9
 
51
10
  export interface PlanResponse {
52
- id: string;
53
- type: "select" | "radio" | "checkbox" | "text" | "range";
54
- label: string;
55
- value: any;
56
- options?: string[];
57
- required?: boolean;
58
- note?: string;
11
+ id: string
12
+ type: 'select' | 'radio' | 'checkbox' | 'text' | 'range'
13
+ label: string
14
+ value: any
15
+ options?: string[]
16
+ required?: boolean
17
+ note?: string
59
18
  }
60
19
 
61
20
  export interface FeedbackEntry {
62
- id: string;
63
- markdown: string;
64
- label?: string;
65
- required?: boolean;
21
+ id: string
22
+ markdown: string
23
+ label?: string
24
+ required?: boolean
66
25
  }
67
26
 
68
- export interface AnnotationContextValue {
69
- annotations: Annotation[];
70
- addAnnotation: (snippet: string, note: string, filePath?: string) => void;
71
- addAnnotationWithId: (id: string, snippet: string, note: string, filePath?: string, context?: AnnotationContext, images?: string[], canvasFile?: string) => void;
72
- updateAnnotation: (id: string, note: string) => void;
73
- removeAnnotation: (id: string) => void;
74
- addAnnotationImage: (id: string, imagePath: string) => void;
75
- removeAnnotationImage: (id: string, imagePath: string) => void;
76
- generalNote: string;
77
- setGeneralNote: (text: string) => void;
78
- clearAll: () => void;
79
- activeAnnotationId: string | null;
80
- setActiveAnnotationId: (id: string | null) => void;
81
- /** Every answer held for this draft, including ones carried in from an
82
- * earlier revision. Controls read and write this one. */
83
- responses: Map<string, PlanResponse>;
84
- setResponse: (id: string, response: PlanResponse) => void;
85
- /** `responses` narrowed to the questions this revision actually asks.
86
- * Everything that reports feedback — markdown, validation, "has content" —
87
- * reads this instead, so a carried-over answer to a since-removed question
88
- * is not submitted. Identical to `responses` until the host supplies the
89
- * canvas file list and every one of them has rendered. */
90
- submittableResponses: Map<string, PlanResponse>;
91
- registerResponse: (id: string) => void;
92
- registerCanvasRendered: (filename: string) => void;
93
- feedbackEntries: Map<string, FeedbackEntry>;
94
- setFeedbackEntry: (id: string, entry: FeedbackEntry) => void;
95
- removeFeedbackEntry: (id: string) => void;
96
- /** Canvas is rendered read-only (reviewing history). */
97
- isReadOnly: boolean;
27
+ export interface AnnotationContextValue extends BaseAnnotationContextValue {
28
+ /** Every answer held for this draft, including ones carried in from an
29
+ * earlier revision. Controls read and write this one. */
30
+ responses: Map<string, PlanResponse>
31
+ setResponse: (id: string, response: PlanResponse) => void
32
+ /** `responses` narrowed to the questions this revision actually asks.
33
+ * Everything that reports feedback — markdown, validation, "has content" —
34
+ * reads this instead, so a carried-over answer to a since-removed question
35
+ * is not submitted. Identical to `responses` until the host supplies the
36
+ * canvas file list and every one of them has rendered. */
37
+ submittableResponses: Map<string, PlanResponse>
38
+ registerResponse: (id: string) => void
39
+ registerCanvasRendered: (filename: string) => void
40
+ feedbackEntries: Map<string, FeedbackEntry>
41
+ setFeedbackEntry: (id: string, entry: FeedbackEntry) => void
42
+ removeFeedbackEntry: (id: string) => void
43
+ /** Canvas is rendered read-only (reviewing history). */
44
+ isReadOnly: boolean
98
45
  }
99
46
 
100
- export const AnnotationCtx = createContext<AnnotationContextValue>(null!);
47
+ export const AnnotationCtx = createContext<AnnotationContextValue>(null!)
101
48
 
102
49
  export function useAnnotations(): AnnotationContextValue {
103
- return useContext(AnnotationCtx);
50
+ return useContext(AnnotationCtx)
104
51
  }
105
52
 
106
53
  /** Record that this revision asks the question, so submission can tell it from
@@ -108,42 +55,42 @@ export function useAnnotations(): AnnotationContextValue {
108
55
  * cleanup: unmounting means the reader navigated away, not that the question
109
56
  * disappeared. */
110
57
  export function useResponseRegistration(id: string): void {
111
- const { registerResponse } = useAnnotations();
112
- useEffect(() => {
113
- registerResponse(id);
114
- }, [id, registerResponse]);
58
+ const { registerResponse } = useAnnotations()
59
+ useEffect(() => {
60
+ registerResponse(id)
61
+ }, [id, registerResponse])
115
62
  }
116
63
 
117
64
  export function useFeedback(
118
- id: string,
119
- markdown: string,
120
- options?: { label?: string; required?: boolean },
65
+ id: string,
66
+ markdown: string,
67
+ options?: { label?: string; required?: boolean },
121
68
  ): void {
122
- const { setFeedbackEntry, removeFeedbackEntry } = useAnnotations();
123
- useEffect(() => {
124
- setFeedbackEntry(id, {
125
- id,
126
- markdown,
127
- label: options?.label,
128
- required: options?.required,
129
- });
130
- return () => removeFeedbackEntry(id);
131
- }, [id, markdown, options?.label, options?.required]);
69
+ const { setFeedbackEntry, removeFeedbackEntry } = useAnnotations()
70
+ useEffect(() => {
71
+ setFeedbackEntry(id, {
72
+ id,
73
+ markdown,
74
+ label: options?.label,
75
+ required: options?.required,
76
+ })
77
+ return () => removeFeedbackEntry(id)
78
+ }, [id, markdown, options?.label, options?.required])
132
79
  }
133
80
 
134
- export { SessionContext } from "./SessionContext";
81
+ export { SessionContext } from '@fabrika/annotations/runtime'
135
82
 
136
83
  // CanvasFile context — tells components which canvas file they belong to
137
- export const CanvasFileCtx = createContext<string>("");
84
+ export const CanvasFileCtx = createContext<string>('')
138
85
  export function useCanvasFile(): string {
139
- return useContext(CanvasFileCtx);
86
+ return useContext(CanvasFileCtx)
140
87
  }
141
88
 
142
89
  // ActiveView navigation — allows components (e.g. FilePreview) to open files
143
- export type ActiveView = { type: "overview" } | { type: "canvas"; filename: string } | { type: "file"; path: string };
90
+ export type ActiveView = { type: 'overview' } | { type: 'canvas'; filename: string } | { type: 'file'; path: string }
144
91
 
145
92
  export const ActiveViewCtx = createContext<{
146
- setActiveView: (v: ActiveView) => void;
93
+ setActiveView: (v: ActiveView) => void
147
94
  }>({
148
- setActiveView: () => {},
149
- });
95
+ setActiveView: () => {},
96
+ })
@@ -2,6 +2,7 @@
2
2
 
3
3
  @import "tailwindcss";
4
4
  @import "./theme.css";
5
+ @import "@fabrika/annotations/styles.css";
5
6
  @source "./**/*.{tsx,ts,jsx,js}";
6
7
 
7
8
  /* ── Non-theme CSS variables (not used by Tailwind utilities, just custom CSS) ── */
@@ -96,80 +97,6 @@ code, pre, .font-mono {
96
97
  font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
97
98
  }
98
99
 
99
- /* Annotation inline marks */
100
- mark[data-annotation-id] {
101
- background: var(--color-highlight-annotation);
102
- border-bottom: 1.5px solid var(--color-highlight-bg);
103
- border-radius: 2px;
104
- cursor: pointer;
105
- transition: background 150ms ease, box-shadow 150ms ease;
106
- color: inherit;
107
- padding: 1px 0;
108
- }
109
-
110
- mark[data-annotation-id].mark-active {
111
- background: var(--color-highlight-active);
112
- border-bottom: 2px solid var(--color-highlight-border);
113
- box-shadow: 0 0 0 1px var(--color-highlight-border);
114
- }
115
-
116
- @keyframes ann-flash {
117
- 0%, 100% { background: var(--color-highlight-annotation); }
118
- 25%, 75% { background: var(--color-highlight-active); }
119
- }
120
-
121
- .ann-flash {
122
- animation: ann-flash 0.6s ease 2;
123
- }
124
-
125
- /* Region annotations — a normalized box drawn over an image block. The host
126
- positions the boxes, so every host that marks one gets them placed right. */
127
- [data-annotation-image] {
128
- position: relative;
129
- cursor: crosshair;
130
- touch-action: none;
131
- }
132
-
133
- .ann-region {
134
- position: absolute;
135
- background: var(--color-highlight-annotation);
136
- border: 2px solid var(--color-highlight-bg);
137
- border-radius: 2px;
138
- cursor: pointer;
139
- transition: background 150ms ease, box-shadow 150ms ease;
140
- }
141
-
142
- .ann-region[data-annotation-region="ellipse"] {
143
- border-radius: 50%;
144
- }
145
-
146
- .ann-region.mark-active {
147
- background: var(--color-highlight-active);
148
- border-color: var(--color-highlight-border);
149
- box-shadow: 0 0 0 1px var(--color-highlight-border);
150
- }
151
-
152
- /* The box being dragged out: not yet an annotation, and never a hit target. */
153
- .ann-region-draft {
154
- pointer-events: none;
155
- border-style: dashed;
156
- border-color: var(--color-highlight-border);
157
- }
158
-
159
- /* Annotation highlight (legacy) */
160
- .annotation-highlight {
161
- background: var(--color-highlight-bg);
162
- border-bottom: 2px solid var(--color-highlight-border);
163
- border-radius: 2px;
164
- cursor: pointer;
165
- transition: background 150ms ease;
166
- }
167
-
168
- .annotation-highlight:hover,
169
- .annotation-highlight.active {
170
- background: var(--color-highlight-active);
171
- }
172
-
173
100
  /* Selection color */
174
101
  ::selection {
175
102
  background-color: var(--color-highlight-active);