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 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, 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
- const reportPanel = report === "" ? null : React.createElement("pre", {
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, report, view = { zoom: 1, full: false, onZoom: () => {
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 ? { position: "fixed", inset: "16px", zIndex: 60, display: "flex", flexDirection: "column", ...chrome, boxShadow: "0 12px 40px rgba(0,0,0,0.35)" } : { display: "flex", flexDirection: "column", ...chrome };
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 whose row matches and let its tool decide. Do not answer from memory.
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 is active: it checks claims about designs and code - facts against the source, behaviour as a model, structure as a graph.
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\` — one state machine: structural checks, adversarial probes, budgets, deadlines, regression.
109
- - \`model_compose_verify\` — two or more machines: deadlock, a rendezvous that never fires, and - with \`shared\` + \`accesses\` - an unordered write (C4) or a non-atomic update (C5).
110
- - \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback symmetry.
111
- - \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width. Graph shape is reported, not judged.
112
- - \`model_interleaving_scan\` — finds claims about threads, async and interrupts, names the family and routes each to what decides it. It proves nothing itself.
113
- - \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It exports; 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 fidelity is measured.
115
- - \`uml_explain_labels\` — the accepted label and comment forms for one notation.
116
- - \`uml_structure_verify\` — dependency review of a structure drawing, one level or several. It audits the drawing, not the code.
117
- - \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size and compare a Mermaid or PlantUML source. Undrawable lines are refused by number.
118
- - \`report_diff\` — baseline against current: what a change added, removed or changed.
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.0",
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, 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
- React.createElement('button', {
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
- const reportPanel = report === '' ? null : React.createElement('pre', {
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, report: unknown, view: { zoom: number; full: boolean; onZoom: (next: number) => void; onFull: (next: boolean) => void } = { zoom: 1, full: false, onZoom: () => {}, onFull: () => {} }): 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
- ? { position: 'fixed' as const, inset: '16px', zIndex: 60, display: 'flex', flexDirection: 'column', ...chrome, boxShadow: '0 12px 40px rgba(0,0,0,0.35)' }
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 whose row matches and let its tool decide. Do not answer from memory.
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 is active: it checks claims about designs and code - facts against the source, behaviour as a model, structure as a graph.
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\` — one state machine: structural checks, adversarial probes, budgets, deadlines, regression.
127
- - \`model_compose_verify\` — two or more machines: deadlock, a rendezvous that never fires, and - with \`shared\` + \`accesses\` - an unordered write (C4) or a non-atomic update (C5).
128
- - \`model_datamodel_verify\` — a data model and its migration: keys, domains, coverage, copy consistency, rollback symmetry.
129
- - \`model_design_review\` — a design against its own declarations: layering, boundaries, substitutability, interface width. Graph shape is reported, not judged.
130
- - \`model_interleaving_scan\` — finds claims about threads, async and interrupts, names the family and routes each to what decides it. It proves nothing itself.
131
- - \`model_export\` — UPPAAL, TLA+, PRISM or SPIN input from a model. It exports; 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 fidelity is measured.
133
- - \`uml_explain_labels\` — the accepted label and comment forms for one notation.
134
- - \`uml_structure_verify\` — dependency review of a structure drawing, one level or several. It audits the drawing, not the code.
135
- - \`diagram_render\`, \`diagram_review\`, \`diagram_diff\` — draw, size and compare a Mermaid or PlantUML source. Undrawable lines are refused by number.
136
- - \`report_diff\` — baseline against current: what a change added, removed or changed.
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