Merge pull request #317 from trails-cool/add-ia-and-spec-drift-skills

Add /ia-review and /spec-drift-review process skills
This commit is contained in:
Ullrich Schäfer 2026-04-26 08:42:31 +02:00 committed by GitHub
commit 34f40d6744
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 648 additions and 33 deletions

View file

@ -0,0 +1,275 @@
---
name: ia-review
description: Take a snapshot of the apps' information architecture (sitemap, navigation, audience-gating per route) and write or refresh `docs/information-architecture.md`. Use when the user wants to review the IA, plan a navigation/surface redesign, or check for drift since the last review.
license: MIT
metadata:
author: trails.cool
version: "1.0"
---
Walk the apps' route table + navigation surfaces, build a sitemap, surface
tensions and open questions, and capture the result in
`docs/information-architecture.md` (fresh write or refresh against the
prior snapshot).
The output is a *review document for the user* — not a unilateral plan.
Decisions are made by the user during the conversation that follows; the
doc captures the snapshot and the open questions that need answering.
---
## When to use
- The user explicitly asks for an IA review ("review the IA", "what's
the information architecture look like").
- Before a navigation/surface redesign, so the redesign is informed by
a current snapshot rather than a vibe.
- After a chunk of new features — pages, modals, settings sections —
to check whether the IA is drifting (busy navbar, duplicated surfaces,
orphaned routes).
- When the user says "we should do another IA review" after the prior
doc has aged.
---
## Steps
1. **Detect mode (fresh vs refresh)**
Check whether `docs/information-architecture.md` already exists.
- **No file:** fresh review. Build the snapshot from scratch.
- **File exists:** refresh review. Read it first; preserve the
decisions/backlog the user has accumulated and only update the
snapshot sections (sitemap, navigation, observations, open
questions). Decisions previously crossed out stay crossed out.
In refresh mode, also read the snapshot date at the top — anything
shipped *since* that date is what your refresh should focus on.
2. **Identify the apps in scope**
trails.cool ships two front-ends; the IA question lives mostly in
the Journal:
- `apps/journal/` — user accounts, social, content. Main IA
surface.
- `apps/planner/` — anonymous, ephemeral. ~5 routes; include for
completeness but don't dwell.
If the project structure has changed and there's a new app, include
it.
3. **Read the route tables**
For each app:
```
apps/<app>/app/routes.ts
```
This is the authoritative URL → route-file mapping. Both apps use
explicit registration (per CLAUDE.md), so `routes.ts` is complete.
4. **Read the navigation surfaces**
- `apps/journal/app/root.tsx` (and `apps/planner/app/root.tsx` if
it has navigation) — the top navbar lives here. Read both the
loader (to see what data the navbar consumes — counts, badges,
user fields) and the `NavBar` component (to see what entries
render).
- `apps/journal/app/components/Footer.tsx` — the footer.
- Any auth-gate / Terms-gate logic in the root loader.
5. **Sample key route loaders to understand audience**
For each top-level route, scan its loader to determine:
- Does it require a session? (loaders typically `redirect("/auth/login")`
for anonymous visitors when so.)
- Does it serve different content per session? (e.g., `home.tsx`
branches on `user`.)
- Does it have an access rule beyond auth? (locked-account 404s,
visibility checks, etc.)
You don't need to read every route — pick the top-level ones and
any that look like they might gate differently than the URL hints.
6. **Build the sitemap**
Group routes by audience: **Public surface** (anonymous-reachable)
and **Authenticated surface** (signed-in only). Within each group,
order by topic (auth, profile, content, settings, legal, etc.).
Use plain code blocks with one URL per line and a one-line gloss
per entry. Keep the format scannable; don't repeat what the URL
already says.
7. **Map navigation surfaces**
Two short tables/snippets:
- **Navbar (signed-in)** — entries left-to-right.
- **Navbar (signed-out)** — entries left-to-right.
- **Footer** — links + any meta text.
Note any entry whose visibility is conditional (badge counts, etc.).
8. **Identify "feed concepts" and other duplications**
trails.cool has historically had multiple feed-like surfaces. Any
IA review should ask: how many lists of activities are there? Is
the same data reachable from multiple URLs? Is there a URL that
shows different products to different audiences?
Capture these in a small table or section if they exist.
9. **Map cross-app linking**
Journal ↔ Planner cross-links (JWT callback URLs, "Try the
Planner" buttons, etc.). One short list.
10. **List observations**
Walk the snapshot and call out tensions worth discussing. Useful
prompts:
- **Busy clusters** — three or more controls for the same concept
side-by-side in the navbar.
- **Redundant paths** — same destination reachable from multiple
surfaces with no clear reason.
- **Dead-end routes** — pages reachable only by typing the URL,
no in-app link.
- **Missing surfaces** — common user need with no in-app path
(e.g., "find people to follow" with no `/explore`).
- **Audience mismatch** — same URL serving meaningfully different
products to anon vs auth.
- **Visual inconsistency** — adjacent navbar entries with
different treatment (icon vs text, different baselines).
- **Mobile hazards** — clusters that will wrap badly under
small viewport widths.
Each observation should be one short paragraph. Each is a
question for the user to answer, not a decision you've made.
11. **List open IA questions**
Distinct from observations: these are larger directional choices
where the answer determines what other observations even matter.
Examples: "Should `/` and `/feed` merge for signed-in users?",
"Where does an `/explore` page live, if at all?", "Mobile
pattern — hamburger? Bottom tab bar?"
Keep these as bullets the user can answer in one line each.
12. **Write the doc**
Output to `docs/information-architecture.md`. Use this top
structure:
```markdown
# Information Architecture Review
*Snapshot date: YYYY-MM-DD.* If the navbar, route table, or feed
model has shifted since then, treat this doc as stale and refresh
against `apps/journal/app/routes.ts` + `apps/journal/app/root.tsx`.
A snapshot of where every page lives, who sees it, and how visitors
navigate between them. Intended for review — flag anything that
doesn't make sense or should change.
## Apps
[...]
## Journal sitemap
### Public surface (logged-out)
[...]
### Authenticated surface (logged-in)
[...]
### Navigation surfaces
[...]
## Logged-in vs logged-out home
[...if `/` does double duty...]
## [Any "N feed concepts" / duplication sections]
[...]
## Cross-app linking
[...]
## Planner sitemap
[...short...]
## Observations worth discussing
[...]
## Open IA questions
[...]
```
Use today's date for the snapshot. Reference the source-of-truth
files at the top so the next review knows what to compare against.
13. **In refresh mode, preserve the user's accumulated decisions**
The prior doc may already contain:
- Resolved observations (struck through with a *Resolved: ...*
note).
- An "Implementation backlog" section with streams.
- An "Open exploration" section.
These are the *user's work*, not the snapshot. Carry them forward
untouched unless one is plainly obsolete (e.g., the feature it
references no longer exists). When in doubt, leave it and let the
user prune.
If a previously-flagged observation is no longer present in the
current code (e.g., it was implemented), update its status note
rather than removing it — preserves history.
14. **Surface a ranked next-action list**
After writing the doc, summarize in 46 lines what changed since
the prior review (or what the most actionable observations are if
fresh). End with a question: which open IA question does the user
want to tackle first?
Don't start implementation work — this skill is for the
*snapshot*. The decisions and the implementation backlog grow
through the conversation that follows.
---
## What this skill is NOT
- **Not an implementation skill.** Don't write code, don't open PRs.
The doc is the deliverable; decisions and code follow in normal
conversation.
- **Not a unilateral redesign.** Observations are questions for the
user. Don't bake "decisions" into the snapshot — those go in the
backlog only when the user has actually answered the question.
- **Not a spec change.** OpenSpec specs describe what's shipped; this
doc describes the IA *as it stands* with tensions flagged. Any
resulting spec updates happen during implementation, not during the
review.
---
## Guardrails
- Always include the snapshot date at the top of the output doc — IA
drifts; future-you needs to know whether to trust the snapshot or
refresh it.
- Reference the source-of-truth files (`routes.ts`, `root.tsx`) so the
next review's diff is mechanical.
- Keep observations as questions, not decrees. The user makes the
call.
- In refresh mode, preserve the user's accumulated decisions verbatim.
Only the snapshot sections are yours to rewrite.
- Don't invent routes or features. If you can't find evidence for it
in the code, don't put it in the snapshot.
- Keep it scannable. The doc is for review; verbose explanations bury
the signal.

View file

@ -0,0 +1,216 @@
---
name: spec-drift-review
description: Walk every spec in `openspec/specs/`, compare it to the shipped code, and produce a categorized drift report (high/medium/low severity per spec, plus code-without-spec findings and structural suggestions). Use when the user wants to check spec drift, after a chunk of features has shipped, or when planning a spec catch-up PR.
license: MIT
metadata:
author: trails.cool
version: "1.0"
---
Walk the specs directory + the shipped code, compare them claim by
claim, and produce a structured drift report. The report is the
deliverable; fixes happen in a follow-up PR after the user has reviewed
the findings.
The goal is to keep `openspec/specs/` honest — specs are useless if they
don't describe the actual product, and worse than useless if they
contradict it.
---
## When to use
- The user explicitly asks to check spec drift ("are the specs in sync",
"review specs against code").
- After a multi-feature chunk has shipped without per-feature spec
promotion — drift accumulates fastest in catch-up phases.
- Before reorganizing the specs directory (split / merge / rename).
- When a spec contradicts the codebase and you're not sure which is
right.
---
## Steps
1. **Get the lay of the land**
Run these in parallel:
```bash
ls openspec/specs/
ls openspec/changes/ # in-flight work — NOT drift
cat openspec/CAPABILITIES.md # if it exists, it's the index
openspec list --json # any active changes that explain drift
```
**Important:** any spec referenced in an active openspec change is
*expected* to drift from current code — that drift is the work in
progress. Note these and exclude them from the report.
2. **Identify the code-side anchors per spec**
For each spec at `openspec/specs/<capability>/spec.md`, find the
primary code locations that implement it. Most capability specs map
to one or more of:
- **Routes:** `apps/journal/app/routes/*.tsx` / `*.ts`
authoritative for URL behavior, redirects, access gating.
- **Server lib:** `apps/journal/app/lib/<topic>.server.ts` — most
business logic.
- **Schema:** `packages/db/src/schema/journal.ts` — table shapes,
visibility values, defaults, indexes.
- **i18n:** `packages/i18n/src/locales/{en,de}.ts` — user-facing
strings, often telling.
- **Tests:** integration tests are a great oracle for the *intended*
behavior; mismatch with the spec usually means the spec is stale.
Don't read every file. Pick the 13 anchors per spec that the
requirements most plausibly map to.
3. **Compare claim by claim**
For each requirement in a spec, ask:
- **Does the code do this?** If not, is it because the requirement
was retired or because it was never shipped?
- **Does the code do *more* than this?** New scenarios shipped
without a spec update.
- **Does the code do this *differently*?** Different URL,
different default, different status code, different rule.
- **Does this requirement reference a name that no longer exists?**
Renamed routes, deleted helpers, removed tables.
Track findings with severity:
| Severity | What it means |
|----------|---------------|
| **High** | Spec actively misleads — claims a behavior the code does not exhibit. A reader implementing against the spec would write the wrong code. |
| **Medium** | Spec is incomplete — code has scenarios the spec doesn't describe. Reader gets less information than they should but isn't actively misled. |
| **Low** | Wording drift — comments/cross-refs mention a renamed thing, but the requirement statements are still accurate. |
4. **Find code-without-spec**
Walk the route tree in `apps/journal/app/routes.ts` and the topic
files in `apps/journal/app/lib/`. For each top-level concept ask:
- Is this concept covered by a spec?
- If yes, does the spec mention this surface?
- If no, should it be?
Genuine "no spec" cases are usually: new feature shipped without
spec promotion, or piece of infrastructure deemed too internal for a
spec. Both are valid; flag the former, leave the latter alone.
5. **Structural review**
Spec organization itself can drift:
- **Specs that grew too big** — multiple unrelated requirements
under one spec. Candidates for a split (we previously split
`account-settings` into `profile-settings`, `account-management`,
`connected-services`).
- **Specs that overlap** — the same requirement appears in two
specs, or one spec keeps cross-referencing another. Candidate for
a merge or a clearer ownership boundary.
- **Specs that should exist but don't** — a capability is shipped
and substantial enough to merit its own spec but is currently
squeezed into another. (We added `sse-broker` and `notifications`
this way.)
- **`CAPABILITIES.md` drift** — if the index exists, check that
every spec dir has an entry and every entry points at a real
spec. The index is the easy thing to forget when adding a spec.
6. **Compose the report**
Output to the conversation as a markdown structure. Don't write a
doc unless the report is unusually large and the user asks for one —
most drift reports get acted on inside a single PR and don't need a
long-lived artifact.
Suggested structure:
```markdown
# Spec drift review — YYYY-MM-DD
**Active changes excluded from this review:**
- <name> (touches: <specs>)
## High-severity drift
### `<spec-name>`
- **Requirement: <name>** says <X>; code at `<file:line>` does <Y>.
[link / one-line action]
- …
## Medium-severity drift
### `<spec-name>`
- <description + code anchor>
- …
## Low-severity drift (wording / cross-refs)
- `<spec-name>`: <description>
- …
## Code-without-spec
- <feature> at `<file>` — should this be in <spec-name>, or a new
spec? Recommendation: <…>
## Structural suggestions
- Split: <spec-name><new-1>, <new-2>
- Merge: <spec-a> + <spec-b><new>
- New spec: <name> covering <area>
- `CAPABILITIES.md` updates: <list>
```
7. **Propose next actions, then stop**
End the review with three concrete options the user can pick from:
- **Ship a catch-up PR for everything** — works when drift is
mostly low/medium and the fix is mechanical.
- **Fix high-severity first, defer the rest** — works when high
items are urgent and the rest can wait for natural per-feature
spec updates.
- **Restructure first, then catch up** — works when structural
suggestions (split / merge / new spec) are large enough that
fixing claims inside the wrong spec shape would just have to be
redone.
Don't pick for them. Don't start fixing yet.
---
## What this skill is NOT
- **Not a fixer.** This skill produces a report. Apply happens after
the user has decided which findings to act on.
- **Not a CI check.** It's interactive — judgment calls (severity,
splits, merges) are part of the value, not an automation target.
- **Not for in-flight work.** Active openspec changes legitimately
cause spec/code mismatch; flag them in the "excluded" section and
move on.
- **Not a code review.** The question is "does the spec match the
code", not "is the code good." Code-quality observations belong in
PR review, not here.
---
## Guardrails
- Always start by reading active openspec changes — drift caused by
in-flight work is not drift.
- Don't write spec edits during the review. The user picks which
findings to act on; edits happen after.
- When code and spec disagree, the prevailing rule on this project is
**code is source of truth** unless the user says otherwise. Surface
the conflict; don't preemptively decide.
- Skip generated/scaffold files (e.g. `.react-router/types/**`) — they
are derived, not product.
- Don't pad the report with low-severity wording drift unless the user
asks for it. Concentrate signal.
- Keep observations specific (file + line + claim), not abstract. A
finding without an anchor isn't actionable.

View file

@ -4,6 +4,12 @@
shifted since then, treat this doc as stale and refresh against
`apps/journal/app/routes.ts` + `apps/journal/app/root.tsx`.
To regenerate this doc (fresh or refresh), invoke the **`/ia-review`**
skill — it walks the route tables + nav surfaces, builds the sitemap,
flags drift since this snapshot, and preserves the decisions/backlog
already captured below. The companion **`/spec-drift-review`** skill
does the equivalent for `openspec/specs/` against shipped code.
A snapshot of where every page lives, who sees it, and how visitors navigate
between them. Intended for review — flag anything that doesn't make sense or
should change.
@ -200,44 +206,42 @@ each is a deliberate IA choice that's worth confirming.
1. **`/` is two different products.** Logged-in `/` is "your stuff," logged-out
`/` is "the instance." Discoverable? Or should logged-in `/` keep showing
*something* of the public surface (e.g., a "Discover" tab)?
*Status: open — not sure yet if this needs resolving.*
2. ~~**Signed-in users can't see the public instance feed.**~~ *Resolved:
merged into `/feed` with a Followed / Public toggle (see above).*
3. **The navbar's account cluster is busy.** `<username>` + `Settings` + `Logout`
is three controls for the same concept. Already on the redesign list —
collapsing into an avatar dropdown is the natural fix.
3. ~~**The navbar's account cluster is busy.**~~ *Folded into Stream C
(navbar redesign): regroup the cluster behind an avatar dropdown.*
4. **`/feed` is reachable from both the navbar AND a button on the home page.**
The button on logged-in `/` (currently labelled "Feed") is mildly redundant
with the navbar entry. Worth keeping if Personal and Followed feel like
separate destinations; worth dropping if the navbar's "Feed" link is
obvious enough on its own.
4. ~~**`/feed` is reachable from both the navbar AND a button on the home
page.**~~ *Decision: drop the button on logged-in `/` — the navbar
entry is enough. See Stream D.*
5. **🔔 vs "Follow requests" are visually inconsistent.** One is an icon, the
other is a text link. Either both icons (consistent) or both text
(discoverable) would be more legible. Mobile wrap is already going to be a
problem.
5. ~~**🔔 vs "Follow requests" are visually inconsistent.**~~ *Resolved by
Stream B — Follow requests folded into the bell as a Requests tab, so
there's a single inbox icon in the navbar.*
6. **Routes and Activities are siblings, not nested.** That's correct today —
an activity can exist without a route, and vice versa. Worth flagging only
because some apps merge them into a single "history" timeline.
an activity can exist without a route, and vice versa. *Flagged for a
broader review: the concept of routes-vs-activities, plus the impending
word collision with social "activities" (comment, like, publish) once
federation lands. Tracked separately — see "Open exploration"
below.*
7. **`/settings` is one page.** Fine for now (5 sections, ~6 controls per
section). At ~10 sections it becomes a tab strip; at ~15 it becomes a left
nav. Not urgent.
7. ~~**`/settings` is one page.**~~ *Decision: break it apart. See Stream E.*
8. **No `/explore`, no `/users` directory, no search.** A signed-in user who
wants to *find* people to follow has no in-app path — they need a username
from outside. Federation (Phase 2) makes this worse before it gets better.
8. ~~**No `/explore`, no `/users` directory, no search.**~~ *Decision:
propose an `/explore` spec. See Stream F.*
9. **No mobile breakpoint for the navbar.** The cluster of 7 links on the
right will wrap on phones today. Tied to the navbar redesign.
9. ~~**No mobile breakpoint for the navbar.**~~ *Decision: include mobile
responsiveness in Stream C (navbar redesign).*
10. **Profile vs identity.** Your own profile is at `/users/<you>` not
`/me` or `/profile`. Reachable via the navbar self-link. Fine, but means
"view as logged-out visitor" is non-trivial — opening incognito is the
only way to see your locked stub.
*Status: open — no decision yet.*
---
@ -300,7 +304,7 @@ Decisions captured above translate into two work-streams. Items marked
- `openspec/specs/journal-landing/spec.md` — no change. Logged-in `/`
already shows the personal stream; this is unchanged.
### Stream B — Merge Follow requests into Notifications
### Stream B — Merge Follow requests into Notifications ✅ Shipped (PR #316)
**Decision (2026-04-26):** fold `/follows/requests` into `/notifications`
as a tabbed sub-page (Activity / Requests). The bell icon stays the
@ -370,17 +374,91 @@ Requests tab is the actionable surface, the Activity tab is the log.
### Stream C — Navbar redesign
*Pending tomorrow. Principles agreed in the IA review; specifics still
need decisions before implementation.*
**Scope (decided):**
**Needs decision:**
- Regroup the account cluster (`<username>` + Settings + Logout) behind
an avatar dropdown.
- Treat mobile responsiveness as a first-class concern in the redesign,
not a phase-2 follow-up. Today the navbar wraps badly on phones.
- The bell + Requests tab from Stream B already gives the navbar a
consistent inbox surface; nothing further needed there.
**Needs decision before implementation:**
- **Avatar dropdown content.** Profile · Settings · Logout — any others?
(Theme toggle? Language toggle? Account switcher when federation lands?)
- **Mobile target.** Hamburger? Bottom tab bar? "Phase 2, just don't break"?
- **Self-link vs avatar.** Today the navbar has a `<username>` text link to
your profile. After the avatar dropdown, does the avatar take that role,
or does Profile live only inside the dropdown?
- **Mobile pattern.** Hamburger drawer? Bottom tab bar? Condensed top
bar with the dropdown absorbing most controls?
- **Self-link vs avatar.** Today the navbar has a `<username>` text link
to your profile. After the avatar dropdown, does the avatar take that
role (click → profile, dropdown chevron → menu), or does Profile live
only inside the dropdown?
### Stream D — Drop the redundant "Feed" button on logged-in `/`
**Decision (2026-04-26):** logged-in `/` no longer needs a "Feed" button
in the page header — the navbar entry is the single discoverable path.
**Code changes:**
- `apps/journal/app/routes/home.tsx` — remove the `<a href="/feed">`
button next to the "New Activity" CTA in the signed-in branch
(currently lines ~170183). Keep "New Activity" as the only header
action.
- No spec changes; `journal-landing/spec.md` has a **Social feed link
for signed-in users** requirement that mentions a "Feed (or
equivalent) link" — that requirement should be retired.
Tiny PR, ~5 lines + a spec update.
### Stream E — Break Settings apart
**Decision (2026-04-26):** `/settings` becomes a sectioned area rather
than a single scrollable page. Spec was already split into three
(`profile-settings`, `account-management`, `connected-services`); the UI
should follow.
**Needs decision before implementation:**
- **Layout pattern.** Tab strip on `/settings` (single URL, JS-driven
tabs)? Nested routes (`/settings/profile`, `/settings/security`,
`/settings/connections`, `/settings/danger`)? Sidebar nav?
- **Section list.** The current page has five concerns:
1. Profile (display name, bio, profile visibility)
2. Email (with re-verification)
3. Passkeys
4. Connected services (Wahoo today)
5. Danger zone (delete account)
Keep five? Merge "Email" into "Profile"? Pull "Danger zone" into
"Account" alongside Email?
**Spec impact:** `profile-settings`, `account-management`, and
`connected-services` are already separate specs — they describe behavior,
not URL structure. Whichever URL pattern wins, only `journal-landing`
(or wherever the navbar currently sits) needs to know.
### Stream F — Propose an `/explore` spec
**Decision (2026-04-26):** the gap between "I want to find people to
follow" and "I have a username from outside" needs an in-app path.
Federation (Phase 2) makes this more valuable, but local-only `/explore`
is useful on day one.
**Approach:** kick off an OpenSpec proposal via `/opsx:propose` rather
than diving into code. The proposal phase decides:
- What `/explore` actually shows. A directory of all local users? A
curated "active in the last N days" list? A randomized rotation? Just
the public activity feed (which is currently buried on logged-out `/`)?
- Whether search is part of v1 or a follow-up.
- Where the link lives in the navbar (its own entry vs. inside `/feed`).
- Privacy: private profiles need to be excluded from any directory; this
is the same locked-account access rule that already gates
followers/following lists.
**Spec impact:** new spec file at
`openspec/specs/explore/spec.md` (or similar — name TBD by the proposal),
plus an entry in `CAPABILITIES.md`.
**Code changes (assuming avatar dropdown + icon-only Follow requests + no
mobile work yet):**
@ -424,12 +502,58 @@ mobile work yet):**
These came up in the IA review but aren't part of this round:
- **Search / `/explore` directory.** No way to find users in-app. Defer
until federation (Phase 2) makes it more painful.
- **Settings sectioning.** Single scrollable page is fine at 5 sections.
- **`/me` alias for own profile.** Marginal value; navbar self-link is
enough.
- **Mobile breakpoints across the app.** Bigger than just the navbar.
Stream C handles the navbar; the rest of the app is a separate effort.
---
## Open exploration
Items that aren't ready for an implementation backlog because the
underlying *concept* still needs work, not just the UI.
### Routes vs Activities — terminology and model
The IA review flagged that Routes and Activities are sibling top-level
concepts. Two reasons to revisit this beyond the URL structure:
1. **Conceptual overlap.** A *route* is a planned path. An *activity*
is a recorded outing (often along a route). Some apps (Strava,
Komoot) collapse these into one timeline; we keep them split. Worth
confirming the split still earns its keep.
2. **Word collision with social "activities".** Once federation lands
(and arguably already, with the `activity_published` notification
type), "activity" will be overloaded:
- **Athletic activity:** a bike ride, a hike, a run.
- **Social activity:** a comment, a like, a follow, a publish event.
This is the ActivityPub sense — and it's the one that will appear
in feeds, notifications, and remote inboxes.
The collision is going to bite. ActivityPub uses "Activity" as a
technical term throughout; the user-facing "Activity" (a ride) will
constantly be next to ActivityPub Activities (a follow, a like) in
logs, diagrams, and possibly UI strings.
**Plan:** dedicate a separate review doc (e.g.
`docs/routes-vs-activities.md`) to think this through before any spec
or code change. Open questions for that doc:
- Do we rename the user-facing "Activity" to something else (Outing?
Trip? Ride? Record? Trace?) before federation cements the social
meaning?
- Or do we keep "Activity" for the user-facing object and use "Event"
or "Stream item" for the social/ActivityPub sense?
- Are Routes still needed as a top-level object, or could they be a
subordinate concept of Activities (a "saved planned version" of
something you may eventually ride)?
- What does the export/import story look like — GPX is route-shaped;
Strava's TCX is activity-shaped; how do we want the model to talk
about each?
Not urgent, but worth resolving before federation is far enough along
that renaming is a migration headache.
- **Is "Follow requests" a sub-page of Notifications or a sibling?** Today
it's a sibling (separate navbar item). Could be a tab inside `/notifications`.
- **Avatar dropdown content.** Profile · Settings · Logout — anything else?