Completed planning phases 0 and 1 for simple landing page feature. ## Plan Overview: **Approach**: Minimal addition to existing Flask app - reuse Product model, add one route, one template. No new dependencies or complexity. **Constitution Check**: ✅ All 5 principles satisfied - Specification-first development (spec.md complete) - Test-first discipline (TDD workflow defined) - Independent user stories (3 stories, all independently testable) - Simplicity (reuses existing Flask/Jinja2/Product architecture) - Documentation as code (all artifacts in specs/002-product-list/) ## Artifacts Created: ### Phase 0: Research (research.md) - Reuses infrastructure from feature 001 (Flask, Jinja2, file storage) - Single new decision: Product.load_active() method for filtering/sorting - Performance analysis: <100ms for 100 products (well under 1s target) ### Phase 1: Design & Contracts - **data-model.md**: Documents Product model extension (load_active method) - **contracts/landing-page.yaml**: OpenAPI contract for GET / route - **quickstart.md**: Developer implementation guide with: - Step-by-step implementation checklist - Code snippets for route, template, tests - TDD workflow (write tests → verify fail → implement → pass) - Manual verification checklist ### Agent Context - Updated CLAUDE.md with feature technologies (no new tech added) ## Implementation Summary: **New Files** (to be created): - app/routes/landing.py - Landing page route handler - app/templates/landing/index.html - Product list template - tests/contract/test_landing_routes.py - Contract tests (6 scenarios) - tests/integration/test_landing_flow.py - User journey test **Modified Files**: - app/models/product.py - Add load_active() class method - app/__init__.py - Register landing blueprint ## Key Technical Decisions: 1. **Filtering**: status=='active' AND submission_url_slug exists 2. **Sorting**: Alphabetical by name (case-insensitive), then product_id 3. **Empty State**: "No products are currently accepting feedback" message 4. **XSS Prevention**: Jinja2 auto-escaping (no manual escaping needed) 5. **Performance**: File I/O sufficient (<1s for 100 products, no caching) ## Next Steps: 1. Run /speckit.tasks to generate tasks.md 2. Run /speckit.implement to execute TDD workflow 3. Verify all tests pass 4. Manual verification checklist 5. Create pull request 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
3.8 KiB
3.8 KiB
Research: Product Selection Landing Page
Branch: 002-product-list | Date: 2025-10-17
This document addresses technical decisions for the product selection landing page feature. Most infrastructure decisions were resolved in feature 001-build-an-application and are reused here.
Existing Infrastructure (from 001-build-an-application)
The following technical decisions from feature 001 are reused without modification:
- Flask 3.0+ with Jinja2: Server-side rendering, no JavaScript
- File-based storage: Product configs in
data/products/*/config.yaml - Product model: Existing
app/models/product.pywith load methods - Template patterns: Minimal HTML/CSS, Jinja2 auto-escaping for XSS prevention
- Routing: Flask route decorators, blueprint organization
- Testing: pytest + pytest-flask for contract and integration tests
Reference: See /home/markus/workspace/reklamator/specs/001-build-an-application/research.md for full details.
New Technical Decision: Product List Retrieval & Sorting
Decision: Extend Product model with load_active() class method
Rationale:
- Centralizes "active products only" filtering logic
- Enables reuse if other features need active product lists
- Encapsulates sorting algorithm in one place
- Follows existing Product model pattern (e.g.,
load_all(),load_by_id())
Implementation:
# app/models/product.py - add class method
@classmethod
def load_active(cls):
"""Load all active products, sorted alphabetically by name then product_id.
Returns:
list[Product]: Active products with valid submission_url_slug, sorted by:
1. name (case-insensitive alphabetical)
2. product_id (alphabetical) as tiebreaker
Products with missing/invalid submission_url_slug are excluded.
"""
all_products = cls.load_all()
active = [p for p in all_products
if p.status == 'active' and p.submission_url_slug]
return sorted(active, key=lambda p: (p.name.lower(), p.product_id))
Sorting Algorithm:
- Primary sort: Product name (case-insensitive) - ensures alphabetical display
- Secondary sort: Product ID - provides stable ordering when names are identical
- Uses Python's built-in
sorted()with tuple key for multi-level sorting
Performance Analysis:
- File I/O for 100 products: ~10-50ms (depends on disk speed)
- In-memory sorting: <1ms for 100 items
- Total expected latency: <100ms (well under 1-second SC-002 target)
- No caching needed for MVP (file reads are sufficiently fast)
Filtering Logic:
status == 'active': Per FR-003, only show active productssubmission_url_slug: Per FR-015, skip products with missing/invalid slugs- Combined with
andoperator: both conditions must be true
Alternatives Considered:
- Sort in route handler: Simpler but violates DRY if multiple routes need sorted product lists
- Database query with ORDER BY: Contradicts file-based architecture decision from 001
- Pre-sorted cache: Premature optimization - file reads are fast enough for 100 products
- Client-side sorting with JavaScript: Violates no-JavaScript constraint from spec
Edge Cases Handled:
- No active products → Returns empty list (handled in template)
- Missing submission_url_slug → Product excluded from list (per FR-015)
- Identical product names → Sorted by product_id as tiebreaker
- Case-insensitive sorting → "Apple" and "apple" sort together
Validation
All technical decisions align with:
- FR-002: Retrieves from file-based storage ✅
- FR-003: Filters for active status ✅
- FR-009: Sorts alphabetically with tiebreaker ✅
- FR-015: Skips invalid submission_url_slug ✅
- SC-002: <1 second load time for 100 products ✅
Next Phase: Proceed to Phase 1 (data-model.md, contracts, quickstart.md)