feat: coordinate sections signs and native navigation

This commit is contained in:
2026-09-02 04:27:10 +03:00
parent 9bbe4652ac
commit 22d456a6ca
11 changed files with 1510 additions and 31 deletions

View File

@@ -0,0 +1,100 @@
import { describe, expect, it } from "vitest";
import { nextSign } from "@/journey/sign-controller";
import type { SignSnapshot } from "@/journey/types";
const passiveSample = (
distanceVh: number,
overrides: Partial<Parameters<typeof nextSign>[1]> = {},
): Parameters<typeof nextSign>[1] => ({
source: "passive-scroll",
deltaY: 10,
viewportHeight: 100,
crossedBoundaries: 0,
candidateId: "profile",
distanceVh,
motionAllowed: true,
...overrides,
});
describe("forward-only sign latching", () => {
it("latches a first eligible candidate at the exact approach boundary", () => {
expect(nextSign(null, passiveSample(1.2))).toEqual({
targetId: "profile",
phase: "approach",
progress: 0,
});
});
it("maps the literal approach and pass ranges independently", () => {
expect(nextSign(null, passiveSample(0.875))).toEqual({
targetId: "profile",
phase: "approach",
progress: expect.closeTo(0.5, 14),
});
expect(nextSign(null, passiveSample(-0.05))).toEqual({
targetId: "profile",
phase: "pass",
progress: 0.5,
});
});
it("uses exact stabilized, pass, and completion boundaries", () => {
const latched: SignSnapshot = {
targetId: "profile",
phase: "approach",
progress: 0,
};
expect(nextSign(latched, passiveSample(0.55))).toEqual({
targetId: "profile",
phase: "stabilized",
progress: 1,
});
expect(nextSign(latched, passiveSample(0.15))).toEqual({
targetId: "profile",
phase: "pass",
progress: 0,
});
expect(nextSign(latched, passiveSample(-0.25))).toBeNull();
});
it("retains the one latched target while the semantic section advances", () => {
const latched: SignSnapshot = {
targetId: "profile",
phase: "stabilized",
progress: 1,
};
expect(
nextSign(latched, passiveSample(0.1, { candidateId: "experience" })),
).toEqual({
targetId: "profile",
phase: "pass",
progress: expect.closeTo(0.125, 14),
});
});
it.each([
["reverse input", { deltaY: -2 }],
["zero-direction input", { deltaY: 0 }],
["explicit navigation", { source: "explicit-navigation" as const }],
["history restoration", { source: "history-restoration" as const }],
["initial positioning", { source: "initial" as const }],
["more than one activation boundary", { crossedBoundaries: 2 }],
["a jump over 0.75 viewport heights", { deltaY: 76 }],
["disabled motion", { motionAllowed: false }],
])("clears or suppresses signs for %s", (_label, overrides) => {
const latched: SignSnapshot = {
targetId: "profile",
phase: "approach",
progress: 0.5,
};
expect(nextSign(latched, passiveSample(0.5, overrides))).toBeNull();
});
it("does not latch before visibility or after a jump directly past completion", () => {
expect(nextSign(null, passiveSample(1.20001))).toBeNull();
expect(nextSign(null, passiveSample(-0.25))).toBeNull();
expect(nextSign(null, passiveSample(-1))).toBeNull();
});
});