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
+147
View File
@@ -0,0 +1,147 @@
# Data Model: Product Selection Landing Page
**Feature**: 002-product-list | **Date**: 2025-10-17
## Overview
This feature reuses the existing **Product** entity from feature 001-build-an-application with a minor extension. No new entities are created.
## Existing Entity: Product
**Source**: `app/models/product.py` (from feature 001)
**Attributes** (relevant to this feature):
| Attribute | Type | Required | Description |
|-----------|------|----------|-------------|
| `product_id` | str | Yes | Unique identifier (e.g., "001-acme-app") |
| `name` | str | Yes | Display name for the product |
| `submission_url_slug` | str | Yes | URL segment for submission form |
| `status` | str | Yes | "active" or "archived" |
| `description` | str | No | Brief product description (optional) |
**Storage**: File-based YAML at `data/products/{product-id}/config.yaml`
**Example**:
```yaml
product_id: "001-acme-app"
name: "Acme Application"
submission_url_slug: "acme-app"
status: "active"
description: "Enterprise resource planning system"
```
---
## Model Extension
### New Class Method: `load_active()`
**Purpose**: Load and return only active products with valid submission URLs, sorted for display.
**Signature**:
```python
@classmethod
def load_active(cls) -> list[Product]:
"""Load all active products, sorted alphabetically by name then product_id."""
```
**Returns**: List of Product instances where:
- `status == 'active'`
- `submission_url_slug` is present and valid
- Sorted by: `(name.lower(), product_id)`
**Implementation Location**: `app/models/product.py`
**Usage Example**:
```python
from app.models.product import Product
# In route handler
products = Product.load_active()
# Returns sorted list of active products ready for display
```
---
## Data Flow
### Landing Page Load Sequence
1. **Request**: User navigates to `/`
2. **Route Handler**: `app/routes/landing.py`
- Calls `Product.load_active()`
3. **Data Loading**: Product model
- Reads all `data/products/*/config.yaml` files
- Filters: `status == 'active'` AND `submission_url_slug` exists
- Sorts: By `(name.lower(), product_id)`
4. **Response**: Render template with product list
- Pass `products` to `templates/landing/index.html`
- Template loops over products, displays name/description/link
### Diagram
```
User → GET / → landing.py → Product.load_active() → [Product, Product, ...]
Templates (Jinja2) → HTML Response → User
```
---
## Validation Rules
### Product Visibility (for landing page)
A product is **visible** on the landing page if and only if:
1. `status == 'active'` (FR-003)
2. `submission_url_slug` is not None/empty (FR-015)
Products failing either condition are **excluded** from the list.
### Sorting Rules (FR-009)
Products are sorted by:
1. **Primary**: `name` (case-insensitive alphabetical)
2. **Secondary**: `product_id` (alphabetical, for ties)
**Examples**:
- Input: `[{"name": "Zebra", "product_id": "001"}, {"name": "Apple", "product_id": "002"}]`
- Output: `[{"name": "Apple", ...}, {"name": "Zebra", ...}]`
- Input: `[{"name": "App", "product_id": "002"}, {"name": "App", "product_id": "001"}]`
- Output: `[{"name": "App", "product_id": "001"}, {"name": "App", "product_id": "002"}]`
---
## No New Entities
This feature introduces **no new entities**. All data structures are reused from feature 001:
- Product entity (extended with `load_active()` method only)
- File-based YAML storage (unchanged)
- No database tables, no new data files
---
## Testing Considerations
### Test Data Requirements
For comprehensive testing, create products with:
- Various statuses: "active", "archived"
- With/without descriptions
- With/without valid `submission_url_slug`
- Duplicate names (to test secondary sort)
- Various name cases ("Apple", "apple", "APPLE")
### Expected Behaviors
| Scenario | Expected Result |
|----------|----------------|
| Product with `status: active` | Included in list |
| Product with `status: archived` | Excluded from list |
| Product with missing `submission_url_slug` | Excluded from list |
| Products with same name | Sorted by product_id |
| Empty product directory | Returns empty list |
**Reference Tests**: See `tests/contract/test_landing_routes.py` for data model validation tests.