feat: URL state persistence for all filters and tabs

This commit is contained in:
Davide Scaini
2026-03-30 20:28:54 +02:00
parent cb345c02a1
commit 5b07c7670c
2 changed files with 46 additions and 0 deletions
+36
View File
@@ -643,6 +643,42 @@ Local `.md` sidecars can annotate remote activities.
- The `site/.env` file is gitignored — document the setup for new users
- Add `--workers` benchmark: on 8 cores, ~7 min for 3,200 activities first run
## URL state — design pattern
All pages with filter or tab state persist it in the URL query string so the browser
back button restores the exact view. The pattern used in every component:
```ts
let mounted = false;
$: if (mounted) {
const params = new URLSearchParams(window.location.search);
if (value === DEFAULT) params.delete('key'); else params.set('key', value);
const qs = params.toString();
history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname);
}
onMount(() => {
value = new URLSearchParams(window.location.search).get('key') ?? DEFAULT;
mounted = true;
});
```
Key decisions:
- `replaceState` not `pushState` — clicking filters doesn't create history entries; back always goes to the previous *page*, not previous filter state
- `mounted` flag — prevents the reactive block from firing before `onMount` reads the URL (which would overwrite the URL with the default before reading it)
- Default values omitted — `?sport=all` and `?tab=power` are never written; clean URLs
Current URL params:
| Page | Param | Values |
|------|-------|--------|
| `/` (feed) | `sport` | `cycling`, `running`, etc. |
| `/stats/` | `sport` | same |
| `/athlete/` | `tab` | `records`, `profile` (power is default) |
| `/athlete/?tab=records` | `sport` | `cycling`, `swimming`, etc. (running is default) |
If you add a new filter to any page, follow this pattern. Never use `pushState` for filter changes.
## What "good" looks like (not yet done)
- [ ] `bincio render` Python CLI wraps `astro build` properly