The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

12 KiB

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

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

// 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.