feat(library): italic abstract classes/methods and lowercase UML class keywords - #799
Conversation
Split the conflated `ClassType` enum into orthogonal UML concepts and fix
how the class diagram renders them:
- Abstract is a modifier, not a keyword: an abstract class shows an italic
name + the standard `{abstract}` annotation instead of the invalid
`«Abstract»` guillemet. Italic renders in the editor; `{abstract}` carries
the cue through export (which strips italic — no italic face is shipped).
- Interface/Enumeration are metaclass keywords, now rendered lowercase
(`«interface»`/`«enumeration»`) per UML 2.5.1 Table C.1.
- New: mark a method abstract from the class editor (per-method toggle) →
italic + `{abstract}` signature. Attributes are excluded — UML attributes
cannot be abstract.
Model: `ClassType` → `ClassStereotype` ({interface, enumeration}), plus an
`isAbstract` boolean on the class and on class members. A shared
`stereotypeLabel`/`withAbstractMarker` pair keeps both SVG renderers and the
width measurement in sync. Existing diagrams migrate on load via an
idempotent normalizer wired at every hydration boundary (editor load bypasses
the public `importDiagram`); serialized version bumped to 4.1.0.
Closes #105.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…picker
Ship real Inter Italic + Bold Italic faces so abstract classes and methods
render italic across the editor and the PNG, PDF and PowerPoint exports,
instead of a browser-only synthetic slant the exporters silently dropped.
Removes the `{abstract}` text fallback and the italic-stripping export pass;
the italic faces are subset by `gen:fonts` and threaded through resvg and
jsPDF, with abstract text measured in italic metrics so boxes still fit.
Replace the class editor's Abstract checkbox + Interface/Enumeration toggle
with one "Class type" Select (Class / Abstract Class / Interface /
Enumeration) — the popover mirror of the palette tiles and the other node/edge
kind selects. Its four options are a lossless projection of the orthogonal
{stereotype, isAbstract} model, so the previously reachable invalid states
(an italic «interface», an abstract enumeration) are unrepresentable, and any
older diagram carrying one self-heals on load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cut over-explaining and history/temporal references ('now', 'previously',
'older two-control editor') from the abstract-italic + class-type-picker work;
keep only durable rationale.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@FelixTJDietrich The implementation pieces I checked look coherent and the focused library tests pass, but this still needs changes: the visual baselines/assets were not regenerated for the changed class UI/rendering, and the title lint check is currently failing. Please update the affected PNG baselines/assets and fix the PR title casing.
The container ran the commit/push script under dash, which rejects `set -o pipefail` and failed the step before it could push refreshed snapshots. Pin the step to bash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@FelixTJDietrich The title/CI issue is fixed and most visual baselines are refreshed, but one requested asset is still missing: standalone/webapp/assets/images/how-to-use-node-edit.png was not regenerated. Leaving changes requested until that modal image is committed.
The howto-assets Playwright project regenerates the modal images under assets/images/, but the commit step only staged tests/**/*-snapshots — so a UI change left those doc images stale. Stage them too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@FelixTJDietrich The latest commit fixes the title and refreshes the Playwright snapshots, but standalone/webapp/assets/images/how-to-use-node-edit.png is still missing from the diff. Keeping this as changes requested until that generated modal asset is regenerated and committed.
test:visual:update used the default --update-snapshots (=changed), which skips snapshots that still pass within tolerance. A sub-threshold UI change (e.g. the class editor's new Class type select) therefore left the how-to-use modal images stale — they rendered old UI but never got rewritten. Use --update-snapshots=all so the baseline job is authoritative. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The export path explicitly warms the Inter faces before its one-shot measure/ render, but only loaded 400/700 upright. Abstract classes/methods are italic, so that text could be measured and rendered in a fallback family until the italic face loaded — drifting box sizes in the exported SVG. Load the italic 400/700 faces too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@FelixTJDietrich All feedback addressed, nice work.
--update-snapshots=all rewrote every snapshot, churning 32 diagram baselines that only differ from CI's render sub-tolerance (visual-regression-tests stays green on them). Restore those to keep the diff scoped to this change: the four class-diagram baselines (lowercase keywords, italic abstract) and the four how-to-use modal images (new Class type select). Revert the update script to the default --update-snapshots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Two commits landed after the approval — flagging them for the record since one is a genuine behavior change:
CI is green on the new head (the earlier |
Summary
This makes UML class diagrams notation-correct and the class editor simpler to use.
«Abstract»keyword — UML has no«abstract»keyword; abstractness is a property, not a stereotype.«interface»/«enumeration»(2.5.1 Table C.1), not the capitalized forms.Class · Abstract Class · Interface · Enumeration. It mirrors the four palette tiles and every other node/edge "type" select, and — because the four options are a lossless projection of the underlying{stereotype, isAbstract}data — it makes the previously reachable invalid states (an italic«interface», an abstract enumeration) impossible to create.The italic is a real shipped Inter italic face, so it renders the same in the editor and in PNG, PDF and PowerPoint exports. The editor's browser-synthesized slant can't be reproduced by the export renderers (resvg, jsPDF), so it was dropped on export — a real face fixes that at the source.
Release note
Abstract classes and methods now render in italics that survive PNG/PDF/PowerPoint export, interfaces and enumerations use the correct lowercase UML keywords, and you set all of it from a single "Class type" menu in the class editor. (Full user-facing note lives in the changeset.)
Implementation notes
Real italic, everywhere it's measured or rendered.
gen:fontssubsets Inter Italic + Bold Italic (from the pinned Inter 4.0 release) into the three consumers that must agree byte-for-byte: the editor woff2, resvg's ttffontBuffers(PNG), and the jsPDF VFS (PDF). Abstract text is now measured with italic metrics too, preserving the measured == rendered == exported invariant documented infontStack.ts, so boxes still size correctly.One deliberate, disclosed scope line — keeping the bundle flat. The italic faces are not base64-inlined into the self-contained SVG export (
exportFonts.ts). Inlining them would roughly double that chunk (+180 KB gzip) to serve a single case: a raw.svgopened in a renderer that won't synthesize oblique (e.g. Inkscape). Browsers synthesize the slant from the embedded regular face, and our own PNG/PDF paths carry the real ttf, so the webapp bundle-size budget stays green with no ceiling bump. The only trade-off: abstract text is upright when a.svgis opened directly in a non-synthesizing external editor.The picker is a projection, not a new data model. Storage stays orthogonal (
{stereotype, isAbstract}); the popover just reads/writes a 4-way kind. Writing both fields on every change is what eliminates a real bug in the old two-control design — a strayisAbstractcould survive a switch to Enumeration and paint an italic«enumeration»on canvas. A migration guard also clears such a stray modifier on load, and the header-height delta is derived from theLAYOUTconstants instead of a magic10.Migration is idempotent and covers every hydration boundary — constructor,
set model,importDiagram, and the off-screen SVG export — so no load path bypasses it.Not a breaking change. The reshaped class types (
ClassStereotype,ClassNodeProps) are not part of the published API — the package entry only re-exportstypings.ts, and they're emitted as ambientdeclare, notexport declare. The published JSON schema leaves nodedataopen, so old and new models both validate.minoris correct. One known edge is out of scope: a peer joining a live Yjs collaboration room during a mixed-version rollout hydrates from the CRDT, which bypasses the load-time migration — a deployment-window concern no semver level addresses.Steps for testing
«interface»/«enumeration»line; height only changes when a keyword line appears or disappears."stereotype": "Abstract"/"Interface") → it loads as an italic abstract class / lowercase«interface»with no manual step.Screenshots / screencasts
The class editor's classifier control changes from a checkbox + two-button toggle to a single Class type dropdown (each option previews its notation — italic name for Abstract Class, a
«keyword»chip for Interface/Enumeration). The new popover and the exported-diagram rendering are both captured by the refreshed Playwright visual baselines in this PR (node-edit.pngand the class-diagram snapshots). A maintainer can attach a quick screencast if desired.Checklist
@tumaet/apollon, minor)feat) matches the kind of changepnpm lint && pnpm format:check && pnpm build && pnpm testlocally — green (library 1221, ui 42, webapp 263)