@fabrika/canvas-kernel 0.9.0-dev.1 → 0.9.0-dev.2

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 (44) hide show
  1. package/package.json +18 -11
  2. package/types/index.d.ts +11 -0
  3. package/types/src/client/AnnotationProvider.d.ts +31 -0
  4. package/types/src/client/AnnotationSidebar.d.ts +12 -0
  5. package/types/src/client/FileIcon.d.ts +8 -0
  6. package/types/src/client/PlanRenderer.d.ts +7 -0
  7. package/types/src/client/RenderErrorContext.d.ts +8 -0
  8. package/types/src/client/ResponsePreview.d.ts +12 -0
  9. package/types/src/client/annotationDom.d.ts +12 -0
  10. package/types/src/client/annotationDraft.d.ts +18 -0
  11. package/types/src/client/appContext.d.ts +48 -0
  12. package/types/src/client/components/Callout.d.ts +7 -0
  13. package/types/src/client/components/Checklist.d.ts +9 -0
  14. package/types/src/client/components/Choice.d.ts +21 -0
  15. package/types/src/client/components/CodeBlock.d.ts +7 -0
  16. package/types/src/client/components/Diff.d.ts +7 -0
  17. package/types/src/client/components/FilePreview.d.ts +8 -0
  18. package/types/src/client/components/Image.d.ts +9 -0
  19. package/types/src/client/components/Markdown.d.ts +11 -0
  20. package/types/src/client/components/Mermaid.d.ts +6 -0
  21. package/types/src/client/components/Note.d.ts +6 -0
  22. package/types/src/client/components/Priority.d.ts +5 -0
  23. package/types/src/client/components/ResponseNote.d.ts +7 -0
  24. package/types/src/client/components/Section.d.ts +7 -0
  25. package/types/src/client/components/Table.d.ts +6 -0
  26. package/types/src/client/components/Task.d.ts +15 -0
  27. package/types/src/client/components/UserInput.d.ts +22 -0
  28. package/types/src/client/components/index.d.ts +16 -0
  29. package/types/src/client/components/renderMarkdownChildren.d.ts +8 -0
  30. package/types/src/client/diffLines.d.ts +7 -0
  31. package/types/src/client/generateMarkdown.d.ts +29 -0
  32. package/types/src/client/hostContext.d.ts +30 -0
  33. package/types/src/client/index.d.ts +20 -0
  34. package/types/src/client/pageGlobals.d.ts +29 -0
  35. package/types/src/client/persistenceSchemas.d.ts +1147 -0
  36. package/types/src/client/preact-all.d.ts +4 -0
  37. package/types/src/client/runtime.d.ts +62 -0
  38. package/types/src/engine/compiler.d.ts +44 -0
  39. package/types/src/engine/file-admission.d.ts +6 -0
  40. package/types/src/engine/paths.d.ts +36 -0
  41. package/types/src/engine/session.d.ts +176 -0
  42. package/types/src/engine/sessionSchemas.d.ts +2630 -0
  43. package/types/src/engine/watcher.d.ts +16 -0
  44. package/types/src/engine/websocket.d.ts +38 -0
@@ -0,0 +1,4 @@
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';
@@ -0,0 +1,62 @@
1
+ import type { AnnotationContextValue as BaseAnnotationContextValue } from '@fabrika/annotations/runtime';
2
+ export type { Annotation, AnnotationAttachment, AnnotationAuthor, AnnotationContext } from '@fabrika/annotations/runtime';
3
+ export interface PlanResponse {
4
+ id: string;
5
+ type: 'select' | 'radio' | 'checkbox' | 'text' | 'range';
6
+ label: string;
7
+ value: unknown;
8
+ options?: string[];
9
+ required?: boolean;
10
+ note?: string;
11
+ }
12
+ export interface FeedbackEntry {
13
+ id: string;
14
+ markdown: string;
15
+ label?: string;
16
+ required?: boolean;
17
+ }
18
+ export interface AnnotationContextValue extends BaseAnnotationContextValue {
19
+ /** Every answer held for this draft, including ones carried in from an
20
+ * earlier revision. Controls read and write this one. */
21
+ responses: Map<string, PlanResponse>;
22
+ setResponse: (id: string, response: PlanResponse) => void;
23
+ /** `responses` narrowed to the questions this revision actually asks.
24
+ * Everything that reports feedback — markdown, validation, "has content" —
25
+ * reads this instead, so a carried-over answer to a since-removed question
26
+ * is not submitted. Identical to `responses` until the host supplies the
27
+ * canvas file list and every one of them has rendered. */
28
+ submittableResponses: Map<string, PlanResponse>;
29
+ registerResponse: (id: string) => void;
30
+ registerCanvasRendered: (filename: string) => void;
31
+ feedbackEntries: Map<string, FeedbackEntry>;
32
+ setFeedbackEntry: (id: string, entry: FeedbackEntry) => void;
33
+ removeFeedbackEntry: (id: string) => void;
34
+ /** Canvas is rendered read-only (reviewing history). */
35
+ isReadOnly: boolean;
36
+ }
37
+ export declare const AnnotationCtx: import("react").Context<AnnotationContextValue>;
38
+ export declare function useAnnotations(): AnnotationContextValue;
39
+ /** Record that this revision asks the question, so submission can tell it from
40
+ * an answer carried in for a question that is gone. Deliberately has no
41
+ * cleanup: unmounting means the reader navigated away, not that the question
42
+ * disappeared. */
43
+ export declare function useResponseRegistration(id: string): void;
44
+ export declare function useFeedback(id: string, markdown: string, options?: {
45
+ label?: string;
46
+ required?: boolean;
47
+ }): void;
48
+ export { SessionContext } from '@fabrika/annotations/runtime';
49
+ export declare const CanvasFileCtx: import("react").Context<string>;
50
+ export declare function useCanvasFile(): string;
51
+ export type ActiveView = {
52
+ type: 'overview';
53
+ } | {
54
+ type: 'canvas';
55
+ filename: string;
56
+ } | {
57
+ type: 'file';
58
+ path: string;
59
+ };
60
+ export declare const ActiveViewCtx: import("react").Context<{
61
+ setActiveView: (v: ActiveView) => void;
62
+ }>;
@@ -0,0 +1,44 @@
1
+ import { type AssertReadable } from './file-admission';
2
+ export type CompileResult = {
3
+ ok: true;
4
+ js: string;
5
+ } | {
6
+ ok: false;
7
+ error: string;
8
+ };
9
+ type RequiredPropKind = 'array' | 'string';
10
+ /**
11
+ * One name the compiler makes available to authored JSX without an import.
12
+ *
13
+ * `requiredProps` is enforced during the render validation pass below, so a
14
+ * canvas that forgets a prop fails at push time with a precise message instead
15
+ * of rendering blank in the browser.
16
+ */
17
+ export interface CanvasComponentSpec {
18
+ requiredProps?: Record<string, RequiredPropKind>;
19
+ }
20
+ export type CanvasComponents = Record<string, CanvasComponentSpec>;
21
+ /**
22
+ * The components every canvas host provides. Order is the order they appear in
23
+ * the injected destructure, so keep it stable for readable generated code.
24
+ */
25
+ export declare const KERNEL_COMPONENTS: CanvasComponents;
26
+ export interface CompileOptions {
27
+ /** Admission for static file reads, including resolved imports. Not a JavaScript sandbox. */
28
+ assertReadable?: AssertReadable;
29
+ /** Resolves `<FilePreview>` / `<Markdown file>` references at compile time. */
30
+ projectRoot?: string;
31
+ /** Scratch dir for the temp `.jsx` handed to Bun.build. Created on demand. */
32
+ tempDir?: string;
33
+ /**
34
+ * Host components to add on top of KERNEL_COMPONENTS, keyed by name. Each one
35
+ * must also be exported from the host's `#canvas/components` barrel — that is
36
+ * what the injected import actually reads at runtime. Re-declaring a kernel
37
+ * name overrides its spec.
38
+ */
39
+ components?: CanvasComponents;
40
+ }
41
+ export declare function compileJsx(jsx: string, options?: CompileOptions): Promise<CompileResult>;
42
+ /** @deprecated Use `compileJsx(jsx, { projectRoot, tempDir })`. */
43
+ export declare function compilePlan(jsx: string, projectRoot?: string): Promise<CompileResult>;
44
+ export {};
@@ -0,0 +1,6 @@
1
+ export type AssertReadable = (path: string) => void;
2
+ export declare class CanvasFileAdmissionError extends Error {
3
+ constructor();
4
+ }
5
+ export declare function assertCanvasFileReadable(path: string, assertReadable?: AssertReadable): void;
6
+ export declare function readCanvasFile(path: string, assertReadable?: AssertReadable): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * On-disk layout for one canvas host (agent-canvas, fabrika, …).
3
+ *
4
+ * Built explicitly by the host at startup rather than resolved from module-level
5
+ * constants, so importing the kernel has no side effects and two hosts can
6
+ * coexist in one process (tests rely on this).
7
+ */
8
+ export interface CanvasPaths {
9
+ /** Persistent root — survives reboots. */
10
+ dataDir: string;
11
+ /** Session + revision store. */
12
+ sessionsDir: string;
13
+ /** Ephemeral root — may be lost on reboot. */
14
+ tempDir: string;
15
+ /** Daemon PID file. */
16
+ pidFile: string;
17
+ /** Scratch space for JSX compilation. */
18
+ compileTempDir: string;
19
+ /** Uploaded annotation images. */
20
+ uploadsDir: string;
21
+ }
22
+ export interface CanvasPathsConfig {
23
+ /** Namespace for on-disk locations, e.g. `"agent-canvas"` or `"fabrika"`. */
24
+ appName: string;
25
+ /**
26
+ * Persistent root. Defaults to `~/.claude/<appName>`. Hosts that keep data
27
+ * elsewhere — or that migrate from a legacy location — resolve it themselves
28
+ * and pass the result; the kernel holds no migration policy.
29
+ */
30
+ dataDir?: string;
31
+ /** Ephemeral root. Defaults to `<tmpdir>/<appName>`. */
32
+ tempDir?: string;
33
+ /** Create the directories. Pass `false` when only the strings are needed. */
34
+ ensure?: boolean;
35
+ }
36
+ export declare function createCanvasPaths(config: CanvasPathsConfig): CanvasPaths;
@@ -0,0 +1,176 @@
1
+ export interface DiffStats {
2
+ added: number;
3
+ removed: number;
4
+ }
5
+ export interface CanvasFileInfo {
6
+ filename: string;
7
+ diffStats?: DiffStats;
8
+ }
9
+ /** How the caller selected the canvases in a revision. New revisions always
10
+ * carry this explicitly; its absence identifies legacy metadata. */
11
+ export type CanvasScope = {
12
+ kind: 'view';
13
+ filename: string;
14
+ } | {
15
+ kind: 'directory';
16
+ };
17
+ export interface RevisionInfo {
18
+ revision: number;
19
+ label?: string;
20
+ canvasFiles: CanvasFileInfo[];
21
+ createdAt: string;
22
+ hasFeedback: boolean;
23
+ feedbackConsumed: boolean;
24
+ response?: string;
25
+ canvasScope?: CanvasScope;
26
+ }
27
+ /**
28
+ * A single piece of feedback received from a remote (shared) reviewer.
29
+ * Structured so it can be merged into the local annotation system alongside
30
+ * the author's own annotations. `annotations` is the structured list of
31
+ * highlighted-text comments; `generalNote` is the free-form markdown.
32
+ */
33
+ export interface RemoteFeedbackEntry {
34
+ /** Stable unique id assigned by the worker; used to dedupe on re-poll */
35
+ id: string;
36
+ shareId: string;
37
+ revision: number;
38
+ /** ISO timestamp when the reviewer submitted */
39
+ submittedAt: string;
40
+ author: {
41
+ id: string;
42
+ name: string;
43
+ };
44
+ annotations: RemoteAnnotation[];
45
+ generalNote?: string;
46
+ }
47
+ /**
48
+ * Mirror of the client-side Annotation shape, minus fields that only make
49
+ * sense locally. Kept intentionally narrow so the wire format is stable.
50
+ */
51
+ export interface RemoteAnnotation {
52
+ id: string;
53
+ snippet: string;
54
+ note: string;
55
+ createdAt: string;
56
+ filePath?: string;
57
+ canvasFile?: string;
58
+ context?: {
59
+ before: string;
60
+ after: string;
61
+ hierarchy: string[];
62
+ lineStart?: number;
63
+ lineEnd?: number;
64
+ };
65
+ attachments?: {
66
+ url: string;
67
+ mime?: string;
68
+ }[];
69
+ }
70
+ /**
71
+ * A share is a snapshot of a specific revision that has been pushed to a
72
+ * remote endpoint (CF Worker) for external review. The `shareId` is the
73
+ * opaque capability token returned by the worker; the `url` is the full
74
+ * public URL the user shared with reviewers.
75
+ */
76
+ export interface ShareEntry {
77
+ shareId: string;
78
+ url: string;
79
+ revision: number;
80
+ createdAt: string;
81
+ /** Bearer token returned by the worker — proves ownership for revoke. */
82
+ ownerToken?: string;
83
+ /** ISO timestamp when the share will expire on the worker side. */
84
+ expiresAt?: string;
85
+ /** ISO timestamp of the most recent remote feedback we've seen */
86
+ lastFeedbackAt?: string;
87
+ /** Base64url-encoded AES-GCM key for end-to-end encrypted shares. The
88
+ * same key is also embedded in `url` as a `#k=...` fragment so reviewers
89
+ * can decrypt; the daemon keeps a copy here so it can decrypt feedback
90
+ * it polls back from the worker. Absent on legacy unencrypted shares. */
91
+ encryptionKey?: string;
92
+ }
93
+ export interface SessionData {
94
+ id: string;
95
+ projectRoot: string;
96
+ canvasFiles: string[];
97
+ currentRevision: number;
98
+ revisions: RevisionInfo[];
99
+ createdAt: string;
100
+ updatedAt: string;
101
+ shares?: ShareEntry[];
102
+ }
103
+ export type SecretResolution = {
104
+ ok: true;
105
+ values: Map<string, string>;
106
+ } | {
107
+ ok: false;
108
+ missing: string[];
109
+ };
110
+ export declare class SessionManager {
111
+ private sessions;
112
+ private sessionSecrets;
113
+ private readonly sessionsDir;
114
+ /**
115
+ * @param sessionsDir On-disk sessions root, from `createCanvasPaths()`.
116
+ * Tests pass an isolated temp directory so they don't interfere with
117
+ * the user's real sessions.
118
+ */
119
+ constructor(sessionsDir: string);
120
+ private sessionDir;
121
+ private revisionDir;
122
+ /** Compute file order from revision history: first-appearance order, alpha for ties */
123
+ private deriveFileOrder;
124
+ /** List *.jsx filenames in a revision directory */
125
+ getRevisionCanvasFiles(id: string, rev: number): string[];
126
+ private loadFromDisk;
127
+ readRevisionJsx(id: string, rev: number, filename: string): string | null;
128
+ /**
129
+ * Create or update a session with a set of canvas files.
130
+ * @param canvasFiles Map of filename -> JSX content
131
+ */
132
+ upsert(id: string, canvasFiles: Map<string, string>, projectRoot: string, label?: string, response?: string, canvasScope?: CanvasScope): SessionData;
133
+ private persistMeta;
134
+ /** Record a new share for a session revision. */
135
+ addShare(id: string, entry: ShareEntry): void;
136
+ getShares(id: string): ShareEntry[];
137
+ removeShare(id: string, shareId: string): void;
138
+ updateShareLastFeedback(id: string, shareId: string, at: string): void;
139
+ /** List all sessions that have at least one share. Used by the remote feedback poller. */
140
+ listSessionsWithShares(): SessionData[];
141
+ private persistToDisk;
142
+ get(id: string): SessionData | undefined;
143
+ list(): SessionData[];
144
+ remove(id: string): void;
145
+ setSecret(id: string, fieldId: string, value: string): boolean;
146
+ hasSecret(id: string, fieldId: string): boolean;
147
+ clearSecret(id: string, fieldId: string): boolean;
148
+ resolveSecrets(id: string, fieldIds: string[]): SecretResolution | null;
149
+ saveCompiled(id: string, filename: string, js: string, rev?: number): void;
150
+ getCompiled(id: string, filename: string, rev?: number): string | null;
151
+ saveFeedback(id: string, rev: number, markdown: string): void;
152
+ getFeedback(id: string, rev: number): string | null;
153
+ consumeFeedback(id: string, rev: number): void;
154
+ getLatestUnconsumedFeedback(id: string): {
155
+ revision: number;
156
+ feedback: string;
157
+ } | null;
158
+ getRevisionJsxPath(id: string, rev: number, filename: string): string;
159
+ /**
160
+ * Remote feedback from shared views. Stored as one JSON file per revision
161
+ * at `revisions/{rev}/remote_feedback.json`, containing an array of
162
+ * RemoteFeedbackEntry. Appended to (not replaced) as new feedback arrives.
163
+ */
164
+ appendRemoteFeedback(id: string, rev: number, entries: RemoteFeedbackEntry[]): void;
165
+ getRemoteFeedback(id: string, rev: number): RemoteFeedbackEntry[];
166
+ /**
167
+ * Server-side backing store for an in-progress draft. Hosts that wire the
168
+ * AnnotationProvider's loadState/saveState to these endpoints get drafts that
169
+ * survive a reload and follow the author across browsers, instead of living
170
+ * in one browser's localStorage. Opaque payload — the shape is the client's.
171
+ */
172
+ saveResponses(id: string, rev: number, payload: unknown): void;
173
+ getResponses(id: string, rev: number): unknown | null;
174
+ /** Drop sessions untouched for longer than `maxAge` (default 24h). */
175
+ cleanupStale(maxAge?: number): void;
176
+ }