Repository navigation
Give genre its own provenance field and make data canonical - #234
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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. Soapply_presetsilently overwrote a genre thatcanvas_set_genrehad stamped, and every preset name read as a genre. This PR gives genre its own field and makesdatathe genre agents are told to declare.Genre has its own field
canvas_set_genrenow writesmetadata.provenance.genre, andapply_presetwrites onlymetadata.provenance.preset. Applyingdarkto a canvas declaredcommerceleaves itcommerce.Canvases stored before this change keep their verdicts. When a canvas has no
genrekey, apresetvalue 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 firstcanvas_set_genreorapply_preseton such a canvas moves that value intogenre. A storedpreset: "material"now reads as the material preset rather than as a genre, sogenre.activebecomes null on those canvases while their relaxation and score stay the same.The
genrekey itself marks a canvas as migrated: once it exists, even asnull, the preset field is always a preset. Without this marker, a preset imported withimport_design_mdunder 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_autofixand 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 ascanvas_evaluatedoes, 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
genreparameter 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 optionalbyPresetfield, which explains a non-emptyrelaxedlist whenactiveis null.canvas_set_genrereturnspresetRelaxesfor the same reason, since clearing a genre does not remove a preset's relaxation.datais the canonical genredatais now the canonical genre anddashboardits 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 adashboardstamp reportsactive: "data"and acheckoutstamp reportscommerce. Stored values stay exactly as written.canvas_set_genrereturns a newcanonicalfield. Thedashboardstructure keeps its name.Smaller fixes found on the way
apply_presetnow reloads the canvas from disk before writing, ascanvas_set_genreandapply_structurealready did. Before, a stale in-memory copy could overwrite a genre that had just been pulled."constructor"is an unknown genre instead of crashing evaluation.Docs
The tool docstrings,
GOTCHAS, the genre parameter doc, the honest-content suggestion,docs/GUIDELINES.md, the README and VISION now recommenddataand describe the two separate fields.test-discoverability.tsgains a check that no agent surface recommendsgenre: "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.tscovers 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.tsandtest-calibration.tsnow assert the new contract. They also cover viewer score parity and the provenance chip.test-genre-mcp.tsdrives the real tools over MCP stdio, including a hand-edited canvas on disk.apply_presetreload test fails without the reload.npm run test:fullpasses (106/106), andtscis clean.