Skip to content

Feat semantic compare merge - #52

Merged
dbose merged 7 commits into
mainfrom
feat-semantic-compare-merge
Oct 4, 2026
Merged

dbose merged 7 commits into
mainfrom
feat-semantic-compare-merge

Conversation

@dbose

@dbose dbose commented Oct 4, 2026

Copy link
Copy Markdown
Owner
image

One unified semantic diff/merge review -

A single ModelDiffDoc payload and one ReviewScreen renderer - that every model-change flow reuses, in both the Canvas and VS Code. The spec (specs/modelith-semantic-compare-merge-spec.md) defines 6 phases; Phases 1 and 2 are shipped on this branch.

Commits (newest first):

43260f9 Phase 2 — erwin import pre-write review: /api/import/preview (parse-without-write → enriched diff doc), ReviewScreen action prop + "Apply import" (writes only selected new objects), modelith.reviewImport in the Reverse Review view, .xml→stash→prefill channel.
749b40c Merge main into the branch (conflicts resolved).
889320d Wire per-field selection through the live review screen.
baef9ce Phase 1 — display filters + per-field selection on the 2-pane review.
3e0c2c1 VS Code Model tree: entities → attributes, click opens the YAML.
20892a3 The spec itself (Tier 1 semantic compare & merge).
6c0cb15 Toolbar declutter (grouped, labelled, two-row).

The top bar crammed brand, search, an unbounded subject-area legend,
counts, diagnostics, and ten identical Unicode-glyph buttons into one flex
row. Panel-openers looked the same as view actions, nothing was legible
without hovering, and the legend shoved everything as it grew.

Reorganised into information zones:
- Two rows: row 1 is identity + search + live status (counts + validity
  pill clustered on the right); row 2 is the actions.
- Actions grouped into bordered segments with quiet tags — Panels
  (Ontology/Layers/Changes/Decisions, icon+label since they are the least
  guessable), View (types/collapse/layout/fit, icon-only + tooltip), and
  Data (refresh, Export/Import). The grouping itself teaches which controls
  open a panel vs change the view vs move data.
- The subject-area colour key moves to a thin reference strip below the
  toolbar, so it can never push the controls and wraps safely for many areas.
- Real inline SVG icons (new canvas/src/icons.tsx, no new dependency)
  replace the Unicode glyphs, which rendered inconsistently across OSes.

Wraps gracefully at narrow VS Code webview widths where the old single row
overflowed. Verified: typecheck + build clean, panel toggles and layout
menu work, no console errors, legible at 640px.
The Tier 1 GTM capability: one semantic diff/merge view reused across
every model-change flow (preview, propose, drift, reverse-accept, import,
conflicts) in both the Studio Canvas and VS Code. Free local review is the
bottom-up funnel; the blocking CI gate stays the paid wedge.

The toolbar declutter (prior commit) is the first step of making the Canvas
the polished home for this review experience; the compare/merge view builds
on the same surface.
Adds a logical Model Explorer to the VS Code side panel: every entity
expands to its attributes (PK marked with a key icon), and clicking an
entity or attribute opens logical/entities/<name>.yaml with the cursor on
that object. This is the 'what is my model' view.

The 'Entities' shown in the old Warehouse Config tree actually come from
classifying dbt MODELS via reverse-config explain, which confused a user
into thinking it was their logical model. That tree is renamed to
'Warehouse Mapping' (how your dbt models map to entities), so the two are
no longer conflated: Model = the logical model, Warehouse Mapping = the
warehouse classification.

Backed by a new CLI command:
- mdl model tree --format json — emits every entity with its attributes
  (name, domain, role, nullable, is_pk) and the source file per entity, so
  the extension stays thin and the shape is testable in pytest. Built on
  mdl_core.query.model_tree(model, path_for=...), reusing ModelRepo's file
  map for the click target.

Tests: core query (pk flag, source file exists, subject-area scope), CLI
(json shape, files resolve on disk, text format), and the tree-view
rendering harness (entity→attribute drill-down, key icon, both clicks open
the owning YAML, resting states). 932 pytest pass; all 6 extension gates
green (44 commands). CLI reused, extension 0.3.17.
The review DiffView gains the first slice of the semantic compare/merge
view (Tier 1 spec): display filters and per-field change selection, so a
reviewer can isolate additions/renames/etc. and accept or reject changes
one field at a time — not just whole objects.

- Filters (All / Additions / Modifications / Renames / Deletions /
  Conflicts) are VIEW-ONLY: they change what is visible, never what is
  selected, so a hidden change can never be silently dropped from a
  proposal. Each chip shows its count; empty categories are hidden.
- Per-field selection keys on (ulid, field). An object is included when
  ≥1 of its fields is selected; the header checkbox shows a tri-state
  (all/some/none) and toggles every field at once. The original per-object
  path (onToggle) is kept intact, so ReviewScreen works unchanged; the new
  per-field mode activates only when a host passes onToggleField.
- Pure semantics live in reviewModel.ts (filter + selection), so they are
  unit-testable apart from the component.

Testing (per the 'system + interaction tests matter now' directive):
- Vitest + Testing Library added to canvas/ (first JS test runner here):
  reviewModel unit tests (8) + DiffView interaction tests (5) that render
  the real component and click filters + per-field checkboxes, asserting
  filtering never changes selection and per-field exclusion drops the right
  objects.
- pytest server-side system test (test_review_diff_contract): the
  /api/preview ModelDiffDoc carries the (ulid, field) identity per-field
  selection depends on, and is stable across re-previews — the wire
  contract guard between server and the canvas selection.

935 pytest pass, 13 canvas tests pass, ruff clean, canvas typechecks.
Validating Phase 1 against a real project surfaced a gap: DiffView had the
per-field capability but ReviewScreen still passed the per-OBJECT callback
(onToggle), so on the real SME review screen the filter toolbar rendered but
the per-field checkboxes stayed dormant — no caller activated them.

Wire it through: ReviewScreen now tracks excluded (ulid, field) keys and
passes onToggleField; SmeApp toggles field keys in its excluded set. The
selected set is derived as all field keys minus the excluded ones (default:
everything included), matching the object auto-derivation the DiffView tests
assert.

Verified live on the ibor demo: staged a real definition edit, opened the
review screen, confirmed the per-field 'DEFINITION CHANGED' checkbox renders
and that unchecking it derives the object header to excluded — the exact
semantics the unit + interaction tests cover. 13 canvas tests pass,
typecheck clean.
# Conflicts:
#	canvas/src/TopBar.tsx
#	canvas/src/icons.tsx
#	canvas/src/styles.css
#	packages/server/src/mdl_server/static/assets/ModelDiagram-C9iVTvG9.js
#	packages/server/src/mdl_server/static/assets/ModelDiagram-DfdI4kly.js
#	packages/server/src/mdl_server/static/assets/ModelDiagram-oAb1enKe.js
#	packages/server/src/mdl_server/static/assets/sme-rGO675Bx.js
#	packages/server/src/mdl_server/static/assets/styles-Cn8AFsR1.js
#	packages/server/src/mdl_server/static/index.html
#	packages/server/src/mdl_server/static/sme.html
#	uv.lock
#	vscode/package.json
Make an erwin import show the unified semantic review (filters, per-field
selection, severity, break-impact) BEFORE anything is written, with an
"Apply import" action that writes only the selected new objects. Reuses the
Phase 1 ReviewScreen/ModelDiffDoc end to end; no change to the propose flow.

Server
- POST /api/import/preview (erwin only; 422 otherwise): parse without writing,
  diff the imported model against the current one by name, and return the
  enriched ModelDiffDoc (paths + break-impact) plus the import's command list
  and warnings for the apply action. Writes nothing.
- POST /api/import/stash + GET /api/import/stash/{token}: one-shot content
  channel so a VS Code .xml can pre-fill the canvas review (the webview can't
  read local files).

Core
- Extract remap_head_by_name() from diff_models_by_name() so a caller needing
  both the diff and the ULID-remapped head (break-impact enrichment) shares one
  remapping. Signature of diff_models_by_name unchanged.

Canvas
- ReviewScreen gains an optional `action` prop; when present it renders that
  action's button (Apply import) over the included ULIDs, else the unchanged
  "Submit for review" propose button.
- reviewModel: includedEntityNames (added-only) + filterImportChanges map a
  field selection to the import commands that should be written.
- api: importPreview, importStashGet, applyImportBatch(tolerateExisting).
- SmeApp routes ?import=<token|1> to a pre-filled import → review → apply.

VS Code
- modelith.reviewImport command, framed as a Reverse Review view/title action
  and an .xml explorer context action; stashes file content then opens
  /sme?import=<token>.

Tests: import/preview diff-doc + writes-nothing, non-erwin 422, stash one-shot
roundtrip; reviewModel write-filter cases. Spec phasing marks Phases 1–2 shipped.
@dbose
dbose merged commit d4a0762 into main Oct 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant