@fabrika/canvas-kernel 0.1.44

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/annotate.ts +94 -0
  4. package/daemon.ts +46 -0
  5. package/index.ts +84 -0
  6. package/package.json +74 -0
  7. package/src/client/AnnotationDraftFooter.tsx +76 -0
  8. package/src/client/AnnotationEditor.tsx +320 -0
  9. package/src/client/AnnotationList.tsx +190 -0
  10. package/src/client/AnnotationProvider.tsx +294 -0
  11. package/src/client/AnnotationSidebar.tsx +374 -0
  12. package/src/client/FileIcon.tsx +76 -0
  13. package/src/client/PlanRenderer.tsx +587 -0
  14. package/src/client/Popover.tsx +361 -0
  15. package/src/client/RenderErrorContext.ts +11 -0
  16. package/src/client/ResponsePreview.tsx +186 -0
  17. package/src/client/SessionContext.tsx +3 -0
  18. package/src/client/annotationContext.ts +130 -0
  19. package/src/client/annotationDom.ts +115 -0
  20. package/src/client/annotationDraft.ts +130 -0
  21. package/src/client/annotationMarkdown.ts +102 -0
  22. package/src/client/annotationTarget.ts +73 -0
  23. package/src/client/appContext.tsx +61 -0
  24. package/src/client/blockTarget.ts +127 -0
  25. package/src/client/components/Callout.tsx +27 -0
  26. package/src/client/components/Checklist.tsx +37 -0
  27. package/src/client/components/Choice.tsx +178 -0
  28. package/src/client/components/CodeBlock.tsx +35 -0
  29. package/src/client/components/Diff.tsx +36 -0
  30. package/src/client/components/FilePreview.tsx +88 -0
  31. package/src/client/components/Image.tsx +48 -0
  32. package/src/client/components/Markdown.tsx +44 -0
  33. package/src/client/components/Mermaid.tsx +576 -0
  34. package/src/client/components/Note.tsx +14 -0
  35. package/src/client/components/Priority.tsx +19 -0
  36. package/src/client/components/ResponseNote.tsx +19 -0
  37. package/src/client/components/Section.tsx +33 -0
  38. package/src/client/components/Table.tsx +35 -0
  39. package/src/client/components/Task.tsx +76 -0
  40. package/src/client/components/UserInput.tsx +176 -0
  41. package/src/client/components/index.ts +18 -0
  42. package/src/client/components/renderMarkdownChildren.tsx +100 -0
  43. package/src/client/diffLines.ts +50 -0
  44. package/src/client/generateMarkdown.ts +250 -0
  45. package/src/client/highlightRange.ts +262 -0
  46. package/src/client/hostContext.tsx +55 -0
  47. package/src/client/index.ts +128 -0
  48. package/src/client/popoverPosition.ts +46 -0
  49. package/src/client/preact-all.ts +4 -0
  50. package/src/client/regionTarget.ts +180 -0
  51. package/src/client/runtime.ts +149 -0
  52. package/src/client/styles.css +492 -0
  53. package/src/client/testing/dom.ts +58 -0
  54. package/src/client/textTarget.ts +31 -0
  55. package/src/client/theme.css +66 -0
  56. package/src/client/useRegionAnnotation.tsx +188 -0
  57. package/src/client/useTextAnnotation.tsx +194 -0
  58. package/src/client/utils.ts +33 -0
  59. package/src/engine/channel.ts +278 -0
  60. package/src/engine/compiler.ts +415 -0
  61. package/src/engine/http-utils.ts +14 -0
  62. package/src/engine/lifecycle.ts +302 -0
  63. package/src/engine/paths.ts +63 -0
  64. package/src/engine/router.ts +16 -0
  65. package/src/engine/session.ts +678 -0
  66. package/src/engine/waiter.ts +188 -0
  67. package/src/engine/watcher.ts +84 -0
  68. package/src/engine/websocket.ts +159 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Contember Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @fabrika/canvas-kernel
2
+
3
+ The reusable half of [Agent Canvas](https://github.com/contember/agent-canvas): a
4
+ host-agnostic SDK for building annotatable, agent-driven canvases.
5
+
6
+ Ships as **TypeScript sources**, not a build artifact. It is written for Bun and
7
+ consumed by bundlers, so a host typechecks the kernel under its own flags and can
8
+ read every line it depends on.
9
+
10
+ ## Rings
11
+
12
+ A host takes only the rings it needs. Each has its own entry point, and the
13
+ entries are ordered by how much they assume:
14
+
15
+ | Entry | What it holds | Assumes |
16
+ |---|---|---|
17
+ | `@fabrika/canvas-kernel/daemon` | WebSocket channel, CLI waiter, process lifecycle, router, paths | Nothing about canvases |
18
+ | `@fabrika/canvas-kernel/annotate` | Annotation state and drafts, list + draft footer, editor and popovers, locator strategies (text, block, region), `renderAnnotation` | A DOM |
19
+ | `@fabrika/canvas-kernel/server` | JSX compiler, session/revision store, watcher, the canvas WS vocabulary | A canvas |
20
+ | `@fabrika/canvas-kernel/client` | The annotation ring plus `PlanRenderer`, `AnnotationSidebar`, `generateMarkdown`, revision contexts, component library | A canvas |
21
+
22
+ Taking `/daemon` alone costs nothing at runtime — no canvas code is reachable
23
+ from it.
24
+
25
+ ## Example: a daemon that is not a canvas
26
+
27
+ ```ts
28
+ import { createNotificationChannel, browserFrame } from '@fabrika/canvas-kernel/daemon'
29
+
30
+ const channel = createNotificationChannel({
31
+ roleOf: (ws) => ws.data.role, // 'browser' | 'wait'
32
+ topicOf: (ws) => ws.data.sessionId,
33
+ waiterPolicy: 'keep-open',
34
+ frames: [
35
+ browserFrame(isSubmit, async (frame, topic) => {
36
+ topic.relayToWaiters(frame)
37
+ }),
38
+ ],
39
+ })
40
+
41
+ Bun.serve({ fetch: handler, websocket: channel.handlers })
42
+ ```
43
+
44
+ The channel knows about two socket roles keyed by an opaque topic string. The
45
+ frame vocabulary is the host's — the kernel never learns what a "submission" is.
46
+
47
+ ## Annotation locators
48
+
49
+ An annotation points at something via an `AnnotationTarget<L>`, a strategy that
50
+ owns one locator shape end to end:
51
+
52
+ ```ts
53
+ export interface AnnotationTarget<L extends object> {
54
+ readonly kind: string
55
+ parse(snippet: string): L | null
56
+ format(locator: L): string
57
+ find(locator: L, root: ParentNode): HTMLElement | null
58
+ restore?(locator: L, root: HTMLElement, ann: TargetAnnotation): void
59
+ describe?(locator: L): string
60
+ }
61
+ ```
62
+
63
+ Three ship: text ranges, document blocks, and rectangular regions of an image.
64
+ Creation and lookup read the same selector, so an annotation minted where the
65
+ lookup does not scan is not expressible.
66
+
67
+ ## Versioning
68
+
69
+ Released in lockstep with the `agent-canvas` package — same repository, same
70
+ commit, same version number. Pin exactly.
71
+
72
+ ## License
73
+
74
+ MIT
package/annotate.ts ADDED
@@ -0,0 +1,94 @@
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 ADDED
@@ -0,0 +1,46 @@
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";
package/index.ts ADDED
@@ -0,0 +1,84 @@
1
+ // @fabrika/canvas-kernel — server entry.
2
+ //
3
+ // The host-agnostic canvas engine: compile pipeline, session/revision store,
4
+ // JSX watcher, WebSocket notification channel, daemon lifecycle, CLI waiter,
5
+ // router, and the on-disk layout.
6
+ // Imports nothing from preact/DOM, so a Bun.serve daemon can pull it in without
7
+ // dragging the browser bundle along.
8
+ //
9
+ // The browser-facing surface (components + annotation runtime) is exposed
10
+ // separately through the package `imports`/`exports` maps as `#canvas/components`
11
+ // and `#canvas/runtime`.
12
+
13
+ // --- Compile pipeline (JSX -> Bun.build) -----------------------------------
14
+ export { compileJsx, compilePlan, KERNEL_COMPONENTS } from "./src/engine/compiler";
15
+ export type {
16
+ CompileResult,
17
+ CompileOptions,
18
+ CanvasComponents,
19
+ CanvasComponentSpec,
20
+ } from "./src/engine/compiler";
21
+
22
+ // --- Session / revision / feedback store -----------------------------------
23
+ export { SessionManager } from "./src/engine/session";
24
+ export type {
25
+ SessionData,
26
+ RevisionInfo,
27
+ CanvasFileInfo,
28
+ CanvasScope,
29
+ DiffStats,
30
+ SecretResolution,
31
+ RemoteFeedbackEntry,
32
+ RemoteAnnotation,
33
+ ShareEntry,
34
+ } from "./src/engine/session";
35
+
36
+ // --- WebSocket notification channel (host-agnostic) ------------------------
37
+ export { createNotificationChannel, browserFrame } from "./src/engine/channel";
38
+ export type {
39
+ ChannelRole,
40
+ ChannelSocket,
41
+ TopicChannel,
42
+ RelayOptions,
43
+ BrowserFrameHandler,
44
+ NotificationChannel,
45
+ NotificationChannelOptions,
46
+ } from "./src/engine/channel";
47
+
48
+ // --- WebSocket feedback protocol (canvas vocabulary) -----------------------
49
+ export { createWebSocketManager } from "./src/engine/websocket";
50
+ export type { WSData, CanvasSocket, WebSocketManagerOptions } from "./src/engine/websocket";
51
+
52
+ // --- CLI waiter ------------------------------------------------------------
53
+ export { waitForEvent, WaitError } from "./src/engine/waiter";
54
+ export type { WaiterSocket, WaitVerdict, WaitOptions, WaitMessages, WaitFailure } from "./src/engine/waiter";
55
+
56
+ // --- Daemon process lifecycle ----------------------------------------------
57
+ export { createDaemonLifecycle, DaemonStartError } from "./src/engine/lifecycle";
58
+ export type {
59
+ DaemonLifecycle,
60
+ DaemonLifecycleConfig,
61
+ DaemonHealth,
62
+ DaemonHealthUp,
63
+ DaemonHealthDown,
64
+ DaemonPid,
65
+ DaemonStopResult,
66
+ DaemonStopReason,
67
+ DaemonEvent,
68
+ DaemonTimeouts,
69
+ } from "./src/engine/lifecycle";
70
+
71
+ // --- JSX watcher -----------------------------------------------------------
72
+ export { watchSession, unwatchSession, unwatchAll } from "./src/engine/watcher";
73
+ export type { WatchOptions } from "./src/engine/watcher";
74
+
75
+ // --- Router ----------------------------------------------------------------
76
+ export { dispatch } from "./src/engine/router";
77
+ export type { Route, RouteHandler } from "./src/engine/router";
78
+
79
+ // --- HTTP helpers ----------------------------------------------------------
80
+ export { jsonResponse, corsHeaders } from "./src/engine/http-utils";
81
+
82
+ // --- On-disk layout --------------------------------------------------------
83
+ export { createCanvasPaths } from "./src/engine/paths";
84
+ export type { CanvasPaths, CanvasPathsConfig } from "./src/engine/paths";
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@fabrika/canvas-kernel",
3
+ "version": "0.1.44",
4
+ "type": "module",
5
+ "description": "Host-agnostic canvas SDK: a daemon runtime (WebSocket channel, CLI waiter, process lifecycle), a locator-agnostic annotation surface, and the canvas engine (JSX→Bun.build compiler, session/revision store, watcher).",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/contember/agent-canvas",
10
+ "directory": "packages/canvas-kernel"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "index.ts",
17
+ "daemon.ts",
18
+ "annotate.ts",
19
+ "src",
20
+ "!src/**/*.test.ts"
21
+ ],
22
+ "main": "./index.ts",
23
+ "types": "./index.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./index.ts",
27
+ "default": "./index.ts"
28
+ },
29
+ "./server": {
30
+ "types": "./index.ts",
31
+ "default": "./index.ts"
32
+ },
33
+ "./daemon": {
34
+ "types": "./daemon.ts",
35
+ "default": "./daemon.ts"
36
+ },
37
+ "./client": {
38
+ "types": "./src/client/index.ts",
39
+ "default": "./src/client/index.ts"
40
+ },
41
+ "./annotate": {
42
+ "types": "./annotate.ts",
43
+ "default": "./annotate.ts"
44
+ },
45
+ "./components": {
46
+ "types": "./src/client/components/index.ts",
47
+ "default": "./src/client/components/index.ts"
48
+ },
49
+ "./runtime": {
50
+ "types": "./src/client/runtime.ts",
51
+ "default": "./src/client/runtime.ts"
52
+ },
53
+ "./client/*": "./src/client/*",
54
+ "./engine/*": "./src/engine/*",
55
+ "./styles.css": "./src/client/styles.css",
56
+ "./theme.css": "./src/client/theme.css"
57
+ },
58
+ "imports": {
59
+ "#canvas/components": "./src/client/components/index.ts",
60
+ "#canvas/runtime": "./src/client/runtime.ts"
61
+ },
62
+ "dependencies": {
63
+ "marked": "^17.0.4",
64
+ "mermaid-parser-bundle": "^0.1.2",
65
+ "preact": "^10.25.0",
66
+ "preact-render-to-string": "^6.6.6"
67
+ },
68
+ "devDependencies": {
69
+ "@happy-dom/global-registrator": "^20.11.2",
70
+ "@types/react": "^18.3.12",
71
+ "@types/react-dom": "^18.3.1",
72
+ "happy-dom": "^20.11.2"
73
+ }
74
+ }
@@ -0,0 +1,76 @@
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
+ }