---
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: `` and ``.
- **`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;
```
### 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`.