The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

componentize-home-view.md 12 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. ---
  2. sessionId: session-261002-153750-168j
  3. ---
  4. # Requirements
  5. ### Overview & Goals
  6. `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.
  7. 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.
  8. ### Scope
  9. #### In Scope
  10. - Extracting standalone proposal components:
  11. - `ProposalDetail.vue`: Primary article layout combining proposal header, formatted paragraphs, amendments section, comments section, and the action rail.
  12. - `ProposalVotingCard.vue`: Voting options card (Approve, Reject, Abstain).
  13. - `ProposalExportCard.vue`: Markdown export card with download trigger.
  14. - `ProposalEmptyState.vue`: Informational empty state when no proposal is selected.
  15. - Extracting reusable utilities:
  16. - `src/utils/markdown.ts`: Centralized markdown parsing and DOM sanitization.
  17. - `src/utils/proposalExport.ts`: Proposal Markdown file generator and file download trigger.
  18. - Refactoring `src/views/HomeView.vue` to orchestrate layout, data fetching, and loading states cleanly.
  19. - Updating `ProposalContainer.vue` to use the shared markdown utility.
  20. #### Out of Scope
  21. - Backend API schema changes or new endpoints.
  22. - Redesigning the visual styles or changing Tailwind classes and responsive behavior.
  23. - Modifying `Sidebar.vue` or modal dialogs beyond their existing integration.
  24. ### User Stories
  25. - **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.
  26. - **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.
  27. - **As a user**, I want proposal display, voting, and file downloading to function seamlessly with identical UI and responsive interactions.
  28. ### Functional Requirements
  29. - **FR-1**: When no proposal is selected, the application displays `ProposalEmptyState` with the title "Select a proposal" and helper text.
  30. - **FR-2**: When a proposal is selected, `ProposalDetail` renders:
  31. - Proposal header with tags, creator wallet address, and proposal name.
  32. - Proposal paragraphs formatted using sanitized Markdown.
  33. - `AmendmentCarousel` with the current proposal ID.
  34. - Comments section placeholder.
  35. - Right-hand action rail containing `ProposalVotingCard` and `ProposalExportCard`.
  36. - **FR-3**: Clicking the download button in `ProposalExportCard` triggers the Markdown export and downloads a sanitized `.md` file containing proposal title, author, and paragraphs.
  37. - **FR-4**: Voting actions in `ProposalVotingCard` maintain their existing UI styling and emit selection events.
  38. - **FR-5**: Sidebar visibility toggles and loading spinners remain fully operational across desktop and mobile viewports.
  39. ### Non-Functional Requirements
  40. - **Maintainability**: Clear separation of presentation, business logic, and file utilities.
  41. - **Type Safety**: Full TypeScript typing for props, emits, and utility functions conforming to generated API models.
  42. - **Performance**: Zero overhead from component decomposition; no extra re-renders or layout shifts.
  43. # Technical Design
  44. ### Current Implementation
  45. `HomeView.vue` (245 lines) contains:
  46. - Primary container with responsive sidebar integration and mobile backdrop.
  47. - Proposal article layout with header, paragraph loops using inline `DOMPurify` / `marked` formatting, `AmendmentCarousel`, and comments container.
  48. - Aside rail containing voting buttons and the markdown download card.
  49. - Empty selection state.
  50. - Script containing proposal data fetching, markdown sanitization, file download generation, loading states, and sidebar toggling.
  51. - `ProposalContainer.vue` also duplicates markdown parsing logic (`marked` + `DOMPurify`).
  52. ### Key Decisions
  53. 1. **Component Hierarchy**:
  54. - `HomeView.vue` remains the top-level route coordinator: handles sidebar toggle state, fetching proposals list, and active proposal selection.
  55. - `ProposalDetail.vue` acts as the container for proposal inspection: receives `proposal: ProposalWithParagraphs` as a prop and embeds `AmendmentCarousel`, `ProposalVotingCard`, and `ProposalExportCard`.
  56. - `ProposalVotingCard.vue` and `ProposalExportCard.vue` are extracted as dedicated components to keep the voting workflow and export logic decoupled from reading layout.
  57. - `ProposalEmptyState.vue` handles the "no proposal selected" display.
  58. 2. **Utility Extraction (`src/utils/markdown.ts` & `src/utils/proposalExport.ts`)**:
  59. - Extract `applyFormatting` / `renderMarkdown` into `src/utils/markdown.ts` to prevent code duplication between `HomeView.vue` and `ProposalContainer.vue`.
  60. - Extract `downloadProposalMarkdown` and `sanitizeFilename` into `src/utils/proposalExport.ts` so file generation logic is pure, reusable, and easily testable.
  61. ### Components
  62. - **`src/views/HomeView.vue` (Modified)**:
  63. - Retains: `Sidebar`, mobile overlay, loading spinner, selection state, and window resize listeners.
  64. - Uses: `<ProposalDetail :proposal="selected" />` and `<ProposalEmptyState />`.
  65. - **`src/components/ProposalDetail.vue` (New)**:
  66. - Props: `proposal: ProposalWithParagraphs`.
  67. - Template: Proposal header, sanitized paragraph list, `AmendmentCarousel`, comments block, and sidebar rail embedding `ProposalVotingCard` and `ProposalExportCard`.
  68. - **`src/components/ProposalVotingCard.vue` (New)**:
  69. - Props: `proposalId: number`.
  70. - Emits: `vote: [type: 'Positive' | 'Negative' | 'Abstain']`.
  71. - Template: "Cast your vote" card with Approve, Reject, Abstain buttons.
  72. - **`src/components/ProposalExportCard.vue` (New)**:
  73. - Props: `proposalId: number`, `proposalName: string`.
  74. - Template: Proposal record card with `.md` badge and download action button.
  75. - **`src/components/ProposalEmptyState.vue` (New)**:
  76. - Template: Centered container with "Select a proposal" header and instructions.
  77. - **`src/components/ProposalContainer.vue` (Modified)**:
  78. - Refactored to import `renderMarkdown` from `src/utils/markdown.ts`.
  79. ### File Structure
  80. ```
  81. src/
  82. ├── components/
  83. │ ├── AmendmentCarousel.vue (existing)
  84. │ ├── LoadingSpinner.vue (existing)
  85. │ ├── ProposalContainer.vue (modified - use markdown util)
  86. │ ├── ProposalDetail.vue (new)
  87. │ ├── ProposalEmptyState.vue (new)
  88. │ ├── ProposalExportCard.vue (new)
  89. │ ├── ProposalVotingCard.vue (new)
  90. │ └── Sidebar.vue (existing)
  91. ├── utils/
  92. │ ├── markdown.ts (new)
  93. │ ├── proposalExport.ts (new)
  94. │ └── wallet.ts (existing)
  95. └── views/
  96. └── HomeView.vue (modified - simplified coordinator)
  97. ```
  98. ### Architecture Diagram
  99. ```mermaid
  100. graph TD
  101. HomeView["HomeView.vue (Coordinator)"]
  102. Sidebar["Sidebar.vue"]
  103. ProposalEmptyState["ProposalEmptyState.vue"]
  104. ProposalDetail["ProposalDetail.vue"]
  105. AmendmentCarousel["AmendmentCarousel.vue"]
  106. ProposalVotingCard["ProposalVotingCard.vue"]
  107. ProposalExportCard["ProposalExportCard.vue"]
  108. MarkdownUtil["utils/markdown.ts"]
  109. ExportUtil["utils/proposalExport.ts"]
  110. HomeView --> Sidebar
  111. HomeView --> ProposalEmptyState
  112. HomeView --> ProposalDetail
  113. ProposalDetail --> AmendmentCarousel
  114. ProposalDetail --> ProposalVotingCard
  115. ProposalDetail --> ProposalExportCard
  116. ProposalDetail --> MarkdownUtil
  117. ProposalExportCard --> ExportUtil
  118. ```
  119. ### Data Models / Contracts
  120. ```typescript
  121. // src/utils/markdown.ts
  122. export function renderMarkdown(markdown?: string | null): string;
  123. // src/utils/proposalExport.ts
  124. export function sanitizeFilename(name: string): string;
  125. export async function downloadProposalMarkdown(proposalId: number): Promise<void>;
  126. ```
  127. ### Risks
  128. - **Styling or Layout Breakage**: Moving aside rail or article container into subcomponents could disrupt CSS grid or sticky positioning.
  129. - *Mitigation*: Preserve exact Tailwind classes (`grid xl:grid-cols-[minmax(0,1fr)_15rem]`, `xl:sticky xl:top-6`) on the container and child components.
  130. - **Event / Props Wiring**: Ensure proposal ID and markdown download methods properly receive required props.
  131. - *Mitigation*: Strict TypeScript interfaces for all component props.
  132. # Testing
  133. ### Validation Approach
  134. Verify the refactored components through type checking, build verification, and functional validation against key user workflows.
  135. ### Key Scenarios
  136. 1. **Initial Page Load & Empty State**:
  137. - On navigating to `/`, proposals list loads in sidebar.
  138. - Main content area displays `ProposalEmptyState` ("Select a proposal").
  139. 2. **Proposal Selection**:
  140. - Clicking a proposal in the sidebar loads details and renders `ProposalDetail`.
  141. - Title, author badge, creator wallet, and paragraphs are displayed with correct markdown formatting.
  142. - `AmendmentCarousel` receives the selected proposal ID and displays amendments.
  143. 3. **Proposal Export / Download**:
  144. - Clicking the download button in `ProposalExportCard` generates and downloads the formatted `.md` file with sanitized filename.
  145. 4. **Voting Actions**:
  146. - Approve, Reject, and Abstain buttons in `ProposalVotingCard` render properly with hover and focus states.
  147. 5. **Mobile Responsiveness**:
  148. - Sidebar toggle and overlay function on small screens; sidebar closes upon selecting a proposal.
  149. ### Edge Cases
  150. - **Missing or Empty Proposal Paragraphs**: `ProposalDetail` handles empty paragraph list gracefully without throwing runtime errors.
  151. - **Unsafe HTML in Markdown**: `DOMPurify` via `markdown.ts` sanitizes all rendered HTML in both `ProposalDetail` and `ProposalContainer`.
  152. - **Special Characters in Proposal Title**: `sanitizeFilename` replaces forbidden filesystem characters with dashes before triggering file download.
  153. ### Test Changes
  154. - Execute `npm run build` (`vue-tsc -b && vite build`) to ensure 0 TypeScript or template compilation errors.
  155. # Delivery Steps
  156. ### ✓ Step 1: Extract markdown formatting and export utilities
  157. Create shared utilities to eliminate duplicate markdown handling and file export logic across components.
  158. - Create `src/utils/markdown.ts` exporting `renderMarkdown` / `applyFormatting` using `marked` and `DOMPurify`.
  159. - Create `src/utils/proposalExport.ts` to encapsulate the Markdown file generator, string sanitization (`sanitizeFilename`), and browser download trigger.
  160. - Refactor `src/components/ProposalContainer.vue` to use the shared markdown helper instead of local inline parsing.
  161. ### ✓ Step 2: Implement standalone proposal action and state components
  162. Create focused components for proposal actions and state placeholders.
  163. - Create `src/components/ProposalVotingCard.vue` containing the "Cast your vote" card with Approve, Reject, and Abstain action buttons and emit events.
  164. - Create `src/components/ProposalExportCard.vue` containing the Markdown download action and integration with `proposalExport.ts`.
  165. - Create `src/components/ProposalEmptyState.vue` displaying the empty selection message and guidance when no proposal is active.
  166. ### ✓ Step 3: Implement ProposalDetail component
  167. Encapsulate proposal content presentation into a dedicated detail component.
  168. - Create `src/components/ProposalDetail.vue` accepting a `proposal: ProposalWithParagraphs` prop.
  169. - Include the proposal header (tags, creator attribution, title), content body rendering paragraphs via `applyFormatting`, `AmendmentCarousel` integration, and comments section.
  170. - Compose `ProposalVotingCard` and `ProposalExportCard` into the sticky action sidebar rail.
  171. ### ✓ Step 4: Refactor HomeView and verify integration
  172. Streamline HomeView to act purely as the top-level page orchestrator.
  173. - Replace inline proposal markup and action rail in `src/views/HomeView.vue` with `ProposalDetail` and `ProposalEmptyState`.
  174. - Clean up unused imports, methods (`applyFormatting`, `downloadProposalMarkdown`, `sanitizeFilename`), and redundant styles from `HomeView.vue`.
  175. - Verify TypeScript compilation and runtime behavior with `vue-tsc -b` and `vite build`.