Add implementation plan for product selection landing page (Feature 002)

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>
This commit is contained in:
2025-10-17 14:26:47 +02:00
co-authored by Claude
parent fb418bac65
commit 0f71ba969f
7 changed files with 868 additions and 13 deletions
+276
View File
@@ -0,0 +1,276 @@
# Quickstart: Product Selection Landing Page
**Feature**: 002-product-list | **For**: Developers implementing this feature
## Overview
Add a landing page at `/` that lists all active products for feedback submission. This is a simple addition to the existing Flask app: one route, one template, and corresponding tests.
## Implementation Checklist
### 1. Extend Product Model
**File**: `app/models/product.py`
**Add this class method**:
```python
@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.
"""
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))
```
**Why**: Centralizes filtering and sorting logic. Reusable if other features need active product lists.
---
### 2. Create Landing Route
**File**: `app/routes/landing.py` (new file)
**Implementation**:
```python
from flask import Blueprint, render_template, current_app
from app.models.product import Product
landing_bp = Blueprint('landing', __name__)
@landing_bp.route('/')
def index():
"""Landing page showing all active products."""
try:
products = Product.load_active()
current_app.logger.info(
f'Landing page accessed: {len(products)} active products'
)
return render_template('landing/index.html', products=products)
except Exception as e:
current_app.logger.error(f'Error loading landing page: {e}', exc_info=True)
return render_template('landing/index.html', products=[])
```
**Register blueprint** in `app/__init__.py`:
```python
from app.routes.landing import landing_bp
app.register_blueprint(landing_bp)
```
---
### 3. Create Landing Template
**File**: `app/templates/landing/index.html` (new file)
**Template structure**:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Select a Product - Reklamator</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
h1 { color: #333; }
ul { list-style: none; padding: 0; }
li { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
a { font-size: 1.2em; color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
p { margin: 5px 0 0 0; color: #666; }
.empty-state { color: #666; padding: 20px; text-align: center; }
</style>
</head>
<body>
<h1>Select a Product for Feedback</h1>
{% if products %}
<ul>
{% for product in products %}
<li>
<a href="/submit/{{ product.submission_url_slug }}">
{{ product.name }}
</a>
{% if product.description %}
<p>{{ product.description }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="empty-state">
No products are currently accepting feedback. Please check back later.
</p>
{% endif %}
</body>
</html>
```
**Key points**:
- Jinja2 auto-escaping prevents XSS (product.name, product.description)
- No JavaScript (pure server-side rendering)
- Minimal inline CSS (no frameworks)
- Conditional rendering for empty state
- Only shows description if present (no placeholder text)
---
### 4. Write Contract Tests
**File**: `tests/contract/test_landing_routes.py` (new file)
**Test scenarios to implement**:
```python
import pytest
import os
import yaml
from app.models.product import Product
@pytest.mark.contract
def test_get_landing_page_with_products(client, temp_data_dir):
"""T301: GET / returns 200 with product list"""
# Setup: Create 2 active products
# Assert: 200 OK, both products in HTML
pass
@pytest.mark.contract
def test_get_landing_page_no_products(client, temp_data_dir):
"""T302: GET / with no active products shows empty state"""
# Assert: 200 OK, contains "No products are currently accepting feedback"
pass
@pytest.mark.contract
def test_get_landing_page_filters_archived(client, temp_data_dir):
"""T303: GET / excludes archived products"""
# Setup: 1 active, 1 archived
# Assert: Only active product shown
pass
@pytest.mark.contract
def test_get_landing_page_sorting(client, temp_data_dir):
"""T304: GET / sorts products alphabetically (name, then product_id)"""
# Setup: Products with names "Zebra", "Apple", "apple" (different product_ids)
# Assert: Correct alphabetical order
pass
@pytest.mark.contract
def test_get_landing_page_xss_prevention(client, temp_data_dir):
"""T305: GET / escapes HTML in product names"""
# Setup: Product with name "<script>alert('xss')</script>"
# Assert: HTML is escaped, script not executed
pass
@pytest.mark.contract
def test_get_landing_page_missing_slug(client, temp_data_dir):
"""T306: GET / excludes products with missing submission_url_slug"""
# Setup: Product with submission_url_slug = None
# Assert: Product not shown in list
pass
```
---
### 5. Write Integration Tests
**File**: `tests/integration/test_landing_flow.py` (new file)
**User journey test**:
```python
@pytest.mark.integration
def test_landing_to_submission_flow(client, temp_data_dir):
"""T307: Complete flow - landing page → product selection → submission form"""
# Step 1: Visit landing page, see products
# Step 2: Click product link
# Step 3: Verify redirected to /submit/{slug}
pass
```
---
## Key Implementation Notes
### XSS Prevention
- ✅ Jinja2 auto-escaping handles product names and descriptions
- ✅ No manual HTML escaping needed
- ✅ Test with `<script>` tags in product names to verify
### Empty State Handling
- ✅ Check `{% if products %}` in template
- ✅ Display message: "No products are currently accepting feedback. Please check back later."
- ✅ No blank page or error
### Logging
- ✅ Log landing page access with product count
- ✅ Log errors if product loading fails
- ✅ Use `current_app.logger.info()` for access logs
### Backwards Compatibility
- ✅ Existing `/submit/{slug}` routes unchanged
- ✅ Direct product URLs still work
- ✅ Landing page is additive only
### Performance
- ✅ Target: <1 second for up to 100 products
- ✅ File I/O ~10-50ms for 100 YAML files
- ✅ No caching needed for MVP
---
## Testing Workflow (TDD)
**Follow this order** (Constitution Principle II):
1. **Write contract tests** (test_landing_routes.py) - all should FAIL
2. **Verify tests fail** - proves they test something meaningful
3. **Implement Product.load_active()** method
4. **Implement landing route** (landing.py)
5. **Create landing template** (index.html)
6. **Run tests** - contract tests should PASS
7. **Write integration tests** (test_landing_flow.py) - should FAIL
8. **Fix any issues** - integration tests should PASS
9. **Refactor** while keeping tests green
---
## Manual Verification Checklist
After all tests pass, manually verify:
- [ ] Visit `/` - see product list or empty state
- [ ] Click product link - redirected to `/submit/{slug}`
- [ ] Check with 0 active products - see empty message
- [ ] Check with 1 active product - see single product
- [ ] Check with 10+ active products - alphabetical order
- [ ] Check product with no description - no placeholder text
- [ ] Check product with long name - proper wrapping
- [ ] Check as anonymous user - page accessible
- [ ] Check as authenticated user - same page shown
- [ ] Check page source - no JavaScript present
---
## Completion Criteria
✅ All contract tests passing
✅ All integration tests passing
✅ Product.load_active() method implemented
✅ Landing route registered and functional
✅ Landing template created with proper escaping
✅ Manual verification completed
✅ Code follows existing Flask/Jinja2 patterns
✅ No new dependencies added
✅ Documentation updated (this file)
**Next**: Commit to branch `002-product-list` and create pull request