agentic-mermaid 0.1.0 → 0.1.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/CHANGELOG.md +149 -4
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +25 -32
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/fonts/FONT-LICENSES.md +9 -5
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +81 -1822
- package/dist/agent.js +336 -36839
- package/dist/agentic-mermaid-mcp.js +7 -37908
- package/dist/am.js +33638 -36261
- package/dist/chunk-4QECACIA.js +4702 -0
- package/dist/chunk-CDPKJ2BQ.js +37 -0
- package/dist/chunk-QKDLMOJJ.js +3487 -0
- package/dist/chunk-WI35L7IR.js +615 -0
- package/dist/chunk-WN3YDOBG.js +149 -0
- package/dist/chunk-ZYMMKQ3I.js +62637 -0
- package/dist/index-D54dqSdC.d.ts +6777 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -32836
- package/dist/mcp-cli-SBQSTJFC.js +17 -0
- package/docs/README.md +14 -6
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +70 -43
- package/server.json +41 -0
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/AGENT_NATIVE.md +0 -602
- package/Instructions_for_agents.md +0 -83
- package/TODO.md +0 -662
- package/assets/hero.png +0 -0
- package/dist/index-x2Cj1dVv.d.ts +0 -1089
- package/docs/agent-api-cookbook.md +0 -308
- package/docs/agent-mutation-policy.md +0 -104
- package/docs/agent-workflow-examples.md +0 -46
- package/docs/api.md +0 -203
- package/docs/ascii.md +0 -103
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/comparison.md +0 -94
- package/docs/config.md +0 -105
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -71
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/custom-style-cookbook.md +0 -154
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -433
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -125
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/diagram-families.md +0 -193
- package/docs/features.md +0 -147
- package/docs/fork-differences.md +0 -130
- package/docs/getting-started.md +0 -175
- package/docs/issue-derived-test-cases.md +0 -156
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -496
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -118
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/mcp-code-mode-rationale.md +0 -101
- package/docs/mcp-http-transport.md +0 -223
- package/docs/mermaid-layout-complaints.md +0 -837
- package/docs/mutation-testing.md +0 -206
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/docs/quality.md +0 -212
- package/docs/react.md +0 -97
- package/docs/style-authoring.md +0 -133
- package/docs/testing-strategy.md +0 -335
- package/docs/theming.md +0 -162
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -364
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -337
- package/src/agent/families-builtin.ts +0 -934
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -826
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -414
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -439
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -117
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -979
- package/src/agent/verify.ts +0 -571
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -189
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -128
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1117
- package/src/cli/init-agent.ts +0 -147
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/font-manifest.ts +0 -27
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -520
- package/src/journey/parser.ts +0 -218
- package/src/journey/renderer.ts +0 -811
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -387
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -72
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -639
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -454
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Quadrant structured body (promotes quadrant from opaque-only fallback
|
|
3
|
-
// semantics to structured mutation, following the journey/xychart/architecture
|
|
4
|
-
// pilots).
|
|
5
|
-
//
|
|
6
|
-
// Modeled grammar (mirrors the legacy renderer parser, src/quadrant/parser.ts):
|
|
7
|
-
// quadrantChart
|
|
8
|
-
// title <text>
|
|
9
|
-
// x-axis <near> [--> <far>]
|
|
10
|
-
// y-axis <near> [--> <far>]
|
|
11
|
-
// quadrant-1..quadrant-4 <label>
|
|
12
|
-
// <Label>: [x, y] x,y in [0,1]
|
|
13
|
-
//
|
|
14
|
-
// Mermaid quadrant numbering (reused from the renderer, src/quadrant/types.ts):
|
|
15
|
-
// 1 = top-right, 2 = top-left, 3 = bottom-left, 4 = bottom-right.
|
|
16
|
-
// Stored as a 0-based 4-tuple where index `n-1` holds quadrant-`n`.
|
|
17
|
-
//
|
|
18
|
-
// Structured-or-opaque: any other non-blank, non-comment line (accTitle,
|
|
19
|
-
// accDescr, styling `:::`/`classDef`, unmodeled syntax) returns null so the
|
|
20
|
-
// caller falls back to a lossless opaque body. The serializer re-emits a
|
|
21
|
-
// canonical form the legacy parser re-parses identically (differential-tested).
|
|
22
|
-
// The legacy renderer ERRORS LOUDLY on malformed lines / out-of-range coords;
|
|
23
|
-
// here those fall back to opaque (preserved verbatim) and still surface the
|
|
24
|
-
// loud render-time error.
|
|
25
|
-
// ============================================================================
|
|
26
|
-
|
|
27
|
-
import { unknownOpMessage } from './mutation-ops.ts'
|
|
28
|
-
import type {
|
|
29
|
-
QuadrantBody, QuadrantAxis, QuadrantMutationOp,
|
|
30
|
-
MutationError, Result, LayoutWarning, VerifyOptions,
|
|
31
|
-
} from './types.ts'
|
|
32
|
-
import { ok, err, DEFAULT_LABEL_CHAR_CAP } from './types.ts'
|
|
33
|
-
import { labelOverflowWarning } from './label-metrics.ts'
|
|
34
|
-
|
|
35
|
-
// ---- Number format ----------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
function formatNumber(n: number): string {
|
|
38
|
-
return String(n)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isCoord(n: unknown): n is number {
|
|
42
|
-
return typeof n === 'number' && Number.isFinite(n) && n >= 0 && n <= 1
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ---- Parser -----------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
const TITLE_RE = /^title\s+(.+)$/i
|
|
48
|
-
const AXIS_RE = /^([xy])-axis\s+(.+)$/i
|
|
49
|
-
const QUADRANT_RE = /^quadrant-([1-4])\s+(.+)$/i
|
|
50
|
-
const POINT_RE = /^(.+?)\s*:\s*\[\s*([^,\]]+)\s*,\s*([^,\]]+)\s*\]\s*$/
|
|
51
|
-
const COORD_RE = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/
|
|
52
|
-
|
|
53
|
-
function parseCoord(raw: string): number | null {
|
|
54
|
-
if (!COORD_RE.test(raw)) return null
|
|
55
|
-
const v = Number.parseFloat(raw)
|
|
56
|
-
return isCoord(v) ? v : null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Parse an axis tail `<near> [--> <far>]`, or null when malformed. */
|
|
60
|
-
function parseAxis(tail: string): QuadrantAxis | null {
|
|
61
|
-
const arrow = tail.indexOf('-->')
|
|
62
|
-
if (arrow >= 0) {
|
|
63
|
-
const near = tail.slice(0, arrow).trim()
|
|
64
|
-
const far = tail.slice(arrow + 3).trim()
|
|
65
|
-
if (!near || !far) return null
|
|
66
|
-
return { near, far }
|
|
67
|
-
}
|
|
68
|
-
const near = tail.trim()
|
|
69
|
-
if (!near) return null
|
|
70
|
-
return { near }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse quadrant body lines (header excluded). Returns a structured body only
|
|
75
|
-
* if EVERY non-blank, non-comment line is a modeled title / axis / quadrant /
|
|
76
|
-
* point directive with in-range coordinates and no duplicate point labels.
|
|
77
|
-
* Otherwise returns null (opaque fallback). Unlike pie/xychart, an empty
|
|
78
|
-
* quadrant chart (header only) still renders, so there is no minimum-content
|
|
79
|
-
* floor.
|
|
80
|
-
*/
|
|
81
|
-
export function parseQuadrantBody(lines: string[]): QuadrantBody | null {
|
|
82
|
-
const body: QuadrantBody = {
|
|
83
|
-
kind: 'quadrant',
|
|
84
|
-
quadrants: [undefined, undefined, undefined, undefined],
|
|
85
|
-
points: [],
|
|
86
|
-
}
|
|
87
|
-
const seen = new Set<string>()
|
|
88
|
-
|
|
89
|
-
for (const raw of lines) {
|
|
90
|
-
const line = raw.trim()
|
|
91
|
-
if (!line) continue
|
|
92
|
-
if (line.startsWith('%%')) continue
|
|
93
|
-
|
|
94
|
-
// Styling is unmodeled — fall back to opaque (legacy parser errors loudly).
|
|
95
|
-
if (/^classDef\b/i.test(line) || line.includes(':::')) return null
|
|
96
|
-
|
|
97
|
-
let m: RegExpMatchArray | null
|
|
98
|
-
|
|
99
|
-
if ((m = line.match(TITLE_RE))) {
|
|
100
|
-
const title = m[1]!.trim()
|
|
101
|
-
if (!title) return null
|
|
102
|
-
body.title = title
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if ((m = line.match(AXIS_RE))) {
|
|
107
|
-
const axis = parseAxis(m[2]!.trim())
|
|
108
|
-
if (!axis) return null
|
|
109
|
-
if (m[1]!.toLowerCase() === 'x') body.xAxis = axis
|
|
110
|
-
else body.yAxis = axis
|
|
111
|
-
continue
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if ((m = line.match(QUADRANT_RE))) {
|
|
115
|
-
const idx = Number.parseInt(m[1]!, 10) - 1
|
|
116
|
-
const label = m[2]!.trim()
|
|
117
|
-
if (!label) return null
|
|
118
|
-
body.quadrants[idx] = label
|
|
119
|
-
continue
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if ((m = line.match(POINT_RE))) {
|
|
123
|
-
const label = m[1]!.trim()
|
|
124
|
-
const x = parseCoord(m[2]!.trim())
|
|
125
|
-
const y = parseCoord(m[3]!.trim())
|
|
126
|
-
if (!label || x === null || y === null) return null
|
|
127
|
-
if (seen.has(label)) return null
|
|
128
|
-
seen.add(label)
|
|
129
|
-
body.points.push({ label, x, y })
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Unmodeled line → opaque fallback.
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return body
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---- Serializer -------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
function renderAxis(keyword: 'x-axis' | 'y-axis', axis: QuadrantAxis): string {
|
|
143
|
-
const far = axis.far !== undefined ? ` --> ${axis.far}` : ''
|
|
144
|
-
return ` ${keyword} ${axis.near}${far}`
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function renderQuadrant(body: QuadrantBody): string {
|
|
148
|
-
const lines: string[] = ['quadrantChart']
|
|
149
|
-
if (body.title !== undefined) lines.push(` title ${body.title}`)
|
|
150
|
-
if (body.xAxis) lines.push(renderAxis('x-axis', body.xAxis))
|
|
151
|
-
if (body.yAxis) lines.push(renderAxis('y-axis', body.yAxis))
|
|
152
|
-
for (let i = 0; i < 4; i++) {
|
|
153
|
-
const label = body.quadrants[i]
|
|
154
|
-
if (label !== undefined) lines.push(` quadrant-${i + 1} ${label}`)
|
|
155
|
-
}
|
|
156
|
-
for (const p of body.points) {
|
|
157
|
-
lines.push(` ${p.label}: [${formatNumber(p.x)}, ${formatNumber(p.y)}]`)
|
|
158
|
-
}
|
|
159
|
-
return lines.join('\n') + '\n'
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---- Mutator ----------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
function cloneAxis(a: QuadrantAxis): QuadrantAxis {
|
|
165
|
-
return { near: a.near, far: a.far }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function cloneQuadrant(b: QuadrantBody): QuadrantBody {
|
|
169
|
-
return {
|
|
170
|
-
kind: 'quadrant',
|
|
171
|
-
title: b.title,
|
|
172
|
-
xAxis: b.xAxis ? cloneAxis(b.xAxis) : undefined,
|
|
173
|
-
yAxis: b.yAxis ? cloneAxis(b.yAxis) : undefined,
|
|
174
|
-
quadrants: [...b.quadrants] as QuadrantBody['quadrants'],
|
|
175
|
-
points: b.points.map(p => ({ label: p.label, x: p.x, y: p.y })),
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** A label that round-trips through the canonical serializer: non-empty, no
|
|
180
|
-
* newline, and (for points) no `:` / `[` that would re-parse as syntax. */
|
|
181
|
-
function validLabel(value: unknown, field: string, opts: { point?: boolean } = {}): Result<string, MutationError> {
|
|
182
|
-
if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Quadrant ${field} must be a string` })
|
|
183
|
-
const trimmed = value.trim()
|
|
184
|
-
if (!trimmed || trimmed.includes('\n')) {
|
|
185
|
-
return err({ code: 'INVALID_OP', message: `Quadrant ${field} must be non-empty single-line text` })
|
|
186
|
-
}
|
|
187
|
-
if (opts.point && (trimmed.includes(':') || trimmed.includes('[') || trimmed.includes(']'))) {
|
|
188
|
-
return err({ code: 'INVALID_OP', message: `Quadrant ${field} must not contain ':' or brackets` })
|
|
189
|
-
}
|
|
190
|
-
return ok(trimmed)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** An axis label must not contain the `-->` operator (it would re-parse). */
|
|
194
|
-
function validAxisLabel(value: unknown, field: string): Result<string, MutationError> {
|
|
195
|
-
const base = validLabel(value, field)
|
|
196
|
-
if (!base.ok) return base
|
|
197
|
-
if (base.value.includes('-->')) {
|
|
198
|
-
return err({ code: 'INVALID_OP', message: `Quadrant ${field} must not contain "-->"` })
|
|
199
|
-
}
|
|
200
|
-
return base
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function validCoord(value: unknown, field: string): Result<number, MutationError> {
|
|
204
|
-
if (!isCoord(value)) {
|
|
205
|
-
return err({ code: 'INVALID_OP', message: `Quadrant ${field} must be a number in [0, 1], got ${JSON.stringify(value)}` })
|
|
206
|
-
}
|
|
207
|
-
return ok(value)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function findPoint(body: QuadrantBody, label: string): number {
|
|
211
|
-
return body.points.findIndex(p => p.label === label)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function mutateQuadrant(body: QuadrantBody, op: QuadrantMutationOp): Result<QuadrantBody, MutationError> {
|
|
215
|
-
const next = cloneQuadrant(body)
|
|
216
|
-
|
|
217
|
-
switch (op.kind) {
|
|
218
|
-
case 'set_title': {
|
|
219
|
-
if (op.title === null) { delete next.title; break }
|
|
220
|
-
const t = validLabel(op.title, 'title')
|
|
221
|
-
if (!t.ok) return t
|
|
222
|
-
next.title = t.value
|
|
223
|
-
break
|
|
224
|
-
}
|
|
225
|
-
case 'set_axis_labels': {
|
|
226
|
-
if (op.axis !== 'x' && op.axis !== 'y') {
|
|
227
|
-
return err({ code: 'INVALID_OP', message: `Quadrant set_axis_labels axis must be 'x' or 'y', got ${JSON.stringify(op.axis)}` })
|
|
228
|
-
}
|
|
229
|
-
if (op.near === null) {
|
|
230
|
-
if (op.axis === 'x') delete next.xAxis
|
|
231
|
-
else delete next.yAxis
|
|
232
|
-
break
|
|
233
|
-
}
|
|
234
|
-
const near = validAxisLabel(op.near, `${op.axis}-axis near label`)
|
|
235
|
-
if (!near.ok) return near
|
|
236
|
-
const axis: QuadrantAxis = { near: near.value }
|
|
237
|
-
if (op.far !== undefined && op.far !== null) {
|
|
238
|
-
const far = validAxisLabel(op.far, `${op.axis}-axis far label`)
|
|
239
|
-
if (!far.ok) return far
|
|
240
|
-
axis.far = far.value
|
|
241
|
-
}
|
|
242
|
-
if (op.axis === 'x') next.xAxis = axis
|
|
243
|
-
else next.yAxis = axis
|
|
244
|
-
break
|
|
245
|
-
}
|
|
246
|
-
case 'set_quadrant_label': {
|
|
247
|
-
if (!Number.isInteger(op.quadrant) || op.quadrant < 1 || op.quadrant > 4) {
|
|
248
|
-
return err({ code: 'INVALID_OP', message: `Quadrant number must be 1..4, got ${JSON.stringify(op.quadrant)}` })
|
|
249
|
-
}
|
|
250
|
-
const idx = op.quadrant - 1
|
|
251
|
-
if (op.label === null) { next.quadrants[idx] = undefined; break }
|
|
252
|
-
const label = validLabel(op.label, 'quadrant label')
|
|
253
|
-
if (!label.ok) return label
|
|
254
|
-
next.quadrants[idx] = label.value
|
|
255
|
-
break
|
|
256
|
-
}
|
|
257
|
-
case 'add_point': {
|
|
258
|
-
const label = validLabel(op.label, 'point label', { point: true })
|
|
259
|
-
if (!label.ok) return label
|
|
260
|
-
if (findPoint(next, label.value) >= 0) {
|
|
261
|
-
return err({ code: 'INVALID_OP', message: `Quadrant point "${label.value}" already exists` })
|
|
262
|
-
}
|
|
263
|
-
const x = validCoord(op.x, 'point x')
|
|
264
|
-
if (!x.ok) return x
|
|
265
|
-
const y = validCoord(op.y, 'point y')
|
|
266
|
-
if (!y.ok) return y
|
|
267
|
-
next.points.push({ label: label.value, x: x.value, y: y.value })
|
|
268
|
-
break
|
|
269
|
-
}
|
|
270
|
-
case 'remove_point': {
|
|
271
|
-
const label = validLabel(op.label, 'point label', { point: true })
|
|
272
|
-
if (!label.ok) return label
|
|
273
|
-
const idx = findPoint(next, label.value)
|
|
274
|
-
if (idx < 0) return err({ code: 'POINT_NOT_FOUND', message: `Quadrant point "${label.value}" not found` })
|
|
275
|
-
next.points.splice(idx, 1)
|
|
276
|
-
break
|
|
277
|
-
}
|
|
278
|
-
case 'move_point': {
|
|
279
|
-
const label = validLabel(op.label, 'point label', { point: true })
|
|
280
|
-
if (!label.ok) return label
|
|
281
|
-
const idx = findPoint(next, label.value)
|
|
282
|
-
if (idx < 0) return err({ code: 'POINT_NOT_FOUND', message: `Quadrant point "${label.value}" not found` })
|
|
283
|
-
const x = validCoord(op.x, 'point x')
|
|
284
|
-
if (!x.ok) return x
|
|
285
|
-
const y = validCoord(op.y, 'point y')
|
|
286
|
-
if (!y.ok) return y
|
|
287
|
-
next.points[idx]!.x = x.value
|
|
288
|
-
next.points[idx]!.y = y.value
|
|
289
|
-
break
|
|
290
|
-
}
|
|
291
|
-
case 'rename_point': {
|
|
292
|
-
const from = validLabel(op.from, 'point label', { point: true })
|
|
293
|
-
if (!from.ok) return from
|
|
294
|
-
const to = validLabel(op.to, 'point label', { point: true })
|
|
295
|
-
if (!to.ok) return to
|
|
296
|
-
const idx = findPoint(next, from.value)
|
|
297
|
-
if (idx < 0) return err({ code: 'POINT_NOT_FOUND', message: `Quadrant point "${from.value}" not found` })
|
|
298
|
-
if (from.value !== to.value && findPoint(next, to.value) >= 0) {
|
|
299
|
-
return err({ code: 'INVALID_OP', message: `Quadrant point "${to.value}" already exists` })
|
|
300
|
-
}
|
|
301
|
-
next.points[idx]!.label = to.value
|
|
302
|
-
break
|
|
303
|
-
}
|
|
304
|
-
default: {
|
|
305
|
-
const _x: never = op
|
|
306
|
-
return err({ code: 'INVALID_OP', message: unknownOpMessage('quadrant', _x) })
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return ok(next)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// ---- Verifier (FamilyPlugin.verify hook) ------------------------------------
|
|
314
|
-
|
|
315
|
-
export function verifyQuadrant(body: QuadrantBody, opts: VerifyOptions): LayoutWarning[] {
|
|
316
|
-
const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP
|
|
317
|
-
const warnings: LayoutWarning[] = []
|
|
318
|
-
const overflow = (target: string, text: string) => {
|
|
319
|
-
const w = labelOverflowWarning(target, text, cap)
|
|
320
|
-
if (w) warnings.push(w)
|
|
321
|
-
}
|
|
322
|
-
// A quadrant chart with no axes, no quadrant labels, and no points renders as
|
|
323
|
-
// an empty grid — flag it as empty, mirroring the other families' floor.
|
|
324
|
-
const empty = body.title === undefined && !body.xAxis && !body.yAxis &&
|
|
325
|
-
body.quadrants.every(q => q === undefined) && body.points.length === 0
|
|
326
|
-
if (empty) warnings.push({ code: 'EMPTY_DIAGRAM' })
|
|
327
|
-
if (body.title !== undefined) overflow('title', body.title)
|
|
328
|
-
if (body.xAxis) { overflow('x-axis', body.xAxis.near); if (body.xAxis.far !== undefined) overflow('x-axis', body.xAxis.far) }
|
|
329
|
-
if (body.yAxis) { overflow('y-axis', body.yAxis.near); if (body.yAxis.far !== undefined) overflow('y-axis', body.yAxis.far) }
|
|
330
|
-
for (let i = 0; i < 4; i++) {
|
|
331
|
-
const label = body.quadrants[i]
|
|
332
|
-
if (label !== undefined) overflow(`quadrant-${i + 1}`, label)
|
|
333
|
-
}
|
|
334
|
-
for (const p of body.points) overflow(p.label, p.label)
|
|
335
|
-
return warnings
|
|
336
|
-
}
|