Add /ia-review and /spec-drift-review process skills
Captures two recurring review processes as repeatable Claude skills so they don't have to be reinvented from scratch each time: - /ia-review: walks the route tables + nav surfaces, builds the sitemap, flags drift, and writes/refreshes docs/information-architecture.md. Refresh mode preserves the user's accumulated decisions and implementation backlog while only rewriting the snapshot sections. - /spec-drift-review: walks every spec in openspec/specs/, compares it to shipped code, and produces a categorized drift report (high/medium/low severity) plus code-without-spec findings and structural suggestions (split/merge/new specs, CAPABILITIES.md catch-up). Excludes specs touched by active openspec changes since that mismatch is intended in-flight work, not drift. Also extends the IA doc with the items resolved/deferred during the review (observations 1-10), three new streams (D: drop redundant Feed button on logged-in /, E: break Settings apart, F: propose /explore spec), and an "Open exploration" section for the routes-vs-activities terminology question. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ad11624c83
commit
a847ff2e52
3 changed files with 648 additions and 33 deletions
275
.claude/skills/ia-review/SKILL.md
Normal file
275
.claude/skills/ia-review/SKILL.md
Normal 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 4–6 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.
|
||||||
216
.claude/skills/spec-drift-review/SKILL.md
Normal file
216
.claude/skills/spec-drift-review/SKILL.md
Normal 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 1–3 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.
|
||||||
|
|
@ -4,6 +4,12 @@
|
||||||
shifted since then, treat this doc as stale and refresh against
|
shifted since then, treat this doc as stale and refresh against
|
||||||
`apps/journal/app/routes.ts` + `apps/journal/app/root.tsx`.
|
`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
|
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
|
between them. Intended for review — flag anything that doesn't make sense or
|
||||||
should change.
|
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
|
1. **`/` is two different products.** Logged-in `/` is "your stuff," logged-out
|
||||||
`/` is "the instance." Discoverable? Or should logged-in `/` keep showing
|
`/` is "the instance." Discoverable? Or should logged-in `/` keep showing
|
||||||
*something* of the public surface (e.g., a "Discover" tab)?
|
*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:
|
2. ~~**Signed-in users can't see the public instance feed.**~~ *Resolved:
|
||||||
merged into `/feed` with a Followed / Public toggle (see above).*
|
merged into `/feed` with a Followed / Public toggle (see above).*
|
||||||
|
|
||||||
3. **The navbar's account cluster is busy.** `<username>` + `Settings` + `Logout`
|
3. ~~**The navbar's account cluster is busy.**~~ *Folded into Stream C
|
||||||
is three controls for the same concept. Already on the redesign list —
|
(navbar redesign): regroup the cluster behind an avatar dropdown.*
|
||||||
collapsing into an avatar dropdown is the natural fix.
|
|
||||||
|
|
||||||
4. **`/feed` is reachable from both the navbar AND a button on the home page.**
|
4. ~~**`/feed` is reachable from both the navbar AND a button on the home
|
||||||
The button on logged-in `/` (currently labelled "Feed") is mildly redundant
|
page.**~~ *Decision: drop the button on logged-in `/` — the navbar
|
||||||
with the navbar entry. Worth keeping if Personal and Followed feel like
|
entry is enough. See Stream D.*
|
||||||
separate destinations; worth dropping if the navbar's "Feed" link is
|
|
||||||
obvious enough on its own.
|
|
||||||
|
|
||||||
5. **🔔 vs "Follow requests" are visually inconsistent.** One is an icon, the
|
5. ~~**🔔 vs "Follow requests" are visually inconsistent.**~~ *Resolved by
|
||||||
other is a text link. Either both icons (consistent) or both text
|
Stream B — Follow requests folded into the bell as a Requests tab, so
|
||||||
(discoverable) would be more legible. Mobile wrap is already going to be a
|
there's a single inbox icon in the navbar.*
|
||||||
problem.
|
|
||||||
|
|
||||||
6. **Routes and Activities are siblings, not nested.** That's correct today —
|
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
|
an activity can exist without a route, and vice versa. *Flagged for a
|
||||||
because some apps merge them into a single "history" timeline.
|
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
|
7. ~~**`/settings` is one page.**~~ *Decision: break it apart. See Stream E.*
|
||||||
section). At ~10 sections it becomes a tab strip; at ~15 it becomes a left
|
|
||||||
nav. Not urgent.
|
|
||||||
|
|
||||||
8. **No `/explore`, no `/users` directory, no search.** A signed-in user who
|
8. ~~**No `/explore`, no `/users` directory, no search.**~~ *Decision:
|
||||||
wants to *find* people to follow has no in-app path — they need a username
|
propose an `/explore` spec. See Stream F.*
|
||||||
from outside. Federation (Phase 2) makes this worse before it gets better.
|
|
||||||
|
|
||||||
9. **No mobile breakpoint for the navbar.** The cluster of 7 links on the
|
9. ~~**No mobile breakpoint for the navbar.**~~ *Decision: include mobile
|
||||||
right will wrap on phones today. Tied to the navbar redesign.
|
responsiveness in Stream C (navbar redesign).*
|
||||||
|
|
||||||
10. **Profile vs identity.** Your own profile is at `/users/<you>` not
|
10. **Profile vs identity.** Your own profile is at `/users/<you>` not
|
||||||
`/me` or `/profile`. Reachable via the navbar self-link. Fine, but means
|
`/me` or `/profile`. Reachable via the navbar self-link. Fine, but means
|
||||||
"view as logged-out visitor" is non-trivial — opening incognito is the
|
"view as logged-out visitor" is non-trivial — opening incognito is the
|
||||||
only way to see your locked stub.
|
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 `/`
|
- `openspec/specs/journal-landing/spec.md` — no change. Logged-in `/`
|
||||||
already shows the personal stream; this is unchanged.
|
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`
|
**Decision (2026-04-26):** fold `/follows/requests` into `/notifications`
|
||||||
as a tabbed sub-page (Activity / Requests). The bell icon stays the
|
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
|
### Stream C — Navbar redesign
|
||||||
|
|
||||||
*Pending tomorrow. Principles agreed in the IA review; specifics still
|
**Scope (decided):**
|
||||||
need decisions before implementation.*
|
|
||||||
|
|
||||||
**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?
|
- **Avatar dropdown content.** Profile · Settings · Logout — any others?
|
||||||
(Theme toggle? Language toggle? Account switcher when federation lands?)
|
(Theme toggle? Language toggle? Account switcher when federation lands?)
|
||||||
- **Mobile target.** Hamburger? Bottom tab bar? "Phase 2, just don't break"?
|
- **Mobile pattern.** Hamburger drawer? Bottom tab bar? Condensed top
|
||||||
- **Self-link vs avatar.** Today the navbar has a `<username>` text link to
|
bar with the dropdown absorbing most controls?
|
||||||
your profile. After the avatar dropdown, does the avatar take that role,
|
- **Self-link vs avatar.** Today the navbar has a `<username>` text link
|
||||||
or does Profile live only inside the dropdown?
|
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 ~170–183). 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
|
**Code changes (assuming avatar dropdown + icon-only Follow requests + no
|
||||||
mobile work yet):**
|
mobile work yet):**
|
||||||
|
|
@ -424,12 +502,58 @@ mobile work yet):**
|
||||||
|
|
||||||
These came up in the IA review but aren't part of this round:
|
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
|
- **`/me` alias for own profile.** Marginal value; navbar self-link is
|
||||||
enough.
|
enough.
|
||||||
- **Mobile breakpoints across the app.** Bigger than just the navbar.
|
- **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
|
- **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`.
|
it's a sibling (separate navbar item). Could be a tab inside `/notifications`.
|
||||||
- **Avatar dropdown content.** Profile · Settings · Logout — anything else?
|
- **Avatar dropdown content.** Profile · Settings · Logout — anything else?
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue