# 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 %}
{% 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.