@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.
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/cli/bin/maude.exe +15 -0
- package/cli/bin/maude.mjs +45 -0
- package/cli/bin/mdcc.exe +10 -0
- package/cli/bin/mdcc.mjs +7 -0
- package/cli/cli-wrapper.cjs +67 -0
- package/cli/commands/config.mjs +94 -0
- package/cli/commands/design.mjs +386 -0
- package/cli/commands/help.mjs +57 -0
- package/cli/commands/init.mjs +178 -0
- package/cli/commands/version.mjs +7 -0
- package/cli/install.cjs +113 -0
- package/cli/lib/argv.mjs +37 -0
- package/cli/lib/argv.test.mjs +46 -0
- package/cli/lib/copy-tree.mjs +78 -0
- package/package.json +94 -0
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +397 -0
- package/plugins/design/dev-server/annotations-layer.tsx +1717 -0
- package/plugins/design/dev-server/api.ts +674 -0
- package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +50 -0
- package/plugins/design/dev-server/bin/bootstrap-check.sh +83 -0
- package/plugins/design/dev-server/bin/canvas-edit.sh +48 -0
- package/plugins/design/dev-server/bin/handoff.sh +27 -0
- package/plugins/design/dev-server/bin/screenshot.sh +232 -0
- package/plugins/design/dev-server/bin/server-up.sh +135 -0
- package/plugins/design/dev-server/bin/slug.sh +22 -0
- package/plugins/design/dev-server/bin/smoke.sh +272 -0
- package/plugins/design/dev-server/build.ts +267 -0
- package/plugins/design/dev-server/canvas-build.ts +219 -0
- package/plugins/design/dev-server/canvas-edit.ts +388 -0
- package/plugins/design/dev-server/canvas-header.ts +165 -0
- package/plugins/design/dev-server/canvas-icons.tsx +131 -0
- package/plugins/design/dev-server/canvas-lib-inline.ts +260 -0
- package/plugins/design/dev-server/canvas-lib-resolver.ts +85 -0
- package/plugins/design/dev-server/canvas-lib.tsx +1995 -0
- package/plugins/design/dev-server/canvas-meta.schema.json +181 -0
- package/plugins/design/dev-server/canvas-pipeline.ts +270 -0
- package/plugins/design/dev-server/canvas-shell.tsx +813 -0
- package/plugins/design/dev-server/client/app.jsx +2027 -0
- package/plugins/design/dev-server/client/hmr.mjs +85 -0
- package/plugins/design/dev-server/client/iframe-lazy.mjs +121 -0
- package/plugins/design/dev-server/client/index.html +15 -0
- package/plugins/design/dev-server/client/styles/0-reset.css +18 -0
- package/plugins/design/dev-server/client/styles/1-tokens.css +297 -0
- package/plugins/design/dev-server/client/styles/2-layout.css +35 -0
- package/plugins/design/dev-server/client/styles/3-shell.css +906 -0
- package/plugins/design/dev-server/client/styles/4-components.css +1268 -0
- package/plugins/design/dev-server/client/styles/5-utilities.css +4 -0
- package/plugins/design/dev-server/client/styles/_index.css +24 -0
- package/plugins/design/dev-server/client/styles.css +1419 -0
- package/plugins/design/dev-server/config.schema.json +147 -0
- package/plugins/design/dev-server/context-menu.tsx +343 -0
- package/plugins/design/dev-server/context.ts +173 -0
- package/plugins/design/dev-server/dist/client.bundle.js +20323 -0
- package/plugins/design/dev-server/dist/styles.css +2875 -0
- package/plugins/design/dev-server/examples/README.md +9 -0
- package/plugins/design/dev-server/examples/perf-100-artboards.tsx +113 -0
- package/plugins/design/dev-server/fs-watch.ts +63 -0
- package/plugins/design/dev-server/handoff.ts +721 -0
- package/plugins/design/dev-server/history.ts +125 -0
- package/plugins/design/dev-server/hmr-broadcast.ts +114 -0
- package/plugins/design/dev-server/http.ts +413 -0
- package/plugins/design/dev-server/input-router.tsx +485 -0
- package/plugins/design/dev-server/inspect.ts +365 -0
- package/plugins/design/dev-server/locator.ts +159 -0
- package/plugins/design/dev-server/mem.ts +97 -0
- package/plugins/design/dev-server/runtime-bundle.ts +235 -0
- package/plugins/design/dev-server/server.mjs +1246 -0
- package/plugins/design/dev-server/server.ts +131 -0
- package/plugins/design/dev-server/test/_helpers.ts +81 -0
- package/plugins/design/dev-server/test/active-state.test.ts +145 -0
- package/plugins/design/dev-server/test/annotations-api.test.ts +146 -0
- package/plugins/design/dev-server/test/annotations-layer.test.ts +419 -0
- package/plugins/design/dev-server/test/binary-smoke.test.ts +47 -0
- package/plugins/design/dev-server/test/bundle-smoke.test.ts +29 -0
- package/plugins/design/dev-server/test/canvas-build.test.ts +78 -0
- package/plugins/design/dev-server/test/canvas-edit.test.ts +139 -0
- package/plugins/design/dev-server/test/canvas-header.test.ts +127 -0
- package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +146 -0
- package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +112 -0
- package/plugins/design/dev-server/test/canvas-meta-api.test.ts +236 -0
- package/plugins/design/dev-server/test/canvas-pipeline.test.ts +180 -0
- package/plugins/design/dev-server/test/canvas-route.test.ts +176 -0
- package/plugins/design/dev-server/test/fs-watch.test.ts +41 -0
- package/plugins/design/dev-server/test/handoff-static-frames.test.ts +215 -0
- package/plugins/design/dev-server/test/handoff.test.ts +281 -0
- package/plugins/design/dev-server/test/history-rollback.test.ts +62 -0
- package/plugins/design/dev-server/test/hmr-broadcast.test.ts +108 -0
- package/plugins/design/dev-server/test/input-router.test.ts +316 -0
- package/plugins/design/dev-server/test/locator.test.ts +214 -0
- package/plugins/design/dev-server/test/perf-harness.ts +193 -0
- package/plugins/design/dev-server/test/phase-3.6-smoke.test.ts +77 -0
- package/plugins/design/dev-server/test/runtime-bundle.test.ts +69 -0
- package/plugins/design/dev-server/test/server-lifecycle.test.ts +28 -0
- package/plugins/design/dev-server/test/tool-palette.test.tsx +55 -0
- package/plugins/design/dev-server/test/use-annotation-selection.test.tsx +77 -0
- package/plugins/design/dev-server/test/use-artboard-drag.test.ts +325 -0
- package/plugins/design/dev-server/test/use-selection-set.test.tsx +166 -0
- package/plugins/design/dev-server/test/use-snap-guides.test.ts +190 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +93 -0
- package/plugins/design/dev-server/test/ws-handshake.test.ts +33 -0
- package/plugins/design/dev-server/tool-palette.tsx +278 -0
- package/plugins/design/dev-server/tsconfig.json +26 -0
- package/plugins/design/dev-server/use-annotation-selection.tsx +92 -0
- package/plugins/design/dev-server/use-annotations-visibility.tsx +43 -0
- package/plugins/design/dev-server/use-artboard-drag.tsx +445 -0
- package/plugins/design/dev-server/use-selection-set.tsx +224 -0
- package/plugins/design/dev-server/use-snap-guides.tsx +215 -0
- package/plugins/design/dev-server/use-tool-mode.tsx +114 -0
- package/plugins/design/dev-server/ws.ts +90 -0
- package/plugins/design/templates/_shell.html +177 -0
- package/plugins/design/templates/canvas.tsx.template +54 -0
- package/plugins/design/templates/design-system-inspiration/_MAPPING.md +277 -0
- package/plugins/design/templates/design-system-inspiration/_README.md +71 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-banner.html +68 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-empty-state-generous.html +39 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-feature-grid.html +62 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-marketing-card.html +63 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-testimonial.html +80 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-code-block.html +71 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-diff-view.html +65 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-log-stream.html +62 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-monospace-table.html +57 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-terminal-pane.html +67 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/type-mono.html +57 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +74 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-command-palette.html +90 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-keyboard.html +51 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-list.html +89 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-shortcuts-overlay.html +85 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-toast-menu.html +80 -0
- package/plugins/design/templates/design-system-inspiration/core/INDEX.md.tpl +25 -0
- package/plugins/design/templates/design-system-inspiration/core/README.orchestration.md.tpl +71 -0
- package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +77 -0
- package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +50 -0
- package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +111 -0
- package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +28 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/_layout.css +113 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +48 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-surfaces.html +49 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-text.html +52 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-buttons.html +83 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-cards.html +79 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-inputs.html +82 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/motion.html +66 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/spacing-scale.html +53 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/type-scale.html +62 -0
- package/plugins/design/templates/design-system-inspiration/foundations/borders.html +39 -0
- package/plugins/design/templates/design-system-inspiration/foundations/elevation.html +46 -0
- package/plugins/design/templates/design-system-inspiration/foundations/focus.html +48 -0
- package/plugins/design/templates/design-system-inspiration/foundations/grid.html +51 -0
- package/plugins/design/templates/design-system-inspiration/foundations/iconography.html +73 -0
- package/plugins/design/templates/design-system-inspiration/foundations/opacity.html +45 -0
- package/plugins/design/templates/design-system-inspiration/foundations/radii.html +40 -0
- package/plugins/design/templates/design-system-inspiration/foundations/selection.html +45 -0
- package/plugins/design/templates/design-system-inspiration/meta/accessibility.html +62 -0
- package/plugins/design/templates/design-system-inspiration/meta/i18n.html +73 -0
- package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +71 -0
- package/plugins/design/templates/design-system-inspiration/meta/tokens-index.html +80 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-auth.html +78 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-data-density.html +61 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-error-pages.html +70 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-form-layouts.html +70 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-onboarding.html +71 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-pricing.html +83 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/components-resize-panels.html +63 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-index.html +55 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +302 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-bottom-sheet.html +63 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-pull-to-refresh.html +74 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-segmented-control.html +51 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-tab-bar.html +57 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-index.html +58 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +237 -0
- package/plugins/design/templates/design-system-inspiration/status/colors-status.html +49 -0
- package/plugins/design/templates/design-system-inspiration/status/components-status.html +63 -0
- package/plugins/design/templates/design-system-inspiration/status/skeletons.html +74 -0
- package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +59 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-callout.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +81 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-tables.html +101 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-toggles.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-tooltips.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/empty-state.html.tpl +34 -0
- package/plugins/design/templates/design-system-inspiration/universal/logo.html +42 -0
- package/plugins/design/templates/ds-specimen.tsx.template +59 -0
- package/plugins/flow/.claude-plugin/config.schema.json +398 -0
- package/plugins/flow/README.md +45 -0
- package/plugins/flow/templates/ai-skeleton/INDEX.md +50 -0
- package/plugins/flow/templates/ai-skeleton/README.md +46 -0
- package/plugins/flow/templates/ai-skeleton/browser/har/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/browser/snapshots/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/business/README.md +12 -0
- package/plugins/flow/templates/ai-skeleton/context/README.md +12 -0
- package/plugins/flow/templates/ai-skeleton/decisions/README.md +20 -0
- package/plugins/flow/templates/ai-skeleton/design-import/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/dev-logs/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/device/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/docs/README.md +5 -0
- package/plugins/flow/templates/ai-skeleton/logs/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/logs/README.md +13 -0
- package/plugins/flow/templates/ai-skeleton/plans/README.md +16 -0
- package/plugins/flow/templates/ai-skeleton/plans/archive/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/release-guide.md +35 -0
- package/plugins/flow/templates/ai-skeleton/reviews/README.md +15 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +26 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/_lib/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/state/STATE.md +25 -0
- package/plugins/flow/templates/ai-skeleton/templates/HANDOFF.md +32 -0
- 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
|
+
}
|