drafted 1.7.16 → 1.7.18

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/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.7.16",
3
+ "version": "1.7.18",
4
4
  "description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "cli/",
8
8
  "mcp/",
9
9
  "src/shared/",
10
+ "agent-instructions/",
10
11
  "install-mcp.sh"
11
12
  ],
12
13
  "bin": {
@@ -15,7 +16,7 @@
15
16
  },
16
17
  "scripts": {
17
18
  "dev": "./dev.sh",
18
- "dev:server": "tsx watch server/server.mjs",
19
+ "dev:server": "tsx watch --import ./server/instrument.mjs server/server.mjs",
19
20
  "dev:local": "node server/server-local.mjs",
20
21
  "install-global": "npm install -g .",
21
22
  "db:migrate": "psql $DATABASE_URL -f drizzle/migrate.sql",
@@ -28,7 +29,9 @@
28
29
  "test:watch": "vitest",
29
30
  "version": "node scripts/sync-versions.mjs && git add plugin/.claude-plugin/plugin.json .claude-plugin/marketplace.json",
30
31
  "version:check": "node scripts/sync-versions.mjs",
31
- "postpublish": "bash scripts/sync-plugin.sh \"chore: sync plugin to v$npm_package_version\""
32
+ "postpublish": "bash scripts/sync-plugin.sh \"chore: sync plugin to v$npm_package_version\"",
33
+ "deploy:check:google": "node scripts/check-google-drive-deploy.mjs",
34
+ "build:excalidraw": "node scripts/build-excalidraw-editor.mjs"
32
35
  },
33
36
  "dependencies": {
34
37
  "@aws-sdk/client-s3": "^3.1007.0",
@@ -36,13 +39,20 @@
36
39
  "@clerk/backend": "^3.2.12",
37
40
  "@clerk/express": "^2.1.4",
38
41
  "@clerk/mcp-tools": "^0.3.1",
42
+ "@excalidraw/excalidraw": "^0.18.1",
43
+ "@excalidraw/mermaid-to-excalidraw": "^2.2.2",
39
44
  "@modelcontextprotocol/ext-apps": "^1.6.0",
40
45
  "@modelcontextprotocol/sdk": "^1.27.1",
46
+ "@sentry/browser": "^10.53.1",
47
+ "@sentry/node": "^10.53.1",
41
48
  "commander": "^11.1.0",
42
49
  "dagre": "^0.8.5",
43
50
  "dotenv": "^17.3.1",
44
51
  "drizzle-orm": "^0.45.1",
52
+ "esbuild": "^0.28.0",
45
53
  "express": "^4.18.2",
54
+ "jsdom": "^27.0.1",
55
+ "lucide": "^1.16.0",
46
56
  "mdast-util-from-markdown": "^2.0.3",
47
57
  "multer": "^2.1.1",
48
58
  "nodemailer": "^8.0.2",
@@ -50,6 +60,8 @@
50
60
  "playwright": "^1.58.2",
51
61
  "puppeteer": "^24.37.5",
52
62
  "qrcode-terminal": "^0.12.0",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
53
65
  "sharp": "^0.34.5",
54
66
  "ws": "^8.16.0",
55
67
  "zod": "^4.3.6"
@@ -12,22 +12,24 @@ import { extname } from 'path';
12
12
  // Layer definitions — default creative pipeline for projects without templates.
13
13
  // research → requirements → plans → drafts → designs → reviews
14
14
  export const LAYERS = {
15
- 'research': { label: 'Research', type: 'document', width: 800, height: 1000 },
16
- 'requirements': { label: 'Requirements', type: 'document', width: 800, height: 1000 },
17
- 'plans': { label: 'Plans', type: 'document', width: 800, height: 1000 },
15
+ 'research': { label: 'Research', type: 'document', width: 794, height: 1123 },
16
+ 'requirements': { label: 'Requirements', type: 'document', width: 794, height: 1123 },
17
+ 'plans': { label: 'Plans', type: 'document', width: 794, height: 1123 },
18
18
  'drafts': { label: 'Drafts', type: 'wireframe', width: 1200, height: 800 },
19
19
  'designs': { label: 'Designs', type: 'design', width: 1440, height: 900 },
20
- 'reviews': { label: 'Reviews', type: 'document', width: 800, height: 1000 },
20
+ 'reviews': { label: 'Reviews', type: 'document', width: 794, height: 1123 },
21
21
  };
22
22
 
23
23
  // File extensions recognized per layer
24
24
  export const DESIGN_EXTENSIONS = new Set(['.html', '.htm']);
25
25
  export const DOC_EXTENSIONS = new Set(['.md', '.txt']);
26
- export const ASSET_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.pdf']);
26
+ export const EXCALIDRAW_EXTENSIONS = new Set(['.excalidraw']);
27
+ export const ASSET_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.pdf', '.mp4', '.webm', '.mov', '.m4v']);
27
28
 
28
29
  export function getFileType(filePath, layer) {
29
30
  const ext = extname(filePath).toLowerCase();
30
31
  if (ASSET_EXTENSIONS.has(ext) || layer === 'brand-assets') return 'asset';
31
32
  if (DOC_EXTENSIONS.has(ext)) return 'document';
33
+ if (EXCALIDRAW_EXTENSIONS.has(ext)) return 'excalidraw';
32
34
  return LAYERS[layer]?.type || 'design';
33
35
  }
@@ -0,0 +1,101 @@
1
+ export const EXCALIDRAW_MIME = 'application/vnd.excalidraw+json';
2
+
3
+ export function emptyExcalidrawScene() {
4
+ return {
5
+ type: 'excalidraw',
6
+ version: 2,
7
+ source: 'https://drafted.live',
8
+ elements: [],
9
+ appState: {},
10
+ files: {},
11
+ };
12
+ }
13
+
14
+ export function normalizeExcalidrawScene(input) {
15
+ const scene = typeof input === 'string' ? JSON.parse(input) : input;
16
+ if (!scene || typeof scene !== 'object' || Array.isArray(scene)) {
17
+ throw new Error('Excalidraw data must be an object');
18
+ }
19
+ if (!Array.isArray(scene.elements)) {
20
+ throw new Error('Excalidraw data must include elements array');
21
+ }
22
+ const appState = scene.appState && typeof scene.appState === 'object' && !Array.isArray(scene.appState)
23
+ ? scene.appState
24
+ : {};
25
+ const files = scene.files && typeof scene.files === 'object' && !Array.isArray(scene.files)
26
+ ? scene.files
27
+ : {};
28
+ return {
29
+ type: scene.type || 'excalidraw',
30
+ version: scene.version || 2,
31
+ source: scene.source || 'https://drafted.live',
32
+ elements: scene.elements,
33
+ appState,
34
+ files,
35
+ };
36
+ }
37
+
38
+ export function stringifyExcalidrawScene(input) {
39
+ return JSON.stringify(normalizeExcalidrawScene(input), null, 2);
40
+ }
41
+
42
+ function restoreMermaidDom(previous, dom) {
43
+ for (const [key, value] of Object.entries(previous)) {
44
+ if (value === undefined) {
45
+ try { delete globalThis[key]; } catch {}
46
+ } else if (key === 'navigator') {
47
+ Object.defineProperty(globalThis, 'navigator', { value, configurable: true });
48
+ } else {
49
+ globalThis[key] = value;
50
+ }
51
+ }
52
+ if (dom) dom.window.close();
53
+ }
54
+
55
+ export async function excalidrawSceneFromMermaid(mermaid) {
56
+ let dom;
57
+ const previous = {
58
+ window: globalThis.window,
59
+ document: globalThis.document,
60
+ DOMParser: globalThis.DOMParser,
61
+ XMLSerializer: globalThis.XMLSerializer,
62
+ Element: globalThis.Element,
63
+ SVGElement: globalThis.SVGElement,
64
+ navigator: globalThis.navigator,
65
+ CSSStyleSheet: globalThis.CSSStyleSheet,
66
+ };
67
+ try {
68
+ if (typeof document === 'undefined') {
69
+ const { JSDOM } = await import('jsdom');
70
+ dom = new JSDOM('<!doctype html><html><body></body></html>');
71
+ globalThis.window = dom.window;
72
+ globalThis.document = dom.window.document;
73
+ globalThis.DOMParser = dom.window.DOMParser;
74
+ globalThis.XMLSerializer = dom.window.XMLSerializer;
75
+ globalThis.Element = dom.window.Element;
76
+ globalThis.SVGElement = dom.window.SVGElement;
77
+ if (globalThis.SVGElement && !globalThis.SVGElement.prototype.getBBox) {
78
+ globalThis.SVGElement.prototype.getBBox = function () {
79
+ const text = this.textContent || '';
80
+ return { x: 0, y: 0, width: Math.max(24, text.length * 8), height: 20 };
81
+ };
82
+ }
83
+ Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator, configurable: true });
84
+ globalThis.CSSStyleSheet = class {
85
+ constructor() { this.cssRules = []; }
86
+ replaceSync() { this.cssRules = []; }
87
+ insertRule(rule) { this.cssRules.push({ cssText: rule }); }
88
+ };
89
+ }
90
+ const { parseMermaidToExcalidraw } = await import('@excalidraw/mermaid-to-excalidraw');
91
+ const result = await parseMermaidToExcalidraw(mermaid);
92
+ return {
93
+ ...emptyExcalidrawScene(),
94
+ elements: result.elements || [],
95
+ files: result.files || {},
96
+ appState: { viewBackgroundColor: '#ffffff' },
97
+ };
98
+ } finally {
99
+ if (dom) restoreMermaidDom(previous, dom);
100
+ }
101
+ }