@1agh/maude 0.15.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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/cli/bin/maude.exe +15 -0
  4. package/cli/bin/maude.mjs +45 -0
  5. package/cli/bin/mdcc.exe +10 -0
  6. package/cli/bin/mdcc.mjs +7 -0
  7. package/cli/cli-wrapper.cjs +67 -0
  8. package/cli/commands/config.mjs +94 -0
  9. package/cli/commands/design.mjs +386 -0
  10. package/cli/commands/help.mjs +57 -0
  11. package/cli/commands/init.mjs +178 -0
  12. package/cli/commands/version.mjs +7 -0
  13. package/cli/install.cjs +113 -0
  14. package/cli/lib/argv.mjs +37 -0
  15. package/cli/lib/argv.test.mjs +46 -0
  16. package/cli/lib/copy-tree.mjs +78 -0
  17. package/package.json +94 -0
  18. package/plugins/design/dev-server/annotations-context-toolbar.tsx +397 -0
  19. package/plugins/design/dev-server/annotations-layer.tsx +1717 -0
  20. package/plugins/design/dev-server/api.ts +674 -0
  21. package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +50 -0
  22. package/plugins/design/dev-server/bin/bootstrap-check.sh +83 -0
  23. package/plugins/design/dev-server/bin/canvas-edit.sh +48 -0
  24. package/plugins/design/dev-server/bin/handoff.sh +27 -0
  25. package/plugins/design/dev-server/bin/screenshot.sh +232 -0
  26. package/plugins/design/dev-server/bin/server-up.sh +135 -0
  27. package/plugins/design/dev-server/bin/slug.sh +22 -0
  28. package/plugins/design/dev-server/bin/smoke.sh +272 -0
  29. package/plugins/design/dev-server/build.ts +267 -0
  30. package/plugins/design/dev-server/canvas-build.ts +219 -0
  31. package/plugins/design/dev-server/canvas-edit.ts +388 -0
  32. package/plugins/design/dev-server/canvas-header.ts +165 -0
  33. package/plugins/design/dev-server/canvas-icons.tsx +131 -0
  34. package/plugins/design/dev-server/canvas-lib-inline.ts +260 -0
  35. package/plugins/design/dev-server/canvas-lib-resolver.ts +85 -0
  36. package/plugins/design/dev-server/canvas-lib.tsx +1995 -0
  37. package/plugins/design/dev-server/canvas-meta.schema.json +181 -0
  38. package/plugins/design/dev-server/canvas-pipeline.ts +270 -0
  39. package/plugins/design/dev-server/canvas-shell.tsx +813 -0
  40. package/plugins/design/dev-server/client/app.jsx +2027 -0
  41. package/plugins/design/dev-server/client/hmr.mjs +85 -0
  42. package/plugins/design/dev-server/client/iframe-lazy.mjs +121 -0
  43. package/plugins/design/dev-server/client/index.html +15 -0
  44. package/plugins/design/dev-server/client/styles/0-reset.css +18 -0
  45. package/plugins/design/dev-server/client/styles/1-tokens.css +297 -0
  46. package/plugins/design/dev-server/client/styles/2-layout.css +35 -0
  47. package/plugins/design/dev-server/client/styles/3-shell.css +906 -0
  48. package/plugins/design/dev-server/client/styles/4-components.css +1268 -0
  49. package/plugins/design/dev-server/client/styles/5-utilities.css +4 -0
  50. package/plugins/design/dev-server/client/styles/_index.css +24 -0
  51. package/plugins/design/dev-server/client/styles.css +1419 -0
  52. package/plugins/design/dev-server/config.schema.json +147 -0
  53. package/plugins/design/dev-server/context-menu.tsx +343 -0
  54. package/plugins/design/dev-server/context.ts +173 -0
  55. package/plugins/design/dev-server/dist/client.bundle.js +20323 -0
  56. package/plugins/design/dev-server/dist/styles.css +2875 -0
  57. package/plugins/design/dev-server/examples/README.md +9 -0
  58. package/plugins/design/dev-server/examples/perf-100-artboards.tsx +113 -0
  59. package/plugins/design/dev-server/fs-watch.ts +63 -0
  60. package/plugins/design/dev-server/handoff.ts +721 -0
  61. package/plugins/design/dev-server/history.ts +125 -0
  62. package/plugins/design/dev-server/hmr-broadcast.ts +114 -0
  63. package/plugins/design/dev-server/http.ts +413 -0
  64. package/plugins/design/dev-server/input-router.tsx +485 -0
  65. package/plugins/design/dev-server/inspect.ts +365 -0
  66. package/plugins/design/dev-server/locator.ts +159 -0
  67. package/plugins/design/dev-server/mem.ts +97 -0
  68. package/plugins/design/dev-server/runtime-bundle.ts +235 -0
  69. package/plugins/design/dev-server/server.mjs +1246 -0
  70. package/plugins/design/dev-server/server.ts +131 -0
  71. package/plugins/design/dev-server/test/_helpers.ts +81 -0
  72. package/plugins/design/dev-server/test/active-state.test.ts +145 -0
  73. package/plugins/design/dev-server/test/annotations-api.test.ts +146 -0
  74. package/plugins/design/dev-server/test/annotations-layer.test.ts +419 -0
  75. package/plugins/design/dev-server/test/binary-smoke.test.ts +47 -0
  76. package/plugins/design/dev-server/test/bundle-smoke.test.ts +29 -0
  77. package/plugins/design/dev-server/test/canvas-build.test.ts +78 -0
  78. package/plugins/design/dev-server/test/canvas-edit.test.ts +139 -0
  79. package/plugins/design/dev-server/test/canvas-header.test.ts +127 -0
  80. package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +146 -0
  81. package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +112 -0
  82. package/plugins/design/dev-server/test/canvas-meta-api.test.ts +236 -0
  83. package/plugins/design/dev-server/test/canvas-pipeline.test.ts +180 -0
  84. package/plugins/design/dev-server/test/canvas-route.test.ts +176 -0
  85. package/plugins/design/dev-server/test/fs-watch.test.ts +41 -0
  86. package/plugins/design/dev-server/test/handoff-static-frames.test.ts +215 -0
  87. package/plugins/design/dev-server/test/handoff.test.ts +281 -0
  88. package/plugins/design/dev-server/test/history-rollback.test.ts +62 -0
  89. package/plugins/design/dev-server/test/hmr-broadcast.test.ts +108 -0
  90. package/plugins/design/dev-server/test/input-router.test.ts +316 -0
  91. package/plugins/design/dev-server/test/locator.test.ts +214 -0
  92. package/plugins/design/dev-server/test/perf-harness.ts +193 -0
  93. package/plugins/design/dev-server/test/phase-3.6-smoke.test.ts +77 -0
  94. package/plugins/design/dev-server/test/runtime-bundle.test.ts +69 -0
  95. package/plugins/design/dev-server/test/server-lifecycle.test.ts +28 -0
  96. package/plugins/design/dev-server/test/tool-palette.test.tsx +55 -0
  97. package/plugins/design/dev-server/test/use-annotation-selection.test.tsx +77 -0
  98. package/plugins/design/dev-server/test/use-artboard-drag.test.ts +325 -0
  99. package/plugins/design/dev-server/test/use-selection-set.test.tsx +166 -0
  100. package/plugins/design/dev-server/test/use-snap-guides.test.ts +190 -0
  101. package/plugins/design/dev-server/test/use-tool-mode.test.tsx +93 -0
  102. package/plugins/design/dev-server/test/ws-handshake.test.ts +33 -0
  103. package/plugins/design/dev-server/tool-palette.tsx +278 -0
  104. package/plugins/design/dev-server/tsconfig.json +26 -0
  105. package/plugins/design/dev-server/use-annotation-selection.tsx +92 -0
  106. package/plugins/design/dev-server/use-annotations-visibility.tsx +43 -0
  107. package/plugins/design/dev-server/use-artboard-drag.tsx +445 -0
  108. package/plugins/design/dev-server/use-selection-set.tsx +224 -0
  109. package/plugins/design/dev-server/use-snap-guides.tsx +215 -0
  110. package/plugins/design/dev-server/use-tool-mode.tsx +114 -0
  111. package/plugins/design/dev-server/ws.ts +90 -0
  112. package/plugins/design/templates/_shell.html +177 -0
  113. package/plugins/design/templates/canvas.tsx.template +54 -0
  114. package/plugins/design/templates/design-system-inspiration/_MAPPING.md +277 -0
  115. package/plugins/design/templates/design-system-inspiration/_README.md +71 -0
  116. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-banner.html +68 -0
  117. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-empty-state-generous.html +39 -0
  118. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-feature-grid.html +62 -0
  119. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-marketing-card.html +63 -0
  120. package/plugins/design/templates/design-system-inspiration/audience-consumer/components-testimonial.html +80 -0
  121. package/plugins/design/templates/design-system-inspiration/audience-developer/components-code-block.html +71 -0
  122. package/plugins/design/templates/design-system-inspiration/audience-developer/components-diff-view.html +65 -0
  123. package/plugins/design/templates/design-system-inspiration/audience-developer/components-log-stream.html +62 -0
  124. package/plugins/design/templates/design-system-inspiration/audience-developer/components-monospace-table.html +57 -0
  125. package/plugins/design/templates/design-system-inspiration/audience-developer/components-terminal-pane.html +67 -0
  126. package/plugins/design/templates/design-system-inspiration/audience-developer/type-mono.html +57 -0
  127. package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +74 -0
  128. package/plugins/design/templates/design-system-inspiration/audience-pro/components-command-palette.html +90 -0
  129. package/plugins/design/templates/design-system-inspiration/audience-pro/components-keyboard.html +51 -0
  130. package/plugins/design/templates/design-system-inspiration/audience-pro/components-list.html +89 -0
  131. package/plugins/design/templates/design-system-inspiration/audience-pro/components-shortcuts-overlay.html +85 -0
  132. package/plugins/design/templates/design-system-inspiration/audience-pro/components-toast-menu.html +80 -0
  133. package/plugins/design/templates/design-system-inspiration/core/INDEX.md.tpl +25 -0
  134. package/plugins/design/templates/design-system-inspiration/core/README.orchestration.md.tpl +71 -0
  135. package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +77 -0
  136. package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +50 -0
  137. package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +111 -0
  138. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +28 -0
  139. package/plugins/design/templates/design-system-inspiration/core/preview/_layout.css +113 -0
  140. package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +48 -0
  141. package/plugins/design/templates/design-system-inspiration/core/preview/colors-surfaces.html +49 -0
  142. package/plugins/design/templates/design-system-inspiration/core/preview/colors-text.html +52 -0
  143. package/plugins/design/templates/design-system-inspiration/core/preview/components-buttons.html +83 -0
  144. package/plugins/design/templates/design-system-inspiration/core/preview/components-cards.html +79 -0
  145. package/plugins/design/templates/design-system-inspiration/core/preview/components-inputs.html +82 -0
  146. package/plugins/design/templates/design-system-inspiration/core/preview/motion.html +66 -0
  147. package/plugins/design/templates/design-system-inspiration/core/preview/spacing-scale.html +53 -0
  148. package/plugins/design/templates/design-system-inspiration/core/preview/type-scale.html +62 -0
  149. package/plugins/design/templates/design-system-inspiration/foundations/borders.html +39 -0
  150. package/plugins/design/templates/design-system-inspiration/foundations/elevation.html +46 -0
  151. package/plugins/design/templates/design-system-inspiration/foundations/focus.html +48 -0
  152. package/plugins/design/templates/design-system-inspiration/foundations/grid.html +51 -0
  153. package/plugins/design/templates/design-system-inspiration/foundations/iconography.html +73 -0
  154. package/plugins/design/templates/design-system-inspiration/foundations/opacity.html +45 -0
  155. package/plugins/design/templates/design-system-inspiration/foundations/radii.html +40 -0
  156. package/plugins/design/templates/design-system-inspiration/foundations/selection.html +45 -0
  157. package/plugins/design/templates/design-system-inspiration/meta/accessibility.html +62 -0
  158. package/plugins/design/templates/design-system-inspiration/meta/i18n.html +73 -0
  159. package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +71 -0
  160. package/plugins/design/templates/design-system-inspiration/meta/tokens-index.html +80 -0
  161. package/plugins/design/templates/design-system-inspiration/patterns/patterns-auth.html +78 -0
  162. package/plugins/design/templates/design-system-inspiration/patterns/patterns-data-density.html +61 -0
  163. package/plugins/design/templates/design-system-inspiration/patterns/patterns-error-pages.html +70 -0
  164. package/plugins/design/templates/design-system-inspiration/patterns/patterns-form-layouts.html +70 -0
  165. package/plugins/design/templates/design-system-inspiration/patterns/patterns-onboarding.html +71 -0
  166. package/plugins/design/templates/design-system-inspiration/patterns/patterns-pricing.html +83 -0
  167. package/plugins/design/templates/design-system-inspiration/platform-desktop/components-resize-panels.html +63 -0
  168. package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-index.html +55 -0
  169. package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +302 -0
  170. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-bottom-sheet.html +63 -0
  171. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-pull-to-refresh.html +74 -0
  172. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-segmented-control.html +51 -0
  173. package/plugins/design/templates/design-system-inspiration/platform-mobile/components-tab-bar.html +57 -0
  174. package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-index.html +58 -0
  175. package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +237 -0
  176. package/plugins/design/templates/design-system-inspiration/status/colors-status.html +49 -0
  177. package/plugins/design/templates/design-system-inspiration/status/components-status.html +63 -0
  178. package/plugins/design/templates/design-system-inspiration/status/skeletons.html +74 -0
  179. package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +59 -0
  180. package/plugins/design/templates/design-system-inspiration/universal/components-callout.html +74 -0
  181. package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +81 -0
  182. package/plugins/design/templates/design-system-inspiration/universal/components-tables.html +101 -0
  183. package/plugins/design/templates/design-system-inspiration/universal/components-toggles.html +74 -0
  184. package/plugins/design/templates/design-system-inspiration/universal/components-tooltips.html +74 -0
  185. package/plugins/design/templates/design-system-inspiration/universal/empty-state.html.tpl +34 -0
  186. package/plugins/design/templates/design-system-inspiration/universal/logo.html +42 -0
  187. package/plugins/design/templates/ds-specimen.tsx.template +59 -0
  188. package/plugins/flow/.claude-plugin/config.schema.json +398 -0
  189. package/plugins/flow/README.md +45 -0
  190. package/plugins/flow/templates/ai-skeleton/INDEX.md +50 -0
  191. package/plugins/flow/templates/ai-skeleton/README.md +46 -0
  192. package/plugins/flow/templates/ai-skeleton/browser/har/.gitkeep +0 -0
  193. package/plugins/flow/templates/ai-skeleton/browser/snapshots/.gitkeep +0 -0
  194. package/plugins/flow/templates/ai-skeleton/business/README.md +12 -0
  195. package/plugins/flow/templates/ai-skeleton/context/README.md +12 -0
  196. package/plugins/flow/templates/ai-skeleton/decisions/README.md +20 -0
  197. package/plugins/flow/templates/ai-skeleton/design-import/.gitkeep +0 -0
  198. package/plugins/flow/templates/ai-skeleton/dev-logs/.gitkeep +0 -0
  199. package/plugins/flow/templates/ai-skeleton/device/.gitkeep +0 -0
  200. package/plugins/flow/templates/ai-skeleton/docs/README.md +5 -0
  201. package/plugins/flow/templates/ai-skeleton/logs/.gitkeep +0 -0
  202. package/plugins/flow/templates/ai-skeleton/logs/README.md +13 -0
  203. package/plugins/flow/templates/ai-skeleton/plans/README.md +16 -0
  204. package/plugins/flow/templates/ai-skeleton/plans/archive/.gitkeep +0 -0
  205. package/plugins/flow/templates/ai-skeleton/release-guide.md +35 -0
  206. package/plugins/flow/templates/ai-skeleton/reviews/README.md +15 -0
  207. package/plugins/flow/templates/ai-skeleton/scenarios/README.md +26 -0
  208. package/plugins/flow/templates/ai-skeleton/scenarios/_lib/.gitkeep +0 -0
  209. package/plugins/flow/templates/ai-skeleton/state/STATE.md +25 -0
  210. package/plugins/flow/templates/ai-skeleton/templates/HANDOFF.md +32 -0
  211. package/plugins/flow/templates/ai-skeleton/workflows.config.json +74 -0
@@ -0,0 +1,93 @@
1
+ // use-tool-mode — Phase 4.1 Task 2. Provider transitions + cursor sync.
2
+
3
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
4
+
5
+ import { renderToStaticMarkup } from 'react-dom/server';
6
+
7
+ import {
8
+ DEFAULT_TOOLS,
9
+ ToolProvider,
10
+ useToolMode,
11
+ useToolModeOptional,
12
+ } from '../use-tool-mode.tsx';
13
+
14
+ describe('use-tool-mode / static', () => {
15
+ test('DEFAULT_TOOLS exposes V/H/C + Phase 5.1 draw set B/R/O/A/E', () => {
16
+ expect(DEFAULT_TOOLS.map((t) => t.id)).toEqual([
17
+ 'move',
18
+ 'hand',
19
+ 'comment',
20
+ 'pen',
21
+ 'rect',
22
+ 'ellipse',
23
+ 'arrow',
24
+ 'eraser',
25
+ ]);
26
+ expect(DEFAULT_TOOLS.map((t) => t.shortcut)).toEqual(['V', 'H', 'C', 'B', 'R', 'O', 'A', 'E']);
27
+ });
28
+
29
+ test('DEFAULT_TOOLS is immutable (Object.freeze applied)', () => {
30
+ expect(Object.isFrozen(DEFAULT_TOOLS)).toBe(true);
31
+ });
32
+
33
+ test('default cursors per tool', () => {
34
+ const byId = Object.fromEntries(DEFAULT_TOOLS.map((t) => [t.id, t.cursor]));
35
+ expect(byId.move).toBe('default');
36
+ expect(byId.hand).toBe('grab');
37
+ expect(byId.comment).toBe('crosshair');
38
+ expect(byId.pen).toBe('crosshair');
39
+ expect(byId.rect).toBe('crosshair');
40
+ expect(byId.ellipse).toBe('crosshair');
41
+ expect(byId.arrow).toBe('crosshair');
42
+ expect(byId.eraser).toBe('cell');
43
+ });
44
+ });
45
+
46
+ describe('use-tool-mode / useToolModeOptional', () => {
47
+ test('returns null outside provider (SSR-safe path)', () => {
48
+ // useToolModeOptional uses useContext directly; outside a provider it
49
+ // returns the context's default value, which we set to null.
50
+ // We can't render hooks standalone without a renderer, but importing the
51
+ // module and calling useContext directly mirrors the runtime behavior.
52
+ // This documents the contract via the export shape.
53
+ expect(typeof useToolModeOptional).toBe('function');
54
+ });
55
+ });
56
+
57
+ describe('use-tool-mode / SSR render', () => {
58
+ test('ToolProvider with consumer renders without throwing', () => {
59
+ function Consumer() {
60
+ const { tool } = useToolMode();
61
+ return <span data-tool={tool}>{tool}</span>;
62
+ }
63
+ const html = renderToStaticMarkup(
64
+ <ToolProvider>
65
+ <Consumer />
66
+ </ToolProvider>
67
+ );
68
+ expect(html).toContain('data-tool="move"');
69
+ });
70
+
71
+ test('ToolProvider honors initial tool', () => {
72
+ function Consumer() {
73
+ const { tool } = useToolMode();
74
+ return <span>{tool}</span>;
75
+ }
76
+ const html = renderToStaticMarkup(
77
+ <ToolProvider initial="comment">
78
+ <Consumer />
79
+ </ToolProvider>
80
+ );
81
+ expect(html).toContain('comment');
82
+ });
83
+
84
+ test('useToolMode outside ToolProvider throws', () => {
85
+ function BareConsumer() {
86
+ useToolMode();
87
+ return null;
88
+ }
89
+ expect(() => renderToStaticMarkup(<BareConsumer />)).toThrow(
90
+ /useToolMode must be used inside <ToolProvider>/
91
+ );
92
+ });
93
+ });
@@ -0,0 +1,33 @@
1
+ // Smoke: WebSocket upgrade against Bun.serve's native handler.
2
+
3
+ import { describe, expect, test } from 'bun:test';
4
+
5
+ import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
6
+
7
+ describe('ws handshake', () => {
8
+ test('upgrades and receives initial snapshot', async () => {
9
+ const { root } = makeSandbox();
10
+ const port = nextPort();
11
+ const proc = await bootServer(root, port);
12
+ try {
13
+ const ws = new WebSocket(`ws://localhost:${port}/_ws`);
14
+ const first = await new Promise<string>((resolve, reject) => {
15
+ const t = setTimeout(() => reject(new Error('no message')), 2000);
16
+ ws.addEventListener('message', (e) => {
17
+ clearTimeout(t);
18
+ resolve(
19
+ typeof e.data === 'string' ? e.data : new TextDecoder().decode(e.data as ArrayBuffer)
20
+ );
21
+ });
22
+ ws.addEventListener('error', (e) => reject(e));
23
+ });
24
+ const parsed = JSON.parse(first);
25
+ expect(parsed.type).toBe('snapshot');
26
+ expect(parsed.state).toBeDefined();
27
+ expect(parsed.state.session_started).toBeTypeOf('string');
28
+ ws.close();
29
+ } finally {
30
+ await killProc(proc);
31
+ }
32
+ });
33
+ });
@@ -0,0 +1,278 @@
1
+ /**
2
+ * @file tool-palette.tsx — Phase 5.1 centered icon toolbar
3
+ * @scope plugins/design/dev-server/tool-palette.tsx
4
+ * @purpose Bottom-center floating chrome — navigate (V/H/C), draw
5
+ * (B/R/O/A/E), and zoom in one shell. Adopts the dev-server
6
+ * menubar's visual language (8 px radius, soft shadow, hairline
7
+ * border) so canvas chrome and app chrome read as one product.
8
+ *
9
+ * The zoom popover lazy-mounts on demand and groups Fit / Reset
10
+ * / Zoom In / Zoom Out from `useViewportControllerContext`. The
11
+ * presentation toggle posts upstream so the dev-server's
12
+ * `view-annotations` state stays in sync.
13
+ */
14
+
15
+ import { useEffect, useRef, useState } from 'react';
16
+
17
+ import { IconChevronDown, IconPresentation, TOOL_ICONS } from './canvas-icons.tsx';
18
+ import { useViewportControllerContext } from './canvas-lib.tsx';
19
+ import { useAnnotationsVisibility } from './use-annotations-visibility.tsx';
20
+ import { useToolMode } from './use-tool-mode.tsx';
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Styles — pulled from menubar tokens (`.mb` family). Centered bottom toolbar,
24
+ // 32 × 32 buttons with iconography. Grouped pill segments separated by 1 px
25
+ // dividers — visual cue that nav / draw / view-controls are distinct kinds.
26
+
27
+ const PALETTE_CSS = `
28
+ .dc-tool-palette {
29
+ position: absolute;
30
+ left: 50%;
31
+ bottom: 16px;
32
+ transform: translateX(-50%);
33
+ display: flex;
34
+ align-items: stretch;
35
+ background: var(--bg-1, rgba(255,255,255,0.98));
36
+ border: 1px solid var(--u-border-2, rgba(0,0,0,0.08));
37
+ border-radius: 8px;
38
+ box-shadow: 0 6px 24px rgba(0,0,0,0.08);
39
+ font-family: ui-sans-serif, system-ui, sans-serif;
40
+ font-size: 12px;
41
+ color: var(--fg-0, #1a1a1a);
42
+ z-index: 6;
43
+ user-select: none;
44
+ /* Intentionally NO overflow:hidden — the zoom popover (.dc-tp-popover) is
45
+ a position:absolute child anchored above the toolbar; clipping the
46
+ parent would hide it. Inner buttons have their own border-radius so the
47
+ rounded outer corners are preserved by content shape, not overflow. */
48
+ }
49
+ .dc-tool-palette .dc-tp-group {
50
+ display: flex;
51
+ align-items: stretch;
52
+ padding: 4px;
53
+ gap: 2px;
54
+ }
55
+ .dc-tool-palette .dc-tp-sep {
56
+ width: 1px;
57
+ background: var(--u-border-3, rgba(0,0,0,0.08));
58
+ margin: 6px 0;
59
+ }
60
+ .dc-tool-palette button {
61
+ appearance: none;
62
+ background: transparent;
63
+ border: 0;
64
+ border-radius: 6px;
65
+ padding: 0;
66
+ width: 32px;
67
+ height: 32px;
68
+ display: inline-flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ color: var(--fg-1, rgba(40,30,20,0.75));
72
+ cursor: pointer;
73
+ transition: background-color 80ms linear, color 80ms linear;
74
+ }
75
+ .dc-tool-palette button:hover { background: rgba(0,0,0,0.04); color: var(--fg-0, #1a1a1a); }
76
+ .dc-tool-palette button:focus-visible {
77
+ outline: 2px solid var(--accent, #d63b1f);
78
+ outline-offset: -2px;
79
+ }
80
+ .dc-tool-palette button[aria-pressed="true"] {
81
+ background: var(--accent, #d63b1f);
82
+ color: var(--accent-fg, #fff);
83
+ }
84
+ .dc-tool-palette .dc-tp-zoom {
85
+ min-width: 56px;
86
+ padding: 0 8px;
87
+ font-variant-numeric: tabular-nums;
88
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
89
+ font-size: 11px;
90
+ letter-spacing: 0.04em;
91
+ border-radius: 6px;
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: 4px;
95
+ }
96
+ .dc-tool-palette .dc-tp-zoom svg { opacity: 0.6; }
97
+ .dc-tp-popover {
98
+ position: absolute;
99
+ right: 4px;
100
+ bottom: 44px;
101
+ background: var(--bg-1, rgba(255,255,255,0.98));
102
+ border: 1px solid var(--u-border-2, rgba(0,0,0,0.08));
103
+ border-radius: 8px;
104
+ box-shadow: 0 6px 24px rgba(0,0,0,0.08);
105
+ display: flex;
106
+ flex-direction: column;
107
+ padding: 4px;
108
+ min-width: 160px;
109
+ z-index: 7;
110
+ }
111
+ .dc-tp-popover button {
112
+ appearance: none;
113
+ background: transparent;
114
+ border: 0;
115
+ border-radius: 6px;
116
+ padding: 6px 10px;
117
+ text-align: left;
118
+ font: inherit;
119
+ color: inherit;
120
+ cursor: pointer;
121
+ display: flex;
122
+ justify-content: space-between;
123
+ gap: 16px;
124
+ font-family: ui-sans-serif, system-ui, sans-serif;
125
+ width: 100%;
126
+ }
127
+ .dc-tp-popover button:hover { background: rgba(0,0,0,0.04); }
128
+ .dc-tp-popover .dc-tp-kbd {
129
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
130
+ font-size: 11px;
131
+ opacity: 0.55;
132
+ }
133
+ `.trim();
134
+
135
+ function ensurePaletteStyles(): void {
136
+ if (typeof document === 'undefined') return;
137
+ if (document.getElementById('dc-tool-palette-css')) return;
138
+ const s = document.createElement('style');
139
+ s.id = 'dc-tool-palette-css';
140
+ s.textContent = PALETTE_CSS;
141
+ document.head.appendChild(s);
142
+ }
143
+
144
+ const NAV_TOOLS = ['move', 'hand', 'comment'] as const;
145
+ const DRAW_TOOLS = ['pen', 'rect', 'ellipse', 'arrow', 'eraser'] as const;
146
+
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+ // Component
149
+
150
+ export function ToolPalette() {
151
+ ensurePaletteStyles();
152
+ const { tool, setTool, tools } = useToolMode();
153
+ const controller = useViewportControllerContext();
154
+ const visibilityCtx = useAnnotationsVisibility();
155
+ const [mounted, setMounted] = useState(false);
156
+ const [zoomOpen, setZoomOpen] = useState(false);
157
+ const containerRef = useRef<HTMLDivElement | null>(null);
158
+
159
+ useEffect(() => setMounted(true), []);
160
+ useEffect(() => {
161
+ if (!zoomOpen) return;
162
+ const onDown = (e: PointerEvent) => {
163
+ if (!containerRef.current?.contains(e.target as Node)) setZoomOpen(false);
164
+ };
165
+ document.addEventListener('pointerdown', onDown, true);
166
+ return () => document.removeEventListener('pointerdown', onDown, true);
167
+ }, [zoomOpen]);
168
+
169
+ if (!mounted) return null;
170
+
171
+ const byId = new Map(tools.map((t) => [t.id, t]));
172
+ const navList = NAV_TOOLS.map((id) => byId.get(id)).filter(Boolean);
173
+ const drawList = DRAW_TOOLS.map((id) => byId.get(id)).filter(Boolean);
174
+ const pct = controller ? Math.round(controller.viewport.zoom * 100) : 100;
175
+ const annotationsHidden = visibilityCtx ? !visibilityCtx.visible : false;
176
+
177
+ const renderToolButton = (id: string, label: string, shortcut: string) => {
178
+ const Icon = TOOL_ICONS[id];
179
+ return (
180
+ <button
181
+ key={id}
182
+ type="button"
183
+ aria-label={`${label} (${shortcut})`}
184
+ aria-pressed={tool === id}
185
+ title={`${label} (${shortcut})`}
186
+ onClick={() => setTool(id as never)}
187
+ >
188
+ {Icon ? <Icon /> : <span style={{ fontSize: 11 }}>{shortcut}</span>}
189
+ </button>
190
+ );
191
+ };
192
+
193
+ return (
194
+ <div ref={containerRef} className="dc-tool-palette" role="toolbar" aria-label="Canvas tools">
195
+ <div className="dc-tp-group">
196
+ {navList.map((t) => (t ? renderToolButton(t.id, t.label, t.shortcut) : null))}
197
+ </div>
198
+ <div className="dc-tp-sep" />
199
+ <div className="dc-tp-group">
200
+ {drawList.map((t) => (t ? renderToolButton(t.id, t.label, t.shortcut) : null))}
201
+ </div>
202
+ <div className="dc-tp-sep" />
203
+ <div className="dc-tp-group">
204
+ <button
205
+ type="button"
206
+ aria-label={
207
+ annotationsHidden ? 'Show annotations (Shift+P)' : 'Hide annotations (Shift+P)'
208
+ }
209
+ aria-pressed={annotationsHidden}
210
+ title="Presentation (Shift+P)"
211
+ onClick={() => visibilityCtx?.setVisible(!visibilityCtx.visible)}
212
+ >
213
+ <IconPresentation />
214
+ </button>
215
+ <button
216
+ type="button"
217
+ className="dc-tp-zoom"
218
+ aria-label={`Zoom ${pct}%, open zoom menu`}
219
+ aria-expanded={zoomOpen}
220
+ title={`Zoom ${pct}%`}
221
+ onClick={() => setZoomOpen((o) => !o)}
222
+ >
223
+ <span>{pct}%</span>
224
+ <IconChevronDown size={12} />
225
+ </button>
226
+ </div>
227
+ {zoomOpen && controller ? (
228
+ <div className="dc-tp-popover" role="menu" aria-label="Zoom">
229
+ <button
230
+ type="button"
231
+ role="menuitem"
232
+ onClick={() => {
233
+ controller.zoomIn();
234
+ setZoomOpen(false);
235
+ }}
236
+ >
237
+ <span>Zoom In</span>
238
+ <span className="dc-tp-kbd">⌘ +</span>
239
+ </button>
240
+ <button
241
+ type="button"
242
+ role="menuitem"
243
+ onClick={() => {
244
+ controller.zoomOut();
245
+ setZoomOpen(false);
246
+ }}
247
+ >
248
+ <span>Zoom Out</span>
249
+ <span className="dc-tp-kbd">⌘ −</span>
250
+ </button>
251
+ <button
252
+ type="button"
253
+ role="menuitem"
254
+ onClick={() => {
255
+ controller.fit();
256
+ setZoomOpen(false);
257
+ }}
258
+ >
259
+ <span>Fit to Screen</span>
260
+ <span className="dc-tp-kbd">⌘ 0</span>
261
+ </button>
262
+ <button
263
+ type="button"
264
+ role="menuitem"
265
+ onClick={() => {
266
+ controller.reset();
267
+ setZoomOpen(false);
268
+ }}
269
+ >
270
+ <span>Actual Size 100%</span>
271
+ <span className="dc-tp-kbd">⌘ 1</span>
272
+ </button>
273
+ </div>
274
+ ) : null}
275
+ </div>
276
+ );
277
+ }
278
+ ToolPalette.displayName = 'ToolPalette';
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "types": ["bun-types"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
8
+ "jsx": "react-jsx",
9
+ "jsxImportSource": "react",
10
+ "strict": true,
11
+ "noUncheckedIndexedAccess": false,
12
+ "noImplicitOverride": true,
13
+ "useDefineForClassFields": true,
14
+ "resolveJsonModule": true,
15
+ "esModuleInterop": true,
16
+ "isolatedModules": true,
17
+ "verbatimModuleSyntax": false,
18
+ "skipLibCheck": true,
19
+ "noEmit": true,
20
+ "allowJs": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "allowImportingTsExtensions": true
23
+ },
24
+ "include": ["*.ts", "client/**/*.ts", "client/**/*.tsx", "client/**/*.jsx", "test/**/*.ts"],
25
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
26
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @file use-annotation-selection.tsx — Phase 5.1 annotation selection store
3
+ * @scope plugins/design/dev-server/use-annotation-selection.tsx
4
+ * @purpose Parallel to `use-selection-set`, but holds annotation stroke IDs
5
+ * instead of DOM-element selections. Annotation IDs are local
6
+ * identifiers (`s_*`) generated by `rid()` — they never live in
7
+ * `_active.json` and don't post upstream to the dev-server shell.
8
+ *
9
+ * In-memory only. Scope is per canvas mount: when the iframe reloads, the
10
+ * selection resets (the strokes themselves persist via `.annotations.svg`).
11
+ */
12
+
13
+ import { type ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
14
+
15
+ export interface AnnotationSelectionValue {
16
+ selectedIds: string[];
17
+ replace: (id: string | string[]) => void;
18
+ add: (id: string | string[]) => void;
19
+ toggle: (id: string) => void;
20
+ clear: () => void;
21
+ contains: (id: string) => boolean;
22
+ }
23
+
24
+ const AnnotationSelectionContext = createContext<AnnotationSelectionValue | null>(null);
25
+
26
+ function dedupe(ids: string[]): string[] {
27
+ const out: string[] = [];
28
+ const seen = new Set<string>();
29
+ for (const id of ids) {
30
+ if (seen.has(id)) continue;
31
+ seen.add(id);
32
+ out.push(id);
33
+ }
34
+ return out;
35
+ }
36
+
37
+ export function AnnotationSelectionProvider({
38
+ children,
39
+ }: {
40
+ children: ReactNode;
41
+ }) {
42
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
43
+
44
+ const replace = useCallback((id: string | string[]) => {
45
+ const next = dedupe(Array.isArray(id) ? id : [id]);
46
+ setSelectedIds(next);
47
+ }, []);
48
+
49
+ const add = useCallback((id: string | string[]) => {
50
+ const incoming = Array.isArray(id) ? id : [id];
51
+ setSelectedIds((prev) => dedupe([...prev, ...incoming]));
52
+ }, []);
53
+
54
+ const toggle = useCallback((id: string) => {
55
+ setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
56
+ }, []);
57
+
58
+ const clear = useCallback(() => {
59
+ setSelectedIds([]);
60
+ }, []);
61
+
62
+ // Read live from state at call time so multi-step flows in the same tick
63
+ // (e.g. a router callback that adds then asks `contains`) see the staged
64
+ // result instead of a stale closure capture.
65
+ const containsRef = { current: selectedIds };
66
+ containsRef.current = selectedIds;
67
+ // biome-ignore lint/correctness/useExhaustiveDependencies: ref read intentionally
68
+ const contains = useCallback((id: string) => containsRef.current.includes(id), []);
69
+
70
+ const value = useMemo<AnnotationSelectionValue>(
71
+ () => ({ selectedIds, replace, add, toggle, clear, contains }),
72
+ [selectedIds, replace, add, toggle, clear, contains]
73
+ );
74
+
75
+ return (
76
+ <AnnotationSelectionContext.Provider value={value}>
77
+ {children}
78
+ </AnnotationSelectionContext.Provider>
79
+ );
80
+ }
81
+
82
+ export function useAnnotationSelection(): AnnotationSelectionValue {
83
+ const ctx = useContext(AnnotationSelectionContext);
84
+ if (!ctx) {
85
+ throw new Error('useAnnotationSelection must be used inside <AnnotationSelectionProvider>');
86
+ }
87
+ return ctx;
88
+ }
89
+
90
+ export function useAnnotationSelectionOptional(): AnnotationSelectionValue | null {
91
+ return useContext(AnnotationSelectionContext);
92
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @file use-annotations-visibility.tsx — Phase 5.1 visibility store
3
+ * @scope plugins/design/dev-server/use-annotations-visibility.tsx
4
+ * @purpose Tiny boolean store shared by AnnotationsLayer (render gate),
5
+ * ToolPalette (presentation toggle button), and the menubar
6
+ * bridge (`view-annotations` postMessage handler in
7
+ * AnnotationsLayer). Mounted by CanvasShell so the layer + its
8
+ * siblings under CanvasRouter all consume the same value.
9
+ */
10
+
11
+ import { type ReactNode, createContext, useCallback, useContext, useMemo, useState } from 'react';
12
+
13
+ export interface AnnotationsVisibilityValue {
14
+ visible: boolean;
15
+ setVisible: (v: boolean) => void;
16
+ toggle: () => void;
17
+ }
18
+
19
+ const AnnotationsVisibilityContext = createContext<AnnotationsVisibilityValue | null>(null);
20
+
21
+ export function AnnotationsVisibilityProvider({
22
+ children,
23
+ initial = true,
24
+ }: {
25
+ children: ReactNode;
26
+ initial?: boolean;
27
+ }) {
28
+ const [visible, setVisible] = useState<boolean>(initial);
29
+ const toggle = useCallback(() => setVisible((v) => !v), []);
30
+ const value = useMemo<AnnotationsVisibilityValue>(
31
+ () => ({ visible, setVisible, toggle }),
32
+ [visible, toggle]
33
+ );
34
+ return (
35
+ <AnnotationsVisibilityContext.Provider value={value}>
36
+ {children}
37
+ </AnnotationsVisibilityContext.Provider>
38
+ );
39
+ }
40
+
41
+ export function useAnnotationsVisibility(): AnnotationsVisibilityValue | null {
42
+ return useContext(AnnotationsVisibilityContext);
43
+ }