clay-server 4.8.2-beta.1 → 4.9.0-beta.1

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 (56) hide show
  1. package/LICENSES/README.md +13 -0
  2. package/LICENSES/open-iconic.txt +21 -0
  3. package/LICENSES/plantuml-salt.txt +674 -0
  4. package/lib/assets/salt-assets.json +15 -0
  5. package/lib/assets/salt-colors.json +156 -0
  6. package/lib/assets/salt-font-10.bin.gz +0 -0
  7. package/lib/assets/salt-font-13.bin.gz +0 -0
  8. package/lib/assets/salt-font-14.bin.gz +0 -0
  9. package/lib/assets/salt-font-16.bin.gz +0 -0
  10. package/lib/assets/salt-font-18.bin.gz +0 -0
  11. package/lib/assets/salt-font-20.bin.gz +0 -0
  12. package/lib/assets/salt-font-metrics.bin.gz +0 -0
  13. package/lib/assets/salt-font-metrics.json +101 -0
  14. package/lib/assets/salt-font-monospace-10.bin.gz +0 -0
  15. package/lib/assets/salt-font-monospace-13.bin.gz +0 -0
  16. package/lib/assets/salt-font-monospace-14.bin.gz +0 -0
  17. package/lib/assets/salt-font-monospace-16.bin.gz +0 -0
  18. package/lib/assets/salt-font-monospace-18.bin.gz +0 -0
  19. package/lib/assets/salt-font-monospace-20.bin.gz +0 -0
  20. package/lib/assets/salt-font-monospace.bin.gz +0 -0
  21. package/lib/assets/salt-icons.json +671 -0
  22. package/lib/bundled-skills/clay-sketch/SKILL.md +198 -0
  23. package/lib/plantuml-renderer.js +43 -0
  24. package/lib/project-file-http.js +2 -0
  25. package/lib/project-plantuml.js +48 -0
  26. package/lib/project-session-document.js +17 -1
  27. package/lib/project-wireframe-http.js +88 -0
  28. package/lib/public/app.js +6 -0
  29. package/lib/public/css/filebrowser.css +5 -0
  30. package/lib/public/css/wireframe.css +95 -0
  31. package/lib/public/modules/app-messages.js +8 -1
  32. package/lib/public/modules/filebrowser.js +29 -3
  33. package/lib/public/modules/markdown.js +3 -1
  34. package/lib/public/modules/plantuml-preview.js +76 -0
  35. package/lib/public/modules/split-view.js +14 -0
  36. package/lib/public/modules/wireframe-chat.js +52 -0
  37. package/lib/public/modules/wireframe-render.js +69 -0
  38. package/lib/public/modules/wireframe-style.js +6 -0
  39. package/lib/public/modules/wireframe-viewport.js +70 -0
  40. package/lib/public/modules/wireframe-workbench.js +116 -0
  41. package/lib/public/style.css +1 -0
  42. package/lib/salt-chrome.js +36 -0
  43. package/lib/salt-hand.js +37 -0
  44. package/lib/salt-images.js +87 -0
  45. package/lib/salt-layout.js +60 -0
  46. package/lib/salt-options.js +23 -0
  47. package/lib/salt-overlays.js +45 -0
  48. package/lib/salt-parser.js +153 -0
  49. package/lib/salt-screen.js +111 -0
  50. package/lib/salt-svg-shapes.js +80 -0
  51. package/lib/salt-svg.js +164 -0
  52. package/lib/salt-text.js +112 -0
  53. package/lib/salt-tree.js +45 -0
  54. package/lib/sdk-bridge.js +2 -1
  55. package/lib/session-document-mcp-server.js +9 -0
  56. package/package.json +1 -1
@@ -0,0 +1,164 @@
1
+ // Geometry adapted from PlantUML 1.2026.8 Salt, (c) Arnaud Roques, GPL-3.0-or-later.
2
+ // See LICENSES/plantuml-salt.txt. Rendering is local and bounded; image bytes are pre-resolved.
3
+ var layout = require('./salt-layout');
4
+ var shapes = require('./salt-svg-shapes');
5
+ var overlays = require('./salt-overlays');
6
+ function renderSaltSvg(root) {
7
+ layout.measure(root);
8
+ require('./salt-screen').apply(root);
9
+ var out = [];
10
+ var scale = (root.options || {}).scale || 1;
11
+ var s = shapes.createShapes(out, scale, (root.options || {}).handwritten);
12
+ var front = [];
13
+ function grid(node, x, y) {
14
+ var horizontal = new Set();
15
+ var vertical = new Set();
16
+ var rows = node.heights.length;
17
+ var cols = node.columns.length;
18
+ var style = node.style;
19
+ if (/[+#^]/.test(style)) {
20
+ for (var c = 0; c < cols; c++) { horizontal.add('0,' + c); horizontal.add(rows + ',' + c); }
21
+ for (var r = 0; r < rows; r++) { vertical.add(r + ',0'); vertical.add(r + ',' + cols); }
22
+ }
23
+ node.rows.forEach(function (row, r) {
24
+ row.forEach(function (child, c) {
25
+ if (child.type === 'span') return;
26
+ var end = c + (child.span || 1);
27
+ if (/[#-]/.test(style)) for (var cc = c; cc < end; cc++) { horizontal.add(r + ',' + cc); horizontal.add((r + 1) + ',' + cc); }
28
+ if (/[#!]/.test(style)) { vertical.add(r + ',' + c); vertical.add(r + ',' + end); }
29
+ });
30
+ });
31
+ function referenceOrder(set) {
32
+ var capacity = 16;
33
+ while (set.size > capacity * 0.75) capacity *= 2;
34
+ function bucket(key) {
35
+ var p = key.split(',').map(Number);
36
+ var hash = p[0] * 47 + p[1];
37
+ return (hash ^ (hash >>> 16)) & (capacity - 1);
38
+ }
39
+ return Array.from(set).sort(function (a, b) { return bucket(a) - bucket(b); });
40
+ }
41
+ referenceOrder(horizontal).forEach(function (key) {
42
+ var p = key.split(',').map(Number);
43
+ s.line(x + node.xs[p[1]], y + node.ys[p[0]], x + node.xs[p[1] + 1], y + node.ys[p[0]]);
44
+ });
45
+ referenceOrder(vertical).forEach(function (key) {
46
+ var p = key.split(',').map(Number);
47
+ s.line(x + node.xs[p[1]], y + node.ys[p[0]], x + node.xs[p[1]], y + node.ys[p[0] + 1]);
48
+ });
49
+ if (node.title) {
50
+ s.rect(x + 6, y, node.titleLabel.width, node.titleLabel.height, '#FFF', '#FFF');
51
+ s.label(node.titleLabel, x + 6, y);
52
+ }
53
+ }
54
+ function tabs(node, x, y) {
55
+ var offset = 0;
56
+ node.items.forEach(function (child) {
57
+ var w = child.width;
58
+ var h = child.height;
59
+ if (node.vertical) {
60
+ s.label(child.label, x, y + offset + 2);
61
+ s.line(x, y + offset, x + node.width, y + offset);
62
+ s.line(x, y + offset, x, y + offset + h + 5);
63
+ s.line(x, y + offset + h + 5, x + node.width, y + offset + h + 5);
64
+ s.line(x + node.width, y + offset + h + 5, x + node.width, y + offset + h + 15);
65
+ offset += h + 15;
66
+ } else {
67
+ s.label(child.label, x + offset + 2, y);
68
+ s.line(x + offset, y, x + offset, y + h);
69
+ s.line(x + offset, y, x + offset + w + 5, y);
70
+ s.line(x + offset + w + 5, y, x + offset + w + 5, y + h);
71
+ s.line(x + offset + w + 5, y + h, x + offset + w + 15, y + h);
72
+ offset += w + 15;
73
+ }
74
+ });
75
+ }
76
+ function draw(node, x, y, w, h) {
77
+ if (node.screen) {
78
+ if (/[+#^]/.test(node.style)) s.rect(x, y, w, h);
79
+ if (node.title) s.label(node.titleLabel, x + node.screen.padding, y + 2);
80
+ if (/[#-]/.test(node.style)) node.screen.horizontal.forEach(function (yy) { s.line(x, y + yy, x + w, y + yy, '#AAA'); });
81
+ if (/[#!]/.test(node.style)) node.screen.vertical.forEach(function (line) { s.line(x + line.x, y + line.y, x + line.x, y + line.y + line.height, '#AAA'); });
82
+ node.screen.cells.forEach(function (cell) { draw(cell.node, x + cell.x, y + cell.y, cell.width, cell.height); });
83
+ return;
84
+ }
85
+ if (node.type === 'container') {
86
+ if (node.style[0] === 'T') return require('./salt-tree').draw(node, x, y, s, draw);
87
+ if (node.style === '/') return tabs(node, x, y);
88
+ if (node.style === '*') {
89
+ s.rect(x, y, w, h, '#DDD');
90
+ var mx = x;
91
+ node.items.forEach(function (child) { s.label(child.label, mx, y); mx += child.width + 10; });
92
+ front.push(function () { overlays.menu(node, x, y, s); });
93
+ return;
94
+ }
95
+ node.rows.forEach(function (row, r) {
96
+ row.forEach(function (child, c) {
97
+ if (child.type === 'span') return;
98
+ draw(child, x + node.xs[c] + 1, y + node.ys[r] + 1 + (r === 0 ? node.titleLabel.height / 2 : 0), node.xs[c + (child.span || 1)] - node.xs[c] - 1, node.heights[r] - 1);
99
+ });
100
+ });
101
+ grid(node, x, y);
102
+ if (node.scroll) { overlays.scroll(node, x, y, s); front.push(function () { overlays.scroll(node, x, y, s); }); }
103
+ return;
104
+ }
105
+ if (node.type === 'space') return;
106
+ if (node.type === 'sprite') {
107
+ for (var xx = 0; xx < node.width; xx++) for (var yy = 0; yy < node.height; yy++) {
108
+ if (node.pixels[yy][xx] === 'X') s.rect(x + xx, y + yy, 0.5, 0.5, s.lastInk() || '#000', '#000', 0, 0.5);
109
+ }
110
+ return;
111
+ }
112
+ if (node.type === 'separator') {
113
+ var yy = y + h / 2 - (node.style === '==' ? 1 : 0);
114
+ s.line(x, yy, x + w, yy, '#AAA', node.style === '~~' ? 1.5 : 1, node.style === '..' ? '1,2' : null);
115
+ if (node.style === '==') s.line(x, yy + 2, x + w, yy + 2, '#AAA');
116
+ return;
117
+ }
118
+ var label = node.label;
119
+ if (node.type === 'button') {
120
+ s.rect(x + 2.5, y + 2.5, node.width - 5, node.height - 5, '#EEE', '#000', 5, 2.5);
121
+ s.label(label, x + (node.width - label.width) / 2, y + 4.5);
122
+ } else if (node.type === 'input') {
123
+ s.label(label, x + 3, y);
124
+ s.line(x + 1, y + label.height, x + node.width - 2, y + label.height);
125
+ s.line(x + 1, y + label.height - 3, x + 1, y + label.height - 1);
126
+ s.line(x + node.width - 2, y + label.height - 3, x + node.width - 2, y + label.height - 1);
127
+ } else if (node.type === 'select') {
128
+ s.rect(x, y, node.width - 1, node.height - 1, '#EEE');
129
+ s.label(label, x + 2, y + 2);
130
+ var xx = x + node.width - 12;
131
+ s.line(xx, y, xx, y + node.height - 1);
132
+ s.polygon([[xx + 3, y + 6], [xx + 9, y + 6], [xx + 6, y + label.height - 2]]);
133
+ overlays.dropdown(node, x, y, s);
134
+ front.push(function () { overlays.dropdown(node, x, y, s); });
135
+ } else if (node.type === 'checkbox' || node.type === 'radio') {
136
+ s.label(label, x + 20, y);
137
+ if (node.type === 'radio') {
138
+ s.ellipse(x + 2, y + (node.height - 10) / 2, 10, 10);
139
+ if (node.checked) s.ellipse(x + 5, y + (node.height - 4) / 2, 4, 4, '#000');
140
+ } else {
141
+ s.rect(x + 2, y + (node.height - 10) / 2, 10, 10, 'none', '#000', 0, 1.5);
142
+ if (node.checked) s.polygon([[x + 3, y + 6], [x + 6, y + 9], [x + 13, y], [x + 6, y + 7]], 1.5);
143
+ }
144
+ } else s.label(label, x, y);
145
+ }
146
+ var decorated = require('./salt-chrome').decorate(root, function (x, y) { draw(root, x, y, root.width, root.height); }, s);
147
+ decorated.draw(root.options.screen ? 1 : 5, root.options.screen ? 1 : 5);
148
+ front.forEach(function (drawOverlay) { drawOverlay(); });
149
+ var margin = out.length ? 1 : 0;
150
+ var width = Math.floor(Math.max(decorated.width + 10, s.bounds.x) + margin);
151
+ var height = Math.floor(Math.max(decorated.height + 10, s.bounds.y) + margin);
152
+ if (root.options.screen) {
153
+ width = root.options.screen.width; height = root.options.screen.height;
154
+ if (s.bounds.x > width || s.bounds.y > height) throw Object.assign(new Error('Clay screen layout: drawing extends outside the canvas. Increase its dimensions or simplify the contents.'), { status: 422 });
155
+ }
156
+ width = Math.round(width * scale * 1000) / 1000; height = Math.round(height * scale * 1000) / 1000;
157
+ if (width > 8192 || height > 8192) throw Object.assign(new Error('Wireframe exceeds the 8192-pixel canvas limit.'), { status: 413 });
158
+ var background = (root.options || {}).background || '#FFFFFF';
159
+ if (background !== '#FFFFFF') out.unshift('<rect x="0" y="0" width="' + width + '" height="' + height + '" fill="' + background + '" stroke="none"/>');
160
+ out.unshift('<svg xmlns="http://www.w3.org/2000/svg"' + (root.options.screen ? ' data-clay-screen="true"' : '') + ' width="' + width + '" height="' + height + '" viewBox="0 0 ' + Math.floor(width) + ' ' + Math.floor(height) + '" preserveAspectRatio="none" style="width:' + Math.floor(width) + 'px;height:' + Math.floor(height) + 'px;background:' + ((root.options || {}).background || '#FFFFFF') + '" role="img" aria-label="Wireframe"><g font-family="sans-serif" lengthAdjust="spacing">');
161
+ out.push('</g></svg>');
162
+ return out.join('');
163
+ }
164
+ module.exports = { renderSaltSvg: renderSaltSvg };
@@ -0,0 +1,112 @@
1
+ // Salt text layout uses the pinned reference metrics, without fonts or Java at runtime.
2
+ var fs = require('node:fs');
3
+ var path = require('node:path');
4
+ var zlib = require('node:zlib');
5
+ var profiles = {};
6
+ var colors = require('./assets/salt-colors.json');
7
+ var icons = require('./assets/salt-icons.json');
8
+ function color(value) {
9
+ var hex = colors[value.toLowerCase()] || value.toUpperCase();
10
+ if (!/^#(?:[A-F0-9]{3}|[A-F0-9]{6})$/.test(hex)) fail('Unsupported color: ' + value);
11
+ return hex.replace(/^#(.)\1(.)\2(.)\3$/, '#$1$2$3');
12
+ }
13
+ function fail(message) { throw Object.assign(new Error(message), { status: 422 }); }
14
+ function parseLabel(value) {
15
+ var heading = /^(={1,6})\s*(.*)$/.exec(value);
16
+ var order = heading ? heading[1].length - 1 - (/^</.test(heading[2]) ? 1 : 0) : -1;
17
+ var size = heading ? order <= 0 ? 16 : order === 1 ? 14 : order === 2 ? 13 : 12 : 12;
18
+ if (heading) value = heading[2];
19
+ var list = /^(\*+|#+)\s+(\S.*)$/.exec(value);
20
+ if (list) value = list[2];
21
+ value = value.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
22
+ value = value.replace(/<U\+([0-9a-f]{4,6})>/gi, function (_, cp) {
23
+ var code = parseInt(cp, 16); if (code > 0x10ffff || code >= 0xd800 && code <= 0xdfff) fail('Invalid Unicode code point.');
24
+ return String.fromCodePoint(code);
25
+ });
26
+ var state = { color: '#000', bold: !!heading && order < 3, italic: !!heading && order >= 3, size: size };
27
+ var runs = [];
28
+ var formats = { '**': 'bold', '//': 'italic', '""': 'mono', '--': 'strike', '__': 'underline', '~~': 'wave' };
29
+ var parts = value.split(/(<[^>]+>|\*\*|\/\/|""|--|__|~~)/g);
30
+ parts.forEach(function (part, index) {
31
+ if (!part) return;
32
+ if (formats[part]) {
33
+ var format = formats[part];
34
+ var closing = parts.indexOf(part, index + 1);
35
+ if (state[format] || closing > index && parts.slice(index + 1, closing).some(Boolean)) { state[format] = !state[format]; return; }
36
+ }
37
+ if (/^<&/.test(part)) {
38
+ var icon = part.slice(2, -1); if (!icons[icon]) fail('Unknown built-in icon: ' + icon);
39
+ runs.push(Object.assign({}, state, { text: '', icon: icon })); return;
40
+ }
41
+ if (/^<img:/i.test(part)) {
42
+ var url = part.slice(5, -1);
43
+ if (!/^https:\/\//i.test(url)) fail('Wireframe images require a public HTTPS URL.');
44
+ runs.push(Object.assign({}, state, { text: '', imageUrl: url })); return;
45
+ }
46
+ var tag = /^<(\/?)(b|i|s|u|w|color|back|font|size)(?::([^>]+))?>$/i.exec(part);
47
+ if (tag) {
48
+ var name = tag[2].toLowerCase(); var end = !!tag[1];
49
+ var key = { b: 'bold', i: 'italic', s: 'strike', u: 'underline', w: 'wave' }[name];
50
+ if (key) { state[key] = !end; state[key + 'Color'] = !end && tag[3] ? color(tag[3]) : null; }
51
+ else if (name === 'color') state.color = end ? '#000' : color(tag[3] || 'black');
52
+ else if (name === 'back') state.background = end ? null : color(tag[3] || 'white');
53
+ else if (name === 'font') { if (!end && !/^(monospaced?|sansserif|sans-serif)$/i.test(tag[3] || '')) fail('Unsupported font family.'); state.mono = !end && /^mono/i.test(tag[3]); }
54
+ else if (name === 'size') { state.size = end ? size : Number(tag[3]); if ([10, 12, 13, 14, 16, 18, 20].indexOf(state.size) === -1) fail('Supported font sizes are 10, 12, 13, 14, 16, 18, and 20.'); }
55
+ return;
56
+ }
57
+ if (/<\/?[a-z][^>]*>|<\$|\[\[/i.test(part)) fail('Unsupported embedded markup in wireframe text.');
58
+ var previous = runs[runs.length - 1];
59
+ if (previous && !previous.icon && !previous.imageUrl && JSON.stringify(Object.assign({}, previous, { text: '' })) === JSON.stringify(Object.assign({}, state, { text: '' }))) previous.text += part;
60
+ else runs.push(Object.assign({}, state, { text: part }));
61
+ });
62
+ return { text: runs.map(function (r) { return r.text || r.icon || ''; }).join(''), runs: runs,
63
+ list: list ? { type: list[1][0] === '*' ? 'bullet' : 'number', level: list[1].length } : null,
64
+ bold: runs.some(function (r) { return r.bold; }), italic: runs.some(function (r) { return r.italic; }) };
65
+ }
66
+ function measureRun(run) {
67
+ if (run.icon) return Object.assign({}, run, { width: 10, height: 8, descent: 0 });
68
+ if (run.imageUrl) {
69
+ if (!run.image) fail('Wireframe image was not resolved.');
70
+ return Object.assign({}, run, { width: run.image.width, height: run.image.height, descent: 0 });
71
+ }
72
+ var profile = run.mono ? 'monospace' + (run.size && run.size !== 12 ? '-' + run.size : '') : !run.size || run.size === 12 ? 'metrics' : String(run.size);
73
+ if (!profiles[profile]) profiles[profile] = zlib.gunzipSync(fs.readFileSync(path.join(__dirname, 'assets/salt-font-' + profile + '.bin.gz')));
74
+ var metrics = profiles[profile];
75
+ var style = (run.bold ? 1 : 0) + (run.italic ? 2 : 0);
76
+ var width = 0;
77
+ var height = 0;
78
+ var descent = 0;
79
+ for (var c of run.text) {
80
+ var cp = c.codePointAt(0);
81
+ // Supplementary characters use the reference missing-glyph advance.
82
+ if (cp > 65535) cp = 0xfffd;
83
+ var offset = (style * 65536 + cp) * 12;
84
+ width += metrics.readFloatBE(offset);
85
+ height = Math.max(height, metrics.readFloatBE(offset + 4));
86
+ descent = Math.max(descent, metrics.readFloatBE(offset + 8));
87
+ }
88
+ return Object.assign({}, run, { width: width, height: height, descent: descent });
89
+ }
90
+ function measureLabel(node, trim) {
91
+ var runs = (node.runs || parseLabel(node.text || '').runs).map(function (r) { return Object.assign({}, r); });
92
+ if (trim && runs.length) {
93
+ runs[0].text = runs[0].text.trimStart();
94
+ runs[runs.length - 1].text = runs[runs.length - 1].text.trimEnd();
95
+ }
96
+ runs = runs.filter(function (r) { return r.text || r.icon || r.imageUrl; });
97
+ if (!runs.length) runs.push({ text: '\u00a0', bold: false, italic: false });
98
+ runs = runs.filter(function (r) { return r.text || r.icon || r.imageUrl; }).map(function (r) {
99
+ var measured = measureRun(r);
100
+ var leading = /^ */.exec(r.text)[0];
101
+ if (r.icon || r.imageUrl) return measured;
102
+ measured.displayText = r.text.trim() || '\u00a0';
103
+ measured.displayWidth = measureRun(Object.assign({}, r, { text: measured.displayText })).width;
104
+ measured.leadingWidth = r.text.trim() ? measureRun(Object.assign({}, r, { text: leading })).width : 0;
105
+ return measured;
106
+ });
107
+ var list = node.list;
108
+ var indent = list ? list.type === 'number' ? measureRun({ text: '1. ', size: 12 }).width * list.level : list.level === 1 ? 12 : list.level * 8 : 0;
109
+ return { runs: runs, list: list, indent: indent, width: indent + runs.reduce(function (sum, r) { return sum + r.width; }, 0),
110
+ height: Math.max(0, runs.reduce(function (h, r) { return Math.max(h, r.height); }, 0)) };
111
+ }
112
+ module.exports = { color: color, icons: icons, parseLabel: parseLabel, measureLabel: measureLabel };
@@ -0,0 +1,45 @@
1
+ // Adapted from PlantUML Salt ElementTree, GPL-3.0-or-later; see LICENSES/plantuml-salt.txt.
2
+ function measure(node) {
3
+ node.columns = [];
4
+ node.heights = [];
5
+ node.rows.forEach(function (row) {
6
+ row.forEach(function (item, c) { node.columns[c] = Math.max(node.columns[c] || 0, item.width + (c ? 0 : (item.level || 0) * 10)); });
7
+ node.heights.push(Math.max(0, ...row.map(function (item) { return item.height; })));
8
+ });
9
+ node.width = node.columns.reduce(function (a, b) { return a + b; }, 0) + Math.max(0, node.columns.length - 1) * 10 + 2;
10
+ node.height = node.heights.reduce(function (a, b) { return a + b; }, 0);
11
+ }
12
+ function draw(node, x, y, s, drawChild) {
13
+ var cy = y;
14
+ var skeleton = [];
15
+ var rows = [y];
16
+ var cols = [x, x + (node.columns[0] || 0) + 5];
17
+ node.columns.slice(1).forEach(function (width) { cols.push(cols[cols.length - 1] + width + 10); });
18
+ node.rows.forEach(function (row, r) {
19
+ var cx = x;
20
+ row.forEach(function (item, c) {
21
+ drawChild(item, c ? cx : x + (item.level || 0) * 10, cy, item.width, item.height);
22
+ cx += node.columns[c] + 10;
23
+ });
24
+ skeleton.push({ x: x + (row[0].level || 0) * 10 - 7, y: cy + node.heights[r] / 2 - 1 });
25
+ cy += node.heights[r]; rows.push(cy);
26
+ });
27
+ skeleton.forEach(function (item, i) {
28
+ if (skeleton[i + 1] && skeleton[i + 1].x > item.x) s.rect(item.x, item.y, 2, 2, 'none', '#888');
29
+ var parent;
30
+ for (var j = 0; j < i; j++) if (skeleton[j].x < item.x) parent = skeleton[j];
31
+ if (parent) {
32
+ s.line(parent.x + 1, parent.y + 3, parent.x + 1, item.y + 1, '#888');
33
+ s.line(parent.x + 1, item.y + 1, item.x - 1, item.y + 1, '#888');
34
+ }
35
+ });
36
+ var mode = node.style.slice(1);
37
+ var right = cols[cols.length - 1];
38
+ if (mode === '+') {
39
+ s.line(x, y, right, y, '#888'); s.line(x, cy, right, cy, '#888');
40
+ s.line(x, y, x, cy, '#888'); s.line(right, y, right, cy, '#888');
41
+ }
42
+ if (/[#-]/.test(mode)) rows.forEach(function (yy) { s.line(x, yy, right, yy, '#888'); });
43
+ if (/[#!]/.test(mode)) cols.forEach(function (xx) { s.line(xx, y, xx, cy, '#888'); });
44
+ }
45
+ module.exports = { measure: measure, draw: draw };
package/lib/sdk-bridge.js CHANGED
@@ -713,7 +713,8 @@ function createSDKBridge(opts) {
713
713
  // This tool only prepares a read-only UI projection for the document the
714
714
  // agent is about to edit. The subsequent Edit/Write keeps its own normal
715
715
  // permission behavior.
716
- if (toolName.indexOf("mcp__clay-documents__present_markdown_edit") === 0 ||
716
+ if (toolName === "mcp__clay-documents__present_wireframe" || toolName === "clay-documents__present_wireframe" ||
717
+ toolName.indexOf("mcp__clay-documents__present_markdown_edit") === 0 ||
717
718
  toolName.indexOf("clay-documents__present_markdown_edit") !== -1) {
718
719
  return { behavior: "allow", updatedInput: input };
719
720
  }
@@ -10,6 +10,15 @@ function getToolDefs(handlers) {
10
10
  path: { type: "string", description: "Project-relative or absolute path to the Markdown document that will be edited." },
11
11
  }, ["path"]),
12
12
  handler: function (args) { return handlers.present(args || {}); },
13
+ }, {
14
+ name: "present_wireframe",
15
+ description: "Open or update the temporary clay-sketch viewer in the right panel when the user wants to revise or discuss a wireframe. Supports nested layouts and common UI controls, not general UML or preprocessor directives. Supply complete Salt diagram source, a stable id reused across revisions, and a short title. This displays only; it never saves or edits files. Include the same source in a fenced clay-sketch response so the diagram remains in chat history.",
16
+ inputSchema: buildShape({
17
+ source: { type: "string", description: "Complete @startsalt/@endsalt source, or @startuml with a salt body (100 KB maximum)." },
18
+ id: { type: "string", description: "Stable design id, using letters, digits, hyphens or underscores (80 characters maximum)." },
19
+ title: { type: "string", description: "Short title for the viewer." },
20
+ }, ["source", "id"]),
21
+ handler: function (args) { return handlers.presentWireframe(args || {}); },
13
22
  }];
14
23
  }
15
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.8.2-beta.1",
3
+ "version": "4.9.0-beta.1",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js"