# Media Display Fixes - Architectural Design ## Status Active ## Problem Statement Three issues with current media display implementation: 1. **Images too large** - No CSS constraints on image dimensions 2. **Captions visible** - Currently showing figcaption, should use alt text only 3. **Images missing on homepage** - Media not fetched or displayed in index.html ## Root Cause Analysis ### Issue 1: Images Too Large The current CSS (`/static/css/style.css`) has NO styles for: - `.note-media` container - `.media-item` figure elements - `.u-photo` images - Responsive image constraints Images display at their native dimensions, which can break layouts. ### Issue 2: Captions Visible Template (`note.html` lines 25-27) explicitly renders figcaption: ```html {% if item.caption %}
{{ item.caption }}
{% endif %} ``` This violates the social media pattern where captions are for accessibility (alt text) only. ### Issue 3: Missing Homepage Media The index route (`public.py` line 231) doesn't fetch media: ```python notes = list_notes(published_only=True, limit=20) ``` Compare to the note route (lines 263-267) which DOES fetch media. ## Architectural Solution ### Design Principles 1. **Consistency**: Same media display logic on all pages 2. **Responsive**: Images adapt to viewport and container 3. **Accessible**: Alt text for screen readers, no visible captions 4. **Performance**: Lazy loading for below-fold images 5. **Standards**: Proper Microformats2 markup maintained ### Component Architecture #### 1. CSS Media Display System Create responsive, constrained image display with grid layouts: ```css /* Media container styles */ .note-media { margin-bottom: var(--spacing-md); width: 100%; } /* Single image - full width */ .note-media:has(.media-item:only-child) { max-width: 100%; } .note-media:has(.media-item:only-child) .media-item { width: 100%; } /* Two images - side by side */ .note-media:has(.media-item:nth-child(2):last-child) { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-sm); } /* Three or four images - grid */ .note-media:has(.media-item:nth-child(3)), .note-media:has(.media-item:nth-child(4)) { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--spacing-sm); } /* Media item wrapper */ .media-item { margin: 0; padding: 0; background: var(--color-bg-alt); border-radius: var(--border-radius); overflow: hidden; aspect-ratio: 1 / 1; /* Instagram-style square crop */ display: flex; align-items: center; justify-content: center; } /* Image constraints */ .media-item img, .u-photo { width: 100%; height: 100%; object-fit: cover; /* Crop to fill container */ display: block; } /* For single images, allow natural aspect ratio */ .note-media:has(.media-item:only-child) .media-item { aspect-ratio: auto; max-height: 500px; /* Prevent extremely tall images */ } .note-media:has(.media-item:only-child) .media-item img { object-fit: contain; /* Show full image for singles */ width: 100%; height: auto; max-height: 500px; } /* Remove figcaption from display */ .media-item figcaption { display: none; /* Captions are for alt text only */ } /* Mobile responsive adjustments */ @media (max-width: 767px) { /* Stack images vertically on small screens */ .note-media:has(.media-item:nth-child(2):last-child) { grid-template-columns: 1fr; } .media-item { aspect-ratio: 16 / 9; /* Wider aspect on mobile */ } } ``` #### 2. Template Refactoring Create a reusable macro for media display to ensure consistency: **New template partial: `templates/partials/media.html`** ```jinja2 {# Reusable media display macro #} {% macro display_media(media_items) %} {% if media_items %}
{% for item in media_items %}
{{ item.caption or 'Image' }} {# No figcaption - caption is for alt text only #}
{% endfor %}
{% endif %} {% endmacro %} ``` **Updated `note.html`** (lines 16-31): ```jinja2 {# Import media macro #} {% from "partials/media.html" import display_media %} {# Media display at TOP (v1.2.0 Phase 3, per ADR-057) #} {{ display_media(note.media) }} ``` **Updated `index.html`** (after line 26, before e-content): ```jinja2 {# Import media macro at top of file #} {% from "partials/media.html" import display_media %} {# In the note loop, after the title check #} {% if has_explicit_title %}

{{ note.title }}

{% endif %} {# Media preview (if available) #} {{ display_media(note.media) }} {# e-content: note content (preview) #}
``` #### 3. Route Handler Updates Update the index route to fetch media for each note: **`starpunk/routes/public.py`** (lines 219-233): ```python @bp.route("/") def index(): """ Homepage displaying recent published notes with media Returns: Rendered homepage template with note list including media Template: templates/index.html Microformats: h-feed containing h-entry items with u-photo """ from starpunk.media import get_note_media # Get recent published notes (limit 20) notes = list_notes(published_only=True, limit=20) # Attach media to each note for display for note in notes: media = get_note_media(note.id) # Use object.__setattr__ since Note is frozen dataclass object.__setattr__(note, 'media', media) return render_template("index.html", notes=notes) ``` ### Implementation Guidelines #### Phase 1: CSS Foundation 1. Add media display styles to `/static/css/style.css` 2. Test with 1, 2, 3, and 4 image layouts 3. Verify responsive behavior on mobile/tablet/desktop 4. Ensure images don't overflow containers #### Phase 2: Template Refactoring 1. Create `templates/partials/` directory if not exists 2. Create `media.html` with display macro 3. Update `note.html` to use macro 4. Update `index.html` to import and use macro 5. Remove figcaption rendering completely #### Phase 3: Route Updates 1. Import `get_note_media` in index route 2. Fetch media for each note in loop 3. Attach media using `object.__setattr__` 4. Verify media passes to template ### Testing Checklist #### Visual Tests - [ ] Single image displays at reasonable size - [ ] Two images display side-by-side - [ ] Three images display in 2x2 grid (one empty) - [ ] Four images display in 2x2 grid - [ ] Images maintain aspect ratio appropriately - [ ] No layout overflow on any screen size - [ ] Captions not visible (alt text only) #### Functional Tests - [ ] Homepage shows media for notes - [ ] Individual note page shows media - [ ] Media lazy loads below fold - [ ] Alt text present for accessibility - [ ] Microformats2 u-photo preserved #### Performance Tests - [ ] Page load time acceptable with media - [ ] Images don't block initial render - [ ] Lazy loading works correctly ### Security Considerations - Media paths already sanitized in media_file route - Alt text must be HTML-escaped in templates - No user-controlled CSS injection points ### Accessibility Requirements - Alt text MUST be present (fallback to "Image") - Images must not convey information not in text - Focus indicators for keyboard navigation - Proper semantic HTML (figure elements) ### Future Enhancements (Not for V1) - Image optimization/resizing on upload - WebP format support with fallbacks - Lightbox for full-size viewing - Video/audio media support - CDN integration for media serving ## Decision Rationale ### Why Grid Layout? - Native CSS, no JavaScript required - Excellent responsive support - Handles variable image counts elegantly - Familiar social media pattern ### Why Hide Captions? - Follows Twitter/Mastodon pattern - Captions are for accessibility (alt text) - Cleaner visual presentation - Text content provides context ### Why Lazy Loading? - Improves initial page load - Reduces bandwidth for visitors - Native browser support - Progressive enhancement ### Why Aspect Ratio Control? - Prevents layout shift during load - Creates consistent grid appearance - Matches social media expectations - Improves visual harmony ## Implementation Priority 1. **Critical**: Fix homepage media display (functionality gap) 2. **High**: Add CSS constraints (UX/visual issue) 3. **Medium**: Hide captions (visual polish) All three fixes should be implemented together for consistency.