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.
- package/LICENSES/README.md +13 -0
- package/LICENSES/open-iconic.txt +21 -0
- package/LICENSES/plantuml-salt.txt +674 -0
- package/lib/assets/salt-assets.json +15 -0
- package/lib/assets/salt-colors.json +156 -0
- package/lib/assets/salt-font-10.bin.gz +0 -0
- package/lib/assets/salt-font-13.bin.gz +0 -0
- package/lib/assets/salt-font-14.bin.gz +0 -0
- package/lib/assets/salt-font-16.bin.gz +0 -0
- package/lib/assets/salt-font-18.bin.gz +0 -0
- package/lib/assets/salt-font-20.bin.gz +0 -0
- package/lib/assets/salt-font-metrics.bin.gz +0 -0
- package/lib/assets/salt-font-metrics.json +101 -0
- package/lib/assets/salt-font-monospace-10.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-13.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-14.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-16.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-18.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-20.bin.gz +0 -0
- package/lib/assets/salt-font-monospace.bin.gz +0 -0
- package/lib/assets/salt-icons.json +671 -0
- package/lib/bundled-skills/clay-sketch/SKILL.md +198 -0
- package/lib/plantuml-renderer.js +43 -0
- package/lib/project-file-http.js +2 -0
- package/lib/project-plantuml.js +48 -0
- package/lib/project-session-document.js +17 -1
- package/lib/project-wireframe-http.js +88 -0
- package/lib/public/app.js +6 -0
- package/lib/public/css/filebrowser.css +5 -0
- package/lib/public/css/wireframe.css +95 -0
- package/lib/public/modules/app-messages.js +8 -1
- package/lib/public/modules/filebrowser.js +29 -3
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/plantuml-preview.js +76 -0
- package/lib/public/modules/split-view.js +14 -0
- package/lib/public/modules/wireframe-chat.js +52 -0
- package/lib/public/modules/wireframe-render.js +69 -0
- package/lib/public/modules/wireframe-style.js +6 -0
- package/lib/public/modules/wireframe-viewport.js +70 -0
- package/lib/public/modules/wireframe-workbench.js +116 -0
- package/lib/public/style.css +1 -0
- package/lib/salt-chrome.js +36 -0
- package/lib/salt-hand.js +37 -0
- package/lib/salt-images.js +87 -0
- package/lib/salt-layout.js +60 -0
- package/lib/salt-options.js +23 -0
- package/lib/salt-overlays.js +45 -0
- package/lib/salt-parser.js +153 -0
- package/lib/salt-screen.js +111 -0
- package/lib/salt-svg-shapes.js +80 -0
- package/lib/salt-svg.js +164 -0
- package/lib/salt-text.js +112 -0
- package/lib/salt-tree.js +45 -0
- package/lib/sdk-bridge.js +2 -1
- package/lib/session-document-mcp-server.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clay-sketch
|
|
3
|
+
description: Draw and revise UI wireframes directly in Clay chat with the built-in clay-sketch renderer. Automatically use for requests to see a wireframe, sketch a screen, visualize a page layout or UI plan, or revise an existing sketch, including entire webpages and the current project. The user does not need to name clay-sketch. Also use for /clay-sketch or $clay-sketch. Do not substitute this for implementing a working website, generating artwork, or drawing architecture and sequence diagrams.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# clay-sketch
|
|
7
|
+
|
|
8
|
+
Use clay-sketch to make UI ideas visible while keeping design changes in the
|
|
9
|
+
conversation. It ships with Clay: no installation, browser automation, external
|
|
10
|
+
renderer, or generated image is needed. Its markup is a supported subset of
|
|
11
|
+
PlantUML Salt with optional Clay screen-layout extensions, not the full PlantUML language.
|
|
12
|
+
|
|
13
|
+
Treat ordinary requests such as "show me a wireframe of our work", "sketch this
|
|
14
|
+
screen", or "what would this page look like?" in a UI planning conversation as
|
|
15
|
+
requests for clay-sketch. Do not require the feature name or ask the user to
|
|
16
|
+
choose a rendering format. Use the current project and conversation as context.
|
|
17
|
+
Default to a rendered sketch; use ASCII wireframes only when explicitly requested.
|
|
18
|
+
|
|
19
|
+
## Draw in chat
|
|
20
|
+
|
|
21
|
+
1. Use the user's requirements and existing conversation to choose a sensible
|
|
22
|
+
first layout. Ask a structured question only when a missing decision would
|
|
23
|
+
materially change the design. Avoid setup questions or requiring a file.
|
|
24
|
+
2. Show a complete sketch in a fenced `clay-sketch` block. Include `@startsalt`
|
|
25
|
+
and `@endsalt`. Clay renders the block inline and retains its source.
|
|
26
|
+
3. For a whole page, use the screen-layout mode below with an explicit canvas
|
|
27
|
+
and region tracks. Default to 1440 × 900 for desktop unless context suggests
|
|
28
|
+
another size. Do not simulate a screen by increasing scale, font sizes or
|
|
29
|
+
padding labels with spaces. Use realistic labels and a clear primary action.
|
|
30
|
+
4. Briefly explain the important layout decisions. The controls are drawings,
|
|
31
|
+
not interactive application controls. Keep the sketch focused on structure.
|
|
32
|
+
|
|
33
|
+
Use one sketch per block. Existing `plantuml`, `puml`, and `salt` fences also
|
|
34
|
+
work. Prefer `clay-sketch` for new responses. Don't replace a rendered sketch
|
|
35
|
+
with a screenshot or unsolicited ASCII art.
|
|
36
|
+
|
|
37
|
+
## Revise together
|
|
38
|
+
|
|
39
|
+
When the user requests changes or wants to work together on a sketch, call
|
|
40
|
+
`present_wireframe` (or its available `clay-documents` qualified equivalent)
|
|
41
|
+
with the complete revised Salt source, a short title, and a stable design id
|
|
42
|
+
such as `account-settings`. Reuse that id for revisions of the same design.
|
|
43
|
+
This opens or updates clay-sketch in Sketch viewer, the right-side viewer. It only presents the
|
|
44
|
+
sketch; it does not save a file.
|
|
45
|
+
|
|
46
|
+
Include the same complete revised source in a `clay-sketch` response block so
|
|
47
|
+
the revision remains in chat history. Preserve requirements from earlier turns.
|
|
48
|
+
Do not send a patch or a fragment as the diagram source.
|
|
49
|
+
|
|
50
|
+
All editing happens through conversation. The user
|
|
51
|
+
can choose **Save to project** to create a `.puml` file, or **Download** to save
|
|
52
|
+
the source to their device. Do not create files automatically. If explicitly
|
|
53
|
+
asked to save through your file tools, use the requested path and normal file
|
|
54
|
+
permissions. Do not silently overwrite existing work.
|
|
55
|
+
|
|
56
|
+
If the presentation tool is unavailable, provide the inline sketch and explain
|
|
57
|
+
that **Open sketch** opens the viewer. Do not claim you opened it.
|
|
58
|
+
|
|
59
|
+
Use clean lines for new sketches. Do not add handwritten directives or suggest
|
|
60
|
+
a handwritten mode. The viewer credits PlantUML Salt and links to its documentation.
|
|
61
|
+
|
|
62
|
+
## Supported notation
|
|
63
|
+
|
|
64
|
+
- `{ ... }`: layout container; newlines separate rows and `|` separates columns.
|
|
65
|
+
Nest containers to build a whole page. Put a space or newline after `{`.
|
|
66
|
+
- `{+ ... }`: outer border; `{# ... }`: grid; `{! ... }`: column lines;
|
|
67
|
+
`{- ... }`: row lines.
|
|
68
|
+
- `{^"Account" ... }`: titled group. Put its rows on following lines.
|
|
69
|
+
- `{/ <b>Overview | Settings }`: tabs. Newline-separated tabs form a vertical list.
|
|
70
|
+
- `{* File | Edit | Help }`: a menu bar; following rows such as `File | Open | - | Exit` show an open menu.
|
|
71
|
+
- `Plain label`, `**Bold label**`, `<b>Bold label`, `<i>Italic label`.
|
|
72
|
+
- `[Continue]`: button; `"Email address "`: text field. Spaces inside
|
|
73
|
+
quotes reserve width; keep labels short enough to remain readable.
|
|
74
|
+
- `[] Remember me`, `[X] Enabled`: checkboxes.
|
|
75
|
+
- `() Option`, `(X) Selected`: radio controls.
|
|
76
|
+
- `^Choose a team^`: closed dropdown; `^Choose^^One^^Two^`: open dropdown.
|
|
77
|
+
- `.`: empty cell or vertical spacer; `..`, `--`, `==`, `~~`: separators.
|
|
78
|
+
- `*` following a cell spans the preceding cell across that column.
|
|
79
|
+
- `{T` with `+ Root`, `++ Child` rows: tree; `T!`, `T#`, `T+`, `T-` add grid lines.
|
|
80
|
+
- `{S`, `{SI`, `{S-`: both, vertical-only, horizontal-only scrollbars.
|
|
81
|
+
- `//italic//`, `""monospace""`, `--strike--`, `__underline__`, `~~wave~~`;
|
|
82
|
+
`<color:blue>`, `<back:orange>`, `<size:20>` and `<&folder>` icons.
|
|
83
|
+
- `* Bullet` and `# Numbered item`: list labels; repeat the marker for nesting.
|
|
84
|
+
- `title`, `header`, `footer`, `caption` before the layout; multiline
|
|
85
|
+
`legend` / `end legend`; `scale 2`, `skinparam dpi 200`, background colors.
|
|
86
|
+
- Lines beginning with an apostrophe are comments.
|
|
87
|
+
|
|
88
|
+
Use `.` explicitly for empty cells. Avoid gratuitous spacers or very long fields;
|
|
89
|
+
wide diagrams shrink in chat, though the viewer offers Fit, 100% reset and zoom up to 400%.
|
|
90
|
+
The renderer limits source to 100 KB, nesting to 24 levels, cells to 2000,
|
|
91
|
+
columns to 64, and layout dimensions to 8192 pixels.
|
|
92
|
+
|
|
93
|
+
Use bundled icons for ordinary wireframes. When an image is needed and its URL
|
|
94
|
+
is supplied or authorized for the design, `<img:https://…>` embeds a public PNG.
|
|
95
|
+
This requires network access, allows no redirects or private addresses, and is
|
|
96
|
+
limited to four images of 1 MiB and 2048 × 2048 pixels each. Do not invent image
|
|
97
|
+
URLs. Local files and SVG images are not supported. Salt's inline `.`/`X`
|
|
98
|
+
pseudo-sprites also work without networking.
|
|
99
|
+
|
|
100
|
+
Supported font sizes are 10, 12, 13, 14, 16, 18 and 20, with sans-serif or
|
|
101
|
+
monospace text. Avoid arbitrary fonts, includes, macros, external themes,
|
|
102
|
+
general PlantUML sprites, links and other UML types.
|
|
103
|
+
On a syntax error, simplify the sketch using the notation above; never suggest
|
|
104
|
+
installing Java or sending source to a hosted renderer. For architecture,
|
|
105
|
+
flowcharts, or sequence diagrams, use the existing Mermaid support instead.
|
|
106
|
+
|
|
107
|
+
## Compact layout example
|
|
108
|
+
|
|
109
|
+
```clay-sketch
|
|
110
|
+
@startsalt
|
|
111
|
+
{+
|
|
112
|
+
{ **Workspace** | Project: Website | [Settings] }
|
|
113
|
+
{
|
|
114
|
+
{^"Navigation"
|
|
115
|
+
**Overview**
|
|
116
|
+
Projects
|
|
117
|
+
Team
|
|
118
|
+
[New project]
|
|
119
|
+
} |
|
|
120
|
+
{^"Projects"
|
|
121
|
+
{/ <b>Active | Archived }
|
|
122
|
+
{ "Search projects... " | [Create project] }
|
|
123
|
+
..
|
|
124
|
+
{# **Project** | **Status** | **Owner**
|
|
125
|
+
Website | In progress | Alex
|
|
126
|
+
Mobile app | Planning | Sam
|
|
127
|
+
}
|
|
128
|
+
..
|
|
129
|
+
[View all projects]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
@endsalt
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Whole-screen layout (Clay extension)
|
|
137
|
+
|
|
138
|
+
For full-page UI plans, start with the example below. The apostrophe directives
|
|
139
|
+
are Clay extensions: standard PlantUML treats them as comments and renders its
|
|
140
|
+
usual compact layout. Plain Salt without these directives retains reference
|
|
141
|
+
behavior. Do not claim the extended layout is standard PlantUML Salt.
|
|
142
|
+
|
|
143
|
+
- `' @clay-canvas 1440 900` sets logical screen dimensions (240–4096 per axis).
|
|
144
|
+
- `' @clay-layout root ...` configures the outer container. A path such as
|
|
145
|
+
`root.1.0` selects row 1, column 0, zero-based. Append row/column pairs for
|
|
146
|
+
nested containers. Update paths when moving content.
|
|
147
|
+
- `columns=240,*` fixes the sidebar width and gives remaining width to content.
|
|
148
|
+
`rows=64,*` fixes the header and lets the body fill the remaining height.
|
|
149
|
+
Tracks accept pixel integers, `auto` (content size), and `*` (share free space).
|
|
150
|
+
Supply exactly one track per row/column. Without tracks, rows/columns use auto.
|
|
151
|
+
- `padding=24 gap=16` sets container insets and spacing, without scaling text.
|
|
152
|
+
Defaults are 16 and 12. Set padding/gap to 0 for the outer screen shell.
|
|
153
|
+
- Containers fill allocated regions. Text/buttons retain their natural dimensions;
|
|
154
|
+
inputs expand to their column width. Use a `.` in a flexible row to reserve
|
|
155
|
+
deliberate empty space or push a footer down, not repeated blank rows.
|
|
156
|
+
- Rules target regular containers, not trees, tabs, menus or scrollbar containers.
|
|
157
|
+
Those widgets can still appear inside a screen using their natural dimensions.
|
|
158
|
+
- Overflow is reported, not silently shrunk. Increase the relevant track/canvas
|
|
159
|
+
or simplify the content. Keep titles inside the layout; do not combine screen
|
|
160
|
+
mode with scale/DPI changes or outside title/header/footer/legend directives.
|
|
161
|
+
|
|
162
|
+
```clay-sketch
|
|
163
|
+
@startsalt
|
|
164
|
+
' @clay-canvas 1440 900
|
|
165
|
+
' @clay-layout root padding=0 gap=0 columns=240,* rows=64,*
|
|
166
|
+
' @clay-layout root.0.0 padding=20 gap=24 columns=*,auto
|
|
167
|
+
' @clay-layout root.1.0 padding=24 gap=20 columns=* rows=auto,auto,auto,auto,auto,auto,*
|
|
168
|
+
' @clay-layout root.1.1 padding=32 gap=24 columns=* rows=auto,auto,auto,auto,*,auto
|
|
169
|
+
' @clay-layout root.1.1.0.0 padding=0 gap=24 columns=*,auto
|
|
170
|
+
' @clay-layout root.1.1.2.0 padding=0 gap=16 columns=*,auto
|
|
171
|
+
' @clay-layout root.1.1.3.0 padding=16 gap=16 columns=*,140,180,140 rows=40,48,48,48
|
|
172
|
+
{+
|
|
173
|
+
{ **Clay Settings** | [My account] } | *
|
|
174
|
+
{
|
|
175
|
+
[Back to project]
|
|
176
|
+
Profile
|
|
177
|
+
Appearance
|
|
178
|
+
Models
|
|
179
|
+
**MCP connections**
|
|
180
|
+
Notifications
|
|
181
|
+
.
|
|
182
|
+
} |
|
|
183
|
+
{
|
|
184
|
+
{ <size:20>**MCP connections** | [Add connection] }
|
|
185
|
+
Connect tools and services for your conversations.
|
|
186
|
+
{ "Search connections..." | ^All statuses^ }
|
|
187
|
+
{-
|
|
188
|
+
**Connection** | **Type** | **Status** | **Action**
|
|
189
|
+
Team knowledge | Remote URL | Connected | [Manage]
|
|
190
|
+
Support tools | Remote URL | Sign-in required | [Sign in]
|
|
191
|
+
My computer | Extension | Offline | [Manage]
|
|
192
|
+
}
|
|
193
|
+
.
|
|
194
|
+
Connections are personal to your account.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
@endsalt
|
|
198
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
var MAX_SOURCE_BYTES = 100000;
|
|
2
|
+
var MAX_SVG_BYTES = 8 * 1024 * 1024;
|
|
3
|
+
|
|
4
|
+
function failure(message, status) {
|
|
5
|
+
var error = new Error(message);
|
|
6
|
+
error.status = status || 422;
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isPlantUmlFile(file) {
|
|
11
|
+
return /\.(puml|plantuml|pu|uml|salt)$/i.test(String(file || ""));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function prepareSource(source, file) {
|
|
15
|
+
source = String(source || "").replace(/^\uFEFF/, "");
|
|
16
|
+
if (Buffer.byteLength(source) > MAX_SOURCE_BYTES) throw failure("Diagram is too large to preview (100 KB maximum).", 413);
|
|
17
|
+
if (!source.trim()) throw failure("This diagram is empty.");
|
|
18
|
+
if (/\.salt$/i.test(file) && !/@start\w+/i.test(source)) source = "@startsalt\n" + source + "\n@endsalt";
|
|
19
|
+
if (!/^\s*@start\w+\b/im.test(source)) throw failure("Add @startsalt / @endsalt or @startuml / @enduml around the diagram.");
|
|
20
|
+
if ((source.match(/^\s*@start\w+\b/gim) || []).length > 1) throw failure("Preview supports one diagram per file. Split these diagrams into separate files.");
|
|
21
|
+
return source;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderPlantUml(source, options) {
|
|
25
|
+
options = options || {};
|
|
26
|
+
return new Promise(function (resolve, reject) {
|
|
27
|
+
setImmediate(async function () {
|
|
28
|
+
try {
|
|
29
|
+
if (options.signal && options.signal.aborted) throw failure("Diagram rendering cancelled.", 499);
|
|
30
|
+
source = prepareSource(source, "diagram.puml");
|
|
31
|
+
var tree = require("./salt-parser").parseSalt(source);
|
|
32
|
+
await require('./salt-images').resolveImages(tree, options);
|
|
33
|
+
if (options.signal && options.signal.aborted) throw failure("Diagram rendering cancelled.", 499);
|
|
34
|
+
var svg = require("./salt-svg").renderSaltSvg(tree);
|
|
35
|
+
if (Buffer.byteLength(svg) > MAX_SVG_BYTES) throw failure("Rendered wireframe is too large.", 413);
|
|
36
|
+
resolve(svg);
|
|
37
|
+
} catch (error) { reject(error); }
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { renderPlantUml: renderPlantUml, prepareSource: prepareSource,
|
|
43
|
+
isPlantUmlFile: isPlantUmlFile, MAX_SOURCE_BYTES: MAX_SOURCE_BYTES };
|
package/lib/project-file-http.js
CHANGED
|
@@ -14,8 +14,10 @@ function attachFileHTTP(ctx) {
|
|
|
14
14
|
var access = ctx.requestAccess || attachRequestAccess(Object.assign({}, ctx, {
|
|
15
15
|
getOsUserInfoForWs: ctx.getOsUserInfoForReq,
|
|
16
16
|
}));
|
|
17
|
+
var plantUml = require("./project-plantuml").attachPlantUmlHTTP(Object.assign({}, ctx, { requestAccess: access }));
|
|
17
18
|
|
|
18
19
|
function handleHTTP(req, res, urlPath) {
|
|
20
|
+
if (plantUml.handleHTTP(req, res, urlPath)) return true;
|
|
19
21
|
var download = urlPath.startsWith("/api/file/download?");
|
|
20
22
|
if (req.method !== "GET" || (!download && !urlPath.startsWith("/api/file?"))) return false;
|
|
21
23
|
try {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var filePaths = require("./project-file-path");
|
|
3
|
+
var renderer = require("./plantuml-renderer");
|
|
4
|
+
|
|
5
|
+
function attachPlantUmlHTTP(ctx) {
|
|
6
|
+
var wireframes = require("./project-wireframe-http").attachWireframeHTTP(ctx);
|
|
7
|
+
function handleHTTP(req, res, urlPath) {
|
|
8
|
+
if (wireframes.handleHTTP(req, res, urlPath)) return true;
|
|
9
|
+
if (!urlPath.startsWith("/api/file/plantuml?")) return false;
|
|
10
|
+
function fail(error) {
|
|
11
|
+
if (res.destroyed) return;
|
|
12
|
+
var mapped = filePaths.fileError(error);
|
|
13
|
+
res.writeHead(error.status || mapped.status, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
|
|
14
|
+
res.end(error.status ? error.message : mapped.message);
|
|
15
|
+
}
|
|
16
|
+
if (req.method !== "GET") { fail({ status: 405, message: "Use GET to preview a diagram." }); return true; }
|
|
17
|
+
var scope;
|
|
18
|
+
var requested;
|
|
19
|
+
var source;
|
|
20
|
+
try {
|
|
21
|
+
scope = ctx.requestAccess.fileScope(req);
|
|
22
|
+
requested = new URLSearchParams(urlPath.substring(urlPath.indexOf("?"))).get("path");
|
|
23
|
+
var file = filePaths.resolveFilePath(ctx.cwd, requested, scope);
|
|
24
|
+
if (!renderer.isPlantUmlFile(file)) throw { status: 415, message: "Select a PlantUML or Salt file to preview." };
|
|
25
|
+
var stat = scope.identity ? ctx.fsAsUser("stat", { file: file }, scope.identity) : fs.statSync(file);
|
|
26
|
+
if (stat.size > renderer.MAX_SOURCE_BYTES) throw { status: 413, message: "Diagram is too large to preview (100 KB maximum)." };
|
|
27
|
+
source = scope.identity ? ctx.fsAsUser("read", { file: file, readContent: true }, scope.identity).content : fs.readFileSync(file, "utf8");
|
|
28
|
+
source = renderer.prepareSource(source, requested);
|
|
29
|
+
} catch (error) { fail(error); return true; }
|
|
30
|
+
var controller = new AbortController();
|
|
31
|
+
function cancel() { controller.abort(); }
|
|
32
|
+
if (res.once) res.once("close", cancel);
|
|
33
|
+
(ctx.renderPlantUml || renderer.renderPlantUml)(source, { signal: controller.signal }).then(function (svg) {
|
|
34
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
35
|
+
try { ctx.requestAccess.fileScope(req); }
|
|
36
|
+
catch (error) { fail(error); return; }
|
|
37
|
+
res.writeHead(200, {
|
|
38
|
+
"Content-Type": "image/svg+xml; charset=utf-8", "Cache-Control": "no-store",
|
|
39
|
+
"X-Content-Type-Options": "nosniff", "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
40
|
+
});
|
|
41
|
+
res.end(svg);
|
|
42
|
+
}).catch(fail).finally(function () { if (res.removeListener) res.removeListener("close", cancel); });
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return { handleHTTP: handleHTTP };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { attachPlantUmlHTTP: attachPlantUmlHTTP };
|
|
@@ -2,6 +2,9 @@ var fs = require("fs");
|
|
|
2
2
|
var path = require("path");
|
|
3
3
|
var sessionDocumentMcp = require("./session-document-mcp-server");
|
|
4
4
|
|
|
5
|
+
var wireframeRenderer = require("./plantuml-renderer");
|
|
6
|
+
var WIREFRAME_PROMPT = "When the user asks to see a wireframe, visualize a UI plan or sketch a page, automatically use the built-in clay-sketch skill even if they do not name it. Requests such as 'show me a wireframe of our work' use the current project and conversation as context. Do not ask them to invoke the skill or choose a format. Use ASCII wireframes only when explicitly requested. Read its SKILL.md from the shared skills catalog for the supported Salt syntax and whole-page examples. For an entire screen use its Clay canvas/region layout directives, not magnification of a compact diagram. Show complete @startsalt/@endsalt source in a fenced clay-sketch block; Clay renders it inline with no installation or external service. For requested revisions, call present_wireframe with complete source, a stable id and title to open/update the temporary Sketch viewer in the right panel, and include the same revised sketch in your response for history. The viewer has Save to project and Download; edits happen through conversation. Do not create a file unless requested. Other UML types and advanced Salt syntax are unsupported; use Mermaid for architecture or sequence diagrams.";
|
|
7
|
+
|
|
5
8
|
var DOCUMENT_PROMPT = "When the user's primary request is to create or revise Markdown, call present_markdown_edit once per targeted document with its resolved .md/.mdx path, immediately before that document's first Edit or Write. Do not call it for incidental Markdown changes during coding, maintenance, tests, or refactoring.";
|
|
6
9
|
|
|
7
10
|
function toolResult(value) {
|
|
@@ -78,9 +81,22 @@ function attachSessionDocument(ctx) {
|
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
function presentWireframe(args, caller) {
|
|
85
|
+
if (!caller) return toolError("present_wireframe requires a session-bound tool server");
|
|
86
|
+
if (typeof args.source !== "string" || !/^[a-zA-Z0-9_-]{1,80}$/.test(args.id || "")) return toolError("Provide source and a short stable id using letters, digits, hyphens or underscores.");
|
|
87
|
+
try {
|
|
88
|
+
var source = wireframeRenderer.prepareSource(args.source, "diagram.puml");
|
|
89
|
+
require("./salt-parser").parseSalt(source);
|
|
90
|
+
if (!/^\s*@end(?:salt|uml)\b/im.test(source)) return toolError("Provide a complete @startsalt/@endsalt or @startuml/@enduml diagram.");
|
|
91
|
+
sendToSession(caller.localId, { type: "wireframe_present", source: source, id: args.id, title: String(args.title || "clay-sketch").slice(0, 100), sessionId: caller.localId });
|
|
92
|
+
return toolResult({ ready: true, id: args.id, saved: false });
|
|
93
|
+
} catch (e) { return toolError(e.message); }
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
function getToolDefs(boundSession) {
|
|
82
97
|
if (isMate) return [];
|
|
83
98
|
return sessionDocumentMcp.getToolDefs({
|
|
99
|
+
presentWireframe: function (args) { return presentWireframe(args, boundSession || null); },
|
|
84
100
|
present: function (args) { return present(args, boundSession || null); },
|
|
85
101
|
});
|
|
86
102
|
}
|
|
@@ -96,7 +112,7 @@ function attachSessionDocument(ctx) {
|
|
|
96
112
|
|
|
97
113
|
return {
|
|
98
114
|
createMcpServer: createMcpServer,
|
|
99
|
-
getSystemPrompt: function () { return isMate ? "" : DOCUMENT_PROMPT; },
|
|
115
|
+
getSystemPrompt: function () { return isMate ? "" : DOCUMENT_PROMPT + "\n\n" + WIREFRAME_PROMPT; },
|
|
100
116
|
getToolDefs: getToolDefs,
|
|
101
117
|
};
|
|
102
118
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var renderer = require('./plantuml-renderer');
|
|
4
|
+
var filePaths = require('./project-file-path');
|
|
5
|
+
|
|
6
|
+
function error(status, message) { return Object.assign(new Error(message), { status: status }); }
|
|
7
|
+
function readBody(req) {
|
|
8
|
+
return new Promise(function (resolve, reject) {
|
|
9
|
+
var chunks = [];
|
|
10
|
+
var size = 0;
|
|
11
|
+
req.on('data', function (chunk) {
|
|
12
|
+
size += Buffer.byteLength(chunk);
|
|
13
|
+
if (size > 650000) { chunks = []; reject(error(413, 'Diagram request is too large.')); return; }
|
|
14
|
+
chunks.push(chunk);
|
|
15
|
+
});
|
|
16
|
+
req.on('aborted', function () { reject(error(400, 'Request cancelled.')); });
|
|
17
|
+
req.on('error', reject);
|
|
18
|
+
req.on('end', function () {
|
|
19
|
+
if (size > 650000) return;
|
|
20
|
+
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))); }
|
|
21
|
+
catch (e) { reject(error(400, 'Invalid diagram request.')); }
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function attachWireframeHTTP(ctx) {
|
|
27
|
+
function projectAccess(req) {
|
|
28
|
+
if (!ctx.requestAccess.canAccessProject(req, ctx.slug)) throw error(403, 'Project access is not permitted.');
|
|
29
|
+
}
|
|
30
|
+
function save(req, body, source) {
|
|
31
|
+
var scope = ctx.requestAccess.fileScope(req);
|
|
32
|
+
var requested = body.path;
|
|
33
|
+
if (typeof requested !== 'string' || !/\.puml$/i.test(requested) || requested.indexOf('\0') !== -1 || path.isAbsolute(requested)) {
|
|
34
|
+
throw error(400, 'Choose a project-relative .puml path.');
|
|
35
|
+
}
|
|
36
|
+
var root = fs.realpathSync(ctx.cwd);
|
|
37
|
+
var target = path.resolve(root, requested);
|
|
38
|
+
var parent = filePaths.resolveFilePath(root, path.dirname(target), { projectBound: true });
|
|
39
|
+
target = path.join(parent, path.basename(target));
|
|
40
|
+
try {
|
|
41
|
+
if (scope.identity) ctx.fsAsUser('write', { file: target, content: source, flag: 'wx' }, scope.identity);
|
|
42
|
+
else fs.writeFileSync(target, source, { encoding: 'utf8', flag: 'wx' });
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e.code === 'EEXIST' || /EEXIST/.test(String(e.stderr || ''))) throw error(409, 'A file already exists at this path. Choose a new name.');
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
return path.relative(root, target).split(path.sep).join('/');
|
|
48
|
+
}
|
|
49
|
+
function handleHTTP(req, res, urlPath) {
|
|
50
|
+
if (urlPath !== '/api/wireframe/render' && urlPath !== '/api/wireframe/save') return false;
|
|
51
|
+
function fail(e) {
|
|
52
|
+
if (res.destroyed || res.writableEnded) return;
|
|
53
|
+
var mapped = filePaths.fileError(e);
|
|
54
|
+
res.writeHead(e.status || mapped.status, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
55
|
+
res.end(e.status ? e.message : mapped.message);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
projectAccess(req);
|
|
59
|
+
if (req.method !== 'POST') throw error(405, 'Use POST for wireframes.');
|
|
60
|
+
if (!/^application\/json\b/i.test(req.headers['content-type'] || '') || req.headers['sec-fetch-site'] === 'cross-site') throw error(415, 'Use a same-origin JSON request.');
|
|
61
|
+
} catch (e) { fail(e); return true; }
|
|
62
|
+
var controller = new AbortController();
|
|
63
|
+
function cancel() { controller.abort(); }
|
|
64
|
+
res.once('close', cancel);
|
|
65
|
+
readBody(req).then(function (body) {
|
|
66
|
+
if (controller.signal.aborted) return;
|
|
67
|
+
projectAccess(req);
|
|
68
|
+
if (!body || typeof body.source !== 'string') throw error(400, 'Diagram source is required.');
|
|
69
|
+
var source = renderer.prepareSource(body.source, body.language === 'salt' ? 'diagram.salt' : 'diagram.puml');
|
|
70
|
+
if (urlPath === '/api/wireframe/save') {
|
|
71
|
+
var saved = save(req, body, source);
|
|
72
|
+
res.writeHead(201, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
73
|
+
res.end(JSON.stringify({ path: saved }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
return (ctx.renderPlantUml || renderer.renderPlantUml)(source, { signal: controller.signal }).then(function (svg) {
|
|
77
|
+
if (controller.signal.aborted) return;
|
|
78
|
+
projectAccess(req);
|
|
79
|
+
res.writeHead(200, { 'Content-Type': 'image/svg+xml; charset=utf-8', 'Cache-Control': 'no-store',
|
|
80
|
+
'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox" });
|
|
81
|
+
res.end(svg);
|
|
82
|
+
});
|
|
83
|
+
}).catch(fail).finally(function () { res.removeListener('close', cancel); });
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return { handleHTTP: handleHTTP };
|
|
87
|
+
}
|
|
88
|
+
module.exports = { attachWireframeHTTP: attachWireframeHTTP };
|
package/lib/public/app.js
CHANGED
|
@@ -360,6 +360,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
360
360
|
savedMainSlug: null,
|
|
361
361
|
connected: false,
|
|
362
362
|
fileReadRequest: null,
|
|
363
|
+
fileViewerIsPlantUml: false,
|
|
364
|
+
plantUmlPreviewJob: null,
|
|
365
|
+
wireframeDraft: null,
|
|
366
|
+
wireframeRenderQueue: [],
|
|
367
|
+
wireframeRenderActive: null,
|
|
368
|
+
wireframeRenderCache: [],
|
|
363
369
|
pendingFileNavigation: null,
|
|
364
370
|
rightWorkbenchOwner: null,
|
|
365
371
|
rightWorkbenchRevision: 0,
|
|
@@ -2629,3 +2629,8 @@
|
|
|
2629
2629
|
animation: message-blink 0.5s ease 3;
|
|
2630
2630
|
border-radius: 12px;
|
|
2631
2631
|
}
|
|
2632
|
+
|
|
2633
|
+
/* PlantUML is rendered locally and displayed as an inert, scalable image. */
|
|
2634
|
+
.file-viewer-plantuml-preview { padding: 20px; min-height: 100%; box-sizing: border-box; }
|
|
2635
|
+
.file-viewer-plantuml-preview img { display: block; max-width: 100%; height: auto; margin: 0 auto; background: #fff; border-radius: 4px; }
|
|
2636
|
+
.file-viewer-plantuml-preview [role="status"]:empty { display: none; }
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
.wireframe-card { margin: 16px 0; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--bg); }
|
|
2
|
+
.wireframe-card header { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--border-subtle); }
|
|
3
|
+
.wireframe-card header strong { flex: 1; font-size: 12px; color: var(--text-secondary); }
|
|
4
|
+
.wireframe-card button, #wireframe-panel button { font: inherit; font-size: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px; background: var(--bg-alt); color: var(--text); cursor: pointer; }
|
|
5
|
+
.wireframe-card button:hover, #wireframe-panel button:hover { background: var(--bg-hover); }
|
|
6
|
+
.wireframe-card :focus-visible, #wireframe-panel :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
7
|
+
.wireframe-canvas { padding: 16px; overflow: auto; }
|
|
8
|
+
.wireframe-canvas img, .wireframe-workbench-canvas img { display: block; max-width: 100%; height: auto; margin: 0 auto; background: white; border-radius: 4px; }
|
|
9
|
+
.wireframe-card details { border-top: 1px solid var(--border-subtle); }
|
|
10
|
+
.wireframe-card summary { padding: 8px 14px; font-size: 12px; color: var(--text-muted); cursor: pointer; }
|
|
11
|
+
.wireframe-card details pre { margin: 0; border-radius: 0; }
|
|
12
|
+
#wireframe-panel { position: relative; width: 50%; max-width: 720px; min-width: 360px; height: calc(100% - 16px); min-height: 0; margin: 8px 8px 8px 10px; align-self: center; flex-shrink: 0; display: flex; flex-direction: column; overflow: hidden; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 12px; }
|
|
13
|
+
.wireframe-topbar { display: flex; align-items: center; gap: 8px; height: 48px; padding: 0 12px; flex-shrink: 0; border-bottom: 1px solid var(--border-subtle); }
|
|
14
|
+
.wireframe-topbar strong { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; }
|
|
15
|
+
#wireframe-panel .lucide { width: 15px; height: 15px; flex-shrink: 0; }
|
|
16
|
+
.wireframe-window-actions { display: flex; gap: 4px; margin-left: auto; }
|
|
17
|
+
#wireframe-panel .wireframe-window-actions button { display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; }
|
|
18
|
+
.wireframe-toolbar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border-subtle); }
|
|
19
|
+
.wireframe-toolbar span { flex: 1; font-size: 11px; color: var(--text-muted); }
|
|
20
|
+
.wireframe-workbench-canvas { flex: 1; min-height: 0; overflow: auto; padding: 20px; }
|
|
21
|
+
.wireframe-workbench-canvas p, .wireframe-canvas p { color: var(--text-muted); font-size: 12px; overflow-wrap: anywhere; }
|
|
22
|
+
.wireframe-save-form { padding: 12px; display: flex; flex-wrap: wrap; align-items: end; gap: 8px; background: var(--bg-alt); }
|
|
23
|
+
.wireframe-save-form[hidden] { display: none; }
|
|
24
|
+
.wireframe-save-form label { flex: 1; min-width: 140px; font-size: 12px; }
|
|
25
|
+
.wireframe-save-form input { display: block; width: 100%; box-sizing: border-box; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 8px; margin-top: 5px; font: inherit; }
|
|
26
|
+
.wireframe-save-status { margin: 0; padding: 8px 12px; font-size: 12px; overflow-wrap: anywhere; }
|
|
27
|
+
.wireframe-save-status:empty { display: none; }
|
|
28
|
+
#wireframe-panel button:disabled { opacity: .5; cursor: wait; }
|
|
29
|
+
@media (min-width: 1024px) {
|
|
30
|
+
#wireframe-panel { box-shadow: 4px 8px 18px rgba(var(--shadow-rgb), .14), 2px 2px 4px rgba(var(--shadow-rgb), .08), inset 0 1px 0 rgba(255,255,255,.04); animation: workbench-panel-in .18s cubic-bezier(.2,.8,.2,1); }
|
|
31
|
+
#wireframe-panel.wireframe-wide { width: 70%; max-width: 1200px; }
|
|
32
|
+
#wireframe-panel.panel-fullscreen { width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; box-shadow: none; animation: none; }
|
|
33
|
+
}
|
|
34
|
+
@media (max-width: 1023px) {
|
|
35
|
+
#wireframe-panel { position: fixed; inset: 0; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; z-index: 210; padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box; }
|
|
36
|
+
#wireframe-panel .wireframe-window-actions [data-wide], #wireframe-panel .wireframe-window-actions [data-full] { display: none; }
|
|
37
|
+
}
|
|
38
|
+
@media (prefers-reduced-motion: reduce) { #wireframe-panel { animation: none; } }
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
.wireframe-toolbar { flex-wrap: wrap; }
|
|
43
|
+
.wireframe-zoom { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
|
|
44
|
+
.wireframe-zoom button { font: inherit; font-size: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px; background: var(--bg-alt); color: var(--text); cursor: pointer; }
|
|
45
|
+
.wireframe-zoom [data-zoom-value] { min-width: 55px; font-variant-numeric: tabular-nums; }
|
|
46
|
+
.wireframe-zoom [aria-pressed="true"] { background: var(--bg-hover); }
|
|
47
|
+
.wireframe-zoom button:disabled, #wireframe-panel .wireframe-zoom button:disabled { opacity: .4; cursor: default; }
|
|
48
|
+
.wireframe-file-canvas { overflow: auto; min-width: 0; padding-top: 16px; }
|
|
49
|
+
.file-viewer-plantuml-preview > .wireframe-zoom { position: sticky; top: 0; z-index: 1; width: fit-content; padding: 6px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
|
|
50
|
+
@media (max-width: 700px) { .wireframe-toolbar > span { flex-basis: 100%; } }
|
|
51
|
+
|
|
52
|
+
/* Shared sketch identity stays inside the existing right-panel shell. */
|
|
53
|
+
.wireframe-card, #wireframe-panel, .file-viewer-plantuml-preview { --sketch-accent: #c98b64; }
|
|
54
|
+
.wireframe-card { border-radius: 14px; border-top: 2px solid var(--sketch-accent); }
|
|
55
|
+
.sketch-brand { display: flex; align-items: center; gap: 9px; flex-shrink: 0; }
|
|
56
|
+
.wireframe-card header .sketch-brand { flex: 1; }
|
|
57
|
+
.sketch-mark { display: grid; place-items: center; width: 28px; height: 28px; border: 1px solid var(--sketch-accent); border-radius: 8px 8px 8px 2px; color: var(--sketch-accent); font-size: 23px; line-height: 1; }
|
|
58
|
+
.sketch-wordmark { font-size: 14px; font-weight: 700; letter-spacing: -.4px; color: var(--text); }
|
|
59
|
+
.sketch-wordmark > span { margin-left: 3px; font-weight: 400; color: var(--sketch-accent); }
|
|
60
|
+
.sketch-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px 14px; border-bottom: 1px solid var(--border-subtle); }
|
|
61
|
+
.sketch-credit { font-size: 11px; color: var(--text-muted); text-decoration: none; }
|
|
62
|
+
.sketch-credit:hover { color: var(--text); text-decoration: underline; }
|
|
63
|
+
.sketch-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border-subtle); }
|
|
64
|
+
.sketch-footer > span { font-size: 11px; color: var(--text-muted); }
|
|
65
|
+
.wireframe-topbar { height: 56px; gap: 14px; border-top: 2px solid var(--sketch-accent); }
|
|
66
|
+
.wireframe-topbar .sketch-title { min-width: 0; color: var(--text-secondary); font-size: 12px; font-weight: 400; }
|
|
67
|
+
.wireframe-canvas, .wireframe-workbench-canvas, .wireframe-file-canvas { background-image: radial-gradient(circle, var(--border) .7px, transparent .8px); background-size: 16px 16px; }
|
|
68
|
+
.wireframe-canvas img, .wireframe-workbench-canvas img, .wireframe-file-canvas img { box-shadow: 0 2px 10px rgba(0,0,0,.1); }
|
|
69
|
+
.sketch-tools :focus-visible, .sketch-footer :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
70
|
+
@media (max-width: 480px) {
|
|
71
|
+
.wireframe-topbar { gap: 8px; }
|
|
72
|
+
.wireframe-topbar .sketch-title { display: none; }
|
|
73
|
+
.sketch-footer > span { flex-basis: 100%; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Keep drawing controls together and give the canvas the available space. */
|
|
77
|
+
.wireframe-card header { gap: 8px; padding: 8px 12px; flex-wrap: wrap; }
|
|
78
|
+
.wireframe-card .sketch-mark { width: 22px; height: 22px; font-size: 19px; }
|
|
79
|
+
.wireframe-card header button { padding: 5px 8px; }
|
|
80
|
+
.wireframe-canvas { padding: 12px; }
|
|
81
|
+
.sketch-source-footer { position: relative; border-top: 1px solid var(--border-subtle); }
|
|
82
|
+
.sketch-source-footer details { border: 0; }
|
|
83
|
+
.sketch-source-footer summary { padding: 8px 175px 8px 12px; }
|
|
84
|
+
.sketch-source-footer > div { position: absolute; top: 7px; right: 12px; }
|
|
85
|
+
.wireframe-card .sketch-credit { font-size: 10px; color: var(--text-muted); }
|
|
86
|
+
.wireframe-topbar { height: 44px; }
|
|
87
|
+
.wireframe-toolbar { padding: 6px 10px; gap: 6px; }
|
|
88
|
+
.wireframe-workbench-canvas { padding: 12px; }
|
|
89
|
+
.sketch-footer { padding: 6px 12px; }
|
|
90
|
+
@media (max-width: 480px) {
|
|
91
|
+
.wireframe-card header { gap: 6px; }
|
|
92
|
+
.wireframe-card .sketch-brand { gap: 5px; }
|
|
93
|
+
.wireframe-card .sketch-wordmark { font-size: 12px; }
|
|
94
|
+
.wireframe-card header button { font-size: 11px; padding: 6px; }
|
|
95
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { handleLinearResult } from './linear-panel.js';
|
|
2
2
|
import { handleSharedBrowserMessage } from './shared-browser.js';
|
|
3
|
+
import { openWireframe } from './wireframe-workbench.js';
|
|
3
4
|
import { syncGithubSessions } from './session-github.js';
|
|
4
5
|
import { handleIssuesMessageInContext as handleIssuesMessage } from './issues-context.js';
|
|
5
6
|
// app-messages.js - WebSocket message router
|
|
@@ -1148,7 +1149,7 @@ export function processMessage(msg) {
|
|
|
1148
1149
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
1149
1150
|
} else if (msg.name === "ask_user_questions") {
|
|
1150
1151
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
1151
|
-
} else if (msg.name === "present_markdown_edit" || (msg.name && msg.name.indexOf("present_markdown_edit") !== -1)) {
|
|
1152
|
+
} else if (msg.name === "present_wireframe" || (msg.name && msg.name.endsWith("__present_wireframe")) || msg.name === "present_markdown_edit" || (msg.name && msg.name.indexOf("present_markdown_edit") !== -1)) {
|
|
1152
1153
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
1153
1154
|
} else if (getTodoTools()[msg.name]) {
|
|
1154
1155
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
@@ -1584,6 +1585,12 @@ export function processMessage(msg) {
|
|
|
1584
1585
|
handleFileChanged(msg);
|
|
1585
1586
|
break;
|
|
1586
1587
|
|
|
1588
|
+
case "wireframe_present":
|
|
1589
|
+
if (!store.get('replayingHistory') && !store.get('dmMode') && (!msg.sessionId || msg.sessionId === store.get('activeSessionId'))) {
|
|
1590
|
+
if (store.get('paneMode') || !store.get('splitPanes')) openWireframe(msg);
|
|
1591
|
+
}
|
|
1592
|
+
break;
|
|
1593
|
+
|
|
1587
1594
|
case "markdown_edit_present":
|
|
1588
1595
|
if (!store.get('replayingHistory') && !store.get('dmMode')) {
|
|
1589
1596
|
if (store.get('paneMode')) forwardPaneMarkdownPresentation(msg);
|