|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- ---
- sessionId: session-261002-154517-19l9
- ---
-
- # Requirements
-
- ### Overview & Goals
- Enhance user utility and application navigation by making individual proposals linkable and bookmarkable via browser URLs. Replacing in-memory state selection with URL-driven routing maximizes efficiency, enables direct sharing among users, and preserves standard browser history navigation (back/forward).
-
- ### Scope
- - **In Scope:**
- - Transitioning router history from `createMemoryHistory()` to `createWebHistory()`.
- - Configuring nested routes under `HomeView` for the empty state (`/`) and specific proposals (`/proposals/:id`).
- - Replacing the static conditional component rendering in `HomeView.vue` with `<RouterView />`.
- - Creating a dedicated route view (`ProposalView.vue`) to fetch and render proposal details based on route parameters.
- - Updating `Sidebar.vue` and `ProposalContainer.vue` to trigger router navigation and synchronize active item highlights with the active route.
- - Ensuring smooth mobile UX with automatic drawer dismissal upon navigation.
- - **Out of Scope:**
- - Changes to backend APIs or data models.
- - Authentication guard modifications.
- - Modifications to proposal editing or amendment creation workflows beyond preserving their existing modal behavior.
-
- ### User Stories
- - **As a user**, I want to share a direct URL to a specific proposal so that collaborators can immediately view the proposal details without manually finding it in the sidebar.
- - **As a user**, I want browser back and forward buttons to navigate between previously viewed proposals without page reloads.
- - **As a user**, I want refreshing the browser on a proposal page to reload the exact same proposal seamlessly.
-
- ### Functional Requirements
- - Navigating to `/` displays the proposal sidebar alongside `ProposalEmptyState` in the main view area.
- - Navigating to `/proposals/:id` loads and displays the details for the proposal with ID `:id`.
- - Clicking a proposal in `Sidebar.vue` navigates to `/proposals/:id`, highlights the selected proposal in the sidebar, and closes the mobile sidebar drawer if open.
- - Invalid or non-existent proposal IDs show an appropriate empty or not-found state without breaking the application shell.
- - Direct URL access to `/proposals/:id` highlights the respective item in the sidebar once the proposal list is loaded.
-
- ### Non-Functional Requirements
- - **Performance:** Avoid unnecessary re-fetching of the full proposal list when switching between proposal detail routes.
- - **Maintainability:** Clear separation between route-level container components and presentational components (`ProposalDetail.vue`, `ProposalEmptyState.vue`).
- - **User Experience:** Instant visual feedback using existing loading indicators during route transitions.
-
- # Technical Design
-
- ### Current Implementation
- - `src/router/index.ts` uses `createMemoryHistory()` and only registers a single route `{ path: '/', component: HomeView }`.
- - `src/views/HomeView.vue` manages the `selected` proposal state locally in component memory (`ref<ProposalWithParagraphs>()`). Clicking a sidebar item triggers `handleSelection` which directly calls `ProposalService.getProposal(id)` and sets local state.
- - `src/components/Sidebar.vue` tracks selection via an internal `selectedId` ref and emits an event upward.
- - `src/App.vue` renders the top-level `<RouterView />`, which mounts `HomeView.vue`.
-
- ### Key Decisions
- - **Nested Routing in `HomeView.vue`:**
- - *Decision:* Nest child routes inside `HomeView` rather than creating separate top-level pages.
- - *Rationale:* Preserves the persistent sidebar layout and list state across navigation without re-fetching proposals on every route change, maximizing rendering efficiency and minimizing layout shifts.
- - **History Mode:**
- - *Decision:* Switch from `createMemoryHistory()` to `createWebHistory()`.
- - *Rationale:* Enables standard URL paths (`/proposals/:id`) that work with browser history, bookmarks, and link sharing.
- - **Route Component Structure:**
- - *Decision:* Introduce `src/views/ProposalView.vue` as the route-level child component that reads route params, fetches data via `ProposalService`, and passes data to the existing `ProposalDetail.vue`.
- - *Rationale:* Keeps `ProposalDetail.vue` purely presentational and reusable while decoupling route logic and lifecycle hooks.
- - **Active State Synchronization:**
- - *Decision:* Derive sidebar active state from `useRoute().params.id` instead of disconnected local component state.
- - *Rationale:* Guarantees consistency across direct URL hits, back/forward browser navigation, and clicks.
-
- ### Architecture Diagram
- ```mermaid
- graph TD
- App[App.vue - Root RouterView] --> HomeView[HomeView.vue]
- HomeView --> Sidebar[Sidebar.vue - List & Filters]
- HomeView --> ContentRouterView[Nested RouterView]
- ContentRouterView -- path: '/' --> EmptyState[ProposalEmptyState.vue]
- ContentRouterView -- path: '/proposals/:id' --> ProposalView[ProposalView.vue]
- ProposalView --> ProposalDetail[ProposalDetail.vue]
- Sidebar -.->|router.push('/proposals/:id')| ProposalView
- ```
-
- ### Proposed Changes
- 1. **`src/router/index.ts`:**
- - Change history mode to `createWebHistory()`.
- - Update routes:
- ```ts
- {
- path: '/',
- component: HomeView,
- children: [
- { path: '', name: 'home', component: ProposalEmptyState },
- { path: 'proposals/:id', name: 'proposal', component: () => import('../views/ProposalView.vue'), props: true }
- ]
- }
- ```
- 2. **`src/views/HomeView.vue`:**
- - Replace `<ProposalDetail>`, `<ProposalEmptyState>`, and content `<LoadingSpinner>` in `<main>` with `<RouterView />`.
- - Remove manual `selected` state and `handleSelection` logic since routing handles proposal loading.
- - Listen to route changes to auto-close `isSidebarOpen` on mobile viewports.
- 3. **`src/views/ProposalView.vue` (New File):**
- - Accept `id` prop (coerced to number).
- - Watch `id` to invoke `ProposalService.getProposal(id)` with `useLoading('content')`.
- - Render `ProposalDetail` when loaded, or appropriate error/empty fallback on failure.
- 4. **`src/components/Sidebar.vue` & `src/components/ProposalContainer.vue`:**
- - Update `handleSelection` to navigate using `router.push({ name: 'proposal', params: { id: proposal.id } })`.
- - Determine `selected` state dynamically from the active route parameter matching `proposal.id`.
-
- ### File Structure
- - `src/router/index.ts` *(modified)* — Configure HTML5 web history and nested routes.
- - `src/views/HomeView.vue` *(modified)* — Host nested `<RouterView />` and layout shell.
- - `src/views/ProposalView.vue` *(new)* — Route view component for fetching and displaying a proposal.
- - `src/components/Sidebar.vue` *(modified)* — Derive active selection from route and dispatch route navigation.
-
- ### Risks & Mitigations
- - **Route Param Type Coercion:** Route params are strings by default; parsing `Number(route.params.id)` ensures compatibility with `ProposalService.getProposal(number)`.
- - **Rapid Navigation Race Conditions:** Watching the proposal ID and ensuring the latest request updates state avoids stale content overwrites.
-
- # Testing
-
- ### Validation Approach
- Verification focuses on confirming that URLs accurately reflect the displayed proposal, deep links load expected data, and navigation between proposals works smoothly via both UI clicks and browser history.
-
- ### Key Scenarios
- 1. **Direct Deep Link Loading:**
- - Navigating directly to `/proposals/1` loads proposal #1 details and highlights item #1 in the sidebar once the list is loaded.
- 2. **Sidebar Item Click Navigation:**
- - Clicking a proposal in the sidebar navigates to `/proposals/:id`, updates the address bar URL, and displays the corresponding proposal content.
- 3. **Browser History Navigation:**
- - Clicking back and forward buttons in the browser switches between viewed proposals and keeps sidebar selection synchronized.
- 4. **Empty State on Root Route:**
- - Navigating to `/` renders `ProposalEmptyState` in the main content pane without errors.
- 5. **Mobile Drawer Behavior:**
- - Selecting a proposal on mobile screens collapses the sidebar overlay and displays the selected proposal.
-
- ### Edge Cases
- - **Invalid / Non-Numeric Proposal ID:** Navigating to `/proposals/abc` or `/proposals/-1` handles the error gracefully without throwing uncaught exceptions.
- - **Non-Existent Proposal ID (404):** Displays a user-friendly error message or empty state when the API returns an error for a missing proposal.
- - **Rapid Sequential Clicks:** Quickly clicking multiple sidebar items displays only the data for the final selected proposal.
-
- # Delivery Steps
-
- ### ✓ Step 1: Configure Vue Router with Web History and Nested Routes
- Vue Router is configured with web history and nested routes for proposal viewing.
-
- - Replace `createMemoryHistory()` with `createWebHistory()` in `src/router/index.ts` to support browser URLs, bookmarks, and standard history navigation.
- - Define child routes under `HomeView`: an empty child path `''` mapped to `ProposalEmptyState.vue` and a dynamic child path `'proposals/:id'` mapped to a proposal detail route view.
- - Enable `props: true` (or custom prop mapping) on the dynamic route to pass the proposal `id` cleanly as a typed number prop.
-
- ### ✓ Step 2: Implement Proposal Route View and Data Fetching
- Dedicated route view created to load and display individual proposals based on URL parameters.
-
- - Create `src/views/ProposalView.vue` (or `ProposalDetailView.vue`) to encapsulate data retrieval for the selected proposal ID.
- - Integrate `ProposalService.getProposal(id)` with reactive route parameter watchers so switching proposals updates content without remounting overhead.
- - Coordinate loading states with `useLoading('content')` and render `ProposalDetail` or handle not-found/error states gracefully.
-
- ### ✓ Step 3: Update HomeView and Sidebar for Route-Driven Navigation
- HomeView and Sidebar leverage Vue Router navigation and automatic route-based active selection.
-
- - Update `src/views/HomeView.vue` main area to render `<RouterView />` instead of manual conditional rendering.
- - Wire `src/components/Sidebar.vue` and `src/components/ProposalContainer.vue` to navigate to `/proposals/:id` upon proposal click.
- - Sync active proposal highlighting in `Sidebar.vue` with current `route.params.id` to ensure correct state on direct URL loads, back/forward navigation, and clicks.
- - Automatically dismiss the mobile sidebar overlay upon route navigation on small viewports.
|