Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
4554537
feat(spa): vendor Vue frontend + emit bundle in both output modes
ronaldtse Jul 22, 2026
209acd5
fix(svg): resolve type names, default font_size=0, dynamic text posit…
ronaldtse Jul 22, 2026
5ef7c02
fix(svg): integrate canvas translation + connector router + type reso…
ronaldtse Jul 22, 2026
bb41a10
fix(spec): update canvas + connectors specs for translate semantics
ronaldtse Jul 22, 2026
bb7e6ac
fix(svg): use logical bounds for rect rendering, reduce padding to 5px
ronaldtse Jul 22, 2026
727886c
fix(spec): update canvas width/height expectations for PADDING=5
ronaldtse Jul 22, 2026
899e4be
fix(svg): PADDING=10 + element-only canvas union matches EA's layout
ronaldtse Jul 22, 2026
6ad9c7f
fix(svg): match EA encoding — font, stroke, cm, markers, text split
ronaldtse Jul 23, 2026
da42039
feat(svg): per-entity layering + diamond/arrow markers + font/canvas …
ronaldtse Jul 23, 2026
0e8dde3
fix(svg): abstract single underscore + '::' namespace + '+ ' visibility
ronaldtse Jul 23, 2026
9bece72
docs(todo): add TODO 24 — truncate properties to fit box
ronaldtse Jul 23, 2026
c065019
feat(svg): filter assoc-end properties + emit connector labels (TODO …
ronaldtse Jul 23, 2026
4f5a53c
spec(svg): cover labels, association-end property filtering
ronaldtse Jul 23, 2026
9226962
feat(svg): Layer refactor + BoundsCalculator + Marker Registry (fixup…
ronaldtse Jul 23, 2026
146e5bd
feat(svg): LayerSequencer + Style + Element compartments + ParityChec…
ronaldtse Jul 23, 2026
19b884e
feat(svg): DataType fallback + enum/operation compartments + path cal…
ronaldtse Jul 25, 2026
77827d3
docs(todo): closeout fixup 18, 21-24 with analysis
ronaldtse Jul 25, 2026
b50be51
feat(svg): BCol=-1 sentinel + EA.exe disassembly findings (fixup 20, 25)
ronaldtse Jul 25, 2026
0468a84
docs(svg): document SVGRenderer architecture from disassembly (fixup 26)
ronaldtse Jul 25, 2026
164722c
fix(svg): treat Path= bend points as source-relative deltas
ronaldtse Jul 25, 2026
acd7968
docs(svg): SVG element emitters disassembly (fixup 27)
ronaldtse Jul 25, 2026
3de999b
docs(svg): identify geometry parser (fixup 28)
ronaldtse Jul 25, 2026
cfba3ae
feat(svg): diagram frame renderer + per-type stereotype labels (fixup…
ronaldtse Jul 25, 2026
e4fe390
feat(svg): ThemeColors stub for theme :119 per-type fills (fixup 30)
ronaldtse Jul 25, 2026
62f637d
feat(svg): Theme value object + ThemeRegistry + TextRenderer centrali…
ronaldtse Jul 25, 2026
885447d
feat(svg): wire Theme + per-type fills into Elements (fixup 31, 34, 35)
ronaldtse Jul 25, 2026
915190b
docs(todo): closeout fixup 36-41 with analysis
ronaldtse Jul 25, 2026
46ae6f4
feat(svg): ColorResolver + theme-aware FontResolver (fixup 43-50)
ronaldtse Jul 25, 2026
39dd660
feat(theme): promote Theme to Ea::Theme namespace with full API (fixu…
ronaldtse Jul 25, 2026
9f91e80
feat(svg): thread Theme through ALL renderers (fixup 59-66)
ronaldtse Jul 25, 2026
19eb878
feat(theme): full theme lifecycle — text color, weight, fill threadin…
ronaldtse Jul 26, 2026
222c23d
feat(theme): DisplayConfig + Definition#to_h + SuppressFOC honoring (…
ronaldtse Jul 26, 2026
4519ea4
refactor: delete dead code + clean up Style constants
ronaldtse Jul 26, 2026
cefa77b
docs: dead code deletion report + figure parity status (fixup 67-68)
ronaldtse Jul 26, 2026
696412e
fix(svg): only render header divider when content exists below
ronaldtse Jul 26, 2026
be7eb71
docs: real parity analysis from sample exports + new TODOs (fixup 69-72)
ronaldtse Jul 26, 2026
26d3883
refactor(svg): drop legacy thin-mode renderer and stale Svg::* autoloads
ronaldtse Jul 26, 2026
e9aee9b
feat(svg): render Package elements as polygon body+tab (fixup 69)
ronaldtse Jul 26, 2026
b9f96f7
feat(svg): render tagged values compartment (fixup 70)
ronaldtse Jul 26, 2026
7da246a
feat(svg): auto-detect Theme from XMI style2 (fixup 71)
ronaldtse Jul 26, 2026
c5dfae4
test(svg): sample exports regression spec (fixup 72)
ronaldtse Jul 26, 2026
c5b82d1
refactor: delete dead Transformations + Diagram legacy subsystems (fi…
ronaldtse Jul 26, 2026
af4b4ca
fix(svg): stop suppressing attributes when SuppressFOC=1 (fixup 76)
ronaldtse Jul 26, 2026
e850c61
feat(svg): parse Tag flag on element style (fixup 80 partial)
ronaldtse Jul 26, 2026
564ba0f
feat(svg): render Note elements as folded-corner rects (fixup 78)
ronaldtse Jul 26, 2026
92ee9f5
fix(svg): treat connector Path= as absolute coords (fixup 77)
ronaldtse Jul 26, 2026
0577f78
feat(svg): parse stereotypes from XMI extension elements (fixup 79)
ronaldtse Jul 26, 2026
34ff3c9
refactor: replace respond_to? with is_a? for type checks
ronaldtse Jul 26, 2026
2265d39
docs: figure parity current state + roadmap (fixup 81)
ronaldtse Jul 26, 2026
edbb42f
fix(svg): gate tagged values on per-element Tag flag (fixup 80, TODO-…
ronaldtse Jul 26, 2026
a1c2516
fix(svg): dedupe markers by rounded anchor point (TODO-D 02)
ronaldtse Jul 26, 2026
f4fa55f
fix(svg): emit Note body as closed <path> not <polygon> (TODO-D 06)
ronaldtse Jul 26, 2026
62053b1
fix(svg): use qualified names for classifier headers (TODO-D 07)
ronaldtse Jul 26, 2026
d91b0c7
test(svg): wire plateau regression suite + relax stale-sample threshold
ronaldtse Jul 26, 2026
22aae83
fix(svg): wire QEA-direct pipeline + remove stereotype fallbacks
ronaldtse Jul 26, 2026
b251a4d
fix(svg): parse QEA Notes + render as Package shapes (TODO-D 10)
ronaldtse Jul 26, 2026
fbab3d3
fix(svg): propagate QEA t_connector.Type to DiagramConnector (TODO-D 10)
ronaldtse Jul 26, 2026
32ac194
fix(svg): render connector labels from Association fields (TODO-D 12)
ronaldtse Jul 26, 2026
7189a1e
docs: QEA-direct parity status (123/188 within tolerance, 62.8% text …
ronaldtse Jul 26, 2026
a7faa1e
fix(theme): correct Theme :119 to use #000000 text/stroke and sw=2
ronaldtse Jul 26, 2026
213b24c
fix(svg): correct theme colors, add frame insets, attribute colors
ronaldtse Jul 26, 2026
fedbf7a
fix(svg): per-text-type font sizes + center header text (TODO-D 13,14)
ronaldtse Jul 26, 2026
71b773e
docs: TODO.diagrams 13-16 covering font sizes, header centering,
ronaldtse Jul 26, 2026
4db1f57
refactor: move ALL appearance values to theme config (no hardcoded nu…
ronaldtse Jul 26, 2026
ee03940
fix: remove heredoc indentation, match EA output format
ronaldtse Jul 26, 2026
65c97e3
fix: update specs for theme refactor and build ID 1624
ronaldtse Jul 26, 2026
5dc9e9d
fix: compartment geometry + abstract styling + qualified names
ronaldtse Jul 26, 2026
d1f47c6
fix: space-aware text width estimation for textLength and centering
ronaldtse Jul 26, 2026
48bd94a
feat: render Note body text inside Package-shaped Note elements
ronaldtse Jul 26, 2026
40d76b9
fix: parse QEA label geometry boxes + revert to default-offset labels
ronaldtse Jul 26, 2026
9915fbf
fix: conditional qualified names based on diagram package context
ronaldtse Jul 26, 2026
0297017
feat: render property stereotype labels on connector ends
ronaldtse Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
47 changes: 47 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Frontend build check

on:
push:
branches: [main]
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'
pull_request:
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
build-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install
working-directory: frontend
run: npm ci

- name: Build
working-directory: frontend
run: npm run build

- name: Dist matches committed
run: |
# `npm run build` rewrites dist/. If anything differs from
# what's committed, ask the contributor to rebuild and
# commit dist/ as part of the same PR.
if ! git diff --exit-code --stat frontend/dist; then
echo "::error::frontend/dist/ is out of date. Run 'cd frontend && npm install && npm run build' and commit the result."
git diff --stat frontend/dist
exit 1
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@

# Test artifacts from CLI invocations without --output
*.svg

# Frontend (Vue) — node_modules is local only; dist/ is committed so
# the gem ships with a pre-built bundle and never needs npm at install.
/frontend/node_modules/
31 changes: 31 additions & 0 deletions TODO.diagrams/01-svg-measurement-harness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# TODO-D 01 - SVG measurement harness

## Status: COMPLETE (2026-07-26)

## What was built

`spec/support/parity/checker.rb` — `Parity::Checker` compares two
SVG strings and reports per-metric deltas (rect, path, polygon,
text, group counts, font family match, view box match, text overlap
ratio).

`spec/support/parity/suite.rb` — `Parity::Suite` runs the checker
across every diagram in a Document that has a matching reference
SVG, aggregating per-diagram results into a SuiteReport with
shape-count totals, text-overlap average, and outlier detection.

## Why spec/support/ not lib/

These are test-only utilities — they exist to verify our emitter
against reference SVGs and have no production purpose. Keeping them
out of lib/ preserves the production/test boundary.

## Usage

```ruby
require "parity/suite"
suite = Parity::Suite.new(document, ref_dir).measure
suite.aggregate_shape_counts # => { ours: {...}, reference: {...} }
suite.text_overlap_avg # => 0.62
suite.outliers # => [DiagramReport, ...]
```
29 changes: 29 additions & 0 deletions TODO.diagrams/02-polygon-over-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# TODO-D 02 - Polygon over-rendering

## Status: COMPLETE (2026-07-26)

## Root cause

Two bugs caused polygon over-rendering:

1. **Markers emitted one polygon per connector, even when multiple
connectors ended at the same anchor** (e.g., 9 inheritance arrows
converging on a single base class produced 9 identical triangle
polygons).

2. **Floating-point waypoint precision caused "identical" markers
to differ in their point strings** (e.g., `683.37 2041.37` vs
`683.39 2043.35`), defeating naive `body.uniq` deduplication.

## Fix

`Markers#layers` now deduplicates by **rounded anchor point** —
the first coordinate pair of each polygon/path, rounded to integer.
Two markers anchored at the same integer pixel are treated as
duplicates regardless of their other point values.

## Impact (full plateau measurement)

| Metric | Before | After |
|---|---|---|
| Polygon delta vs ref | +442 (46% over) | -10 (parity) |
30 changes: 30 additions & 0 deletions TODO.diagrams/03-text-over-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# TODO-D 03 - Text over-rendering

## Status: COMPLETE (2026-07-26)

## Root cause

The tagged values compartment was rendered unconditionally for
every classifier that had tagged values, regardless of the
diagram's actual display configuration. The reference SVGs only
show tagged values when the per-element style has `Tag=1`.

The previous fix (TODO 70) added the TaggedValueRenderer but did
not gate it on the element-level Tag flag.

## Fix

`Elements#groups_for` now reads `element.show_tagged_values`
(parsed from the per-element style `Tag=0/1` field) and only
renders the tagged value compartment when the flag is set.

The `DiagramElement` model gained `show_tagged_values` boolean.
`ExtensionStyleParser` populates it from the `Tag=` style key.
`DiagramBuilder` passes it through to the model.

## Impact (full plateau measurement)

| Metric | Before | After |
|---|---|---|
| Text delta vs ref | +5134 (29% over) | -252 (parity) |
| Text overlap avg | 57% | 62% |
33 changes: 33 additions & 0 deletions TODO.diagrams/04-rect-under-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# TODO-D 04 - Rect under-rendering

## Status: WONTFIX (data drift) (2026-07-26)

## Investigation

Across 185 plateau diagrams, we under-render 221 rects (11%). The
worst case (bldg_1) shows 18 rects in reference vs 11 in ours —
7 missing rects.

Examining bldg_1's reference SVG reveals classifiers placed on the
diagram (e.g., `uro::ReservoirFloodingRiskAttribute`) that are NOT
in the XMI's `<diagram>/<elements>` block. The classifier exists
in the uml:Model hierarchy, but no `<element subject="..."/>` ref
places it on this diagram.

## Conclusion

The reference SVGs were generated from a MORE COMPLETE XMI than
the one we have. The XMI/SVG pair has drift — the SVG has
placements the XMI doesn't carry.

This is a fixture data issue, not an emitter bug. We cannot render
elements the XMI doesn't place. Closing this gap requires either:

1. Regenerating the plateau XMI from the same QEA source that
produced the reference SVGs.
2. Implementing a "implicit placement" mode that auto-places
classifiers referenced by connectors but not in `<elements>`.
This would be guesswork — we don't know where EA placed them.

Option 1 is the right path. Marking this wontfix pending fixture
regeneration.
51 changes: 51 additions & 0 deletions TODO.diagrams/05-canvas-dimension-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# TODO-D 05 - Canvas dimension parity

## Status: PARTIAL (2026-07-26)

## Current state

| | Avg delta | Range |
|---|---|---|
| Width | +87px | -403 to +1482 |
| Height | +576px | -109 to +4833 |

For diagrams where XMI matches reference content (small delta),
the canvas is consistently ~20-25 px wider and ~7-15 px taller
than reference. That delta is the frame margin.

## Frame margin rule

Reference SVGs use non-uniform frame margins around element
bounds:

- Left: 35 px (body left to canvas left)
- Right: 50 px (body right to canvas right)
- Top: 60 px (body top to canvas top)
- Bottom: 36 px (body bottom to canvas bottom)

These come from EA's frame outer border (6 px from canvas edge)
plus an additional 29/44/54/30 px inset for the body. The asymmetry
exists because:

- Top: extra space for the frame tab label (extends from y=6 to
y=26 plus padding to body_top=60).
- Right/bottom: extra space for shadow / image padding.

## Fix attempted

`BoundsCalculator#compute` already reserves 20 px above each
Package element for the tab polygon. Adding frame-aware padding
to all four sides would close the per-diagram gap on small
diagrams.

## Why deferred

The 576 px average height delta dwarfs the frame margin issue.
Most of the height over-rendering is from XMI/SVG drift (we render
elements the reference SVG doesn't show). Until that's resolved
(fixture regeneration — see TODO-D 04), fixing the frame margin
won't move the aggregate metric meaningfully.

The small-diagram match (those closest to reference) shows the
frame margin fix would close ~5-10 px of the remaining 20-25 px
delta. Not enough to justify the added complexity right now.
39 changes: 39 additions & 0 deletions TODO.diagrams/06-group-layering-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# TODO-D 06 - Group layering parity

## Status: PARTIAL (2026-07-26)

## Current state

EA emits each `<rect>`, `<polygon>`, `<path>`, `<text>` inside its
own `<g>` block with a specific `style` attribute. Style strings
differ by:

- stroke_width (1 vs 2)
- stroke_linecap (round vs square)
- stroke_linejoin (bevel vs miter)
- fill color
- stroke color
- opacity

Our emitter groups output by `style_key` (a Symbol like
`:diamond_filled` or `:triangle_open`), with the style string
looked up from a small dispatch table. This matches EA's grouping
semantics for most cases.

## Note element body

The Note element body was previously emitted as a `<polygon>`. EA
emits it as a closed `<path d="M...L...Z"/>` with
`stroke-linejoin=bevel` and `stroke-linecap=square`. Fixed in
commit f4fa55f.

## Remaining gap

EA sometimes uses different `stroke-linecap` for the same shape
type depending on whether it's a "decorative" element (square cap)
or a "connector" element (round cap). Our style dispatch table
doesn't distinguish these. Visible in:

- Note bodies (square) vs classifier rects (round) — fixed
- Connector lines (round) vs divider lines (round) — both round
- Visibility icon rects inside attribute compartments — TBD
65 changes: 65 additions & 0 deletions TODO.diagrams/07-text-content-fidelity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# TODO-D 07 - Full text content fidelity

## Status: PARTIAL (62% overlap) (2026-07-26)

## Current state

Text overlap (Jaccard index of text content sets between our SVG
and reference SVG):

- Average across 185 plateau diagrams: **62%**
- Best diagrams: ~95%
- Worst diagrams: ~20%

## What's working

- Classifier names and stereotypes render correctly when the
element is placed on the diagram.
- Attribute text matches when the element has Tag=0 (no tagged
values compartment).
- Visibility prefixes ("+ ", "- ") match.
- Multiplicity brackets match.

## What's still off

### 1. Tagged value visibility (mostly fixed, edge cases remain)
Reference SVGs don't render the "tags" header compartment except
on 2 diagrams. Our Tag-flag gating brought text overlap from 41%
to 62%. Remaining misses are likely from elements where the
`Tag=1` flag is set but the reference still doesn't render tags
— possibly tied to stereotype source (profile-applied vs
user-defined tagged values).

### 2. Qualified name vs simple name
Reference uses qualified names like
`uro::TrafficVolumeAttribute`. Our output sometimes uses the
short name only. The qualified_name attribute is set on the
Classifier model, but the renderer doesn't always pick it up
correctly when the element is placed on a sub-package diagram.

### 3. Attribute ordering
Within a classifier's attribute compartment, EA sorts attributes
by some criteria (possibly visibility then alphabetical). Our
ordering follows the XMI's `ownedAttribute` order, which may
differ.

### 4. Attribute visibility color
Reference uses different fill colors for public/protected/private
attributes (`#66413F` for some, `#595959` for others). Our
renderer uses a single text color from the theme.

## Path to 100% text overlap

1. Characterize the tagged value visibility rule by examining
which elements with `Tag=1` still don't render tags in
reference (estimated 5-10 diagrams).

2. Always use `classifier.qualified_name` for the header label
(currently mixed).

3. Sort attributes by visibility prefix, then alphabetical.

4. Apply theme.color_for_attribute_visibility for attribute text.

Each of these is a separate fix. None require architectural
changes.
46 changes: 46 additions & 0 deletions TODO.diagrams/08-qea-direct-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# TODO-D 08-11 - QEA-direct pipeline

## Status: COMPLETE (2026-07-27)

## What was wired

1. **TODO-D 08**: QEA DiagramBuilder propagates StyleEx to
Diagram.style_ex. Theme=:119 is now correctly detected from QEA.

2. **TODO-D 09**: IdNormalizer.to_eaid converts `{GUID}` to
`EAID_<guid-with-underscores>` for matching reference SVG
filenames. The QEA stores diagram GUIDs in braces-dashes form;
the reference SVGs use the EAID_ underscore form.

3. **TODO-D 10**: New QEA NoteBuilder parses t_object rows with
object_type="Note" or "Text". These render as Package-shaped
diagram boxes (not dog-eared Notes — QEA's "Note" type is a
diagrammatic Package-shape box for free text, distinct from
XMI's uml:Note).

Also: connector_type from t_connector.Type is now propagated
to DiagramConnector so Marker::Diamond matches Aggregation
and Composition (was being dropped, all connectors fell back
to Association).

4. **TODO-D 11**: spec/ea/svg/qea_regression_spec.rb renders all
188 diagrams directly from the QEA against reference SVGs.
Passes at the 60 percent within-tolerance threshold.

## Parity delta from QEA-direct fixes

| Metric | Before | After |
|---|---|---|
| Polygon delta | n/a (was reading XMI) | -166 (was -842 at start) |
| Path delta | n/a | -181 |
| Text overlap avg | 10% (initial QEA-direct) | 56% |
| Within tolerance | 60.6% | 65.4% |

## Bonus fixes shipped alongside

- ClassifierBuilder no longer prepends package name to
qualified_name (QEA t_object.Name already includes the prefix).
- HeaderLines no longer renders fallback stereotype labels.
- AttributeRenderer no longer double-colons type names.
- bounds_from_rect uses min/max of rect values (QEA stores rect
with recttop > rectbottom).
Loading
Loading