···2828})
2929export type StoreState = z.infer<typeof StoreState>
30303131-// Settings are still read from localStorage by routes directly;
3232-// this schema documents intent and will be wired up in a future step.
3331export const AppSettings = z.object({
3432 syncLinkUrl: z.string().default(''),
3533 mbType: z.number().int().min(0).max(6).default(0),
+4-5
www/routes/climb.ts
···99 loadBenchmarks,
1010 youtubeUrl,
1111} from '../utils/benchmarks.ts'
1212-import { getClimbNav, setClimbNav } from '../utils/climb-nav.ts'
1312import app from '../models/app.ts'
1413import { activeClimbHeader } from '../components/climb-header.ts'
1514import '../components/ui-counter.ts'
···2019 private dialogRating: number | null = null
21202221 async connectedCallback() {
2323- const nav = getClimbNav()
2222+ const nav = app.getNav()
2423 if (!nav) {
2524 globalThis.location.hash = '/'
2625 return
···7978 }
80798180 private navigate(direction: number): void {
8282- const nav = getClimbNav()
8181+ const nav = app.getNav()
8382 if (!nav?.filteredIds || nav.currentIndex === undefined) return
8483 const next = nav.currentIndex + direction
8584 if (next < 0 || next >= nav.filteredIds.length) return
···8887 if (!this.isConnected) return
8988 const nextClimb = all.find((b) => b.id === nextId)
9089 if (!nextClimb) return
9191- setClimbNav({
9090+ app.setNav({
9291 ...nav,
9392 climbId: nextId,
9493 climb: nextClimb,
···9998 }
10099101100 private renderContent(climb: Benchmark): void {
102102- const nav = getClimbNav()!
101101+ const nav = app.getNav()!
103102 const config = BOARD_CONFIGS[nav.mbType] ?? BOARD_CONFIGS[0]
104103 const height = canvasHeight(config.rows)
105104