Skip to content

Give genre its own provenance field and make data canonical - #234

Merged
vicmaster merged 3 commits into
masterfrom
phase-30-slice-a
Oct 31, 2026
Merged

vicmaster merged 3 commits into
masterfrom
phase-30-slice-a

Conversation

@vicmaster

Copy link
Copy Markdown
Owner

Phase 30 slice A. The genre (what a screen is for) and the preset (what style it uses) used to share one field, metadata.provenance.preset. So apply_preset silently overwrote a genre that canvas_set_genre had stamped, and every preset name read as a genre. This PR gives genre its own field and makes data the genre agents are told to declare.

Genre has its own field

canvas_set_genre now writes metadata.provenance.genre, and apply_preset writes only metadata.provenance.preset. Applying dark to a canvas declared commerce leaves it commerce.

Canvases stored before this change keep their verdicts. When a canvas has no genre key, a preset value that is not one of the four built-in presets (dark, light, material, minimal) is read as the genre an older framesmith stored there. The first canvas_set_genre or apply_preset on such a canvas moves that value into genre. A stored preset: "material" now reads as the material preset rather than as a genre, so genre.active becomes null on those canvases while their relaxation and score stay the same.

The genre key itself marks a canvas as migrated: once it exists, even as null, the preset field is always a preset. Without this marker, a preset imported with import_design_md under any other name would be indistinguishable from an old genre, and the next write would move it into the genre field. The built-in list is fixed in code and never comes from the runtime preset registry, because that registry differs between the MCP server and the standalone viewer.

canvas_evaluate, canvas_autofix and the viewer all resolve the genre through one function, resolveGenre. The viewer used to pass the preset field as an explicit genre. It now scores a canvas exactly as canvas_evaluate does, and its score cache keys on both the genre and the preset.

The material preset relaxes on its own

The material preset now relaxes the accent-hue and pure-black-white tells whatever the canvas's genre is, including when an explicit genre parameter is passed. The phase spec scheduled this for slice B, but shipping slice A without it would have dropped that relaxation from every canvas styled with the material preset. The ruleset table it eventually lives in is still slice B. The genre report gains an optional byPreset field, which explains a non-empty relaxed list when active is null. canvas_set_genre returns presetRelaxes for the same reason, since clearing a genre does not remove a preset's relaxation.

data is the canonical genre

data is now the canonical genre and dashboard its permanent alias. The genre names what the numbers are, not a layout, and the layout word had pushed a fitness app into the wrong label. The genre report folds aliases, so a dashboard stamp reports active: "data" and a checkout stamp reports commerce. Stored values stay exactly as written. canvas_set_genre returns a new canonical field. The dashboard structure keeps its name.

Smaller fixes found on the way

  • apply_preset now reloads the canvas from disk before writing, as canvas_set_genre and apply_structure already did. Before, a stale in-memory copy could overwrite a genre that had just been pulled.
  • Genre table lookups check only the table's own keys, so a stored genre like "constructor" is an unknown genre instead of crashing evaluation.
  • The viewer's provenance chip shows structure, genre and preset as separate parts.

Docs

The tool docstrings, GOTCHAS, the genre parameter doc, the honest-content suggestion, docs/GUIDELINES.md, the README and VISION now recommend data and describe the two separate fields. test-discoverability.ts gains a check that no agent surface recommends genre: "dashboard", and that the new result fields are documented. The phase spec records the decisions above in its slice A plan, plus one accepted limitation: an older framesmith editing a migrated canvas writes its genre into the preset field, which this version ignores, so a repo's collaborators should run the same version.

Testing

  • test-set-genre.ts covers each acceptance case from the slice spec: no clobber, legacy reads, migration, clearing, imported presets, alias folding, and the "constructor" case.
  • test-genre-report.ts, test-eval-cache-key.ts and test-calibration.ts now assert the new contract. They also cover viewer score parity and the provenance chip.
  • New test-genre-mcp.ts drives the real tools over MCP stdio, including a hand-edited canvas on disk.
  • Independent checks ran mutants on every rule. Each mutant fails at least one test, and the apply_preset reload test fails without the reload.
  • npm run test:full passes (106/106), and tsc is clean.

apply_preset overwrote canvas_set_genre's stamp because both wrote provenance.preset. Genre now
lives in provenance.genre, with a legacy read and migrate-on-write for stored canvases; data is
the canonical genre and dashboard its alias. Phase 30 slice A.
@vicmaster
vicmaster merged commit a681b4d into master Oct 31, 2026
3 checks passed
@vicmaster
vicmaster deleted the phase-30-slice-a branch October 31, 2026 20:24
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