diff --git a/doc/testing.md b/doc/testing.md
index a02bff9f..fc86b621 100644
--- a/doc/testing.md
+++ b/doc/testing.md
@@ -70,18 +70,21 @@ against:
exercise the actual deployment end-to-end and confirm the sites stay
consistent.
-| `CYPRESS_ENV` | URL | Mode |
-| -------------------- | ------------------------------------ | ------- |
-| `local` (default) | `http://localhost:5173` | fixture |
-| `pages` | `https://topology.pi-base.org` | live |
-| `workers` | `topology.pi-base.workers.dev` | live |
-| `graphs` | `graphs.pi-base.workers.dev` | live |
-| `preview` | `$PREVIEW_URL` | live |
-
-The whole suite runs in both modes; assertions are written to hold against the
+| `CYPRESS_ENV` | URL | Mode |
+| ----------------- | ------------------------------ | ------- |
+| `local` (default) | `http://localhost:5173` | fixture |
+| `pages` | `https://topology.pi-base.org` | live |
+| `workers` | `topology.pi-base.workers.dev` | live |
+| `graphs` | `graphs.pi-base.workers.dev` | live |
+| `preview` | `$PREVIEW_URL` | live |
+
+The suite runs in both modes; assertions are written to hold against the
real data (stable IDs, math-free name prefixes, behavioral checks) rather than
-exact HTML snapshots. Set `CYPRESS_MODE=fixture` to force the deterministic
-fixture against a deployed URL while debugging.
+exact HTML snapshots. Specs that can only hold against fixture-pinned content
+(e.g. the space-to-space navigation regression, which follows a description
+link that only exists in the fixture) are declared with a `fixtureIt` helper
+and show as pending in live runs. Set `CYPRESS_MODE=fixture` to force the
+deterministic fixture against a deployed URL while debugging.
The fixture (`cypress/fixtures/main.min.json`) is a hand-curated subset; keep its
tested entities (e.g. `S000001`, `S000004`, `P000001`) in sync with live data.
@@ -89,6 +92,20 @@ Note it predates a pi-base property-ID reorganization, so its property `uid`s ar
**not** interchangeable with the current bundle's — refresh display fields per
entity, don't bulk-remap by `uid`.
+Two things to know when running fixture mode against a production-style build
+(`VITE_BUNDLE_HOST=http://localhost:4173 pnpm run build` + `pnpm run preview`):
+
+- The bundle is baked in at build time: with a localhost `VITE_BUNDLE_HOST`,
+ `+layout.server.ts` imports `public/refs/heads/main.json` (a symlink to the
+ Cypress fixture) and injects it during SSR, so the browser never fetches
+ `main.json`. The `cy.intercept` in `cypress/support/commands.ts` is inert in
+ this mode (it matters against `pnpm dev` and deployed targets, where the
+ client fetches the bundle). To vary fixture data here, edit the fixture and
+ rebuild — intercepting won't work.
+- Restart the preview server after every rebuild. A still-running server can
+ serve HTML referencing the previous build's hashed chunks, which fails with
+ "Failed to fetch dynamically imported module".
+
## Remote End-to-End Testing
The `./bin/e2e` script runs the suite against the deployed targets (live data),
diff --git a/packages/viewer/cypress/e2e/space.spec.ts b/packages/viewer/cypress/e2e/space.spec.ts
index 62727b2b..a65de846 100644
--- a/packages/viewer/cypress/e2e/space.spec.ts
+++ b/packages/viewer/cypress/e2e/space.spec.ts
@@ -2,6 +2,10 @@ import { deduce, setup } from '../support'
beforeEach(setup)
+// Marks specs that depend on exact fixture-pinned content and only run in
+// fixture mode (see doc/testing.md).
+const fixtureIt = Cypress.env('mode') === 'live' ? it.skip : it
+
function clickTraitFor(name: string) {
cy.get('.related-traits')
.contains(name)
@@ -51,3 +55,31 @@ it('displays references', () => {
cy.get('ul.references').should('exist')
})
+
+// Navigating between space pages reuses the mounted component, and the traits
+// table must recompute for the new space instead of continuing to show the
+// previous space's data. See https://github.com/pi-base/web/issues/255.
+//
+// Fixture-only: relies on S000001's fixture description linking to {S4} (live
+// descriptions differ) and on "Indiscrete" being asserted false for S000001
+// and true for S000004, so the value icon flips iff the table recomputes.
+fixtureIt('recomputes traits when navigating between spaces', () => {
+ function indiscreteRow() {
+ return cy.get('.related-traits').contains('tr', 'Indiscrete')
+ }
+
+ cy.visit('spaces/S000001')
+
+ cy.contains('h1', 'Discrete topology on')
+ indiscreteRow().find('.bi-x').should('exist')
+
+ // Client-side navigation via the in-description link (note the unpadded id)
+ cy.get('.description').contains('a', 'Indiscrete topology on').click()
+ cy.location('pathname').should('eq', '/spaces/S4')
+ cy.contains('h1', 'Indiscrete topology on')
+ indiscreteRow().find('.bi-check').should('exist')
+
+ cy.go('back')
+ cy.location('pathname').should('eq', '/spaces/S000001')
+ indiscreteRow().find('.bi-x').should('exist')
+})
diff --git a/packages/viewer/cypress/fixtures/main.min.json b/packages/viewer/cypress/fixtures/main.min.json
index 9c0c3bb8..e41c21f0 100644
--- a/packages/viewer/cypress/fixtures/main.min.json
+++ b/packages/viewer/cypress/fixtures/main.min.json
@@ -945,7 +945,7 @@
"uid": "S000001",
"counterexamples_id": 1,
"name": "Discrete topology on $\\{0,1\\}$",
- "description": "-",
+ "description": "Compare with the indiscrete topology {S4} on the same set.",
"aliases": ["Discrete topology on a two-point set", "Finite discrete topology"],
"refs": []
},
diff --git a/packages/viewer/src/components/Properties/Spaces.svelte b/packages/viewer/src/components/Properties/Spaces.svelte
index 4badbdbd..73a8d109 100644
--- a/packages/viewer/src/components/Properties/Spaces.svelte
+++ b/packages/viewer/src/components/Properties/Spaces.svelte
@@ -1,16 +1,12 @@
-
+
diff --git a/packages/viewer/src/components/Spaces/Properties.svelte b/packages/viewer/src/components/Spaces/Properties.svelte
index 2cfbc062..1d23848a 100644
--- a/packages/viewer/src/components/Spaces/Properties.svelte
+++ b/packages/viewer/src/components/Spaces/Properties.svelte
@@ -1,16 +1,12 @@
-
+
diff --git a/packages/viewer/src/components/Traits/Related.svelte b/packages/viewer/src/components/Traits/Related.svelte
index 9df2165e..37279cba 100644
--- a/packages/viewer/src/components/Traits/Related.svelte
+++ b/packages/viewer/src/components/Traits/Related.svelte
@@ -10,11 +10,28 @@
import urlSearchParam from '@/stores/urlSearchParam'
import { checkIfRedundant } from '@/stores/deduction'
- export let related: (traits: Traits) => [Space, Property, Trait | undefined][]
- export let mode: 'spaces' | 'properties'
+ type Row = [Space, Property, Trait | undefined]
+
+ // The entity whose related traits are listed. Passing the entity itself
+ // (rather than a closure over it) keeps the data dependency visible to
+ // Svelte's reactivity, so the table recomputes when navigating between
+ // entities. See https://github.com/pi-base/web/issues/255.
+ export let anchor:
+ | { mode: 'properties'; space: Space }
+ | { mode: 'spaces'; property: Property }
const { theorems, traits } = context()
+ function rows(a: typeof anchor, traits: Traits): Row[] {
+ if (a.mode === 'properties') {
+ const { space } = a
+ return traits.forSpaceAll(space).map(([p, t]) => [space, p, t])
+ } else {
+ const { property } = a
+ return traits.forPropertyAll(property).map(([s, t]) => [s, property, t])
+ }
+ }
+
const filter = writable('')
urlSearchParam('filter', filter)
@@ -39,11 +56,12 @@
}
}
- $: all = related($traits)
- // all has type [Space, Property, Trait][]
+ $: all = rows(anchor, $traits)
// we need to index names in different positions depending on which kind we
// are displaying
- $: index = new Fuse(all, { keys: [`${mode === 'spaces' ? 0 : 1}.name`] })
+ $: index = new Fuse(all, {
+ keys: [`${anchor.mode === 'spaces' ? 0 : 1}.name`],
+ })
$: searched = $filter ? index.search($filter).map(r => r.item) : all
$: filtered = searched.filter(([_space, _property, t]) =>
matchesFilter(filterMode, t),
diff --git a/packages/viewer/src/routes/(app)/spaces/[id]/+page.svelte b/packages/viewer/src/routes/(app)/spaces/[id]/+page.svelte
index c1671a62..367c81f5 100644
--- a/packages/viewer/src/routes/(app)/spaces/[id]/+page.svelte
+++ b/packages/viewer/src/routes/(app)/spaces/[id]/+page.svelte
@@ -5,11 +5,10 @@
import { page } from '$app/stores'
export let data: PageData
- let rel = $page.url.pathname
$: title = `S${data.space.id}: ${data.space.name}`
-
+