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:
564
docs/design/phase-4-quick-reference.md
Normal file
564
docs/design/phase-4-quick-reference.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# Phase 4: Quick Reference
|
||||
|
||||
**Phase**: Web Interface
|
||||
**Version**: 0.5.0
|
||||
**Status**: Design Complete
|
||||
**Dependencies**: Phase 3 (Authentication) ✓
|
||||
|
||||
## Critical Decision: Development Authentication
|
||||
|
||||
**Question**: Should we implement a dev auth mechanism for local testing?
|
||||
|
||||
**Answer**: ✓ **YES** - Implement with strict safeguards
|
||||
|
||||
**Why**: Enable local testing without deploying to IndieLogin.com
|
||||
|
||||
**How**: Separate `/dev/login` route that only works when `DEV_MODE=true`
|
||||
|
||||
**Safety**: Returns 404 when disabled, visual warnings, config validation
|
||||
|
||||
**Details**: See ADR-011
|
||||
|
||||
---
|
||||
|
||||
## What Phase 4 Delivers
|
||||
|
||||
### Public Interface
|
||||
- Homepage with recent notes (`/`)
|
||||
- Note permalinks (`/note/<slug>`)
|
||||
- Microformats2 markup (h-feed, h-entry)
|
||||
|
||||
### Admin Interface
|
||||
- Login via IndieLogin (`/admin/login`)
|
||||
- Dashboard with note list (`/admin`)
|
||||
- Create notes (`/admin/new`)
|
||||
- Edit notes (`/admin/edit/<id>`)
|
||||
- Delete notes (`/admin/delete/<id>`)
|
||||
|
||||
### Development Tools
|
||||
- Dev auth for local testing (`/dev/login`)
|
||||
- Configuration validation
|
||||
- Dev mode warnings
|
||||
|
||||
---
|
||||
|
||||
## Routes Summary
|
||||
|
||||
### Public (No Auth)
|
||||
```
|
||||
GET / Homepage (note list)
|
||||
GET /note/<slug> Note permalink
|
||||
```
|
||||
|
||||
### Auth Flow
|
||||
```
|
||||
GET /admin/login Login form
|
||||
POST /admin/login Start IndieLogin flow
|
||||
GET /auth/callback IndieLogin callback
|
||||
POST /admin/logout Logout
|
||||
```
|
||||
|
||||
### Admin (Auth Required)
|
||||
```
|
||||
GET /admin Dashboard
|
||||
GET /admin/new Create note form
|
||||
POST /admin/new Save new note
|
||||
GET /admin/edit/<id> Edit note form
|
||||
POST /admin/edit/<id> Update note
|
||||
POST /admin/delete/<id> Delete note
|
||||
```
|
||||
|
||||
### Dev (DEV_MODE Only)
|
||||
```
|
||||
GET /dev/login Instant login (bypasses IndieLogin)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
### New Files (~2,770 lines total)
|
||||
|
||||
```
|
||||
starpunk/routes/ # Route handlers
|
||||
├── public.py # Public routes
|
||||
├── admin.py # Admin routes
|
||||
├── auth.py # Auth routes
|
||||
└── dev_auth.py # Dev routes
|
||||
|
||||
starpunk/dev_auth.py # Dev auth module
|
||||
|
||||
templates/ # Jinja2 templates
|
||||
├── base.html
|
||||
├── index.html
|
||||
├── note.html
|
||||
└── admin/
|
||||
├── base.html
|
||||
├── login.html
|
||||
├── dashboard.html
|
||||
├── new.html
|
||||
└── edit.html
|
||||
|
||||
static/css/style.css # ~350 lines
|
||||
static/js/preview.js # Optional markdown preview
|
||||
|
||||
tests/
|
||||
├── test_routes_public.py
|
||||
├── test_routes_admin.py
|
||||
└── test_dev_auth.py
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
|
||||
```
|
||||
starpunk/config.py # Add DEV_MODE, DEV_ADMIN_ME, VERSION
|
||||
app.py # Register routes, validate config
|
||||
CHANGELOG.md # Add v0.5.0 entry
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### New Environment Variables
|
||||
|
||||
```bash
|
||||
# Development Mode (default: false)
|
||||
DEV_MODE=false # Set to 'true' for local dev
|
||||
DEV_ADMIN_ME= # Your identity URL for dev mode
|
||||
|
||||
# Version (for display)
|
||||
VERSION=0.5.0
|
||||
```
|
||||
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
# For local development
|
||||
DEV_MODE=true
|
||||
DEV_ADMIN_ME=https://yoursite.com
|
||||
|
||||
# For production (or leave unset)
|
||||
DEV_MODE=false
|
||||
ADMIN_ME=https://yoursite.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Measures
|
||||
|
||||
### Dev Auth Safeguards
|
||||
|
||||
1. **Explicit Configuration**: Requires `DEV_MODE=true`
|
||||
2. **Separate Routes**: `/dev/login` (not `/admin/login`)
|
||||
3. **Route Protection**: Returns 404 if DEV_MODE=false
|
||||
4. **Config Validation**: Prevents DEV_MODE + production URL
|
||||
5. **Visual Warnings**: Red banner when dev mode active
|
||||
6. **Logging**: All dev auth logged with warnings
|
||||
|
||||
### Production Security
|
||||
|
||||
- All admin routes use `@require_auth`
|
||||
- HttpOnly, Secure, SameSite cookies
|
||||
- CSRF state tokens
|
||||
- Session expiry (30 days)
|
||||
- Jinja2 auto-escaping (XSS prevention)
|
||||
|
||||
---
|
||||
|
||||
## Template Architecture
|
||||
|
||||
### Microformats
|
||||
|
||||
**Homepage** (h-feed):
|
||||
```html
|
||||
<div class="h-feed">
|
||||
<article class="h-entry">
|
||||
<div class="e-content">...</div>
|
||||
<time class="dt-published">...</time>
|
||||
<a class="u-url" href="...">permalink</a>
|
||||
</article>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Note Page** (h-entry):
|
||||
```html
|
||||
<article class="h-entry">
|
||||
<div class="e-content">{{ note.html|safe }}</div>
|
||||
<a class="u-url" href="{{ url_for('public.note', slug=note.slug) }}">
|
||||
<time class="dt-published" datetime="{{ note.created_at.isoformat() }}">
|
||||
{{ note.created_at.strftime('%B %d, %Y') }}
|
||||
</time>
|
||||
</a>
|
||||
</article>
|
||||
```
|
||||
|
||||
### Flash Messages
|
||||
|
||||
```python
|
||||
# In routes
|
||||
flash('Note created successfully', 'success')
|
||||
flash('Error: Note not found', 'error')
|
||||
|
||||
# In templates
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Architecture
|
||||
|
||||
### Variables
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-text: #333;
|
||||
--color-bg: #fff;
|
||||
--color-link: #0066cc;
|
||||
--color-success: #28a745;
|
||||
--color-error: #dc3545;
|
||||
--color-warning: #ffc107;
|
||||
|
||||
/* Typography */
|
||||
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'SF Mono', Monaco, monospace;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 2rem;
|
||||
|
||||
/* Layout */
|
||||
--max-width: 42rem;
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile-First
|
||||
|
||||
```css
|
||||
/* Base: Mobile */
|
||||
body { padding: 1rem; }
|
||||
|
||||
/* Tablet and up */
|
||||
@media (min-width: 768px) {
|
||||
body { padding: 2rem; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Coverage Target: >90%
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Public routes (homepage, note permalink)
|
||||
- Admin routes (dashboard, create, edit, delete)
|
||||
- Dev auth (login, validation, route protection)
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Full auth flow (mocked IndieLogin)
|
||||
- Create note end-to-end
|
||||
- Edit note end-to-end
|
||||
- Delete note end-to-end
|
||||
|
||||
### Manual Tests
|
||||
|
||||
- Browser testing (Chrome, Firefox, Safari)
|
||||
- Mobile responsive
|
||||
- Microformats validation (indiewebify.me)
|
||||
- HTML5 validation (W3C)
|
||||
- Real IndieLogin authentication
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 4.1: Routes (8 hours)
|
||||
- [ ] Create routes package
|
||||
- [ ] Implement public routes
|
||||
- [ ] Implement auth routes
|
||||
- [ ] Implement admin routes
|
||||
|
||||
### Phase 4.2: Templates (6 hours)
|
||||
- [ ] Base templates
|
||||
- [ ] Public templates
|
||||
- [ ] Admin templates
|
||||
|
||||
### Phase 4.3: Dev Auth (4 hours)
|
||||
- [ ] dev_auth.py module
|
||||
- [ ] Config validation
|
||||
- [ ] Visual warnings
|
||||
|
||||
### Phase 4.4: CSS (4 hours)
|
||||
- [ ] style.css
|
||||
- [ ] Responsive design
|
||||
|
||||
### Phase 4.5: JS (Optional, 2 hours)
|
||||
- [ ] preview.js
|
||||
- [ ] Progressive enhancement
|
||||
|
||||
### Phase 4.6: Testing (8 hours)
|
||||
- [ ] Route tests
|
||||
- [ ] Integration tests
|
||||
- [ ] >90% coverage
|
||||
|
||||
### Phase 4.7: Documentation (2 hours)
|
||||
- [ ] Update CHANGELOG
|
||||
- [ ] Document routes
|
||||
- [ ] Version to 0.5.0
|
||||
|
||||
**Total: ~34 hours**
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Must Pass
|
||||
|
||||
- [ ] All routes work correctly
|
||||
- [ ] Authentication enforced on admin routes
|
||||
- [ ] Dev auth blocked when DEV_MODE=false
|
||||
- [ ] Templates render with microformats
|
||||
- [ ] Flash messages work
|
||||
- [ ] Test coverage >90%
|
||||
- [ ] No security vulnerabilities
|
||||
- [ ] Dev mode warnings display
|
||||
- [ ] Mobile responsive
|
||||
|
||||
---
|
||||
|
||||
## Performance Targets
|
||||
|
||||
- Homepage: < 200ms
|
||||
- Note page: < 200ms
|
||||
- Admin pages: < 200ms
|
||||
- Form submit: < 100ms
|
||||
|
||||
---
|
||||
|
||||
## Key Integrations
|
||||
|
||||
### With Existing Modules
|
||||
|
||||
**auth.py** (Phase 3):
|
||||
```python
|
||||
from starpunk.auth import require_auth, verify_session, destroy_session
|
||||
|
||||
@require_auth
|
||||
def dashboard():
|
||||
# User info in g.user_me
|
||||
pass
|
||||
```
|
||||
|
||||
**notes.py** (Phase 2):
|
||||
```python
|
||||
from starpunk.notes import (
|
||||
get_all_notes,
|
||||
get_note_by_slug,
|
||||
create_note,
|
||||
update_note,
|
||||
delete_note
|
||||
)
|
||||
```
|
||||
|
||||
**database.py** (Phase 1):
|
||||
```python
|
||||
from starpunk.database import get_db
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Dev Auth Accidentally Enabled
|
||||
|
||||
**Risk**: Critical
|
||||
**Mitigation**:
|
||||
- Config validation
|
||||
- Startup warnings
|
||||
- Visual indicators
|
||||
- Deployment checklist
|
||||
- Documentation
|
||||
|
||||
### XSS Vulnerabilities
|
||||
|
||||
**Risk**: High
|
||||
**Mitigation**:
|
||||
- Jinja2 auto-escaping
|
||||
- No user HTML
|
||||
- Code review
|
||||
- Security testing
|
||||
|
||||
### Session Theft
|
||||
|
||||
**Risk**: Medium
|
||||
**Mitigation**:
|
||||
- HttpOnly cookies
|
||||
- Secure flag (production)
|
||||
- SameSite=Lax
|
||||
- HTTPS required
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Protected Route
|
||||
|
||||
```python
|
||||
from starpunk.auth import require_auth
|
||||
|
||||
@app.route('/admin/dashboard')
|
||||
@require_auth
|
||||
def dashboard():
|
||||
# g.user_me is set by require_auth
|
||||
notes = get_all_notes()
|
||||
return render_template('admin/dashboard.html', notes=notes)
|
||||
```
|
||||
|
||||
### Creating a Note
|
||||
|
||||
```python
|
||||
@app.route('/admin/new', methods=['POST'])
|
||||
@require_auth
|
||||
def create_note_submit():
|
||||
content = request.form.get('content')
|
||||
published = 'published' in request.form
|
||||
|
||||
try:
|
||||
note = create_note(content, published)
|
||||
flash(f'Note created: {note.slug}', 'success')
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
except ValueError as e:
|
||||
flash(f'Error: {e}', 'error')
|
||||
return redirect(url_for('admin.new_note_form'))
|
||||
```
|
||||
|
||||
### Dev Mode Check
|
||||
|
||||
```python
|
||||
# In dev_auth.py
|
||||
def dev_login():
|
||||
if not current_app.config.get('DEV_MODE'):
|
||||
abort(404) # Route doesn't exist
|
||||
|
||||
me = current_app.config.get('DEV_ADMIN_ME')
|
||||
session_token = create_session(me)
|
||||
|
||||
current_app.logger.warning(
|
||||
f"DEV MODE: Session created for {me} without authentication"
|
||||
)
|
||||
|
||||
# Set cookie and redirect
|
||||
response = redirect(url_for('admin.dashboard'))
|
||||
response.set_cookie('session', session_token, httponly=True)
|
||||
return response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Dev Auth Not Working
|
||||
|
||||
1. Check `DEV_MODE=true` in `.env`
|
||||
2. Check `DEV_ADMIN_ME` is set
|
||||
3. Restart Flask server
|
||||
4. Check logs for warnings
|
||||
|
||||
### Templates Not Found
|
||||
|
||||
1. Check templates/ directory exists
|
||||
2. Check template paths in render_template()
|
||||
3. Restart Flask server
|
||||
|
||||
### CSS Not Loading
|
||||
|
||||
1. Check static/css/style.css exists
|
||||
2. Check url_for('static', filename='css/style.css')
|
||||
3. Clear browser cache
|
||||
|
||||
### Authentication Not Working
|
||||
|
||||
1. Check ADMIN_ME is set correctly
|
||||
2. Check SESSION_SECRET is set
|
||||
3. Check IndieLogin callback URL matches
|
||||
4. Check browser cookies enabled
|
||||
|
||||
---
|
||||
|
||||
## Next Steps After Phase 4
|
||||
|
||||
### Phase 5: RSS Feed
|
||||
- Generate `/feed.xml`
|
||||
- Valid RSS 2.0
|
||||
- Published notes only
|
||||
|
||||
### Phase 6: Micropub
|
||||
- `/api/micropub` endpoint
|
||||
- Accept h-entry posts
|
||||
- IndieAuth token verification
|
||||
|
||||
### V1.0.0
|
||||
- Complete V1 features
|
||||
- Security audit
|
||||
- Performance optimization
|
||||
- Production deployment
|
||||
|
||||
---
|
||||
|
||||
## Documentation References
|
||||
|
||||
- **ADR-011**: Development Auth Decision
|
||||
- **Phase 4 Design**: Complete specification
|
||||
- **Assessment Report**: Architectural review
|
||||
- **Phase 3 Report**: Authentication implementation
|
||||
- **ADR-003**: Frontend Technology
|
||||
- **ADR-005**: IndieLogin Authentication
|
||||
- **ADR-010**: Authentication Module Design
|
||||
|
||||
---
|
||||
|
||||
## Git Workflow
|
||||
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/phase-4-web-interface main
|
||||
|
||||
# Implement, test, commit frequently
|
||||
git commit -m "Add public routes"
|
||||
git commit -m "Add admin routes"
|
||||
git commit -m "Add templates"
|
||||
git commit -m "Add dev auth"
|
||||
git commit -m "Add CSS"
|
||||
git commit -m "Add tests"
|
||||
|
||||
# Update version
|
||||
# Edit starpunk/__init__.py: __version__ = "0.5.0"
|
||||
# Edit CHANGELOG.md
|
||||
|
||||
git commit -m "Bump version to 0.5.0"
|
||||
|
||||
# Merge to main
|
||||
git checkout main
|
||||
git merge feature/phase-4-web-interface
|
||||
|
||||
# Tag
|
||||
git tag -a v0.5.0 -m "Release 0.5.0: Web Interface complete"
|
||||
|
||||
# Push
|
||||
git push origin main v0.5.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Estimated Effort**: 34 hours
|
||||
**Target Version**: 0.5.0
|
||||
**Developer**: Use with Phase 4 Design Document
|
||||
Reference in New Issue
Block a user