Commit Graph

7 Commits

Author SHA1 Message Date
Levi Neuwirth c56ea4845f editor-gui: a thin native (egui) editor over the editor-core seam
A new demo binary crate that proves the EditorSession seam end to end. It renders
the session's resolved layout with epiphany-render-svg, rasterizes that SVG with
resvg into an egui texture, resolves clicks back to staff-space world coordinates
to select, and drives all six note-editing intents from a toolbar and keyboard
(delete, chromatic transpose, staff-step move, add chord note, insert-after). A
debug panel shows the selection and the last applied op, proving the op log is
usable. Intentionally narrow: no duration editing, empty-space insert, or undo UI.

The one piece of nontrivial logic — the screen<->world map a click depends on — is
a pure ViewMap that inverts render-svg's translate(-min_x, max_y) scale(1 -1)
transform, unit-tested for round-trip, the y-up corners, and the logical-vs-ceiled
size mapping (the texture is displayed at the SVG's logical size with a uv crop, so
the click plane is not stretched by the pixmap's rounded-up dimensions). rerender
updates view box, logical size, and texture together on success and clears the
texture on a rasterization failure, so the pixels never disagree with the click
plane.

eframe + resvg are the workspace's first heavy external deps. eframe is pinned to
0.29 for the classic App::update(ctx) + panel API (0.35 redesigned App around
ui()). The crate needs a newer toolchain than the workspace MSRV, so CI excludes
it from the pinned-1.77 workspace commands and checks it in a separate
current-stable editor-gui job; the rest of the workspace is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAtEiJtt9yKVV1zjKYmZhS
2026-06-29 11:47:25 -04:00
Levi Neuwirth a28d888c8e epiphany-editor-core: a headless editor API + a conformance UI-seam gate
Packages the proven editing-loop vertical slice as the API a GUI calls -- no UI,
no rendering backend of its own (it produces a RenderIR). EditorSession owns:

  - selection state (Selection { source, layout_object }): click(point) selects the
    topmost hit, select(id) restores a selection, selection()/clear_selection();
  - render/hit-test query: render() and hit_test() for the GUI to draw and resolve
    clicks/drags;
  - operation minting -- the ergonomics gap the harness exposed, closed before UI
    depends on it: the caller passes an OperationKind to apply() (or an intent like
    transpose_selection(+1)) and the session assembles the OperationEnvelope (id,
    author, stamp, causal context). A GUI never hand-rolls envelope bookkeeping;
  - apply/re-render -- ATOMIC: a minted op the reducer rejects (e.g. a reserved
    replica identity) returns Err(RejectedOperation), not a silent no-op, and a
    diagnostic-only layout returns Err(NotRenderable); on any error nothing mutates,
    operation counter included (the candidate id is committed only on success);
  - selection preservation: the selection is re-resolved against the new layout,
    kept when its layout object survives and cleared when it is gone.

The session is solver-agnostic (Box<dyn ConstraintSolver>), so a GUI plugs in the
Engraver, the stub, or any conformant solver. EditorError implements Display/Error.
epiphany-ops now re-exports AcceptOutcome (accept()'s return type, previously
unreachable) so a caller can inspect a rejection.

Also wires the edit-loop harness into the conformance suite as the [7c] UI-seam
gate: over both fixtures (ten_measure_single_staff and valid_score_rich) every seed
must drive a click->sharpen->re-render cycle whose selection survives the relayout
-- the contract a GUI's correctness rests on.

Full gate green: build, fmt, clippy, 606 tests, conformance scale 1 (incl. [7c]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:47:34 -04:00
Levi Neuwirth 6293734fa0 Agent I-4c: embedded @font-face glyph mode (a second self-contained renderer)
A render mode that references each glyph by its SMuFL codepoint via `<text>`,
drawn from an `@font-face`-embedded Bravura subset, alongside the default inline
`<path>` outlines. The SVG stays self-contained (the font travels in it) and the
text is selectable, at a larger file size.

  - GlyphMode::EmbeddedFont: the SVG declares the font once via
    <defs><style>@font-face{...}</style></defs>, then emits one
    <text transform="translate(x y) scale(1 -1)" font-size="4" ...>&#xNNNN;</text>
    per glyph. Geometry is consistent with PathOutline by construction: same
    origin, a per-glyph counter-flip cancels the outer y-flip, and the SMuFL em is
    four staff spaces. A new `text_count` stat; unbundled glyphs still fall through
    to the visible bbox rect + diagnostic. The metadata comment declares which mode
    produced the SVG (and, on the empty canvas too, via a shared `glyph_note`).
    Path mode stays the byte-golden-locked, pixel-verified reference; the embedded
    mode is structurally tested (well-formed XML — also under `xmllint`; one
    @font-face; one <text>/codepoint per glyph; provenance preserved; determinism).

  - The subset is a GENERATED artifact, not a vendored binary: a deterministic
    base64 OTF emitted into src/font_subset_generated.rs by
    `tools/extract_bravura_outlines.py --font-out` (the same SHA-pinned 1.392 font
    the outlines come from). `recalcTimestamp=False` keeps the source font's fixed
    head.modified so the bytes are reproducible across runs, not just within one.

  - OFL compliance: the subset is a Modified Version, so its PRIMARY font name is
    renamed off the Reserved Font Name "Bravura" to "EpiphanyBravuraSubset" in BOTH
    naming structures an OTF carries — the SFNT `name` table AND the CFF (Name INDEX
    + top-dict FullName/FamilyName). The copyright/trademark/license records, which
    name Bravura as attribution, are kept; the renderer references the renamed
    family in @font-face and <text>. The generator reparses the saved bytes and
    fails if the reserved name leaks into a primary record, and validates the cmap
    covers every glyph.

  - Machine-locked payload: the generator emits the decoded length and a BLAKE3-256
    (the workspace's sole hash) of the font bytes; a render-svg test base64-decodes
    the payload (no new runtime dep) and asserts the length, the OTTO signature, the
    BLAKE3, and — parsing the SFNT name table and CFF Name INDEX — that neither
    primary name is the reserved name. Adds an epiphany-determinism dev-dep.

The demo example gains `--glyph-mode=path|embedded`; lib/README/DECISIONS document
the two modes, the subset's OFL rename, and the regeneration command (--font-out +
the blake3 dependency). PathOutline goldens are byte-unchanged; the outlines stay
byte-identical. Full gate green: build, fmt, clippy, 585 tests, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:09:34 -04:00
Levi Neuwirth af297881e2 Agent I-3: criterion-6 round-trip and golden lock on the real Engraver
Criterion 6 (the Chapter 7 layout round-trip) and the render goldens previously
exercised only the verbatim StubSolver, so a regression in the real Engraver's
geometry could land unseen. I-3 drives both through the Engraver.

  - round_trip_with<S: ConstraintSolver> factors the solver-agnostic provenance
    contract out of round_trip (now a one-line stub wrapper): coverage, the
    complete Provenance surviving constrained -> resolved -> render, the source
    surjection, and no duplicate stable ids hold for *any* conformant solver. The
    Stub tier's verbatim-geometry clause is gated behind solver.tier() == Stub;
    every other tier re-spaces. The status gate accepts any renderable status
    (Solved / SolvedWithWarnings / PartialBudgetExhausted), not exactly Solved, so
    the helper matches its "arbitrary conformant solver" contract while still
    rejecting the diagnostic-only statuses that carry no authoritative layout.
  - criterion_six_round_trips_through_the_engravers_respacing (epiphany-engrave)
    runs the full graph -> logical -> constrained -> *engraved* -> render round
    trip over the criterion-6 hand-off fixtures -- ten_measure_single_staff (the
    measured fixture) and valid_score_rich (cross-cutting tuplet/tie/spanner),
    plus valid_score for breadth -- and asserts the whole provenance contract
    survives the Engraver's re-spacing. A non-vacuity check confirms the Engraver
    genuinely moved geometry, so provenance is preserved *through* a real geometry
    change -- the statement the verbatim stub can never make. This adds an
    epiphany-testkit dev-dep (no cycle: testkit does not depend on this crate).
  - The render-svg engraver acceptance test is upgraded from invariant-only to
    byte-locked: new .engrave.snapshot.txt / .engrave.svg goldens for both
    fixtures capture the Engraver's re-spaced output (e.g. ten_measure view_box
    width 82.26 vs the stub's 88.88, same glyph/stroke/class counts), so an
    Engraver geometry regression is caught at the byte level. A companion test
    asserts the engrave goldens genuinely differ from the stub goldens, catching
    the degeneracy where the Engraver echoes the stub (which would otherwise pass
    both golden checks independently).

Also corrects the epiphany-engrave package description, which still claimed it
reports SolverTier::Stub until it earns Minimal (it earned Minimal in I-2).

Full gate green: build, fmt, clippy, 580 tests, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:58:31 -04:00
Levi Neuwirth ac7c076e73 Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub
Lands the renderer-against-stub slice of Agent I's visible engraving work
(spec/PHASE2_QUICKSTART.md). Two new crates; prerequisites (G Pass 11, H
spelling/decomposition) are in place. Real engraving + Minimal-tier solver
follow next phase.

epiphany-render-svg (the deliverable this phase):
- Renders a ResolvedLayoutIR to well-formed SVG 1.1, drawing each glyph as a
  GENUINE Bravura SMuFL outline <path>. Outlines are extracted reproducibly
  from the official OFL Bravura.otf by a committed generator
  (tools/extract_bravura_outlines.py, OFL.txt); the font is not vendored, only
  the generated Rust (src/outlines_generated.rs). Staff-space/y-up coords with
  one global y-flip wrapper; viewBox in staff spaces, px scale on the root.
- Non-overreach: every element traces to a ResolvedGlyph (data-prov) or a
  declared wrapper; a glyph lacking an outline is surfaced as a diagnostic and
  drawn as a fallback rect, never silently dropped.
- Hand-rolled xml::check_well_formed (no XML dep); acceptance tests cross-check
  with system xmllint when present.
- examples/render_fixture.rs demo (fixture name -> SVG stdout, --solver=stub|real).
- Golden-locked machine acceptance snapshot + full-SVG golden for
  ten_measure_single_staff and valid_score_rich; deterministic output.

epiphany-engrave (honest scaffold):
- Engraver: a deterministic horizontal-spacing pass (first axis of the planned
  two-pass spring layout). Reports SolverTier::Stub — NOT Minimal — until it
  evaluates the declared hard constraints, guarded by a regression test. The
  demo's --solver=real exercises it end to end.

Honesty notes (recorded as Pass-12 candidates P12-I1..I3 in spec/PASS12_BATCH.md
and the crates' DECISIONS.md): the v0 to_logical/to_constrained pipeline is a
structural placeholder (arbitrary glyph per object, y=0), so stub output is not
yet recognizable notation and the QUICKSTART human visual gate is a next-phase
gate; MUSCLOID layout-id derivation stays unwired; bundled BRAVURA_METRICS are
approximations that disagree with the real outlines.

Gates: cargo fmt + clippy -D warnings clean; cargo test --workspace 504 passed,
0 failed, 0 ignored (criterion 6 layout round-trip still green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:01:40 -04:00
Levi Neuwirth 3d1c55d73e Land epiphany-layout-ir (Agent E): layout IR + solver interface
Implements Agent E per spec/QUICKSTART.md — the layout intermediate
representation (Chapter 7) and the constraint-solver interface (Chapter 9):

  * Four IR stages: LogicalLayoutIR -> ConstrainedLayoutIR ->
    ResolvedLayoutIR -> RenderIR (interface only), with the composite-object
    taxonomy, spring slots/constraints, vertical-band model, pages/systems,
    engraving decisions + overrides, and the incremental dependency/cache model.
  * TimeAxisModel tagged enum (Metric/Proportional/Aleatoric/Registered).
  * Provenance back-references with manifestation- and synthesis-aware ids
    ((source, region) and (source, kind, ordinal)), so multiply-manifested and
    synthesized objects never collide.
  * In-tree Bravura GlyphCatalog (Send+Sync, metrics + render-data interface),
    MUSCFNTM-tagged metrics hash with anchors hashed as a name-keyed map.
  * Edit-barrier types keyed on OperationKindTag, with precise EditContext /
    EditOracle scope/condition evaluation.
  * StubSolver: returns SolveStatus::Solved with the input geometry verbatim;
    spec-compliant SolveReport, Minimal tier + all-worst (unmeasured) metric
    vector (no false conformance claim); rejects ill-formed input.
  * f32 staff-space IR coordinates, quantized to the 1/1024 grid only at
    canonical ResolvedLayoutIR serialization (Appendix D); non-finite geometry
    is rejected, not normalized. Canonical encoding is injective in glyph
    identity, provenance, engraving decisions, and catalog identity.

Re-points Agent F's testkit layout harness from its in-tree stub to the real
crate (v0 acceptance criterion 6) and expands its generators to E's public
surface. Expands Agent C's OperationKindTag to the full normative variant set
so edit barriers can prohibit every operation class.

Workspace gates green: fmt, clippy -D warnings, 377 tests, doc tests, rustdoc
-D warnings, and the conformance suite at scale 1. Decisions and Pass 11
candidates recorded in crates/epiphany-layout-ir/DECISIONS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:58:01 -04:00
Levi Neuwirth a2e9ec32f6 A B C D F 2026-06-19 12:42:31 -04:00