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