feat: Implement Phase 4 Web Interface with bugfixes (v0.5.2)

## Phase 4: Web Interface Implementation

Implemented complete web interface with public and admin routes,
templates, CSS, and development authentication.

### Core Features

**Public Routes**:
- Homepage with recent published notes
- Note permalinks with microformats2
- Server-side rendering (Jinja2)

**Admin Routes**:
- Login via IndieLogin
- Dashboard with note management
- Create, edit, delete notes
- Protected with @require_auth decorator

**Development Authentication**:
- Dev login bypass for local testing (DEV_MODE only)
- Security safeguards per ADR-011
- Returns 404 when disabled

**Templates & Frontend**:
- Base layouts (public + admin)
- 8 HTML templates with microformats2
- Custom responsive CSS (114 lines)
- Error pages (404, 500)

### Bugfixes (v0.5.1 → v0.5.2)

1. **Cookie collision fix (v0.5.1)**:
   - Renamed auth cookie from "session" to "starpunk_session"
   - Fixed redirect loop between dev login and admin dashboard
   - Flask's session cookie no longer conflicts with auth

2. **HTTP 404 error handling (v0.5.1)**:
   - Update route now returns 404 for nonexistent notes
   - Delete route now returns 404 for nonexistent notes
   - Follows ADR-012 HTTP Error Handling Policy
   - Pattern consistency across all admin routes

3. **Note model enhancement (v0.5.2)**:
   - Exposed deleted_at field from database schema
   - Enables soft deletion verification in tests
   - Follows ADR-013 transparency principle

### Architecture

**New ADRs**:
- ADR-011: Development Authentication Mechanism
- ADR-012: HTTP Error Handling Policy
- ADR-013: Expose deleted_at Field in Note Model

**Standards Compliance**:
- Uses uv for Python environment
- Black formatted, Flake8 clean
- Follows git branching strategy
- Version incremented per versioning strategy

### Test Results

- 405/406 tests passing (99.75%)
- 87% code coverage
- All security tests passing
- Manual testing confirmed working

### Documentation

- Complete implementation reports in docs/reports/
- Architecture reviews in docs/reviews/
- Design documents in docs/design/
- CHANGELOG updated for v0.5.2

### Files Changed

**New Modules**:
- starpunk/dev_auth.py
- starpunk/routes/ (public, admin, auth, dev_auth)

**Templates**: 10 files (base, pages, admin, errors)
**Static**: CSS and optional JavaScript
**Tests**: 4 test files for routes and templates
**Docs**: 20+ architectural and implementation documents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 23:01:53 -07:00
parent 575a02186b
commit 0cca8169ce
56 changed files with 13151 additions and 304 deletions

View File

@@ -0,0 +1,299 @@
# ADR-012: HTTP Error Handling Policy
## Status
Accepted
## Context
During Phase 4 (Web Interface) implementation, a test failure revealed inconsistent error handling between GET and POST routes when accessing nonexistent resources:
- `GET /admin/edit/99999` returns HTTP 404 (correct)
- `POST /admin/edit/99999` returns HTTP 302 redirect (incorrect)
This inconsistency creates several problems:
1. **Semantic confusion**: HTTP 404 means "Not Found", but we were redirecting instead
2. **API incompatibility**: Future Micropub API implementation requires proper HTTP status codes
3. **Debugging difficulty**: Hard to distinguish between "note doesn't exist" and "operation failed"
4. **Test suite inconsistency**: Tests expect 404, implementation returns 302
### Traditional Web App Pattern
Many traditional web applications use:
- **404 for GET**: Can't render a form for nonexistent resource
- **302 redirect for POST**: Show user-friendly error message via flash
This provides good UX but sacrifices HTTP semantic correctness.
### REST/API Pattern
REST APIs consistently use:
- **404 for all operations** on nonexistent resources
- Applies to GET, POST, PUT, DELETE, etc.
This provides semantic correctness and API compatibility.
### StarPunk's Requirements
1. Human-facing web interface (Phase 4)
2. Future Micropub API endpoint (Phase 5)
3. Single-user system (simpler error handling needs)
4. Standards compliance (IndieWeb specs require proper HTTP)
## Decision
**StarPunk will use REST-style error handling for all routes**, returning HTTP 404 for any operation on a nonexistent resource, regardless of HTTP method.
### Specific Rules
1. **All routes MUST return 404** when the target resource does not exist
2. **All routes SHOULD check resource existence** before processing the request
3. **404 responses MAY include user-friendly flash messages** for web routes
4. **404 responses MAY redirect** to a safe location (e.g., dashboard) while still returning 404 status
### Implementation Pattern
```python
@bp.route("/operation/<int:resource_id>", methods=["GET", "POST"])
@require_auth
def operation(resource_id: int):
# 1. CHECK EXISTENCE FIRST
resource = get_resource(id=resource_id)
if not resource:
flash("Resource not found", "error")
return redirect(url_for("admin.dashboard")), 404 # ← MUST return 404
# 2. VALIDATE INPUT (for POST/PUT)
# ...
# 3. PERFORM OPERATION
# ...
# 4. RETURN SUCCESS
# ...
```
### Status Code Policy
| Scenario | Status Code | Response Type | Flash Message |
|----------|-------------|---------------|---------------|
| Resource not found | 404 | Redirect to dashboard | "Resource not found" |
| Validation failed | 302 | Redirect to form | "Invalid data: {details}" |
| Operation succeeded | 302 | Redirect to dashboard | "Success: {details}" |
| System error | 500 | Error page | "System error occurred" |
| Unauthorized | 302 | Redirect to login | "Authentication required" |
### Flask Pattern for 404 with Redirect
Flask allows returning a tuple `(response, status_code)`:
```python
return redirect(url_for("admin.dashboard")), 404
```
This sends:
- HTTP 404 status code
- Location header pointing to dashboard
- Flash message in session
The client receives 404 but can follow the redirect to see the error message.
## Rationale
### Why REST-Style Over Web-Form-Style?
1. **Future API Compatibility**: Micropub (Phase 5) requires proper HTTP semantics
2. **Standards Compliance**: IndieWeb specs expect REST-like behavior
3. **Semantic Correctness**: 404 means "not found" - this is universally understood
4. **Consistency**: Simpler mental model - all operations follow same rules
5. **Debugging**: Clear distinction between error types
6. **Test Intent**: Test suite already expects this behavior
### UX Considerations
**Concern**: Won't users see ugly error pages?
**Mitigation**:
1. Flash messages provide context ("Note not found")
2. 404 response includes redirect to dashboard
3. Can implement custom 404 error handler with navigation
4. Single-user system = developer is the user (understands HTTP)
### Comparison to Delete Operation
The `delete_note()` function is idempotent - it succeeds even if the note doesn't exist. This is correct for delete operations (common REST pattern). However, the route should still check existence and return 404 for consistency:
- Idempotent implementation: Good (delete succeeds either way)
- Explicit existence check in route: Better (clear 404 for user)
## Consequences
### Positive
1. **Consistent behavior** across all routes (GET, POST, DELETE)
2. **API-ready**: Micropub implementation will work correctly
3. **Standards compliance**: Meets IndieWeb/REST expectations
4. **Better testing**: Status codes clearly indicate error types
5. **Clearer debugging**: Know immediately if resource doesn't exist
6. **Simpler code**: One pattern to follow everywhere
### Negative
1. **Requires existence checks**: Every route must check before operating
2. **Slight performance cost**: Extra database query per request (minimal for SQLite)
3. **Different from some web apps**: Traditional web apps often use redirects for all POST errors
### Neutral
1. **Custom 404 handler needed**: For good UX (but we'd want this anyway)
2. **Test suite updates**: Some tests may need adjustment (but most already expect 404)
3. **Documentation**: Need to document this pattern (but good practice anyway)
## Implementation Checklist
### Immediate (Phase 4 Fix)
- [ ] Fix `POST /admin/edit/<id>` to return 404 for nonexistent notes
- [ ] Verify `GET /admin/edit/<id>` still returns 404 (already correct)
- [ ] Update `POST /admin/delete/<id>` to return 404 (optional, but recommended)
- [ ] Update test `test_delete_nonexistent_note_shows_error` if delete route changed
### Future (Phase 4 Completion)
- [ ] Create custom 404 error handler with navigation
- [ ] Document pattern in `/home/phil/Projects/starpunk/docs/standards/http-error-handling.md`
- [ ] Review all routes for consistency
- [ ] Add error handling section to coding standards
### Phase 5 (Micropub API)
- [ ] Verify Micropub routes follow this pattern
- [ ] Return JSON error responses for API routes
- [ ] Maintain 404 status codes for missing resources
## Examples
### Good Example: Edit Note Form (GET)
```python
@bp.route("/edit/<int:note_id>", methods=["GET"])
@require_auth
def edit_note_form(note_id: int):
note = get_note(id=note_id)
if not note:
flash("Note not found", "error")
return redirect(url_for("admin.dashboard")), 404 # ✓ CORRECT
return render_template("admin/edit.html", note=note)
```
**Status**: Currently implemented correctly
### Bad Example: Update Note (POST) - Before Fix
```python
@bp.route("/edit/<int:note_id>", methods=["POST"])
@require_auth
def update_note_submit(note_id: int):
# ✗ NO EXISTENCE CHECK
try:
note = update_note(id=note_id, content=content, published=published)
# ...
except Exception as e:
flash(f"Error: {e}", "error")
return redirect(url_for("admin.edit_note_form", note_id=note_id)) # ✗ Returns 302
```
**Problem**: Returns 302 redirect, not 404
### Good Example: Update Note (POST) - After Fix
```python
@bp.route("/edit/<int:note_id>", methods=["POST"])
@require_auth
def update_note_submit(note_id: int):
# ✓ CHECK EXISTENCE FIRST
existing_note = get_note(id=note_id, load_content=False)
if not existing_note:
flash("Note not found", "error")
return redirect(url_for("admin.dashboard")), 404 # ✓ CORRECT
# Process the update
# ...
```
**Status**: Needs implementation
## References
- Test failure: `test_update_nonexistent_note_404` in `tests/test_routes_admin.py:386`
- Architectural review: `/home/phil/Projects/starpunk/docs/reviews/error-handling-rest-vs-web-patterns.md`
- RFC 7231 Section 6.5.4 (404 Not Found): https://tools.ietf.org/html/rfc7231#section-6.5.4
- IndieWeb Micropub spec: https://micropub.spec.indieweb.org/
- Flask documentation on status codes: https://flask.palletsprojects.com/en/latest/quickstart/#about-responses
## Alternatives Considered
### Alternative 1: Web-Form Pattern (Redirect All POST Errors)
**Rejected** because:
- Breaks semantic HTTP (404 means "not found")
- Incompatible with future Micropub API
- Makes debugging harder (can't distinguish error types by status code)
- Test suite already expects 404
### Alternative 2: Hybrid Approach (404 for API, 302 for Web)
**Rejected** because:
- Adds complexity (need to detect context)
- Inconsistent behavior confuses developers
- Same route may serve both web and API clients
- Flask blueprint structure makes this awkward
### Alternative 3: Exception-Based (Let Exceptions Propagate to Error Handler)
**Rejected** because:
- Less explicit (harder to understand flow)
- Error handlers are global (less flexibility per route)
- Flash messages harder to customize per route
- Lose ability to redirect to different locations per route
## Notes
### Performance Consideration
The existence check adds one database query per request:
```python
existing_note = get_note(id=note_id, load_content=False) # SELECT query
```
With `load_content=False`, this is just a metadata query (no file I/O):
- SQLite query: ~0.1ms for indexed lookup
- Negligible overhead for single-user system
- Could be optimized later if needed (caching, etc.)
### Future Enhancement: Error Handler
Custom 404 error handler can improve UX:
```python
@app.errorhandler(404)
def not_found(error):
"""Custom 404 page with navigation"""
# Check if there's a flash message (from routes)
# Render custom template with link to dashboard
# Or redirect to dashboard for admin routes
return render_template('errors/404.html'), 404
```
This is optional but recommended for Phase 4 completion.
## Revision History
- 2025-11-18: Initial decision (v0.4.0 development)
- Status: Accepted
- Supersedes: None
- Related: ADR-003 (Frontend Technology), Phase 4 Design