Files
StarPunk/docs/design/media-display-fixes.md
Phil Skentelbery 27501f6381 feat: v1.2.0-rc.2 - Media display fixes and feed enhancements
## Added
- Feed Media Enhancement with Media RSS namespace support
  - RSS enclosure, media:content, media:thumbnail elements
  - JSON Feed image field for first image
- ADR-059: Full feed media standardization roadmap

## Fixed
- Media display on homepage (was only showing on note pages)
- Responsive image sizing with CSS constraints
- Caption display (now alt text only, not visible)
- Logging correlation ID crash in non-request contexts

## Documentation
- Feed media design documents and implementation reports
- Media display fixes design and validation reports
- Updated ROADMAP with v1.3.0/v1.4.0 media plans

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 14:58:37 -07:00

8.4 KiB

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:

{% if item.caption %}
<figcaption>{{ item.caption }}</figcaption>
{% 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:

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:

/* 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

{# Reusable media display macro #}
{% macro display_media(media_items) %}
  {% if media_items %}
  <div class="note-media">
    {% for item in media_items %}
    <figure class="media-item">
      <img src="{{ url_for('public.media_file', path=item.path) }}"
           alt="{{ item.caption or 'Image' }}"
           class="u-photo"
           loading="lazy">
      {# No figcaption - caption is for alt text only #}
    </figure>
    {% endfor %}
  </div>
  {% endif %}
{% endmacro %}

Updated note.html (lines 16-31):

{# 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):

{# 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 %}
      <h3 class="p-name">{{ note.title }}</h3>
      {% endif %}

      {# Media preview (if available) #}
      {{ display_media(note.media) }}

      {# e-content: note content (preview) #}
      <div class="e-content">

3. Route Handler Updates

Update the index route to fetch media for each note:

starpunk/routes/public.py (lines 219-233):

@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.