clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

Specify stable active proof identity and catalog reads

Dami committed bded761
commit
bded761f404d04948e61ebe6e43a28da29a86118
parent
bee2e1734534b05cdfd4478bf0ded347e05a1435
author
Dami <dami.etoile@gmail.com>
5 files changed, +206 -0
Aopenspec/changes/stabilize-active-level-identity/.openspec.yaml2+
Aopenspec/changes/stabilize-active-level-identity/design.md51++++++++++++++++++
Aopenspec/changes/stabilize-active-level-identity/proposal.md40++++++++++++++
Aopenspec/changes/stabilize-active-level-identity/specs/active-level-identity/spec.md83++++++++++++++++++++++++++++++
Aopenspec/changes/stabilize-active-level-identity/tasks.md30+++++++++++
A openspec/changes/stabilize-active-level-identity/.openspec.yaml +2 -0
A openspec/changes/stabilize-active-level-identity/design.md +51 -0
@@ -0,0 +1,51 @@
1+## Context2+3+`createCatalog()` stores normalized pack records in a map and exposes stable4+level IDs. `getLevels()` currently deep-clones every level, `getLevel()` calls5+that full-catalog path before cloning the selected level again, and the stage6+keeps `levelNo` as the primary application identity. `main.js` also calls7+`refreshIndex()` from progress and win callbacks while packs may be replaced8+or removed.9+10+## Decisions11+12+### Stable identity is the source of truth13+14+The application shall store the active proof as `activeLevelId`, using the15+existing `${packId}:${localId}` value. A numeric index may remain an input16+convenience for level selection, but it is resolved once and not retained as17+the identity across catalog mutations. `packId` and `localId` remain unchanged18+in returned levels and shared data.19+20+When a selected level is replaced, the same stable ID remains active if the21+replacement still contains that local level. If it does not, selection falls22+back deterministically to the replacement pack's first level, then the first23+available level. Removing the active pack uses the same deterministic fallback.24+25+### Read boundaries and invalidation26+27+The catalog shall maintain normalized internal records and a mutation28+generation. Register, replace, and remove invalidate only derived lookup29+views affected by that generation. A single-level read resolves the requested30+record directly. Full-list reads retain their current fresh-clone behavior so31+callers cannot mutate catalog state through returned objects.32+33+The optimization must not return a mutable cached object to callers. Any34+internal cache stores canonical normalized data or immutable derived metadata;35+public reads still clone at the boundary. Tests shall prove that mutating one36+returned level, pack, or list does not affect a later read.37+38+### Application handoffs39+40+`loadLevel()` resolves and records the stable level ID before creating the41+game. Progress writes use that level's `packId` and `localId`. Win and42+continue callbacks capture the stable ID and verify it still resolves before43+showing a transition. Catalog replacement/removal refreshes the index and44+re-resolves the active ID before deciding whether to load a fallback.45+46+### Compatibility and failure behavior47+48+Existing methods and return shapes remain compatible. Unknown stable IDs still49+return `undefined` from reads and `setActiveLevel` returns `false`. A removed50+active ID is never left pointing at an unavailable level. Level ordering stays51+registration order; identity resolution does not depend on array position.
A openspec/changes/stabilize-active-level-identity/proposal.md +40 -0
@@ -0,0 +1,40 @@
1+## Why2+3+The catalog already assigns stable `${packId}:${localId}` level IDs, but the4+application still carries the active proof as a mutable array index while5+catalog access repeatedly clones every pack and level. Pack replacement,6+removal, progress updates, and win handoff can therefore resolve different7+levels after the catalog changes, while ordinary UI refreshes pay a full8+catalog clone cost.9+10+## What Changes11+12+- Make the canonical active-level identity the existing stable global level ID,13+ while retaining numeric index arguments for compatibility.14+- Define catalog read boundaries that resolve one level or the active level by15+ stable ID and invalidate derived views after register, replace, and remove.16+- Keep existing public clone and mutation-safety guarantees for `getLevels`,17+ `getLevel`, and `getPack`; add focused cache and identity tests before18+ changing callers.19+- Migrate application handoff, progress marking, level replacement, and pack20+ removal flows to re-resolve by stable ID instead of retaining a stale index.21+22+This change does not alter pack IDs, local level IDs, sharing payloads, save23+keys, solver state, or level ordering semantics.24+25+## Capabilities26+27+### New Capabilities28+29+- `stable-active-level-identity`30+31+### Modified Capabilities32+33+None.34+35+## Impact36+37+`src/catalog.js`, `src/main.js`, and their focused tests will change. Existing38+catalog methods remain compatible and continue returning caller-owned data.39+The implementation may add internal generation counters or immutable records,40+but those details are not exposed in pack or level payloads.
A openspec/changes/stabilize-active-level-identity/specs/active-level-identity/spec.md +83 -0
@@ -0,0 +1,83 @@
1+## ADDED Requirements2+3+### Requirement: The active level SHALL use a stable catalog identity4+5+The application SHALL use the existing `${packId}:${localId}` level ID as the6+canonical active-level identity. Numeric indexes MAY select a level but SHALL7+not be used as the durable identity across catalog mutations.8+9+#### Scenario: Selection survives unrelated registration10+11+- **WHEN** the active level is selected and another pack is registered before12+ it13+- **THEN** the same stable active level ID remains selected and resolves to the14+ same level15+16+#### Scenario: Replacement preserves an existing local level17+18+- **WHEN** a pack is replaced with the same pack ID and still contains the19+ active local level ID20+- **THEN** the active stable ID is preserved and resolves to the replacement21+ level22+23+#### Scenario: Replacement removes the active local level24+25+- **WHEN** a pack replacement omits the active local level26+- **THEN** selection falls back to the replacement pack's first level or the27+ first available catalog level, and no stale ID remains active28+29+### Requirement: Catalog reads SHALL preserve clone safety without repeated full clones30+31+Single-level and active-level reads SHALL resolve directly from normalized32+catalog records. Full-list compatibility methods SHALL continue returning33+fresh caller-owned data, while internal derived views SHALL be invalidated on34+pack register, replacement, and removal.35+36+#### Scenario: A caller cannot mutate a cached catalog record37+38+- **WHEN** a caller mutates a level returned by a catalog read39+- **THEN** a later read returns the normalized authored value40+41+#### Scenario: Replacement invalidates level and pack views42+43+- **WHEN** a pack is replaced with changed metadata or levels44+- **THEN** subsequent reads return only replacement data and no stale cached45+ level, pack, or level count46+47+#### Scenario: Removal invalidates active and list views48+49+- **WHEN** an imported pack is removed50+- **THEN** subsequent reads omit its levels and the active ID resolves to a51+ remaining level or `null`52+53+### Requirement: Progress and win handoffs SHALL resolve by stable identity54+55+Progress marking, win promotion, continue navigation, and pack refresh SHALL56+re-resolve the captured stable level ID before acting. A catalog mutation or57+level transition SHALL not allow an old numeric index or reused authored ID to58+promote the wrong level.59+60+#### Scenario: A win callback cannot promote a replaced level61+62+- **WHEN** a level is won and its pack is replaced before the delayed win63+ handoff runs64+- **THEN** the callback promotes only the still-resolved stable level ID or65+ safely abandons the stale handoff66+67+#### Scenario: Progress remains attached to pack and local IDs68+69+- **WHEN** levels are reordered by registration or an unrelated pack changes70+- **THEN** solved progress remains attached to the original `packId` and71+ `localId`72+73+### Requirement: Cache invalidation SHALL be observable at mutation boundaries74+75+Catalog mutations SHALL invalidate all derived active-level, pack-summary, and76+level-lookup views that can contain changed records. The invalidation SHALL77+occur before the mutation result is returned to application callers.78+79+#### Scenario: Replace refreshes summaries immediately80+81+- **WHEN** a pack replacement changes its name, world metadata, or level count82+- **THEN** the next pack and level reads expose replacement summaries without a83+ refresh-only workaround
A openspec/changes/stabilize-active-level-identity/tasks.md +30 -0
@@ -0,0 +1,30 @@
1+## 1. Catalog identity and read boundaries2+3+- [ ] 1.1 Add stable-ID resolution helpers while preserving existing catalog4+ method signatures and return shapes.5+- [ ] 1.2 Track active level by `${packId}:${localId}` and define replacement,6+ removal, and empty-catalog fallback behavior.7+- [ ] 1.3 Replace repeated full-catalog work for single-level and active-level8+ reads with direct normalized-record lookup and safe boundary cloning.9+- [ ] 1.4 Add cache/generation invalidation for register, replace, and remove;10+ prove summaries and level counts never remain stale.11+12+## 2. Application handoffs and progress13+14+- [ ] 2.1 Migrate level loading, continue navigation, progress marking, and win15+ promotion to stable IDs while retaining numeric selection compatibility.16+- [ ] 2.2 Handle pack replacement/removal during delayed win and gift/workshop17+ handoffs without promoting a stale level.18+- [ ] 2.3 Verify solved progress remains keyed by pack and local level IDs after19+ registration order changes.20+21+## 3. Regression and verification22+23+- [ ] 3.1 Add catalog tests for replace, remove, active fallback, clone safety,24+ direct reads, and cache invalidation.25+- [ ] 3.2 Add application tests for progress and delayed Win handoffs across26+ replacement/removal and reused local IDs.27+- [ ] 3.3 Run the focused catalog/application tests, full Node suite, production28+ build, and browser pack import/share flows.29+- [ ] 3.4 Run strict OpenSpec validation and record the measured catalog read30+ improvement before archiving the change.