Character sheets: Update to version n, with what changes (#958) #1364
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/958-update-to-version"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the "Update to version n" half of #958 (IS-423, D83).
A sheet made under an older version of its system's definition keeps reading with that version (a sheet's
definition_idis its own row; definitions are deprecated, never deleted). When a newer active version exists and ships a migration, the GM is offered Update to version n, with what changes. Never automatic, never forced.What it does
Typed migration ops (
definition_model.py,migrations: list[Migration]; the schema file is regenerated). A definition carries the migrations that arrive at its own version:{from, to, note?, ops: [...]},to == sheet.version.oprenamefrom_path,to_pathmovefrom_path,to_path,kinds?defaultpath,valuedroppathcomputepath,exprValidated on pack upload (
validator._check_migrations,pack_sheetsnow passes the stored versions asprevious):tois the definition's own version, no duplicatefrom/to,fromis a stored version, write paths exist in this definition, read paths exist in the version migrated from,computepaths are values someone types (not computed fields), expressions parse, name no dice, and name real tables.Engine (
app/sheets/migrate.py, pure): fewest-steps chain (v1 to v2 to v3 in order, or a direct v1 to v3 if shipped). Nothing is overwritten silently: a value already at a rename/move destination stays and the source is listed as dropped. An op with nothing to do on this sheet is not listed.Service (
app/services/sheet_upgrade.py):preview_upgradesaves nothing;apply_upgradekeeps the sheet as it was as a version first (so a drop is recoverable), migrates the source half, switchesdefinition_id/definition_version, and writes a version of kindmigration("Updated to version n: 1 renamed, 1 dropped"). Putting either version back from history also puts the old definition back (sheet_service.restore; GM only across a definition version).API (
docs/API.md):GET /api/campaigns/{id}/sheets/{sheet_id}/upgrade?to=NpreviewPOST /api/campaigns/{id}/sheets/{sheet_id}/upgrade?to=N&expected_version=Vapply, returns the sheet403not the GM,404no such active version,409 {detail, current}stale,422no migration path / not above current / still being started / an expression that fails.SheetOut.access.can_upgrade(new, GM) and the existingdefinition.latest_versiondrive the UI.Who: the GM only. D83 says "the GM applies them when ready"; the drawing (
olderVersion()) shows the notice without naming a viewer, and the catalogue row listed "GM, char-owner" while deferred. I went with the decision text. Letting the sheet's player apply it is one line (can_upgradeinaccess()); say if you want it.Frontend (
pages/sheet/UpdateToVersion.jsx, wired intoSheetPage): the drawing's warning banner with Update to version 2 and What changes (the sharedWhatChangestable, Version 1 / Version 2 columns, the reason under the label on a phone), a confirm modal, then the sheet is read again on its new definition and a quiet "Updated to version 2" banner with a Versions button. Shown only whencan_upgrade, a newer version exists, and the preview answers (a 422 or 404 shows nothing, since there is nothing to offer). What is typed is saved first.Every built-in definition is still version 1, so nothing offers an update on a real campaign yet; the tests carry a test-only v1/v2/v3 with one op of each kind.
Wording for review
Banner (
vocabulary.jsandUpdateToVersion.jsx):note); cell "Dropped"; list cells "{n} row(s) in {label}"Verification (docker-test, committed code via git archive)
ruff formaton my three filessrc/pages/sheet/UpdateToVersion*.test.jsx15 passed; full vitest 295 files, 5777 passed, 1 skippedNotes
migration(already inVERSION_KINDSand the CHECK), not a newupgrade, so there is no Alembic migration. No bot API change.resources) is unversioned, so a migration that renamed a resource would not be recoverable; it is left alone.computereads stored values (@path), not other computed fields.🤖 Generated with Claude Code