dsh-model-everything 0.6.1 → 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
+ }
@@ -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.1",
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",
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
+ }