feat: expose stable journey intent signals

This commit is contained in:
2026-09-04 15:25:35 +03:00
parent c73410a956
commit 7632c8b9c0
12 changed files with 197 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ import {
canonicalSectionIdFromHash,
createNavigationController,
validateSectionAnchor,
type NavigationState,
type NavigationRuntime,
} from "@/journey/navigation-controller";
import type { SectionId } from "@/content/types";
@@ -205,6 +206,7 @@ describe("navigation epochs and effects", () => {
expect(controller.state()).toEqual({
epoch: 2,
source: "passive-scroll",
phase: "idle",
destinationId: null,
signDestinationId: null,
});
@@ -221,6 +223,30 @@ describe("navigation epochs and effects", () => {
expect(harness.focused).toEqual(["experience"]);
});
it("publishes travelling and settled only for the latest explicit epoch", () => {
const harness = createRuntime(true);
const controller = createNavigationController(sectionIds, harness.runtime);
const states: NavigationState[] = [];
controller.subscribe((state) => states.push(state));
controller.navigate("contact", target("contact"));
controller.navigate("toolkit", target("toolkit"));
expect(controller.state()).toMatchObject({
epoch: 2,
source: "explicit-navigation",
phase: "travelling",
destinationId: "toolkit",
});
harness.emitScrollEnd();
expect(controller.state()).toMatchObject({
epoch: 2,
phase: "settled",
destinationId: "toolkit",
});
expect(states.filter((state) => state.phase === "settled")).toHaveLength(1);
});
it("falls back to three consecutive sub-pixel-stable animation frames", () => {
const harness = createRuntime(false);
const controller = createNavigationController(sectionIds, harness.runtime);
@@ -271,7 +297,9 @@ describe("navigation epochs and effects", () => {
const controller = createNavigationController(sectionIds, harness.runtime);
expect(controller.initialize("toolkit", target("toolkit"))).toBe(1);
expect(controller.state()).toMatchObject({ phase: "settled" });
expect(controller.restore("profile", target("profile"))).toBe(2);
expect(controller.state()).toMatchObject({ phase: "travelling" });
harness.runFrame();
expect(harness.pushes).toEqual([]);
expect(harness.replacements).toEqual([]);
@@ -283,6 +311,7 @@ describe("navigation epochs and effects", () => {
expect(controller.state()).toEqual({
epoch: 2,
source: "history-restoration",
phase: "settled",
destinationId: "profile",
signDestinationId: null,
});