@plannotator/ui 0.40.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/HANDOFF.md +110 -5
  2. package/README.md +6 -2
  3. package/components/DiagramBlock.tsx +363 -0
  4. package/components/GraphvizBlock.tsx +22 -597
  5. package/components/MermaidBlock.tsx +17 -664
  6. package/components/Viewer.tsx +60 -5
  7. package/components/diagram/DiagramCanvas.tsx +427 -0
  8. package/components/diagram/DiagramComposer.tsx +135 -0
  9. package/components/diagram/DiagramOverlay.tsx +215 -0
  10. package/components/diagram/DiagramPending.tsx +60 -0
  11. package/components/diagram/DiagramPopout.tsx +72 -0
  12. package/components/diagram/DiagramSourcePane.tsx +244 -0
  13. package/components/diagram/DiagramViewer.tsx +285 -0
  14. package/components/diagram/anchorClaims.ts +71 -0
  15. package/components/diagram/diagramControls.ts +41 -0
  16. package/components/diagram/index.ts +39 -0
  17. package/components/diagram/svgContentSize.ts +47 -0
  18. package/components/diagram/useDiagramComments.ts +341 -0
  19. package/components/diagram/useDiagramRender.ts +91 -0
  20. package/components/diagram/useDiagramSourceDraft.ts +143 -0
  21. package/components/diagram/useDiagramViewport.ts +156 -0
  22. package/components/html-viewer/bridge-script.asset.js +25 -0
  23. package/components/html-viewer/bridge-script.lite.ts +1 -1
  24. package/components/html-viewer/bridge-script.ts +37 -0
  25. package/hooks/useAnnotationHighlighter.ts +5 -0
  26. package/package.json +5 -3
  27. package/styles.css +1 -1
  28. package/types.ts +5 -0
  29. package/utils/diagram-anchor-graphviz.ts +143 -0
  30. package/utils/diagram-anchor.ts +401 -0
  31. package/utils/diagram-projection.ts +66 -0
  32. package/utils/diagram-render.ts +668 -0
  33. package/utils/graphviz.ts +93 -0
  34. package/utils/parser.ts +15 -1
  35. package/components/mermaidSvg.ts +0 -33
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Graphviz runtime slot — the same shape as `./mermaid`, one per engine so
3
+ * a host fills either independently.
4
+ *
5
+ * ONE code path feeds the renderer slot (`./diagram-render`):
6
+ * `loadGraphvizRuntime()`. It resolves at once from a filled slot and
7
+ * otherwise imports the engine lazily: `@viz-js/viz` (about 1.2 MB of
8
+ * Emscripten JS with the wasm inlined) is fetched on the first dot fence, a
9
+ * failed import is dropped from the memo so the next call issues a fresh
10
+ * `import()`, and the block re-attempts once and offers Retry. In
11
+ * Plannotator's single-file builds the import is inlined and resolves from
12
+ * the bundle; a host that bundles by route fetches it on demand.
13
+ *
14
+ * This module has NO static import of `@viz-js/viz`; the only place the
15
+ * dependency is named at runtime is the default loader's `import()`.
16
+ */
17
+ import type { Viz } from '@viz-js/viz';
18
+
19
+ export type GraphvizRuntime = Viz;
20
+
21
+ /** Who filled the slot. */
22
+ export type GraphvizRuntimeSource = 'loader' | 'host';
23
+
24
+ export type GraphvizRuntimeLoader = () => Promise<Viz>;
25
+
26
+ /** Default lazy loader: import the engine and instantiate its wasm once. */
27
+ const defaultGraphvizLoader: GraphvizRuntimeLoader = () => import('@viz-js/viz').then((m) => m.instance());
28
+
29
+ let runtime: Viz | null = null;
30
+ let runtimeSource: GraphvizRuntimeSource | null = null;
31
+ let loader: GraphvizRuntimeLoader = defaultGraphvizLoader;
32
+ let pending: Promise<Viz> | null = null;
33
+
34
+ /** Delay before the one automatic re-attempt after a failed lazy import. */
35
+ let retryDelayMs = 750;
36
+
37
+ /** Current runtime, or `null` while the slot is empty. */
38
+ export function getGraphvizRuntime(): Viz | null {
39
+ return runtime;
40
+ }
41
+
42
+ /** How the current runtime was registered, or `null` while the slot is empty. */
43
+ export function getGraphvizRuntimeSource(): GraphvizRuntimeSource | null {
44
+ return runtimeSource;
45
+ }
46
+
47
+ /** Register an already-instantiated engine (a host with its own import). */
48
+ export function setGraphvizRuntime(next: Viz, source: GraphvizRuntimeSource = 'host'): void {
49
+ runtime = next;
50
+ runtimeSource = source;
51
+ pending = null;
52
+ }
53
+
54
+ /** The renderer's retry delay for the lazy path. */
55
+ export function getGraphvizRetryDelayMs(): number {
56
+ return retryDelayMs;
57
+ }
58
+
59
+ /**
60
+ * Resolve the engine: at once from a filled slot, otherwise through the
61
+ * loader. A rejected load is dropped from the memo so the next call (the
62
+ * automatic re-attempt, a later mount, or the Retry button) issues a fresh
63
+ * `import()` instead of replaying the cached rejection.
64
+ */
65
+ export function loadGraphvizRuntime(): Promise<Viz> {
66
+ if (runtime) return Promise.resolve(runtime);
67
+ if (!pending) {
68
+ const attempt = loader().then(
69
+ (loaded) => {
70
+ setGraphvizRuntime(loaded, 'loader');
71
+ return loaded;
72
+ },
73
+ (err: unknown) => {
74
+ if (pending === attempt) pending = null;
75
+ throw err;
76
+ },
77
+ );
78
+ pending = attempt;
79
+ }
80
+ return pending;
81
+ }
82
+
83
+ /** Test hook: empty the slot, stand in for the lazy import, shorten the retry delay. */
84
+ export function __setGraphvizRuntimeLoaderForTests(
85
+ next: GraphvizRuntimeLoader | undefined,
86
+ options?: { retryDelayMs?: number },
87
+ ): void {
88
+ runtime = null;
89
+ runtimeSource = null;
90
+ pending = null;
91
+ loader = next ?? defaultGraphvizLoader;
92
+ retryDelayMs = options?.retryDelayMs ?? 750;
93
+ }
package/utils/parser.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Block, Annotation, CodeAnnotation, EditorAnnotation, ImageAttachment } from '../types';
2
2
  import { planDenyFeedback } from '@plannotator/core/feedback-templates';
3
3
  import { resolveReplyParents } from '@plannotator/core/annotation-threads';
4
+ import { diagramAnchorLocationLine, parseDiagramAnchor } from '@plannotator/core/diagram-anchor';
4
5
  import { skillReferenceExportBlock } from './skillReferences';
5
6
 
6
7
  /**
@@ -1249,7 +1250,7 @@ export const exportAnnotationEntry = (ann: any, opts: ElementContextExportOption
1249
1250
  output += `[${ann.text}] ${commentHeadingLine(ann)}\n`;
1250
1251
  if (ann.quickLabelTip) output += `> ${ann.quickLabelTip}\n`;
1251
1252
  } else {
1252
- output += `${commentHeadingLine(ann)}\n> ${ann?.text ?? ''}\n`;
1253
+ output += `${commentHeadingLine(ann)}\n${diagramLocationExportLine(ann)}> ${ann?.text ?? ''}\n`;
1253
1254
  }
1254
1255
  }
1255
1256
  const resolvedOpts: ElementContextExportOptions = {
@@ -1267,6 +1268,16 @@ export const exportAnnotationEntry = (ann: any, opts: ElementContextExportOption
1267
1268
  return output;
1268
1269
  };
1269
1270
 
1271
+ /** The location line under a comment made on a rendered diagram part:
1272
+ * `Diagram node Approve? (D), line 4` — the part's own id (what the agent
1273
+ * greps the fence for) and the DOCUMENT line that declares it. Emits
1274
+ * nothing for every other annotation, keeping their output byte-identical;
1275
+ * a malformed anchor (an older or foreign writer) is skipped, never thrown. */
1276
+ const diagramLocationExportLine = (ann: any): string => {
1277
+ const anchor = ann?.diagramAnchor === undefined ? null : parseDiagramAnchor(ann.diagramAnchor);
1278
+ return anchor === null ? '' : `${safeInline(diagramAnchorLocationLine(anchor), 600)}\n`;
1279
+ };
1280
+
1270
1281
  const lineLabelForAnnotation = (blocks: Block[], ann: any): string | null => {
1271
1282
  if (!ann.blockId || ann.type === 'GLOBAL_COMMENT') return null;
1272
1283
  if (typeof ann.blockId === 'string' && ann.blockId.startsWith('diff-block-')) return null;
@@ -1435,11 +1446,13 @@ export const exportAnnotations = (
1435
1446
  case 'COMMENT':
1436
1447
  if (ann.isQuickLabel) {
1437
1448
  output += `[${ann.text}] ${commentHeadingLine(ann)}\n`;
1449
+ output += diagramLocationExportLine(ann);
1438
1450
  if (ann.quickLabelTip) {
1439
1451
  output += `> ${ann.quickLabelTip}\n`;
1440
1452
  }
1441
1453
  } else {
1442
1454
  output += `${commentHeadingLine(ann)}\n`;
1455
+ output += diagramLocationExportLine(ann);
1443
1456
  output += `> ${ann.text}\n`;
1444
1457
  }
1445
1458
  break;
@@ -1554,6 +1567,7 @@ export const exportLinkedDocAnnotations = (
1554
1567
 
1555
1568
  case 'COMMENT':
1556
1569
  output += `${commentHeadingLine(ann)}\n`;
1570
+ output += diagramLocationExportLine(ann);
1557
1571
  output += `> ${ann.text}\n`;
1558
1572
  break;
1559
1573
 
@@ -1,33 +0,0 @@
1
- // Pure SVG-markup helpers for MermaidBlock. Kept free of React and the
2
- // mermaid library so they can be unit-tested without loading mermaid's
3
- // browser-only `initialize()` (which throws in headless test environments).
4
-
5
- // Bake sizing attrs into the SVG markup so they survive repeated
6
- // dangerouslySetInnerHTML re-injection — imperative setAttribute gets wiped.
7
- export function normalizeMermaidSvgMarkup(markup: string): string {
8
- return markup.replace(/<svg\b([^>]*)>/i, (_match, attrs: string) => {
9
- let next = attrs;
10
-
11
- if (/\bstyle\s*=\s*"/i.test(next)) {
12
- next = next.replace(/\bstyle\s*=\s*"([^"]*)"/i, (_m, styleVal: string) => {
13
- const rules = styleVal
14
- .split(';')
15
- .map((s) => s.trim())
16
- .filter((s) => s.length > 0 && !/^max-width\s*:/i.test(s));
17
- rules.push('max-width: none');
18
- return `style="${rules.join('; ')}"`;
19
- });
20
- } else {
21
- next += ' style="max-width: none"';
22
- }
23
-
24
- if (!/\bpreserveAspectRatio\s*=/i.test(next)) {
25
- next += ' preserveAspectRatio="xMidYMid meet"';
26
- }
27
- if (!/\bheight\s*=/i.test(next)) {
28
- next += ' height="100%"';
29
- }
30
-
31
- return `<svg${next}>`;
32
- });
33
- }