## 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>
115 lines
7.7 KiB
CSS
115 lines
7.7 KiB
CSS
/* StarPunk CSS - Minimal responsive stylesheet */
|
|
|
|
:root {
|
|
--color-text: #333; --color-text-light: #666; --color-bg: #fff; --color-bg-alt: #f5f5f5;
|
|
--color-link: #0066cc; --color-link-hover: #004499; --color-border: #ddd;
|
|
--color-success: #28a745; --color-error: #dc3545; --color-warning: #ffc107; --color-info: #17a2b8;
|
|
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
|
--font-mono: 'SF Mono', Monaco, Consolas, 'Courier New', monospace;
|
|
--spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 2rem; --spacing-xl: 4rem;
|
|
--max-width: 42rem; --border-radius: 4px;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: var(--font-body); font-size: 1rem; line-height: 1.6; color: var(--color-text); background: var(--color-bg); padding: var(--spacing-md); }
|
|
|
|
h1, h2, h3 { margin-bottom: var(--spacing-md); line-height: 1.2; font-weight: 600; }
|
|
h1 { font-size: 2rem; } h2 { font-size: 1.5rem; } h3 { font-size: 1.25rem; }
|
|
p { margin-bottom: var(--spacing-md); }
|
|
a { color: var(--color-link); text-decoration: none; }
|
|
a:hover { color: var(--color-link-hover); text-decoration: underline; }
|
|
|
|
code, pre { font-family: var(--font-mono); background: var(--color-bg-alt); padding: 0.125rem 0.25rem; border-radius: var(--border-radius); font-size: 0.875rem; }
|
|
pre { padding: var(--spacing-md); overflow-x: auto; margin-bottom: var(--spacing-md); }
|
|
|
|
header, main, footer { max-width: var(--max-width); margin: 0 auto; }
|
|
header { margin-bottom: var(--spacing-lg); padding-bottom: var(--spacing-md); border-bottom: 2px solid var(--color-border); }
|
|
header h1 { margin-bottom: var(--spacing-sm); }
|
|
header h1 a { color: var(--color-text); text-decoration: none; }
|
|
nav { display: flex; gap: var(--spacing-md); flex-wrap: wrap; }
|
|
nav a { padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--border-radius); transition: background 0.2s; }
|
|
nav a:hover { background: var(--color-bg-alt); text-decoration: none; }
|
|
main { min-height: 60vh; margin-bottom: var(--spacing-lg); }
|
|
footer { padding-top: var(--spacing-lg); border-top: 1px solid var(--color-border); text-align: center; color: var(--color-text-light); font-size: 0.875rem; }
|
|
|
|
.dev-mode-warning { background: var(--color-error); color: white; padding: var(--spacing-md); text-align: center; font-weight: bold; margin: calc(-1 * var(--spacing-md)); margin-bottom: var(--spacing-lg); }
|
|
|
|
.flash { padding: var(--spacing-md); border-radius: var(--border-radius); margin-bottom: var(--spacing-md); border-left: 4px solid; }
|
|
.flash-success { background: #d4edda; border-color: var(--color-success); color: #155724; }
|
|
.flash-error { background: #f8d7da; border-color: var(--color-error); color: #721c24; }
|
|
.flash-warning { background: #fff3cd; border-color: var(--color-warning); color: #856404; }
|
|
.flash-info { background: #d1ecf1; border-color: var(--color-info); color: #0c5460; }
|
|
|
|
.h-entry { margin-bottom: var(--spacing-lg); padding-bottom: var(--spacing-lg); border-bottom: 1px solid var(--color-border); }
|
|
.h-entry:last-child { border-bottom: none; }
|
|
.note-preview .e-content { margin-bottom: var(--spacing-md); }
|
|
.note-meta { color: var(--color-text-light); font-size: 0.875rem; }
|
|
.note-meta a { color: var(--color-text-light); }
|
|
.note-nav { margin-top: var(--spacing-md); }
|
|
.empty-state { text-align: center; padding: var(--spacing-xl); color: var(--color-text-light); }
|
|
|
|
.form-group { margin-bottom: var(--spacing-md); }
|
|
label { display: block; margin-bottom: var(--spacing-sm); font-weight: 600; }
|
|
input[type="text"], input[type="url"], input[type="email"], textarea { width: 100%; padding: var(--spacing-sm); border: 1px solid var(--color-border); border-radius: var(--border-radius); font-family: inherit; font-size: 1rem; }
|
|
textarea { font-family: var(--font-mono); resize: vertical; min-height: 10rem; }
|
|
small { display: block; margin-top: var(--spacing-xs); color: var(--color-text-light); font-size: 0.875rem; }
|
|
.form-checkbox { display: flex; align-items: center; gap: var(--spacing-sm); }
|
|
.form-checkbox input[type="checkbox"] { width: auto; margin: 0; }
|
|
.form-checkbox label { margin: 0; font-weight: normal; }
|
|
.form-actions { display: flex; gap: var(--spacing-md); margin-top: var(--spacing-lg); }
|
|
|
|
.button { display: inline-block; padding: var(--spacing-sm) var(--spacing-md); border: 1px solid var(--color-border); border-radius: var(--border-radius); background: var(--color-bg); color: var(--color-text); font-family: inherit; font-size: 1rem; cursor: pointer; text-decoration: none; transition: all 0.2s; }
|
|
.button:hover { background: var(--color-bg-alt); text-decoration: none; }
|
|
.button-primary { background: var(--color-link); color: white; border-color: var(--color-link); }
|
|
.button-primary:hover { background: var(--color-link-hover); border-color: var(--color-link-hover); }
|
|
.button-secondary { background: var(--color-bg-alt); }
|
|
.button-danger { background: var(--color-error); color: white; border-color: var(--color-error); }
|
|
.button-danger:hover { background: #c82333; border-color: #c82333; }
|
|
.button-warning { background: var(--color-warning); color: #333; border-color: var(--color-warning); }
|
|
.button-small { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
|
|
|
|
.admin-container { max-width: 60rem; }
|
|
.admin-nav { display: flex; gap: var(--spacing-md); padding: var(--spacing-md); background: var(--color-bg-alt); border-radius: var(--border-radius); margin-bottom: var(--spacing-lg); flex-wrap: wrap; align-items: center; }
|
|
.admin-nav .logout-form { margin-left: auto; }
|
|
.admin-nav .logout-form button { margin: 0; }
|
|
.admin-content { margin-bottom: var(--spacing-lg); }
|
|
.user-identity { color: var(--color-text-light); font-size: 0.875rem; margin-bottom: var(--spacing-md); }
|
|
|
|
.dashboard-actions { margin-bottom: var(--spacing-md); }
|
|
.note-table { width: 100%; border-collapse: collapse; margin-top: var(--spacing-md); }
|
|
.note-table th, .note-table td { padding: var(--spacing-md); text-align: left; border-bottom: 1px solid var(--color-border); }
|
|
.note-table th { background: var(--color-bg-alt); font-weight: 600; }
|
|
.note-content-preview { max-width: 30rem; }
|
|
.note-excerpt { margin-bottom: var(--spacing-xs); }
|
|
.note-slug { color: var(--color-text-light); font-family: var(--font-mono); font-size: 0.75rem; }
|
|
.note-date { white-space: nowrap; color: var(--color-text-light); }
|
|
.note-actions { white-space: nowrap; }
|
|
.note-actions .button { margin-right: var(--spacing-sm); }
|
|
.note-actions .delete-form { display: inline; }
|
|
.status-badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: var(--border-radius); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }
|
|
.status-published { background: #d4edda; color: var(--color-success); }
|
|
.status-draft { background: #f8d7da; color: var(--color-error); }
|
|
|
|
.login-container { max-width: 30rem; margin: 0 auto; padding: var(--spacing-lg); }
|
|
.login-form { margin: var(--spacing-lg) 0; }
|
|
.dev-login-option { margin-top: var(--spacing-lg); padding-top: var(--spacing-lg); border-top: 1px solid var(--color-border); }
|
|
.dev-warning { color: var(--color-error); font-weight: 600; text-align: center; }
|
|
.login-help { margin-top: var(--spacing-xl); padding-top: var(--spacing-lg); border-top: 1px solid var(--color-border); }
|
|
.login-help h3 { font-size: 1rem; margin-bottom: var(--spacing-sm); }
|
|
|
|
.note-editor { max-width: 50rem; }
|
|
.note-editor .note-meta { margin-bottom: var(--spacing-md); }
|
|
|
|
@media (min-width: 768px) {
|
|
body { padding: var(--spacing-lg); }
|
|
h1 { font-size: 2.5rem; } h2 { font-size: 2rem; } h3 { font-size: 1.5rem; }
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.note-table { font-size: 0.875rem; }
|
|
.note-table th, .note-table td { padding: var(--spacing-sm); }
|
|
.note-actions .button { font-size: 0.75rem; padding: 0.25rem 0.5rem; }
|
|
.admin-nav { flex-direction: column; align-items: stretch; }
|
|
.admin-nav .logout-form { margin-left: 0; }
|
|
}
|