diff --git a/docs/superpowers/specs/2026-06-16-frontend-routing-design.md b/docs/superpowers/specs/2026-06-16-frontend-routing-design.md new file mode 100644 index 0000000..60aa328 --- /dev/null +++ b/docs/superpowers/specs/2026-06-16-frontend-routing-design.md @@ -0,0 +1,80 @@ +# Frontend Routing Design + +## Goal + +Add URL-backed frontend routing so users can refresh or directly open the main app views without losing their place. + +## Scope + +In scope: + +- Add routes for login, book list, book workspace, and admin user management. +- Keep the existing component structure: `LoginPage`, `BookListPage`, `WorkspaceView`, and `AdminPage`. +- Preserve the existing auth behavior and API calls. +- Preserve the existing backend API routes and static SPA fallback. +- Add focused tests for routing behavior. + +Out of scope: + +- Adding nested lesson-level URLs. +- Adding a new navigation layout. +- Replacing the current auth model. +- Adding a third-party router package unless the existing code makes a local router impractical. + +## Approaches Considered + +The recommended approach is a small local router built around `window.history` and `popstate`. The project does not currently use `vue-router`, and the app only needs four top-level route states. A local router keeps the change small and avoids a new dependency. + +A second option is adding `vue-router`. It would be more conventional for a growing Vue app, but it adds dependency and setup overhead for a narrow routing surface. + +A third option is hash routing, such as `/#/books/b1`. It avoids server fallback concerns, but the server already serves `dist/index.html` for unknown paths, so clean history URLs are a better fit. + +## Routes + +The frontend will support these clean URLs: + +- `/login` shows `LoginPage`. +- `/books` shows `BookListPage`. +- `/books/:bookId` shows `WorkspaceView` for the selected book. +- `/admin` shows `AdminPage`. + +Unknown paths redirect to the best available default: `/books` when logged in and `/login` when logged out. + +## Auth Behavior + +`App.vue` still calls `fetchMe()` on mount. While logged out, any route except `/login` resolves to the login page and updates the URL to `/login`. + +After login succeeds, the app routes to `/books`. Logout continues to clear tokens through `useAuth`; when the app observes the logged-out state, it routes to `/login`. + +The admin page remains visible only through the existing admin entry point in `BookListPage`. If a non-admin user reaches `/admin` directly, the backend admin API will still return authorization errors. The frontend may render the page shell, but protected data will not load. + +## Component Behavior + +`BookListPage` keeps emitting `open` and `admin`. `App.vue` will translate those events into route changes: + +- `open(id)` navigates to `/books/{id}`. +- `admin` navigates to `/admin`. + +`WorkspaceView` keeps emitting `back`; `App.vue` maps it to `/books`. + +`AdminPage` keeps emitting `back`; `App.vue` maps it to `/books`. + +This preserves component contracts and confines route ownership to the app shell. + +## Error Handling + +Book load errors remain handled by `WorkspaceView`. Its existing "返回列表" action navigates back to `/books`. + +Route parsing should be strict enough to avoid invalid view state. Empty or malformed book IDs fall back to `/books`. + +## Testing + +Add or update `App.test.ts` coverage for: + +- Starting at `/books` renders the book list. +- Opening a book updates the URL to `/books/:bookId` and renders the workspace. +- Pressing workspace back updates the URL to `/books`. +- Opening admin updates the URL to `/admin`. +- Logged-out users are routed to `/login`. + +Run the focused app tests and the project build after implementation.