Skip to content

Commit 8b2da81

Browse files
committed
feat(spa): add route-driven flow and session recovery
1 parent fa6ecfb commit 8b2da81

14 files changed

Lines changed: 809 additions & 301 deletions

app/web/routes/feed_pages.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ module Routes
66
##
77
# Mounts the root page and legacy feed paths.
88
module FeedPages
9+
SPA_APP_PATHS = %w[create token result].freeze
10+
SPA_APP_PREFIXES = ['result/'].freeze
11+
912
class << self
1013
# @param router [Roda::RodaRequest]
1114
# @param index_renderer [#call]
@@ -18,6 +21,10 @@ def call(router, index_renderer:)
1821
router.get do
1922
feed_name = requested_feed_name(router)
2023
next if feed_name.empty?
24+
if spa_app_path?(feed_name)
25+
index_renderer.call(router)
26+
next
27+
end
2128
next if feed_name.include?('.') && !feed_name.end_with?('.json', '.xml', '.rss')
2229

2330
RequestTarget.mark!(router, RequestTarget::FEED)
@@ -32,6 +39,12 @@ def call(router, index_renderer:)
3239
def requested_feed_name(router)
3340
router.path_info.to_s.delete_prefix('/')
3441
end
42+
43+
# @param path [String]
44+
# @return [Boolean]
45+
def spa_app_path?(path)
46+
SPA_APP_PATHS.include?(path) || SPA_APP_PREFIXES.any? { |prefix| path.start_with?(prefix) }
47+
end
3548
end
3649
end
3750
end

docs/20260405-preact-router.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# 2026-04-05 Preact Router Reliability Plan
2+
3+
## Summary
4+
Upgrade the SPA from a transient single-view flow to a route-driven, recoverable tool experience.
5+
Scope is frontend-only in this pass. Delivery is one broad pass, split into bounded workstreams with strict reviewer `accept` gates before merge.
6+
7+
## Decisions Locked
8+
- Output: agent-ready execution packet.
9+
- Scope shape: single large pass.
10+
- Boundary: frontend-only (no backend/OpenAPI changes).
11+
- Route model: three routes.
12+
- Priority: reliability mechanics first (no polish stream).
13+
- Review gate: strict `accept` only.
14+
15+
## Route Contract (Target)
16+
- `/create`
17+
- `/token`
18+
- `/result/:feedToken`
19+
- Preserve `?url=` prefill support for create flow bootstrap.
20+
21+
## Workstream A: Router + Navigation Skeleton
22+
Goal: replace implicit internal screen switching with explicit route transitions.
23+
24+
In scope:
25+
- Add Preact router integration in app root.
26+
- Migrate create/token/result transitions to route transitions.
27+
- Ensure browser back/forward/reload behavior is deterministic.
28+
29+
Out of scope:
30+
- Backend API changes.
31+
- Visual redesign.
32+
33+
Primary files:
34+
- `frontend/src/main.tsx`
35+
- `frontend/src/components/App.tsx`
36+
37+
Acceptance criteria:
38+
- Direct navigation to each route renders correct state.
39+
- Back/forward does not break task continuity.
40+
- Reload keeps user on intended state frame.
41+
42+
## Workstream B: Durable Session and Recovery
43+
Goal: make flow recoverable after refresh without server-side persistence.
44+
45+
In scope:
46+
- Persist local draft state (url, strategy).
47+
- Persist latest successful result snapshot keyed by `feed_token`.
48+
- Hydrate route state from local snapshot on load.
49+
- Resolve unused auth hook surface (`useAuth`) explicitly (retain with rationale or retire).
50+
51+
Out of scope:
52+
- Server-side history/status endpoints.
53+
54+
Primary files:
55+
- `frontend/src/hooks/useFeedConversion.ts`
56+
- `frontend/src/hooks/useAccessToken.ts`
57+
- new local session helper module(s)
58+
59+
Acceptance criteria:
60+
- Refresh on `/result/:feedToken` restores result view from local snapshot.
61+
- Refresh on `/create` restores draft input and selected strategy.
62+
- No stale error banners shown unless still applicable.
63+
64+
## Workstream C: Reliability State Machine and Error Taxonomy
65+
Goal: convert ad-hoc boolean/error handling into explicit operational states.
66+
67+
In scope:
68+
- Introduce explicit state machine:
69+
- `idle`, `validating`, `submitting`, `token_required`, `warming`, `ready`, `failed`
70+
- Classify errors into actionable categories:
71+
- `auth`, `input`, `readiness`, `network`, `server`
72+
- Enforce one canonical primary action per state.
73+
74+
Out of scope:
75+
- New endpoint design.
76+
77+
Primary files:
78+
- `frontend/src/components/AppPanels.tsx`
79+
- `frontend/src/components/ResultDisplay.tsx`
80+
81+
Acceptance criteria:
82+
- Every failure state has one clear primary recovery action.
83+
- Token rejection flow is deterministic and route-safe.
84+
- Strategy fallback/retry behavior is explicit and test-covered.
85+
86+
## Workstream D: Test Expansion for State Parity and Durability
87+
Goal: lock behavior with tests so reliability regressions are caught.
88+
89+
In scope:
90+
- Add tests for deep links and direct route loads.
91+
- Add tests for refresh/back-forward across create/token/result.
92+
- Add parity assertions for affected states (guest/token/result).
93+
- Add uniqueness checks: one canonical primary action per state.
94+
95+
Primary files:
96+
- `frontend/src/__tests__/App.test.tsx`
97+
- `frontend/src/__tests__/App.contract.test.tsx`
98+
- `frontend/e2e/smoke.spec.ts`
99+
100+
Acceptance criteria:
101+
- Route and recovery regressions fail tests.
102+
- E2E smoke validates primary user journey with parity checks.
103+
104+
## Verification Requirements
105+
Required per workstream:
106+
- `make ready`
107+
- Relevant frontend tests (`pnpm run test:ci`)
108+
- E2E smoke (`pnpm run test:e2e`) where route/state behavior changes
109+
110+
Required manual smoke:
111+
- Run app in Dev Container (`make dev`)
112+
- Validate in chrome-devtools at `http://127.0.0.1:4001/`
113+
- Check create/token/result states for:
114+
- state parity
115+
- one primary action per outcome
116+
- proper focus behavior on transitions
117+
118+
## Agent Workflow (Execution + Review)
119+
Execution order:
120+
1. Start A and B in parallel.
121+
2. Land C after A/B route/session primitives are stable.
122+
3. Finalize D with comprehensive assertions after A/B/C complete.
123+
124+
Review policy:
125+
- Separate reviewer pass per workstream.
126+
- Reviewer output must include:
127+
1. findings first (severity ordered, file/line)
128+
2. residual risks
129+
3. verdict (`accept` or `needs fix`)
130+
- Merge only when verdict is `accept` and required checks are green.
131+
132+
## Implementation Prompt Template (for each agent)
133+
Read:
134+
- `/Users/gil/versioned/html2rss/html2rss-web/AGENTS.md`
135+
- `/Users/gil/versioned/html2rss/html2rss-web/docs/design-system.md`
136+
- this plan file (`docs/20260405-preact-router.md`)
137+
138+
Task:
139+
Deliver only assigned workstream.
140+
141+
Constraints:
142+
- Frontend-only.
143+
- Preserve design-system grammar.
144+
- No cross-workstream scope drift.
145+
146+
Required outputs:
147+
1. Implement slice.
148+
2. Report changed files and acceptance criteria status.
149+
3. Report blockers/risk notes.
150+
151+
Required verification:
152+
- `make ready`
153+
- relevant frontend tests
154+
- manual state checks in chrome-devtools at `http://127.0.0.1:4001/`
155+
156+
## Reviewer Prompt Template
157+
Review assigned workstream for production readiness.
158+
159+
Focus:
160+
- behavioral regressions
161+
- routing/state durability
162+
- action uniqueness per state
163+
- missing tests and edge cases
164+
165+
Output:
166+
1. findings (severity ordered, file/line)
167+
2. residual risks
168+
3. verdict: `accept` or `needs fix`

0 commit comments

Comments
 (0)