dsh-model-everything 0.6.0 → 0.6.2
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/lib/client.js +41 -57
- package/lib/frame.js +40 -0
- package/lib/gate.js +15 -13
- package/lib/types/frame.d.ts +23 -0
- package/package.json +1 -1
- package/skills/model-everything/SKILL.md +1 -1
- package/skills/model-everything-diagram/SKILL.md +1 -1
- package/src/client.ts +17 -58
- package/src/frame.ts +42 -0
- package/src/gate.ts +15 -13
package/lib/client.js
CHANGED
|
@@ -3837,6 +3837,30 @@ window.__ModuleLoader__.load({
|
|
|
3837
3837
|
return block.replace(/\n+Question:\s*$/, "");
|
|
3838
3838
|
}
|
|
3839
3839
|
|
|
3840
|
+
// src/frame.ts
|
|
3841
|
+
var CHROME_HEIGHT = 56;
|
|
3842
|
+
function fullScreenFrame() {
|
|
3843
|
+
return {
|
|
3844
|
+
position: "fixed",
|
|
3845
|
+
top: String(CHROME_HEIGHT) + "px",
|
|
3846
|
+
left: "16px",
|
|
3847
|
+
right: "16px",
|
|
3848
|
+
bottom: "16px",
|
|
3849
|
+
zIndex: "60"
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
function dismissOnBackdrop(full, onDismiss) {
|
|
3853
|
+
if (!full || onDismiss === void 0) return {};
|
|
3854
|
+
return {
|
|
3855
|
+
onClick: (event) => {
|
|
3856
|
+
if (event?.target === event?.currentTarget) onDismiss();
|
|
3857
|
+
}
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3860
|
+
function leavesFullScreen(key) {
|
|
3861
|
+
return key === "Escape";
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3840
3864
|
// src/transcript.ts
|
|
3841
3865
|
var TRANSCRIPT_MARKER = "Diagram annotations -";
|
|
3842
3866
|
var QUESTION = "Question:";
|
|
@@ -4291,7 +4315,7 @@ window.__ModuleLoader__.load({
|
|
|
4291
4315
|
const markup = typeof returned === "string" && returned !== "" ? returned : null;
|
|
4292
4316
|
const hasDrawing = markup !== null || typeof source === "string" && source.trim() !== "";
|
|
4293
4317
|
const useState = React.useState;
|
|
4294
|
-
if (useState === void 0) return cardSurface(React, source, markup, scheme, overrides, null, null, null
|
|
4318
|
+
if (useState === void 0) return cardSurface(React, source, markup, scheme, overrides, null, null, null);
|
|
4295
4319
|
const [annotate, setAnnotate] = useState(false);
|
|
4296
4320
|
const [picked, setPicked] = useState(null);
|
|
4297
4321
|
const [note, setNote] = useState("");
|
|
@@ -4301,7 +4325,6 @@ window.__ModuleLoader__.load({
|
|
|
4301
4325
|
const [pending, setPending] = useState([]);
|
|
4302
4326
|
const [open, setOpen] = useState(false);
|
|
4303
4327
|
const [sent, setSent] = useState("");
|
|
4304
|
-
const [report, setReport] = useState("");
|
|
4305
4328
|
const [, setDockTick] = useState(0);
|
|
4306
4329
|
const [dockFix, setDockFix] = useState({ dx: 0, dy: 0 });
|
|
4307
4330
|
React.useEffect?.(() => {
|
|
@@ -4348,6 +4371,17 @@ window.__ModuleLoader__.load({
|
|
|
4348
4371
|
}, []);
|
|
4349
4372
|
const [zoom, setZoom] = useState(1);
|
|
4350
4373
|
const [full, setFull] = useState(false);
|
|
4374
|
+
React.useEffect?.(() => {
|
|
4375
|
+
if (!full) return void 0;
|
|
4376
|
+
const doc = globalThis.document;
|
|
4377
|
+
const onKey = (event) => {
|
|
4378
|
+
if (leavesFullScreen(event?.key)) setFull(false);
|
|
4379
|
+
};
|
|
4380
|
+
doc?.addEventListener?.("keydown", onKey);
|
|
4381
|
+
return () => {
|
|
4382
|
+
doc?.removeEventListener?.("keydown", onKey);
|
|
4383
|
+
};
|
|
4384
|
+
}, [full]);
|
|
4351
4385
|
if (!hasDrawing) {
|
|
4352
4386
|
const keys = props.block === null || typeof props.block !== "object" ? "none" : Object.keys(props.block).join(",");
|
|
4353
4387
|
return React.createElement("div", { style: { fontSize: "12px", opacity: 0.7 } }, "no drawing in this call \xB7 block: " + (keys === "" ? "empty" : keys));
|
|
@@ -4404,17 +4438,6 @@ window.__ModuleLoader__.load({
|
|
|
4404
4438
|
}
|
|
4405
4439
|
}, style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "5px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.text, cursor: "pointer" } }, annotate ? t("noteOn") : t("note")),
|
|
4406
4440
|
annotate && picked === null ? React.createElement("span", { style: { fontSize: "11px", opacity: 0.7 } }, "\u70B9\u4E00\u4E2A\u8282\u70B9") : null,
|
|
4407
|
-
React.createElement("button", {
|
|
4408
|
-
type: "button",
|
|
4409
|
-
onClick: () => {
|
|
4410
|
-
try {
|
|
4411
|
-
setReport(domReport());
|
|
4412
|
-
} catch (error) {
|
|
4413
|
-
setReport("report failed: " + String(error?.message ?? error));
|
|
4414
|
-
}
|
|
4415
|
-
},
|
|
4416
|
-
style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "5px", border: "1px solid " + TOKEN.border, background: "transparent", color: TOKEN.dim, cursor: "pointer" }
|
|
4417
|
-
}, "\u8BCA\u65AD"),
|
|
4418
4441
|
sent === "" ? null : React.createElement("span", {
|
|
4419
4442
|
style: { fontSize: "11px", lineHeight: "16px", padding: "1px 6px", borderRadius: "9px", border: "1px solid " + TOKEN.border, color: TOKEN.dim, whiteSpace: "nowrap" }
|
|
4420
4443
|
}, sent)
|
|
@@ -4460,12 +4483,9 @@ window.__ModuleLoader__.load({
|
|
|
4460
4483
|
// The count is the body-level layer; this panel keeps the editor, which still needs its anchor.
|
|
4461
4484
|
{ document, dockChip: false }
|
|
4462
4485
|
) : null;
|
|
4463
|
-
|
|
4464
|
-
style: { margin: "6px 0 0", padding: "8px", borderRadius: "8px", border: "1px solid " + TOKEN.border, background: "rgba(127,127,127,0.08)", font: "11px ui-monospace, monospace", whiteSpace: "pre-wrap", userSelect: "text", maxHeight: "220px", overflow: "auto" }
|
|
4465
|
-
}, report);
|
|
4466
|
-
return cardSurface(React, source, markup, scheme, overrides, onPick, toggle, panel, reportPanel, { zoom: Number(zoom), full: Boolean(full), onZoom: (next) => setZoom(next), onFull: (next) => setFull(next) });
|
|
4486
|
+
return cardSurface(React, source, markup, scheme, overrides, onPick, toggle, panel, { zoom: Number(zoom), onDismiss: () => setFull(false), full: Boolean(full), onZoom: (next) => setZoom(next), onFull: (next) => setFull(next) });
|
|
4467
4487
|
}
|
|
4468
|
-
function cardSurface(React, source, returned, scheme, overrides, onClick, toggle, panel,
|
|
4488
|
+
function cardSurface(React, source, returned, scheme, overrides, onClick, toggle, panel, view = { zoom: 1, full: false, onZoom: () => {
|
|
4469
4489
|
}, onFull: () => {
|
|
4470
4490
|
} }) {
|
|
4471
4491
|
if (returned === null && (typeof source !== "string" || source.trim() === "")) {
|
|
@@ -4504,12 +4524,11 @@ window.__ModuleLoader__.load({
|
|
|
4504
4524
|
"div",
|
|
4505
4525
|
{ style: { position: "relative", background: drawn.colours.background, padding: "6px", maxHeight: view.full ? "none" : "260px", overflow: "auto", flex: "1 1 auto", minHeight: "0" } },
|
|
4506
4526
|
drawing,
|
|
4507
|
-
panel
|
|
4508
|
-
report
|
|
4527
|
+
panel
|
|
4509
4528
|
);
|
|
4510
|
-
const frame = view.full ? {
|
|
4529
|
+
const frame = view.full ? { ...fullScreenFrame(), display: "flex", flexDirection: "column", ...chrome, boxShadow: "0 12px 40px rgba(0,0,0,0.35)" } : { display: "flex", flexDirection: "column", ...chrome };
|
|
4511
4530
|
const children = [header, body, refused].filter((child) => child !== null);
|
|
4512
|
-
return React.createElement("div", { style: frame }, ...children);
|
|
4531
|
+
return React.createElement("div", { style: frame, ...dismissOnBackdrop(view.full, view.onDismiss) }, ...children);
|
|
4513
4532
|
}
|
|
4514
4533
|
function currentSessionId() {
|
|
4515
4534
|
const sessions = services?.get?.("sessions");
|
|
@@ -4657,7 +4676,6 @@ window.__ModuleLoader__.load({
|
|
|
4657
4676
|
} else {
|
|
4658
4677
|
hit = findBlockElement(doc);
|
|
4659
4678
|
}
|
|
4660
|
-
chipAttempt = hit === void 0 ? "not found, try " + String(tries) : "found";
|
|
4661
4679
|
if (hit !== void 0) {
|
|
4662
4680
|
insertChipAfter(doc, hit, label);
|
|
4663
4681
|
return;
|
|
@@ -4696,7 +4714,6 @@ window.__ModuleLoader__.load({
|
|
|
4696
4714
|
var dockReact;
|
|
4697
4715
|
var dockEl;
|
|
4698
4716
|
var dockRetries = 0;
|
|
4699
|
-
var chipAttempt = "not attempted";
|
|
4700
4717
|
var chipAnchor;
|
|
4701
4718
|
var chipScrollWatched = false;
|
|
4702
4719
|
var dockReceipt = "";
|
|
@@ -5013,39 +5030,6 @@ window.__ModuleLoader__.load({
|
|
|
5013
5030
|
} catch {
|
|
5014
5031
|
}
|
|
5015
5032
|
}
|
|
5016
|
-
function domReport() {
|
|
5017
|
-
const doc = globalThis.document;
|
|
5018
|
-
if (doc === void 0) return "no document";
|
|
5019
|
-
const lines = [];
|
|
5020
|
-
const rect = (el) => {
|
|
5021
|
-
const r = el?.getBoundingClientRect?.();
|
|
5022
|
-
return r === void 0 || r === null ? "absent" : [Math.round(r.left), Math.round(r.top), Math.round(r.width), Math.round(r.height)].join(",");
|
|
5023
|
-
};
|
|
5024
|
-
const probe = (name, selector) => {
|
|
5025
|
-
const el = doc.querySelector(selector);
|
|
5026
|
-
lines.push(name + " " + (el === null || el === void 0 ? "absent" : "l,t,w,h=" + rect(el)));
|
|
5027
|
-
};
|
|
5028
|
-
probe("[data-composer-card]", "[data-composer-card]");
|
|
5029
|
-
probe("[data-composer-seat]", "[data-composer-seat]");
|
|
5030
|
-
probe("textarea/contenteditable", 'textarea, [contenteditable="true"]');
|
|
5031
|
-
probe("[data-annotation-chip]", "[data-annotation-chip]");
|
|
5032
|
-
probe("[data-conversation-scroll]", "[data-conversation-scroll]");
|
|
5033
|
-
probe("widthHandle", '[class*="widthHandle"]');
|
|
5034
|
-
const dock = doc.querySelector("[data-dme-dock]");
|
|
5035
|
-
lines.push("dock " + (dock === null ? "absent" : "l,t,w,h=" + rect(dock) + " connected=" + String(dock.isConnected)));
|
|
5036
|
-
const chips = doc.querySelectorAll("[data-dme-chip]");
|
|
5037
|
-
lines.push("chips " + String(chips.length) + " " + Array.from(chips).map((chip) => rect(chip)).join(" | "));
|
|
5038
|
-
lines.push("pendingBlock " + String(pendingBlock === "" ? "empty" : pendingBlock.split(String.fromCharCode(10)).length + " lines"));
|
|
5039
|
-
lines.push("chipAttempt " + chipAttempt);
|
|
5040
|
-
lines.push("dockedCount " + String(dockReact?.count ?? 0) + " placedText " + String(placedText.length));
|
|
5041
|
-
lines.push("viewport " + String(globalThis.innerWidth) + "x" + String(globalThis.innerHeight));
|
|
5042
|
-
const report = lines.join(String.fromCharCode(10));
|
|
5043
|
-
try {
|
|
5044
|
-
globalThis.console?.log?.(report);
|
|
5045
|
-
} catch {
|
|
5046
|
-
}
|
|
5047
|
-
return report;
|
|
5048
|
-
}
|
|
5049
5033
|
function installEnterCapture() {
|
|
5050
5034
|
if (pendingBlock === "") return;
|
|
5051
5035
|
const doc = globalThis.document;
|
package/lib/frame.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the full-screen card sits, and what a click on it means.
|
|
3
|
+
*
|
|
4
|
+
* The card is an overlay inside a page that already has a title bar. A full-bleed `inset: 0` sat on top of the
|
|
5
|
+
* window's own minimize/maximize/close controls and left no way out but the controls it was covering, so the
|
|
6
|
+
* top edge starts below that bar. Two ways out are part of the behaviour, not an extra: Escape, and a click on
|
|
7
|
+
* the margin around the card.
|
|
8
|
+
*/
|
|
9
|
+
/** Height of the window chrome the overlay must not cover. */
|
|
10
|
+
const CHROME_HEIGHT = 56;
|
|
11
|
+
export function fullScreenFrame() {
|
|
12
|
+
return {
|
|
13
|
+
position: 'fixed',
|
|
14
|
+
top: String(CHROME_HEIGHT) + 'px',
|
|
15
|
+
left: '16px',
|
|
16
|
+
right: '16px',
|
|
17
|
+
bottom: '16px',
|
|
18
|
+
zIndex: '60',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The click handler for the overlay, or nothing when the card is not full screen.
|
|
23
|
+
*
|
|
24
|
+
* Only the overlay itself counts: the margin around the card is the reader saying "leave", while a click
|
|
25
|
+
* inside the card is the reader using it.
|
|
26
|
+
*/
|
|
27
|
+
export function dismissOnBackdrop(full, onDismiss) {
|
|
28
|
+
if (!full || onDismiss === undefined)
|
|
29
|
+
return {};
|
|
30
|
+
return {
|
|
31
|
+
onClick: (event) => {
|
|
32
|
+
if (event?.target === event?.currentTarget)
|
|
33
|
+
onDismiss();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Whether a key ends full screen. Escape does; nothing else in this UI does. */
|
|
38
|
+
export function leavesFullScreen(key) {
|
|
39
|
+
return key === 'Escape';
|
|
40
|
+
}
|
package/lib/gate.js
CHANGED
|
@@ -93,9 +93,9 @@ export function renderGateText(skills = shippedSkills(), extraGuidance = '') {
|
|
|
93
93
|
const rows = skills.map((skill) => '| ' + (SKILL_ROWS[skill] ?? 'see its catalog line') + ' | `' + skill + '` |');
|
|
94
94
|
const extra = extraGuidance.trim();
|
|
95
95
|
return `<EXTREMELY_IMPORTANT>
|
|
96
|
-
Before answering anything about code, a design or a diagram: load the skill
|
|
96
|
+
Before answering anything about code, a design or a diagram: load the matching skill and let its tool decide. Do not answer from memory.
|
|
97
97
|
|
|
98
|
-
dsh-model-everything
|
|
98
|
+
dsh-model-everything checks claims about designs and code: facts against the source, behaviour as a model, structure as a graph.
|
|
99
99
|
|
|
100
100
|
**Unsure which row applies?** Load \`model-everything\` first: it owns the doctrine, the schema and the routing to its siblings.
|
|
101
101
|
|
|
@@ -105,17 +105,19 @@ ${rows.join('\n')}
|
|
|
105
105
|
|
|
106
106
|
**Tools** (dsh):
|
|
107
107
|
|
|
108
|
-
- \`model_verify\` —
|
|
109
|
-
- \`model_compose_verify\` —
|
|
110
|
-
- \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback
|
|
111
|
-
- \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width.
|
|
112
|
-
- \`model_interleaving_scan\` — finds claims
|
|
113
|
-
- \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It
|
|
114
|
-
- \`uml_render\`, \`uml_parse\`, \`uml_review\` — draw a model, read a diagram into one, audit one
|
|
115
|
-
- \`uml_explain_labels\` — the
|
|
116
|
-
- \`uml_structure_verify\` — dependency review of a structure drawing
|
|
117
|
-
- \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size
|
|
118
|
-
- \`
|
|
108
|
+
- \`model_verify\` — a state machine: structural checks, adversarial probes, budgets, deadlines, regression.
|
|
109
|
+
- \`model_compose_verify\` — several machines: deadlock, a handshake that never fires, an unordered write (C4), a non-atomic update (C5).
|
|
110
|
+
- \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback.
|
|
111
|
+
- \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width.
|
|
112
|
+
- \`model_interleaving_scan\` — finds concurrency claims, names the family, routes each to what decides it. Proves nothing itself.
|
|
113
|
+
- \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It does not check the target.
|
|
114
|
+
- \`uml_render\`, \`uml_parse\`, \`uml_review\` — draw a model, read a diagram into one, audit one; round-trip measured.
|
|
115
|
+
- \`uml_explain_labels\` — the label and comment forms a notation accepts.
|
|
116
|
+
- \`uml_structure_verify\` — dependency review of a structure drawing. It audits the drawing, not the code.
|
|
117
|
+
- \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size, compare a Mermaid or PlantUML source. Undrawable lines are refused by number.
|
|
118
|
+
- \`model_automaton_verify\` — an automaton or two: what it accepts, trap states, language relation, with a witness word.
|
|
119
|
+
- \`model_edit\` — one named change to a model file plus the finding delta. Refuses an edit that leaves the model invalid.
|
|
120
|
+
- \`report_diff\` — baseline against current: findings added, removed or changed.
|
|
119
121
|
|
|
120
122
|
**Read a report**: gate on \`verdict\`, never on \`ok\`. \`verdictReason\` says why and \`nextSteps\` says what to do. \`ran\` says whether it executed, \`checksRun\` how much it examined: a \`skip\` is not a \`pass\`, and a code ending \`_UNDECIDED\` means the search hit its limit and decided nothing. Keys starting with \`_\` are notes: ignored, and left out of the model hash.
|
|
121
123
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the full-screen card sits, and what a click on it means.
|
|
3
|
+
*
|
|
4
|
+
* The card is an overlay inside a page that already has a title bar. A full-bleed `inset: 0` sat on top of the
|
|
5
|
+
* window's own minimize/maximize/close controls and left no way out but the controls it was covering, so the
|
|
6
|
+
* top edge starts below that bar. Two ways out are part of the behaviour, not an extra: Escape, and a click on
|
|
7
|
+
* the margin around the card.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fullScreenFrame(): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* The click handler for the overlay, or nothing when the card is not full screen.
|
|
12
|
+
*
|
|
13
|
+
* Only the overlay itself counts: the margin around the card is the reader saying "leave", while a click
|
|
14
|
+
* inside the card is the reader using it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function dismissOnBackdrop(full: boolean, onDismiss?: () => void): {
|
|
17
|
+
onClick?: (event: {
|
|
18
|
+
target?: unknown;
|
|
19
|
+
currentTarget?: unknown;
|
|
20
|
+
}) => void;
|
|
21
|
+
};
|
|
22
|
+
/** Whether a key ends full screen. Escape does; nothing else in this UI does. */
|
|
23
|
+
export declare function leavesFullScreen(key: string | undefined): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-everything",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Modeling abstraction, architecture visualisation and automated review for designs and code, including a refactoring harness: draw a Mermaid or PlantUML source and measure its structure, executable verification of machines and protocols with before/after regression, data-model and migration checks, concurrency claim routing, export to external checkers, dependency-graph review, and report diffing. Native plugin for DeepSeek Harness (dsh).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: model-everything
|
|
3
|
-
description: "Use when a document claims something checkable about code: API names, paths, enums, feasibility, protocol behaviour, timing, 'always/never'. Enumerates the claims and verifies each with file:line evidence, then builds an executable model when behaviour or safety is at stake. Siblings: model-everything-diagram, -uml, -design, -interleaving, -datamodel, -structure."
|
|
3
|
+
description: "Use when a document claims something checkable about code: API names, paths, enums, feasibility, protocol behaviour, timing, 'always/never'. Enumerates the claims and verifies each with file:line evidence, then builds an executable model when behaviour or safety is at stake. Siblings: model-everything-diagram, -uml, -design, -interleaving, -datamodel, -structure."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Model Everything
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: model-everything-diagram
|
|
3
|
-
description: "Use when drawing a diagram from its source text, showing one to a reader, or checking its structure: Mermaid and PlantUML subsets, structure budgets, and containment diffs. Reach for it when an explanation is clearer shown than told - a layout, a flow, a change - and put the picture beside the explanation. Every line it cannot draw is refused by number. The drawing proves layout, not design. Doctrine: the model-everything skill."
|
|
3
|
+
description: "Use when drawing a diagram from its source text, showing one to a reader, or checking its structure: Mermaid and PlantUML subsets, structure budgets, and containment diffs. Reach for it when an explanation is clearer shown than told - a layout, a flow, a change - and put the picture beside the explanation. Every line it cannot draw is refused by number. The drawing proves layout, not design. Doctrine: the model-everything skill."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Model Everything — Drawing
|
package/src/client.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { render } from './render/index.js'
|
|
|
18
18
|
import { toSvg } from './render/svg.js'
|
|
19
19
|
import { viewModel } from './render/viewmodel.js'
|
|
20
20
|
import { annotationBlock, annotationMessage, composerRect, fillComposer, findComposer, pageCensus, prependBlock, pushBlock, pushNote, sessionFromLocation, submitComposer, withoutEmptyQuestion, type DocumentLike } from './annotation.js'
|
|
21
|
+
import { dismissOnBackdrop, fullScreenFrame, leavesFullScreen } from './frame.js'
|
|
21
22
|
import { decorateTranscript, startTranscriptDecoration, type TranscriptDocument as TranscriptDocumentForClient } from './transcript.js'
|
|
22
23
|
|
|
23
24
|
export interface ReactLike {
|
|
@@ -375,7 +376,7 @@ function drawingCard(React: ReactLike, props: ToolCallProps, overrides: Partial<
|
|
|
375
376
|
// conversation view down with it.
|
|
376
377
|
const hasDrawing = markup !== null || (typeof source === 'string' && source.trim() !== '')
|
|
377
378
|
const useState = React.useState
|
|
378
|
-
if (useState === undefined) return cardSurface(React, source, markup, scheme, overrides, null, null, null
|
|
379
|
+
if (useState === undefined) return cardSurface(React, source, markup, scheme, overrides, null, null, null)
|
|
379
380
|
const [annotate, setAnnotate] = useState(false)
|
|
380
381
|
const [picked, setPicked] = useState<{ id: string } | null>(null)
|
|
381
382
|
const [note, setNote] = useState('')
|
|
@@ -386,8 +387,6 @@ function drawingCard(React: ReactLike, props: ToolCallProps, overrides: Partial<
|
|
|
386
387
|
const [open, setOpen] = useState(false)
|
|
387
388
|
// A note that left: this plugin's own chrome says so, where no other plugin works.
|
|
388
389
|
const [sent, setSent] = useState('')
|
|
389
|
-
// A self-report the reader can copy in one click, so a UI question is answered with facts instead of screenshots.
|
|
390
|
-
const [report, setReport] = useState('')
|
|
391
390
|
// The composer grows when a picture is pasted, and the docked control has to follow it.
|
|
392
391
|
const [, setDockTick] = useState(0)
|
|
393
392
|
// `position: fixed` is relative to the nearest transformed ancestor, not to the viewport. The chip therefore
|
|
@@ -435,6 +434,15 @@ function drawingCard(React: ReactLike, props: ToolCallProps, overrides: Partial<
|
|
|
435
434
|
}, [])
|
|
436
435
|
const [zoom, setZoom] = useState(1)
|
|
437
436
|
const [full, setFull] = useState(false)
|
|
437
|
+
// Escape leaves full screen. Document-level because the overlay is not focusable and the reader may not have
|
|
438
|
+
// clicked into it yet.
|
|
439
|
+
React.useEffect?.(() => {
|
|
440
|
+
if (!full) return undefined
|
|
441
|
+
const doc = (globalThis as { document?: { addEventListener?: (type: string, fn: (event: { key?: string }) => void) => void; removeEventListener?: (type: string, fn: (event: { key?: string }) => void) => void } }).document
|
|
442
|
+
const onKey = (event: { key?: string }): void => { if (leavesFullScreen(event?.key)) setFull(false) }
|
|
443
|
+
doc?.addEventListener?.('keydown', onKey)
|
|
444
|
+
return () => { doc?.removeEventListener?.('keydown', onKey) }
|
|
445
|
+
}, [full])
|
|
438
446
|
if (!hasDrawing) {
|
|
439
447
|
const keys = props.block === null || typeof props.block !== 'object' ? 'none' : Object.keys(props.block as Record<string, unknown>).join(',')
|
|
440
448
|
return React.createElement('div', { style: { fontSize: '12px', opacity: 0.7 } }, 'no drawing in this call · block: ' + (keys === '' ? 'empty' : keys))
|
|
@@ -494,13 +502,7 @@ function drawingCard(React: ReactLike, props: ToolCallProps, overrides: Partial<
|
|
|
494
502
|
{ style: { display: 'flex', gap: '6px', alignItems: 'center' } },
|
|
495
503
|
React.createElement('button', { type: 'button', title: 'Point at a node and send a note about it', onClick: () => { setAnnotate(!annotate); setStatus(''); if (annotate) { setPicked(null); setNote('') } }, style: { fontSize: '11px', lineHeight: '16px', padding: '1px 6px', borderRadius: '5px', border: '1px solid ' + TOKEN.border, background: 'transparent', color: TOKEN.text, cursor: 'pointer' } }, annotate ? t('noteOn') : t('note')),
|
|
496
504
|
annotate && picked === null ? React.createElement('span', { style: { fontSize: '11px', opacity: 0.7 } }, '点一个节点') : null,
|
|
497
|
-
|
|
498
|
-
type: 'button',
|
|
499
|
-
onClick: () => {
|
|
500
|
-
try { setReport(domReport()) } catch (error) { setReport('report failed: ' + String((error as { message?: unknown })?.message ?? error)) }
|
|
501
|
-
},
|
|
502
|
-
style: { fontSize: '11px', lineHeight: '16px', padding: '1px 6px', borderRadius: '5px', border: '1px solid ' + TOKEN.border, background: 'transparent', color: TOKEN.dim, cursor: 'pointer' },
|
|
503
|
-
}, '诊断'),
|
|
505
|
+
|
|
504
506
|
sent === '' ? null : React.createElement('span', {
|
|
505
507
|
style: { fontSize: '11px', lineHeight: '16px', padding: '1px 6px', borderRadius: '9px', border: '1px solid ' + TOKEN.border, color: TOKEN.dim, whiteSpace: 'nowrap' },
|
|
506
508
|
}, sent),
|
|
@@ -536,14 +538,11 @@ function drawingCard(React: ReactLike, props: ToolCallProps, overrides: Partial<
|
|
|
536
538
|
{ document, dockChip: false },
|
|
537
539
|
)
|
|
538
540
|
: null
|
|
539
|
-
|
|
540
|
-
style: { margin: '6px 0 0', padding: '8px', borderRadius: '8px', border: '1px solid ' + TOKEN.border, background: 'rgba(127,127,127,0.08)', font: '11px ui-monospace, monospace', whiteSpace: 'pre-wrap', userSelect: 'text', maxHeight: '220px', overflow: 'auto' },
|
|
541
|
-
}, report)
|
|
542
|
-
return cardSurface(React, source, markup, scheme, overrides, onPick, toggle, panel, reportPanel, { zoom: Number(zoom), full: Boolean(full), onZoom: (next: number) => setZoom(next), onFull: (next: boolean) => setFull(next) })
|
|
541
|
+
return cardSurface(React, source, markup, scheme, overrides, onPick, toggle, panel, { zoom: Number(zoom), onDismiss: () => setFull(false), full: Boolean(full), onZoom: (next: number) => setZoom(next), onFull: (next: boolean) => setFull(next) })
|
|
543
542
|
}
|
|
544
543
|
|
|
545
544
|
/** The drawing itself, plus whatever the reader is doing to it. One surface, drawn once. */
|
|
546
|
-
function cardSurface(React: ReactLike, source: unknown, returned: string | null, scheme: PaletteScheme, overrides: Partial<Palette>, onClick: ((event: never) => void) | null, toggle: unknown, panel: unknown,
|
|
545
|
+
function cardSurface(React: ReactLike, source: unknown, returned: string | null, scheme: PaletteScheme, overrides: Partial<Palette>, onClick: ((event: never) => void) | null, toggle: unknown, panel: unknown, view: { zoom: number; full: boolean; onZoom: (next: number) => void; onFull: (next: boolean) => void; onDismiss?: () => void } = { zoom: 1, full: false, onZoom: () => {}, onFull: () => {} }): unknown {
|
|
547
546
|
if (returned === null && (typeof source !== 'string' || source.trim() === '')) {
|
|
548
547
|
return React.createElement('div', { style: { fontSize: '12px', opacity: 0.7 } }, 'no drawing in this call')
|
|
549
548
|
}
|
|
@@ -595,13 +594,12 @@ function cardSurface(React: ReactLike, source: unknown, returned: string | null,
|
|
|
595
594
|
{ style: { position: 'relative', background: drawn.colours.background, padding: '6px', maxHeight: view.full ? 'none' : '260px', overflow: 'auto', flex: '1 1 auto', minHeight: '0' } },
|
|
596
595
|
drawing,
|
|
597
596
|
panel,
|
|
598
|
-
report,
|
|
599
597
|
)
|
|
600
598
|
const frame = view.full
|
|
601
|
-
? {
|
|
599
|
+
? { ...fullScreenFrame(), display: 'flex', flexDirection: 'column', ...chrome, boxShadow: '0 12px 40px rgba(0,0,0,0.35)' }
|
|
602
600
|
: { display: 'flex', flexDirection: 'column', ...chrome }
|
|
603
601
|
const children = [header, body, refused].filter((child) => child !== null)
|
|
604
|
-
return React.createElement('div', { style: frame }, ...children)
|
|
602
|
+
return React.createElement('div', { style: frame, ...dismissOnBackdrop(view.full, view.onDismiss) }, ...children)
|
|
605
603
|
}
|
|
606
604
|
|
|
607
605
|
/**
|
|
@@ -857,7 +855,6 @@ function attachBelowBubble(label: string, typed: string): void {
|
|
|
857
855
|
} else {
|
|
858
856
|
hit = findBlockElement(doc)
|
|
859
857
|
}
|
|
860
|
-
chipAttempt = hit === undefined ? 'not found, try ' + String(tries) : 'found'
|
|
861
858
|
if (hit !== undefined) { insertChipAfter(doc, hit, label); return }
|
|
862
859
|
} catch { /* a chip not shown is not a broken message */ }
|
|
863
860
|
if (tries < 20) timer(attempt, 500)
|
|
@@ -908,8 +905,6 @@ let dockReact: { count: number; open: boolean; items: Array<{ nodeId: string; no
|
|
|
908
905
|
|
|
909
906
|
let dockEl: any
|
|
910
907
|
let dockRetries = 0
|
|
911
|
-
// What the last attempt to place the per-message count saw, for the diagnostic report.
|
|
912
|
-
let chipAttempt = 'not attempted'
|
|
913
908
|
let chipAnchor: any
|
|
914
909
|
let chipScrollWatched = false
|
|
915
910
|
let dockReceipt = ''
|
|
@@ -1238,43 +1233,7 @@ function observeDivider(doc: any): void {
|
|
|
1238
1233
|
} catch { /* a missing handle is a layout that never changes */ }
|
|
1239
1234
|
}
|
|
1240
1235
|
|
|
1241
|
-
|
|
1242
|
-
* One click, one paste: what the page actually contains.
|
|
1243
|
-
*
|
|
1244
|
-
* Every layout question so far cost a screenshot and a guess. The facts that decide those questions are cheap to
|
|
1245
|
-
* collect - which anchors exist, where they are, whether our nodes are still in the document - so they are
|
|
1246
|
-
* collected here and shown as selectable text.
|
|
1247
|
-
*/
|
|
1248
|
-
function domReport(): string {
|
|
1249
|
-
const doc = (globalThis as { document?: any }).document
|
|
1250
|
-
if (doc === undefined) return 'no document'
|
|
1251
|
-
const lines: string[] = []
|
|
1252
|
-
const rect = (el: any): string => {
|
|
1253
|
-
const r = el?.getBoundingClientRect?.()
|
|
1254
|
-
return r === undefined || r === null ? 'absent' : [Math.round(r.left), Math.round(r.top), Math.round(r.width), Math.round(r.height)].join(',')
|
|
1255
|
-
}
|
|
1256
|
-
const probe = (name: string, selector: string): void => {
|
|
1257
|
-
const el = doc.querySelector(selector)
|
|
1258
|
-
lines.push(name + ' ' + (el === null || el === undefined ? 'absent' : 'l,t,w,h=' + rect(el)))
|
|
1259
|
-
}
|
|
1260
|
-
probe('[data-composer-card]', '[data-composer-card]')
|
|
1261
|
-
probe('[data-composer-seat]', '[data-composer-seat]')
|
|
1262
|
-
probe('textarea/contenteditable', 'textarea, [contenteditable="true"]')
|
|
1263
|
-
probe('[data-annotation-chip]', '[data-annotation-chip]')
|
|
1264
|
-
probe('[data-conversation-scroll]', '[data-conversation-scroll]')
|
|
1265
|
-
probe('widthHandle', '[class*="widthHandle"]')
|
|
1266
|
-
const dock = doc.querySelector('[data-dme-dock]')
|
|
1267
|
-
lines.push('dock ' + (dock === null ? 'absent' : 'l,t,w,h=' + rect(dock) + ' connected=' + String(dock.isConnected)))
|
|
1268
|
-
const chips = doc.querySelectorAll('[data-dme-chip]')
|
|
1269
|
-
lines.push('chips ' + String(chips.length) + ' ' + Array.from(chips).map((chip) => rect(chip)).join(' | '))
|
|
1270
|
-
lines.push('pendingBlock ' + String(pendingBlock === '' ? 'empty' : pendingBlock.split(String.fromCharCode(10)).length + ' lines'))
|
|
1271
|
-
lines.push('chipAttempt ' + chipAttempt)
|
|
1272
|
-
lines.push('dockedCount ' + String(dockReact?.count ?? 0) + ' placedText ' + String(placedText.length))
|
|
1273
|
-
lines.push('viewport ' + String((globalThis as { innerWidth?: number }).innerWidth) + 'x' + String((globalThis as { innerHeight?: number }).innerHeight))
|
|
1274
|
-
const report = lines.join(String.fromCharCode(10))
|
|
1275
|
-
try { (globalThis as { console?: { log?: (value: string) => void } }).console?.log?.(report) } catch { /* console may be gone */ }
|
|
1276
|
-
return report
|
|
1277
|
-
}
|
|
1236
|
+
|
|
1278
1237
|
|
|
1279
1238
|
function refreshDock(): void { scheduleDock() }
|
|
1280
1239
|
function installEnterCapture(): void {
|
package/src/frame.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the full-screen card sits, and what a click on it means.
|
|
3
|
+
*
|
|
4
|
+
* The card is an overlay inside a page that already has a title bar. A full-bleed `inset: 0` sat on top of the
|
|
5
|
+
* window's own minimize/maximize/close controls and left no way out but the controls it was covering, so the
|
|
6
|
+
* top edge starts below that bar. Two ways out are part of the behaviour, not an extra: Escape, and a click on
|
|
7
|
+
* the margin around the card.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Height of the window chrome the overlay must not cover. */
|
|
11
|
+
const CHROME_HEIGHT = 56
|
|
12
|
+
|
|
13
|
+
export function fullScreenFrame(): Record<string, string> {
|
|
14
|
+
return {
|
|
15
|
+
position: 'fixed',
|
|
16
|
+
top: String(CHROME_HEIGHT) + 'px',
|
|
17
|
+
left: '16px',
|
|
18
|
+
right: '16px',
|
|
19
|
+
bottom: '16px',
|
|
20
|
+
zIndex: '60',
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The click handler for the overlay, or nothing when the card is not full screen.
|
|
26
|
+
*
|
|
27
|
+
* Only the overlay itself counts: the margin around the card is the reader saying "leave", while a click
|
|
28
|
+
* inside the card is the reader using it.
|
|
29
|
+
*/
|
|
30
|
+
export function dismissOnBackdrop(full: boolean, onDismiss?: () => void): { onClick?: (event: { target?: unknown; currentTarget?: unknown }) => void } {
|
|
31
|
+
if (!full || onDismiss === undefined) return {}
|
|
32
|
+
return {
|
|
33
|
+
onClick: (event) => {
|
|
34
|
+
if (event?.target === event?.currentTarget) onDismiss()
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Whether a key ends full screen. Escape does; nothing else in this UI does. */
|
|
40
|
+
export function leavesFullScreen(key: string | undefined): boolean {
|
|
41
|
+
return key === 'Escape'
|
|
42
|
+
}
|
package/src/gate.ts
CHANGED
|
@@ -111,9 +111,9 @@ export function renderGateText(skills: string[] = shippedSkills(), extraGuidance
|
|
|
111
111
|
const rows = skills.map((skill) => '| ' + (SKILL_ROWS[skill] ?? 'see its catalog line') + ' | `' + skill + '` |')
|
|
112
112
|
const extra = extraGuidance.trim()
|
|
113
113
|
return `<EXTREMELY_IMPORTANT>
|
|
114
|
-
Before answering anything about code, a design or a diagram: load the skill
|
|
114
|
+
Before answering anything about code, a design or a diagram: load the matching skill and let its tool decide. Do not answer from memory.
|
|
115
115
|
|
|
116
|
-
dsh-model-everything
|
|
116
|
+
dsh-model-everything checks claims about designs and code: facts against the source, behaviour as a model, structure as a graph.
|
|
117
117
|
|
|
118
118
|
**Unsure which row applies?** Load \`model-everything\` first: it owns the doctrine, the schema and the routing to its siblings.
|
|
119
119
|
|
|
@@ -123,17 +123,19 @@ ${rows.join('\n')}
|
|
|
123
123
|
|
|
124
124
|
**Tools** (dsh):
|
|
125
125
|
|
|
126
|
-
- \`model_verify\` —
|
|
127
|
-
- \`model_compose_verify\` —
|
|
128
|
-
- \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback
|
|
129
|
-
- \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width.
|
|
130
|
-
- \`model_interleaving_scan\` — finds claims
|
|
131
|
-
- \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It
|
|
132
|
-
- \`uml_render\`, \`uml_parse\`, \`uml_review\` — draw a model, read a diagram into one, audit one
|
|
133
|
-
- \`uml_explain_labels\` — the
|
|
134
|
-
- \`uml_structure_verify\` — dependency review of a structure drawing
|
|
135
|
-
- \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size
|
|
136
|
-
- \`
|
|
126
|
+
- \`model_verify\` — a state machine: structural checks, adversarial probes, budgets, deadlines, regression.
|
|
127
|
+
- \`model_compose_verify\` — several machines: deadlock, a handshake that never fires, an unordered write (C4), a non-atomic update (C5).
|
|
128
|
+
- \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback.
|
|
129
|
+
- \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width.
|
|
130
|
+
- \`model_interleaving_scan\` — finds concurrency claims, names the family, routes each to what decides it. Proves nothing itself.
|
|
131
|
+
- \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It does not check the target.
|
|
132
|
+
- \`uml_render\`, \`uml_parse\`, \`uml_review\` — draw a model, read a diagram into one, audit one; round-trip measured.
|
|
133
|
+
- \`uml_explain_labels\` — the label and comment forms a notation accepts.
|
|
134
|
+
- \`uml_structure_verify\` — dependency review of a structure drawing. It audits the drawing, not the code.
|
|
135
|
+
- \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size, compare a Mermaid or PlantUML source. Undrawable lines are refused by number.
|
|
136
|
+
- \`model_automaton_verify\` — an automaton or two: what it accepts, trap states, language relation, with a witness word.
|
|
137
|
+
- \`model_edit\` — one named change to a model file plus the finding delta. Refuses an edit that leaves the model invalid.
|
|
138
|
+
- \`report_diff\` — baseline against current: findings added, removed or changed.
|
|
137
139
|
|
|
138
140
|
**Read a report**: gate on \`verdict\`, never on \`ok\`. \`verdictReason\` says why and \`nextSteps\` says what to do. \`ran\` says whether it executed, \`checksRun\` how much it examined: a \`skip\` is not a \`pass\`, and a code ending \`_UNDECIDED\` means the search hit its limit and decided nothing. Keys starting with \`_\` are notes: ignored, and left out of the model hash.
|
|
139
141
|
|