Files
Reklamator/specs/002-product-list/quickstart.md
T
gurixandClaude 0f71ba969f 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>
2025-10-17 14:26:47 +02:00

8.1 KiB

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:

@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:

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:

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:

<!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:

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:

@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