|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- ---
- sessionId: session-261002-153750-168j
- ---
-
- # Requirements
-
- ### Overview & Goals
- `HomeView.vue` currently acts as a monolithic view holding layout orchestration, proposal reading and paragraph rendering, amendment carousel presentation, comments placeholder, voting action buttons, export-to-markdown generation and file downloading, and empty/loading states.
-
- The goal is to componentize `HomeView.vue` into modular, single-responsibility Vue 3 components and shared utility functions. This reduces template verbosity, enhances maintainability and testability, eliminates duplicated markdown parsing across the project, and establishes clean component boundaries.
-
- ### Scope
- #### In Scope
- - Extracting standalone proposal components:
- - `ProposalDetail.vue`: Primary article layout combining proposal header, formatted paragraphs, amendments section, comments section, and the action rail.
- - `ProposalVotingCard.vue`: Voting options card (Approve, Reject, Abstain).
- - `ProposalExportCard.vue`: Markdown export card with download trigger.
- - `ProposalEmptyState.vue`: Informational empty state when no proposal is selected.
- - Extracting reusable utilities:
- - `src/utils/markdown.ts`: Centralized markdown parsing and DOM sanitization.
- - `src/utils/proposalExport.ts`: Proposal Markdown file generator and file download trigger.
- - Refactoring `src/views/HomeView.vue` to orchestrate layout, data fetching, and loading states cleanly.
- - Updating `ProposalContainer.vue` to use the shared markdown utility.
-
- #### Out of Scope
- - Backend API schema changes or new endpoints.
- - Redesigning the visual styles or changing Tailwind classes and responsive behavior.
- - Modifying `Sidebar.vue` or modal dialogs beyond their existing integration.
-
- ### User Stories
- - **As a developer**, I want `HomeView.vue` to be concise and focused on high-level layout so that I can easily navigate and maintain page-level logic.
- - **As a developer**, I want proposal voting, export, and presentation logic split into dedicated components so that they can be tested and modified independently without risking regressions across the entire page.
- - **As a user**, I want proposal display, voting, and file downloading to function seamlessly with identical UI and responsive interactions.
-
- ### Functional Requirements
- - **FR-1**: When no proposal is selected, the application displays `ProposalEmptyState` with the title "Select a proposal" and helper text.
- - **FR-2**: When a proposal is selected, `ProposalDetail` renders:
- - Proposal header with tags, creator wallet address, and proposal name.
- - Proposal paragraphs formatted using sanitized Markdown.
- - `AmendmentCarousel` with the current proposal ID.
- - Comments section placeholder.
- - Right-hand action rail containing `ProposalVotingCard` and `ProposalExportCard`.
- - **FR-3**: Clicking the download button in `ProposalExportCard` triggers the Markdown export and downloads a sanitized `.md` file containing proposal title, author, and paragraphs.
- - **FR-4**: Voting actions in `ProposalVotingCard` maintain their existing UI styling and emit selection events.
- - **FR-5**: Sidebar visibility toggles and loading spinners remain fully operational across desktop and mobile viewports.
-
- ### Non-Functional Requirements
- - **Maintainability**: Clear separation of presentation, business logic, and file utilities.
- - **Type Safety**: Full TypeScript typing for props, emits, and utility functions conforming to generated API models.
- - **Performance**: Zero overhead from component decomposition; no extra re-renders or layout shifts.
-
- # Technical Design
-
- ### Current Implementation
- `HomeView.vue` (245 lines) contains:
- - Primary container with responsive sidebar integration and mobile backdrop.
- - Proposal article layout with header, paragraph loops using inline `DOMPurify` / `marked` formatting, `AmendmentCarousel`, and comments container.
- - Aside rail containing voting buttons and the markdown download card.
- - Empty selection state.
- - Script containing proposal data fetching, markdown sanitization, file download generation, loading states, and sidebar toggling.
- - `ProposalContainer.vue` also duplicates markdown parsing logic (`marked` + `DOMPurify`).
-
- ### Key Decisions
- 1. **Component Hierarchy**:
- - `HomeView.vue` remains the top-level route coordinator: handles sidebar toggle state, fetching proposals list, and active proposal selection.
- - `ProposalDetail.vue` acts as the container for proposal inspection: receives `proposal: ProposalWithParagraphs` as a prop and embeds `AmendmentCarousel`, `ProposalVotingCard`, and `ProposalExportCard`.
- - `ProposalVotingCard.vue` and `ProposalExportCard.vue` are extracted as dedicated components to keep the voting workflow and export logic decoupled from reading layout.
- - `ProposalEmptyState.vue` handles the "no proposal selected" display.
-
- 2. **Utility Extraction (`src/utils/markdown.ts` & `src/utils/proposalExport.ts`)**:
- - Extract `applyFormatting` / `renderMarkdown` into `src/utils/markdown.ts` to prevent code duplication between `HomeView.vue` and `ProposalContainer.vue`.
- - Extract `downloadProposalMarkdown` and `sanitizeFilename` into `src/utils/proposalExport.ts` so file generation logic is pure, reusable, and easily testable.
-
- ### Components
- - **`src/views/HomeView.vue` (Modified)**:
- - Retains: `Sidebar`, mobile overlay, loading spinner, selection state, and window resize listeners.
- - Uses: `<ProposalDetail :proposal="selected" />` and `<ProposalEmptyState />`.
- - **`src/components/ProposalDetail.vue` (New)**:
- - Props: `proposal: ProposalWithParagraphs`.
- - Template: Proposal header, sanitized paragraph list, `AmendmentCarousel`, comments block, and sidebar rail embedding `ProposalVotingCard` and `ProposalExportCard`.
- - **`src/components/ProposalVotingCard.vue` (New)**:
- - Props: `proposalId: number`.
- - Emits: `vote: [type: 'Positive' | 'Negative' | 'Abstain']`.
- - Template: "Cast your vote" card with Approve, Reject, Abstain buttons.
- - **`src/components/ProposalExportCard.vue` (New)**:
- - Props: `proposalId: number`, `proposalName: string`.
- - Template: Proposal record card with `.md` badge and download action button.
- - **`src/components/ProposalEmptyState.vue` (New)**:
- - Template: Centered container with "Select a proposal" header and instructions.
- - **`src/components/ProposalContainer.vue` (Modified)**:
- - Refactored to import `renderMarkdown` from `src/utils/markdown.ts`.
-
- ### File Structure
- ```
- src/
- ├── components/
- │ ├── AmendmentCarousel.vue (existing)
- │ ├── LoadingSpinner.vue (existing)
- │ ├── ProposalContainer.vue (modified - use markdown util)
- │ ├── ProposalDetail.vue (new)
- │ ├── ProposalEmptyState.vue (new)
- │ ├── ProposalExportCard.vue (new)
- │ ├── ProposalVotingCard.vue (new)
- │ └── Sidebar.vue (existing)
- ├── utils/
- │ ├── markdown.ts (new)
- │ ├── proposalExport.ts (new)
- │ └── wallet.ts (existing)
- └── views/
- └── HomeView.vue (modified - simplified coordinator)
- ```
-
- ### Architecture Diagram
- ```mermaid
- graph TD
- HomeView["HomeView.vue (Coordinator)"]
- Sidebar["Sidebar.vue"]
- ProposalEmptyState["ProposalEmptyState.vue"]
- ProposalDetail["ProposalDetail.vue"]
-
- AmendmentCarousel["AmendmentCarousel.vue"]
- ProposalVotingCard["ProposalVotingCard.vue"]
- ProposalExportCard["ProposalExportCard.vue"]
-
- MarkdownUtil["utils/markdown.ts"]
- ExportUtil["utils/proposalExport.ts"]
-
- HomeView --> Sidebar
- HomeView --> ProposalEmptyState
- HomeView --> ProposalDetail
-
- ProposalDetail --> AmendmentCarousel
- ProposalDetail --> ProposalVotingCard
- ProposalDetail --> ProposalExportCard
- ProposalDetail --> MarkdownUtil
- ProposalExportCard --> ExportUtil
- ```
-
- ### Data Models / Contracts
- ```typescript
- // src/utils/markdown.ts
- export function renderMarkdown(markdown?: string | null): string;
-
- // src/utils/proposalExport.ts
- export function sanitizeFilename(name: string): string;
- export async function downloadProposalMarkdown(proposalId: number): Promise<void>;
- ```
-
- ### Risks
- - **Styling or Layout Breakage**: Moving aside rail or article container into subcomponents could disrupt CSS grid or sticky positioning.
- - *Mitigation*: Preserve exact Tailwind classes (`grid xl:grid-cols-[minmax(0,1fr)_15rem]`, `xl:sticky xl:top-6`) on the container and child components.
- - **Event / Props Wiring**: Ensure proposal ID and markdown download methods properly receive required props.
- - *Mitigation*: Strict TypeScript interfaces for all component props.
-
- # Testing
-
- ### Validation Approach
- Verify the refactored components through type checking, build verification, and functional validation against key user workflows.
-
- ### Key Scenarios
- 1. **Initial Page Load & Empty State**:
- - On navigating to `/`, proposals list loads in sidebar.
- - Main content area displays `ProposalEmptyState` ("Select a proposal").
- 2. **Proposal Selection**:
- - Clicking a proposal in the sidebar loads details and renders `ProposalDetail`.
- - Title, author badge, creator wallet, and paragraphs are displayed with correct markdown formatting.
- - `AmendmentCarousel` receives the selected proposal ID and displays amendments.
- 3. **Proposal Export / Download**:
- - Clicking the download button in `ProposalExportCard` generates and downloads the formatted `.md` file with sanitized filename.
- 4. **Voting Actions**:
- - Approve, Reject, and Abstain buttons in `ProposalVotingCard` render properly with hover and focus states.
- 5. **Mobile Responsiveness**:
- - Sidebar toggle and overlay function on small screens; sidebar closes upon selecting a proposal.
-
- ### Edge Cases
- - **Missing or Empty Proposal Paragraphs**: `ProposalDetail` handles empty paragraph list gracefully without throwing runtime errors.
- - **Unsafe HTML in Markdown**: `DOMPurify` via `markdown.ts` sanitizes all rendered HTML in both `ProposalDetail` and `ProposalContainer`.
- - **Special Characters in Proposal Title**: `sanitizeFilename` replaces forbidden filesystem characters with dashes before triggering file download.
-
- ### Test Changes
- - Execute `npm run build` (`vue-tsc -b && vite build`) to ensure 0 TypeScript or template compilation errors.
-
- # Delivery Steps
-
- ### ✓ Step 1: Extract markdown formatting and export utilities
- Create shared utilities to eliminate duplicate markdown handling and file export logic across components.
-
- - Create `src/utils/markdown.ts` exporting `renderMarkdown` / `applyFormatting` using `marked` and `DOMPurify`.
- - Create `src/utils/proposalExport.ts` to encapsulate the Markdown file generator, string sanitization (`sanitizeFilename`), and browser download trigger.
- - Refactor `src/components/ProposalContainer.vue` to use the shared markdown helper instead of local inline parsing.
-
- ### ✓ Step 2: Implement standalone proposal action and state components
- Create focused components for proposal actions and state placeholders.
-
- - Create `src/components/ProposalVotingCard.vue` containing the "Cast your vote" card with Approve, Reject, and Abstain action buttons and emit events.
- - Create `src/components/ProposalExportCard.vue` containing the Markdown download action and integration with `proposalExport.ts`.
- - Create `src/components/ProposalEmptyState.vue` displaying the empty selection message and guidance when no proposal is active.
-
- ### ✓ Step 3: Implement ProposalDetail component
- Encapsulate proposal content presentation into a dedicated detail component.
-
- - Create `src/components/ProposalDetail.vue` accepting a `proposal: ProposalWithParagraphs` prop.
- - Include the proposal header (tags, creator attribution, title), content body rendering paragraphs via `applyFormatting`, `AmendmentCarousel` integration, and comments section.
- - Compose `ProposalVotingCard` and `ProposalExportCard` into the sticky action sidebar rail.
-
- ### ✓ Step 4: Refactor HomeView and verify integration
- Streamline HomeView to act purely as the top-level page orchestrator.
-
- - Replace inline proposal markup and action rail in `src/views/HomeView.vue` with `ProposalDetail` and `ProposalEmptyState`.
- - Clean up unused imports, methods (`applyFormatting`, `downloadProposalMarkdown`, `sanitizeFilename`), and redundant styles from `HomeView.vue`.
- - Verify TypeScript compilation and runtime behavior with `vue-tsc -b` and `vite build`.
|