Implements tag/category system backend following microformats2 p-category specification. Database changes: - Migration 008: Add tags and note_tags tables - Normalized tag storage (case-insensitive lookup, display name preserved) - Indexes for performance New module: - starpunk/tags.py: Tag management functions - normalize_tag: Normalize tag strings - get_or_create_tag: Get or create tag records - add_tags_to_note: Associate tags with notes (replaces existing) - get_note_tags: Retrieve note tags (alphabetically ordered) - get_tag_by_name: Lookup tag by normalized name - get_notes_by_tag: Get all notes with specific tag - parse_tag_input: Parse comma-separated tag input Model updates: - Note.tags property (lazy-loaded, prefer pre-loading in routes) - Note.to_dict() add include_tags parameter CRUD updates: - create_note() accepts tags parameter - update_note() accepts tags parameter (None = no change, [] = remove all) Micropub integration: - Pass tags to create_note() (tags already extracted by extract_tags()) - Return tags in q=source response Per design doc: docs/design/v1.3.0/microformats-tags-design.md Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
3.9 KiB
Hotfix Design: v1.1.1-rc.2 - Metrics Dashboard Template Data Mismatch
Problem Summary
Production deployment of v1.1.1-rc.1 exposed two critical issues in the metrics dashboard:
- Route Conflict (Fixed in initial attempt): Two routes mapped to similar paths causing ambiguity
- Template/Data Mismatch (Root cause): Template expects different data structure than monitoring module provides
The Template/Data Mismatch
Template Expects (metrics_dashboard.html line 163):
{{ metrics.database.count|default(0) }}
{{ metrics.database.avg|default(0) }}
{{ metrics.database.min|default(0) }}
{{ metrics.database.max|default(0) }}
Monitoring Module Returns:
{
"by_type": {
"database": {
"count": 50,
"avg_duration_ms": 12.5,
"min_duration_ms": 2.0,
"max_duration_ms": 45.0
}
}
}
Note the two mismatches:
- Nesting: Template wants
metrics.databasebut getsmetrics.by_type.database - Field Names: Template wants
avgbut getsavg_duration_ms
Solution: Route Adapter Pattern
Transform data at the presentation layer (route handler) to match template expectations.
Implementation
Added a transformer function in admin.py that:
- Flattens the nested structure (
by_type.database→database) - Maps field names (
avg_duration_ms→avg) - Provides safe defaults for missing data
def transform_metrics_for_template(metrics_stats):
"""Transform metrics stats to match template structure"""
transformed = {}
# Map by_type to direct access with field name mapping
for op_type in ['database', 'http', 'render']:
if 'by_type' in metrics_stats and op_type in metrics_stats['by_type']:
type_data = metrics_stats['by_type'][op_type]
transformed[op_type] = {
'count': type_data.get('count', 0),
'avg': type_data.get('avg_duration_ms', 0), # Note field name change
'min': type_data.get('min_duration_ms', 0),
'max': type_data.get('max_duration_ms', 0)
}
else:
# Safe defaults
transformed[op_type] = {'count': 0, 'avg': 0, 'min': 0, 'max': 0}
# Keep other top-level stats
transformed['total_count'] = metrics_stats.get('total_count', 0)
transformed['max_size'] = metrics_stats.get('max_size', 1000)
transformed['process_id'] = metrics_stats.get('process_id', 0)
return transformed
Why This Approach?
- Minimal Risk: Only changes route handler, not core monitoring module
- Preserves API: Monitoring module remains unchanged for other consumers
- No Template Changes: Avoids modifying template and JavaScript
- Clear Separation: Route acts as adapter between business logic and view
Additional Fixes Applied
- Route Path Change:
/admin/dashboard→/admin/metrics-dashboard(prevents conflict) - Defensive Imports: Graceful handling of missing monitoring module
- Error Handling: Safe defaults when metrics collection fails
Testing and Validation
Created comprehensive test script validating:
- Data structure transformation works correctly
- All template fields accessible after transformation
- Safe defaults provided for missing data
- Field name mapping correct
All 32 admin route tests pass with 100% success rate.
Files Modified
-
/starpunk/routes/admin.py:- Lines 218-260: Added transformer function
- Line 263: Changed route path
- Lines 285-314: Applied transformer and added error handling
-
/starpunk/__init__.py: Version bump to 1.1.1-rc.2 -
/CHANGELOG.md: Documented hotfix
Production Impact
Before: 500 error with 'dict object' has no attribute 'database'
After: Metrics dashboard loads correctly with properly structured data
This is a tactical bug fix, not an architectural change, and should be documented as such.