agentic-mermaid 0.1.1 → 0.2.0
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/AGENT_NATIVE.md +155 -91
- package/CHANGELOG.md +163 -4
- package/Instructions_for_agents.md +10 -10
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +31 -38
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +73 -1868
- package/dist/agent.js +336 -37325
- package/dist/agentic-mermaid-mcp.js +7 -38399
- package/dist/am.js +33626 -40446
- package/dist/chunk-2P5FKUQC.js +3485 -0
- package/dist/chunk-2S3VSS6I.js +149 -0
- package/dist/chunk-2SILJDW2.js +37 -0
- package/dist/chunk-BJYMBT4I.js +61963 -0
- package/dist/chunk-FDM43XU7.js +615 -0
- package/dist/chunk-MYPDVZCO.js +4702 -0
- package/dist/index-DWPa2MJs.d.ts +6780 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -33014
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +39 -31
- package/docs/agent-api-cookbook.md +44 -35
- package/docs/agent-mutation-policy.md +1 -1
- package/docs/agent-workflow-examples.md +2 -2
- package/docs/api.md +284 -77
- package/docs/ascii.md +26 -16
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +23 -11
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +26 -10
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +130 -9
- package/docs/diagram-families.md +48 -23
- package/docs/features.md +59 -52
- package/docs/fork-differences.md +6 -3
- package/docs/getting-started.md +23 -16
- package/docs/issue-derived-test-cases.md +29 -22
- package/docs/mcp-code-mode-rationale.md +13 -6
- package/docs/mcp-http-transport.md +20 -13
- package/docs/mermaid-layout-complaints.md +66 -52
- package/docs/mutation-testing.md +145 -66
- package/docs/quality.md +23 -14
- package/docs/react.md +2 -2
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/docs/style-authoring.md +241 -38
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +166 -58
- package/docs/theming.md +19 -32
- 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 +85 -45
- package/server.json +2 -2
- 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/TODO.md +0 -684
- package/dist/index-wjRjbsP-.d.ts +0 -1102
- 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/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 -82
- package/docs/contributing/visual-review-evidence.md +0 -53
- 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-experience-curve-implementation.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 -439
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -153
- 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/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -498
- 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 -98
- 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/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/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 -368
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -360
- package/src/agent/families-builtin.ts +0 -920
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -832
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/font-coverage.ts +0 -288
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -374
- 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 -443
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -171
- 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 -995
- package/src/agent/verify.ts +0 -603
- 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 -169
- 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 -141
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/wrap.ts +0 -131
- 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 -1132
- package/src/cli/init-agent.ts +0 -147
- package/src/cli/run-entrypoint.ts +0 -10
- 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/family-rubric.ts +0 -425
- package/src/font-manifest.ts +0 -31
- 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 -554
- package/src/journey/parse-core.ts +0 -251
- package/src/journey/parser.ts +0 -101
- package/src/journey/renderer.ts +0 -917
- 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 -391
- 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 -93
- 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 -710
- 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 -462
- 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
package/src/er/types.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ER diagram types
|
|
3
|
-
//
|
|
4
|
-
// Models the parsed and positioned representations of a Mermaid ER diagram.
|
|
5
|
-
// ER diagrams show database entities, their attributes, and relationships.
|
|
6
|
-
// ============================================================================
|
|
7
|
-
|
|
8
|
-
import type { PositionedDiagram } from '../types.ts'
|
|
9
|
-
|
|
10
|
-
/** Parsed ER diagram — logical structure from mermaid text */
|
|
11
|
-
export interface ErDiagram {
|
|
12
|
-
/** Optional accessibility title (Mermaid accTitle) */
|
|
13
|
-
accessibilityTitle?: string
|
|
14
|
-
/** Optional accessibility description (Mermaid accDescr) */
|
|
15
|
-
accessibilityDescription?: string
|
|
16
|
-
/** All entity definitions */
|
|
17
|
-
entities: ErEntity[]
|
|
18
|
-
/** Relationships between entities */
|
|
19
|
-
relationships: ErRelationship[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ErEntity {
|
|
23
|
-
id: string
|
|
24
|
-
/** Display name (same as id unless aliased) */
|
|
25
|
-
label: string
|
|
26
|
-
/** Entity attributes (columns) */
|
|
27
|
-
attributes: ErAttribute[]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ErAttribute {
|
|
31
|
-
/** Data type (string, int, varchar, etc.) */
|
|
32
|
-
type: string
|
|
33
|
-
/** Attribute name */
|
|
34
|
-
name: string
|
|
35
|
-
/** Key constraints: PK, FK, UK */
|
|
36
|
-
keys: Array<'PK' | 'FK' | 'UK'>
|
|
37
|
-
/** Optional comment */
|
|
38
|
-
comment?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Cardinality notation (crow's foot):
|
|
43
|
-
* 'one' || exactly one
|
|
44
|
-
* 'zero-one' |o zero or one
|
|
45
|
-
* 'many' }| one or more
|
|
46
|
-
* 'zero-many' o{ zero or more
|
|
47
|
-
*/
|
|
48
|
-
export type Cardinality = 'one' | 'zero-one' | 'many' | 'zero-many'
|
|
49
|
-
|
|
50
|
-
export interface ErRelationship {
|
|
51
|
-
entity1: string
|
|
52
|
-
entity2: string
|
|
53
|
-
/** Cardinality at entity1's end */
|
|
54
|
-
cardinality1: Cardinality
|
|
55
|
-
/** Cardinality at entity2's end */
|
|
56
|
-
cardinality2: Cardinality
|
|
57
|
-
/** Relationship verb/label (e.g., "places", "contains") */
|
|
58
|
-
label: string
|
|
59
|
-
/** Whether the relationship is identifying (solid line) or non-identifying (dashed) */
|
|
60
|
-
identifying: boolean
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ============================================================================
|
|
64
|
-
// Positioned ER diagram — ready for SVG rendering
|
|
65
|
-
// ============================================================================
|
|
66
|
-
|
|
67
|
-
export interface PositionedErDiagram extends PositionedDiagram {
|
|
68
|
-
width: number
|
|
69
|
-
height: number
|
|
70
|
-
accessibilityTitle?: string
|
|
71
|
-
accessibilityDescription?: string
|
|
72
|
-
entities: PositionedErEntity[]
|
|
73
|
-
relationships: PositionedErRelationship[]
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface PositionedErEntity {
|
|
77
|
-
id: string
|
|
78
|
-
label: string
|
|
79
|
-
attributes: ErAttribute[]
|
|
80
|
-
x: number
|
|
81
|
-
y: number
|
|
82
|
-
width: number
|
|
83
|
-
height: number
|
|
84
|
-
/** Height of the header row */
|
|
85
|
-
headerHeight: number
|
|
86
|
-
/** Height per attribute row */
|
|
87
|
-
rowHeight: number
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface PositionedErRelationship {
|
|
91
|
-
entity1: string
|
|
92
|
-
entity2: string
|
|
93
|
-
cardinality1: Cardinality
|
|
94
|
-
cardinality2: Cardinality
|
|
95
|
-
label: string
|
|
96
|
-
identifying: boolean
|
|
97
|
-
/** Path points from entity1 to entity2 */
|
|
98
|
-
points: Array<{ x: number; y: number }>
|
|
99
|
-
}
|
package/src/family-rubric.ts
DELETED
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic FAMILY-GENERIC layout-quality rubric.
|
|
3
|
-
*
|
|
4
|
-
* src/layout-rubric.ts scores the ELK-routed graph families (flowchart/state)
|
|
5
|
-
* from MermaidGraph + PositionedGraph — it cannot see the other ten families.
|
|
6
|
-
* This module is the companion rubric for EVERYTHING ELSE: it consumes the
|
|
7
|
-
* common `RenderedLayout` projection every agent-parsed family already emits
|
|
8
|
-
* (src/agent/family-layouts.ts `layoutFamilyToRendered`, plus the graph path
|
|
9
|
-
* in src/agent/core.ts), so any family — current or future — that projects to
|
|
10
|
-
* RenderedLayout is scored by the same invariants:
|
|
11
|
-
*
|
|
12
|
-
* • finite geometry (no NaN/Infinity, no negative sizes),
|
|
13
|
-
* • nodes/edges/groups inside the canvas bounds,
|
|
14
|
-
* • no text-bearing box overlapping another box (Purchase 1997: overlap is a
|
|
15
|
-
* readability-destroying defect); data MARKS (points/junctions) may
|
|
16
|
-
* legitimately coincide, so mark overlap is a SOFT rate, not a HARD count,
|
|
17
|
-
* • group containment (members inside their group's rect — journey sections
|
|
18
|
-
* are header BANDS above their tasks, so journey guarantees the x axis
|
|
19
|
-
* only; the same reason verify.ts leaves groupContainment off for journey),
|
|
20
|
-
* • group tiling (sibling groups either nest or stay disjoint — a PARTIAL
|
|
21
|
-
* overlap is a defect; full containment is legitimate nesting, e.g.
|
|
22
|
-
* sequence blocks and architecture group trees),
|
|
23
|
-
* • label presence on every text-bearing box.
|
|
24
|
-
*
|
|
25
|
-
* Scores are deterministic numbers with STABLE WEIGHTS (exported below), in
|
|
26
|
-
* the spirit of layout-rubric's fixed metric set: identical input produces
|
|
27
|
-
* identical metrics, violations, and score. This module composes ALONGSIDE
|
|
28
|
-
* layout-rubric (the tracker keeps assessLayout for flowchart); it does not
|
|
29
|
-
* replace it.
|
|
30
|
-
*
|
|
31
|
-
* A journey-specific assessor layers on top. It reads the
|
|
32
|
-
* PositionedJourneyDiagram from layoutJourneyDiagram — NOT the RenderedLayout
|
|
33
|
-
* journey projection — because the projection keeps only task boxes and
|
|
34
|
-
* section bands: the experience-curve markers, actor dots, and score guide
|
|
35
|
-
* (exactly what the renderer rework changed) exist only on the positioned
|
|
36
|
-
* diagram, the same geometry the SVG renderer draws.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import type { RenderedLayout, RenderedLayoutGroup, RenderedLayoutNode, DiagramKind } from './agent/types.ts'
|
|
40
|
-
import type { PositionedJourneyDiagram } from './journey/types.ts'
|
|
41
|
-
|
|
42
|
-
/** Geometry tolerance (px), matching layoutGeometryWarnings / layout-rubric. */
|
|
43
|
-
const TOL = 0.5
|
|
44
|
-
|
|
45
|
-
export interface FamilyRubricViolation {
|
|
46
|
-
metric: string
|
|
47
|
-
detail: string
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface FamilyRubricMetrics {
|
|
51
|
-
nodes: number
|
|
52
|
-
groups: number
|
|
53
|
-
/** HARD (0): NaN/Infinity coordinates or negative sizes anywhere. */
|
|
54
|
-
nonFiniteGeometry: number
|
|
55
|
-
/** HARD (0): nodes/edge points/groups outside the canvas bounds. */
|
|
56
|
-
offCanvas: number
|
|
57
|
-
/** HARD (0): two text-bearing BOXES overlapping (Purchase: readability). */
|
|
58
|
-
nodeOverlaps: number
|
|
59
|
-
/** HARD (0): a group member outside its group's rect (per-family axes). */
|
|
60
|
-
groupBreaches: number
|
|
61
|
-
/** HARD (0): two sibling groups PARTIALLY overlapping (tiling: groups must
|
|
62
|
-
* nest fully or stay disjoint). */
|
|
63
|
-
groupOverlaps: number
|
|
64
|
-
/** SOFT: overlapping pairs involving a data MARK (point/junction marker)
|
|
65
|
-
* over all node pairs — marks may legitimately coincide (a line point on a
|
|
66
|
-
* bar, two quadrant points at one coordinate), so this is a rate to
|
|
67
|
-
* baseline, not a hard zero. 0 when fewer than two nodes. */
|
|
68
|
-
markOverlapRate: number
|
|
69
|
-
/** SOFT: fraction of text-bearing boxes carrying a non-empty label.
|
|
70
|
-
* 1 when there are no boxes. */
|
|
71
|
-
labelledBoxRate: number
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface FamilyRubricResult {
|
|
75
|
-
metrics: FamilyRubricMetrics
|
|
76
|
-
violations: FamilyRubricViolation[]
|
|
77
|
-
/** Deterministic 0–100 score under FAMILY_RUBRIC_WEIGHTS (higher = better). */
|
|
78
|
-
score: number
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** The hard family-rubric metrics — must be zero for every diagram, always. */
|
|
82
|
-
export const FAMILY_HARD_METRICS = [
|
|
83
|
-
'nonFiniteGeometry', 'offCanvas', 'nodeOverlaps', 'groupBreaches', 'groupOverlaps',
|
|
84
|
-
] as const
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Stable score weights. Ordered by impact (Purchase 1997/2002: overlap and
|
|
88
|
-
* off-canvas clipping destroy readability outright; tiling and label defects
|
|
89
|
-
* degrade it). Changing a weight is a scoring-contract change: regenerate the
|
|
90
|
-
* heuristic-tracker baseline and say so in the PR.
|
|
91
|
-
*/
|
|
92
|
-
export const FAMILY_RUBRIC_WEIGHTS = {
|
|
93
|
-
nonFiniteGeometry: 40,
|
|
94
|
-
offCanvas: 12,
|
|
95
|
-
nodeOverlaps: 10,
|
|
96
|
-
groupBreaches: 10,
|
|
97
|
-
groupOverlaps: 8,
|
|
98
|
-
/** Soft: applied as weight × markOverlapRate (a fraction, not a count). */
|
|
99
|
-
markOverlapRate: 10,
|
|
100
|
-
/** Soft: applied as weight × (1 − labelledBoxRate). */
|
|
101
|
-
missingLabelRate: 5,
|
|
102
|
-
} as const
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Shapes that render as text-bearing boxes. Everything else ('circle' line
|
|
106
|
-
* points / quadrant points / architecture junctions) is a data MARK: marks may
|
|
107
|
-
* legitimately coincide, boxes may not.
|
|
108
|
-
*/
|
|
109
|
-
const BOX_SHAPES = new Set(['rectangle', 'rounded', 'note', 'service', 'diamond', 'stadium', 'hexagon', 'cylinder'])
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Which containment axes a family's groups guarantee for their members.
|
|
113
|
-
* Journey sections are header BANDS tiled above the task row (the tiled-
|
|
114
|
-
* section rework): a member task is inside its band's x-span but sits below
|
|
115
|
-
* it, so only 'x' applies — mirroring verify.ts, which enables full
|
|
116
|
-
* groupContainment only for the families whose groups are bounding frames.
|
|
117
|
-
* Default (unlisted family): 'both'.
|
|
118
|
-
*/
|
|
119
|
-
const GROUP_CONTAINMENT_AXES: Partial<Record<DiagramKind, 'both' | 'x' | 'none'>> = {
|
|
120
|
-
journey: 'x',
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isBox(node: RenderedLayoutNode): boolean {
|
|
124
|
-
return BOX_SHAPES.has(node.shape)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function finiteRect(x: number, y: number, w: number, h: number): boolean {
|
|
128
|
-
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(w) && Number.isFinite(h) && w >= 0 && h >= 0
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function rectOverlap(
|
|
132
|
-
a: { x: number; y: number; w: number; h: number },
|
|
133
|
-
b: { x: number; y: number; w: number; h: number },
|
|
134
|
-
): boolean {
|
|
135
|
-
const ox = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)
|
|
136
|
-
const oy = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y)
|
|
137
|
-
return ox > TOL && oy > TOL
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function rectContains(outer: RenderedLayoutGroup, inner: RenderedLayoutGroup): boolean {
|
|
141
|
-
return inner.x >= outer.x - TOL && inner.y >= outer.y - TOL &&
|
|
142
|
-
inner.x + inner.w <= outer.x + outer.w + TOL && inner.y + inner.h <= outer.y + outer.h + TOL
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Whether `ancestor` is on `group`'s parentId chain (flattened group trees). */
|
|
146
|
-
function isAncestor(ancestor: RenderedLayoutGroup, group: RenderedLayoutGroup, byId: Map<string, RenderedLayoutGroup>): boolean {
|
|
147
|
-
let cur: RenderedLayoutGroup | undefined = group
|
|
148
|
-
for (let hops = 0; cur?.parentId && hops < 100; hops++) {
|
|
149
|
-
if (cur.parentId === ancestor.id) return true
|
|
150
|
-
cur = byId.get(cur.parentId)
|
|
151
|
-
}
|
|
152
|
-
return false
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Score a RenderedLayout against the family-generic rubric. Pure and
|
|
157
|
-
* deterministic: identical input produces identical metrics and score.
|
|
158
|
-
*/
|
|
159
|
-
export function assessRenderedLayout(layout: RenderedLayout): FamilyRubricResult {
|
|
160
|
-
const violations: FamilyRubricViolation[] = []
|
|
161
|
-
|
|
162
|
-
let nonFinite = 0
|
|
163
|
-
let offCanvas = 0
|
|
164
|
-
|
|
165
|
-
if (!finiteRect(0, 0, layout.bounds.w, layout.bounds.h)) {
|
|
166
|
-
nonFinite++
|
|
167
|
-
violations.push({ metric: 'nonFiniteGeometry', detail: `bounds ${layout.bounds.w}×${layout.bounds.h}` })
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
for (const n of layout.nodes) {
|
|
171
|
-
if (!finiteRect(n.x, n.y, n.w, n.h)) {
|
|
172
|
-
nonFinite++
|
|
173
|
-
violations.push({ metric: 'nonFiniteGeometry', detail: `node ${n.id}` })
|
|
174
|
-
continue
|
|
175
|
-
}
|
|
176
|
-
if (n.x < -TOL || n.y < -TOL || n.x + n.w > layout.bounds.w + TOL || n.y + n.h > layout.bounds.h + TOL) {
|
|
177
|
-
offCanvas++
|
|
178
|
-
violations.push({ metric: 'offCanvas', detail: `node ${n.id} at (${n.x},${n.y}) ${n.w}×${n.h}` })
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
for (const e of layout.edges) {
|
|
182
|
-
let flagged = false
|
|
183
|
-
for (const [x, y] of e.path) {
|
|
184
|
-
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
185
|
-
nonFinite++
|
|
186
|
-
violations.push({ metric: 'nonFiniteGeometry', detail: `edge ${e.id} path` })
|
|
187
|
-
flagged = true
|
|
188
|
-
break
|
|
189
|
-
}
|
|
190
|
-
if (!flagged && (x < -TOL || y < -TOL || x > layout.bounds.w + TOL || y > layout.bounds.h + TOL)) {
|
|
191
|
-
offCanvas++
|
|
192
|
-
violations.push({ metric: 'offCanvas', detail: `edge ${e.id} point (${x},${y})` })
|
|
193
|
-
flagged = true
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
for (const g of layout.groups) {
|
|
198
|
-
if (!finiteRect(g.x, g.y, g.w, g.h)) {
|
|
199
|
-
nonFinite++
|
|
200
|
-
violations.push({ metric: 'nonFiniteGeometry', detail: `group ${g.id}` })
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
if (g.x < -TOL || g.y < -TOL || g.x + g.w > layout.bounds.w + TOL || g.y + g.h > layout.bounds.h + TOL) {
|
|
204
|
-
offCanvas++
|
|
205
|
-
violations.push({ metric: 'offCanvas', detail: `group ${g.id}` })
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Node-node overlap: HARD for box-box pairs, SOFT rate for pairs involving a
|
|
210
|
-
// mark (marks may legitimately coincide — a line point on a bar, two
|
|
211
|
-
// quadrant points at one coordinate).
|
|
212
|
-
let nodeOverlaps = 0
|
|
213
|
-
let markOverlaps = 0
|
|
214
|
-
let pairs = 0
|
|
215
|
-
for (let i = 0; i < layout.nodes.length; i++) {
|
|
216
|
-
for (let j = i + 1; j < layout.nodes.length; j++) {
|
|
217
|
-
const a = layout.nodes[i]!, b = layout.nodes[j]!
|
|
218
|
-
if (!finiteRect(a.x, a.y, a.w, a.h) || !finiteRect(b.x, b.y, b.w, b.h)) continue
|
|
219
|
-
pairs++
|
|
220
|
-
if (!rectOverlap(a, b)) continue
|
|
221
|
-
if (isBox(a) && isBox(b)) {
|
|
222
|
-
nodeOverlaps++
|
|
223
|
-
violations.push({ metric: 'nodeOverlaps', detail: `${a.id} overlaps ${b.id}` })
|
|
224
|
-
} else {
|
|
225
|
-
markOverlaps++
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Group containment, honouring each family's guaranteed axes.
|
|
231
|
-
const axes = GROUP_CONTAINMENT_AXES[layout.kind] ?? 'both'
|
|
232
|
-
let groupBreaches = 0
|
|
233
|
-
if (axes !== 'none') {
|
|
234
|
-
const nodeById = new Map(layout.nodes.map(n => [n.id, n]))
|
|
235
|
-
for (const g of layout.groups) {
|
|
236
|
-
for (const memberId of g.members) {
|
|
237
|
-
const n = nodeById.get(memberId)
|
|
238
|
-
if (!n || !finiteRect(n.x, n.y, n.w, n.h)) continue
|
|
239
|
-
const xOk = n.x >= g.x - TOL && n.x + n.w <= g.x + g.w + TOL
|
|
240
|
-
const yOk = axes === 'x' || (n.y >= g.y - TOL && n.y + n.h <= g.y + g.h + TOL)
|
|
241
|
-
if (!xOk || !yOk) {
|
|
242
|
-
groupBreaches++
|
|
243
|
-
violations.push({ metric: 'groupBreaches', detail: `${memberId} outside ${g.id}` })
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Group tiling: sibling groups must nest fully or stay disjoint. Ancestry is
|
|
250
|
-
// checked both via parentId (architecture's flattened tree) and geometric
|
|
251
|
-
// containment (sequence blocks nest without parentId).
|
|
252
|
-
let groupOverlaps = 0
|
|
253
|
-
const groupById = new Map(layout.groups.map(g => [g.id, g]))
|
|
254
|
-
for (let i = 0; i < layout.groups.length; i++) {
|
|
255
|
-
for (let j = i + 1; j < layout.groups.length; j++) {
|
|
256
|
-
const a = layout.groups[i]!, b = layout.groups[j]!
|
|
257
|
-
if (!finiteRect(a.x, a.y, a.w, a.h) || !finiteRect(b.x, b.y, b.w, b.h)) continue
|
|
258
|
-
if (!rectOverlap(a, b)) continue
|
|
259
|
-
if (rectContains(a, b) || rectContains(b, a)) continue
|
|
260
|
-
if (isAncestor(a, b, groupById) || isAncestor(b, a, groupById)) continue
|
|
261
|
-
groupOverlaps++
|
|
262
|
-
violations.push({ metric: 'groupOverlaps', detail: `${a.id} partially overlaps ${b.id}` })
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Label presence on text-bearing boxes.
|
|
267
|
-
const boxes = layout.nodes.filter(isBox)
|
|
268
|
-
const labelledBoxes = boxes.filter(n => (n.label ?? '').trim().length > 0)
|
|
269
|
-
for (const n of boxes) {
|
|
270
|
-
if ((n.label ?? '').trim().length === 0) violations.push({ metric: 'missingLabel', detail: `box ${n.id} has no label` })
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const metrics: FamilyRubricMetrics = {
|
|
274
|
-
nodes: layout.nodes.length,
|
|
275
|
-
groups: layout.groups.length,
|
|
276
|
-
nonFiniteGeometry: nonFinite,
|
|
277
|
-
offCanvas,
|
|
278
|
-
nodeOverlaps,
|
|
279
|
-
groupBreaches,
|
|
280
|
-
groupOverlaps,
|
|
281
|
-
markOverlapRate: pairs === 0 ? 0 : markOverlaps / pairs,
|
|
282
|
-
labelledBoxRate: boxes.length === 0 ? 1 : labelledBoxes.length / boxes.length,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const w = FAMILY_RUBRIC_WEIGHTS
|
|
286
|
-
const score = Math.max(0, Math.round((
|
|
287
|
-
100
|
|
288
|
-
- w.nonFiniteGeometry * metrics.nonFiniteGeometry
|
|
289
|
-
- w.offCanvas * metrics.offCanvas
|
|
290
|
-
- w.nodeOverlaps * metrics.nodeOverlaps
|
|
291
|
-
- w.groupBreaches * metrics.groupBreaches
|
|
292
|
-
- w.groupOverlaps * metrics.groupOverlaps
|
|
293
|
-
- w.markOverlapRate * metrics.markOverlapRate
|
|
294
|
-
- w.missingLabelRate * (1 - metrics.labelledBoxRate)
|
|
295
|
-
) * 10) / 10)
|
|
296
|
-
|
|
297
|
-
return { metrics, violations, score }
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function familyHardViolations(result: FamilyRubricResult): FamilyRubricViolation[] {
|
|
301
|
-
const hard = new Set<string>(FAMILY_HARD_METRICS)
|
|
302
|
-
return result.violations.filter(v => hard.has(v.metric))
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ============================================================================
|
|
306
|
-
// Journey-specific assessor
|
|
307
|
-
// ============================================================================
|
|
308
|
-
|
|
309
|
-
export interface JourneyRubricMetrics {
|
|
310
|
-
sections: number
|
|
311
|
-
tasks: number
|
|
312
|
-
/** HARD (0): section spans overlapping in x — spans must tile. */
|
|
313
|
-
sectionSpanOverlaps: number
|
|
314
|
-
/** HARD (0): a task's score marker off its column center (marker.cx must
|
|
315
|
-
* equal the task's centerX — the experience curve reads per-column). */
|
|
316
|
-
markerOffCenter: number
|
|
317
|
-
/** HARD (0): score→y ordering violations — a HIGHER score must sit STRICTLY
|
|
318
|
-
* higher (smaller y), equal scores at equal y, and the score-guide ticks
|
|
319
|
-
* must run 5 (top) … 1 (bottom). */
|
|
320
|
-
scoreOrderViolations: number
|
|
321
|
-
/** HARD (0): an actor dot escaping its task's box. */
|
|
322
|
-
actorDotsOutsideTask: number
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export interface JourneyRubricResult {
|
|
326
|
-
metrics: JourneyRubricMetrics
|
|
327
|
-
violations: FamilyRubricViolation[]
|
|
328
|
-
/** Deterministic 0–100 score under JOURNEY_RUBRIC_WEIGHTS. */
|
|
329
|
-
score: number
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/** Stable weights for the journey assessor — same contract as above. */
|
|
333
|
-
export const JOURNEY_RUBRIC_WEIGHTS = {
|
|
334
|
-
sectionSpanOverlaps: 15,
|
|
335
|
-
markerOffCenter: 10,
|
|
336
|
-
scoreOrderViolations: 15,
|
|
337
|
-
actorDotsOutsideTask: 5,
|
|
338
|
-
} as const
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Score a positioned journey diagram against the journey-specific invariants
|
|
342
|
-
* the RenderedLayout projection cannot see (markers, dots, score guide). Every
|
|
343
|
-
* journey metric is HARD: the layout produces these properties by
|
|
344
|
-
* construction, so any violation means a later pass broke the geometry.
|
|
345
|
-
*/
|
|
346
|
-
export function assessJourneyLayout(positioned: PositionedJourneyDiagram): JourneyRubricResult {
|
|
347
|
-
const violations: FamilyRubricViolation[] = []
|
|
348
|
-
|
|
349
|
-
// Section spans tile in x: pairwise interval overlap beyond tolerance.
|
|
350
|
-
let sectionSpanOverlaps = 0
|
|
351
|
-
for (let i = 0; i < positioned.sections.length; i++) {
|
|
352
|
-
for (let j = i + 1; j < positioned.sections.length; j++) {
|
|
353
|
-
const a = positioned.sections[i]!, b = positioned.sections[j]!
|
|
354
|
-
const ox = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)
|
|
355
|
-
if (ox > TOL) {
|
|
356
|
-
sectionSpanOverlaps++
|
|
357
|
-
violations.push({ metric: 'sectionSpanOverlaps', detail: `${a.id} overlaps ${b.id} by ${ox.toFixed(1)}px` })
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const tasks = positioned.sections.flatMap(s => s.tasks)
|
|
363
|
-
|
|
364
|
-
// Marker x == column center, actor dots inside the task box.
|
|
365
|
-
let markerOffCenter = 0
|
|
366
|
-
let actorDotsOutsideTask = 0
|
|
367
|
-
for (const t of tasks) {
|
|
368
|
-
if (Math.abs(t.marker.cx - t.centerX) > TOL) {
|
|
369
|
-
markerOffCenter++
|
|
370
|
-
violations.push({ metric: 'markerOffCenter', detail: `${t.id} marker at ${t.marker.cx}, column center ${t.centerX}` })
|
|
371
|
-
}
|
|
372
|
-
for (const d of t.actorDots) {
|
|
373
|
-
const inside = d.x - d.r >= t.x - TOL && d.x + d.r <= t.x + t.width + TOL &&
|
|
374
|
-
d.y - d.r >= t.y - TOL && d.y + d.r <= t.y + t.height + TOL
|
|
375
|
-
if (!inside) {
|
|
376
|
-
actorDotsOutsideTask++
|
|
377
|
-
violations.push({ metric: 'actorDotsOutsideTask', detail: `${t.id} dot for ${d.label} at (${d.x},${d.y})` })
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Score→y strictly monotone across tasks (score 5 highest ⇒ smallest y),
|
|
383
|
-
// and the guide ticks run 5 (top) … 1 (bottom).
|
|
384
|
-
let scoreOrderViolations = 0
|
|
385
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
386
|
-
for (let j = i + 1; j < tasks.length; j++) {
|
|
387
|
-
const a = tasks[i]!, b = tasks[j]!
|
|
388
|
-
const bad = (a.score > b.score && !(a.marker.cy < b.marker.cy - 1e-6)) ||
|
|
389
|
-
(a.score < b.score && !(b.marker.cy < a.marker.cy - 1e-6)) ||
|
|
390
|
-
(a.score === b.score && Math.abs(a.marker.cy - b.marker.cy) > 1e-6)
|
|
391
|
-
if (bad) {
|
|
392
|
-
scoreOrderViolations++
|
|
393
|
-
violations.push({ metric: 'scoreOrderViolations', detail: `${a.id}(score ${a.score}, y ${a.marker.cy}) vs ${b.id}(score ${b.score}, y ${b.marker.cy})` })
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
const ticks = positioned.scoreGuide.ticks
|
|
398
|
-
for (let i = 1; i < ticks.length; i++) {
|
|
399
|
-
const prev = ticks[i - 1]!, cur = ticks[i]!
|
|
400
|
-
if (!(prev.score > cur.score && prev.y < cur.y - 1e-6)) {
|
|
401
|
-
scoreOrderViolations++
|
|
402
|
-
violations.push({ metric: 'scoreOrderViolations', detail: `guide ticks out of order at index ${i} (score ${prev.score}→${cur.score}, y ${prev.y}→${cur.y})` })
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const metrics: JourneyRubricMetrics = {
|
|
407
|
-
sections: positioned.sections.length,
|
|
408
|
-
tasks: tasks.length,
|
|
409
|
-
sectionSpanOverlaps,
|
|
410
|
-
markerOffCenter,
|
|
411
|
-
scoreOrderViolations,
|
|
412
|
-
actorDotsOutsideTask,
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const w = JOURNEY_RUBRIC_WEIGHTS
|
|
416
|
-
const score = Math.max(0, Math.round((
|
|
417
|
-
100
|
|
418
|
-
- w.sectionSpanOverlaps * sectionSpanOverlaps
|
|
419
|
-
- w.markerOffCenter * markerOffCenter
|
|
420
|
-
- w.scoreOrderViolations * scoreOrderViolations
|
|
421
|
-
- w.actorDotsOutsideTask * actorDotsOutsideTask
|
|
422
|
-
) * 10) / 10)
|
|
423
|
-
|
|
424
|
-
return { metrics, violations, score }
|
|
425
|
-
}
|
package/src/font-manifest.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export interface HostedFontFace {
|
|
2
|
-
family: string
|
|
3
|
-
file: string
|
|
4
|
-
weight: string
|
|
5
|
-
style: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const HOSTED_FONT_FACES: readonly HostedFontFace[] = [
|
|
9
|
-
{ family: 'Inter', file: 'Inter-Regular.ttf', weight: '400', style: 'normal' },
|
|
10
|
-
{ family: 'Inter', file: 'Inter-Medium.ttf', weight: '500', style: 'normal' },
|
|
11
|
-
{ family: 'Inter', file: 'Inter-SemiBold.ttf', weight: '600', style: 'normal' },
|
|
12
|
-
{ family: 'Inter', file: 'Inter-Bold.ttf', weight: '700', style: 'normal' },
|
|
13
|
-
{ family: 'Caveat', file: 'Caveat.ttf', weight: '400 700', style: 'normal' },
|
|
14
|
-
{ family: 'EB Garamond', file: 'EBGaramond.ttf', weight: '400 700', style: 'normal' },
|
|
15
|
-
{ family: 'Architects Daughter', file: 'ArchitectsDaughter.ttf', weight: '400', style: 'normal' },
|
|
16
|
-
{ family: 'Share Tech Mono', file: 'ShareTechMono.ttf', weight: '400', style: 'normal' },
|
|
17
|
-
{ family: 'DejaVu Sans', file: 'DejaVuSans.ttf', weight: '400', style: 'normal' },
|
|
18
|
-
{ family: 'DejaVu Sans', file: 'DejaVuSans-Bold.ttf', weight: '700', style: 'normal' },
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
export const HOSTED_FONT_FILES = [...new Set(HOSTED_FONT_FACES.map((font) => font.file))] as readonly string[]
|
|
22
|
-
|
|
23
|
-
function cssString(value: string): string {
|
|
24
|
-
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function hostedFontFaceCss(prefix = '/fonts/'): string {
|
|
28
|
-
return HOSTED_FONT_FACES.map((font) =>
|
|
29
|
-
`@font-face { font-family: '${cssString(font.family)}'; src: url('${prefix}${font.file}') format('truetype'); font-weight: ${font.weight}; font-style: ${font.style}; font-display: swap; }`,
|
|
30
|
-
).join('\n')
|
|
31
|
-
}
|