Files
StarPunk/docs/design/feed-media-handling-options.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

12 KiB

Feed Media Handling: Architecture Options Analysis

Date: 2025-12-09 Author: StarPunk Architect Status: Proposed Related: ADR-057, Q24, Q27, Q28

Executive Summary

Analysis of the current feed output reveals that RSS 2.0 lacks proper media enclosure elements, while ATOM and JSON Feed have partial implementations. This document proposes three options for fixing media handling across all feed formats.

Current State Analysis

RSS Feed (Problem)

<item>
  <title>Test</title>
  <link>http://localhost:8000/note/with-a-test-slug</link>
  <description>&lt;div class="media"&gt;&lt;img src="..." alt="Just some dude" /&gt;&lt;/div&gt;&lt;p&gt;Test&lt;/p&gt;</description>
  <guid isPermaLink="true">http://localhost:8000/note/with-a-test-slug</guid>
  <pubDate>Fri, 28 Nov 2025 23:23:13 +0000</pubDate>
</item>

Issues Identified:

  1. No <enclosure> element for the image
  2. Image is only embedded as HTML in description
  3. Many feed readers (Feedly, Reeder) won't display the image prominently
  4. No media:content or media:thumbnail elements

ATOM Feed (Partial)

<entry>
  <link rel="enclosure" type="image/jpeg" href="..." length="1796654"/>
  <content type="html">...</content>
</entry>

Status: Correctly includes enclosure link. ATOM implementation is acceptable.

JSON Feed (Partial)

{
  "attachments": [
    {
      "url": "...",
      "mime_type": "image/jpeg",
      "size_in_bytes": 1796654,
      "title": "Just some dude"
    }
  ]
}

Issues Identified:

  1. Has attachments array (correct per JSON Feed 1.1 spec)
  2. Missing top-level image field for featured image
  3. Some readers use image for thumbnail display

Standards Research Summary

RSS 2.0 Specification

Per the RSS 2.0 Specification:

  • <enclosure> element requires: url, length, type
  • Only ONE enclosure per item is officially supported (though many readers accept multiple)
  • Images in <description> are fallback, not primary

Media RSS (MRSS) Extension

Per the Media RSS Specification:

  • Namespace: http://search.yahoo.com/mrss/
  • <media:content> for primary media with medium="image"
  • <media:thumbnail> for preview images
  • Provides richer metadata than basic enclosure

JSON Feed 1.1 Specification

Per the JSON Feed 1.1 spec:

  • image field: URL of the main/featured image (for preview/thumbnail)
  • attachments array: Related resources (files, media)
  • Both can coexist - image for display, attachments for download

Feed Reader Compatibility Notes

Reader Enclosure media:content HTML Images Notes
Feedly Good Excellent Fallback Prefers media:thumbnail
NetNewsWire Good Good Yes Displays HTML images inline
Reeder Good Good Yes Uses enclosure for preview
Inoreader Good Excellent Yes Full MRSS support
FreshRSS Good Good Yes Displays all sources
Feedbin Good Good Yes Clean HTML rendering

Option 1: RSS Enclosure Only (Minimal)

Description

Add the standard RSS <enclosure> element to RSS feeds for the first image only, keeping HTML images in description as fallback.

Implementation Changes

File: /home/phil/Projects/starpunk/starpunk/feeds/rss.py

# In generate_rss() after setting description
if hasattr(note, 'media') and note.media:
    first_media = note.media[0]
    media_url = f"{site_url}/media/{first_media['path']}"
    fe.enclosure(
        url=media_url,
        length=str(first_media.get('size', 0)),
        type=first_media.get('mime_type', 'image/jpeg')
    )

File: /home/phil/Projects/starpunk/starpunk/feeds/rss.py (streaming version)

# In generate_rss_streaming() item generation
if hasattr(note, 'media') and note.media:
    first_media = note.media[0]
    media_url = f"{site_url}/media/{first_media['path']}"
    mime_type = first_media.get('mime_type', 'image/jpeg')
    size = first_media.get('size', 0)
    yield f'      <enclosure url="{_escape_xml(media_url)}" length="{size}" type="{mime_type}"/>\n'

Pros

  1. Simplest implementation - Single element addition
  2. Spec-compliant - Pure RSS 2.0, no extensions
  3. Wide compatibility - All RSS readers support enclosure
  4. Low risk - Minimal code changes

Cons

  1. Single image only - RSS spec ambiguous about multiple enclosures
  2. No thumbnail metadata - Readers must use full-size image
  3. No alt text/caption - Enclosure has no description attribute
  4. Less prominent display - Some readers treat enclosure as "download" not "display"

Complexity Score: 2/10


Description

Add both standard <enclosure> and Media RSS (media:content, media:thumbnail) elements. This provides maximum compatibility across modern feed readers while supporting multiple images and richer metadata.

Implementation Changes

File: /home/phil/Projects/starpunk/starpunk/feeds/rss.py

Add namespace to feed:

# Register Media RSS namespace
fg.register_extension('media', 'http://search.yahoo.com/mrss/')

Add media elements per item:

if hasattr(note, 'media') and note.media:
    for i, media_item in enumerate(note.media):
        media_url = f"{site_url}/media/{media_item['path']}"
        mime_type = media_item.get('mime_type', 'image/jpeg')
        size = media_item.get('size', 0)
        caption = media_item.get('caption', '')

        # First image: use as enclosure AND thumbnail
        if i == 0:
            fe.enclosure(url=media_url, length=str(size), type=mime_type)
            # Would need custom extension handling for media:thumbnail

        # All images: add as media:content
        # Note: feedgen doesn't support media:* natively
        # May need to use custom XML generation or switch to streaming

File: /home/phil/Projects/starpunk/starpunk/feeds/rss.py (streaming - cleaner approach)

# In XML header
yield '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">\n'

# In item generation
if hasattr(note, 'media') and note.media:
    for i, media_item in enumerate(note.media):
        media_url = f"{site_url}/media/{media_item['path']}"
        mime_type = media_item.get('mime_type', 'image/jpeg')
        size = media_item.get('size', 0)
        caption = _escape_xml(media_item.get('caption', ''))

        # First image as enclosure (RSS 2.0 standard)
        if i == 0:
            yield f'      <enclosure url="{_escape_xml(media_url)}" length="{size}" type="{mime_type}"/>\n'
            # Also as thumbnail for readers that prefer it
            yield f'      <media:thumbnail url="{_escape_xml(media_url)}"/>\n'

        # All images as media:content
        yield f'      <media:content url="{_escape_xml(media_url)}" type="{mime_type}" fileSize="{size}" medium="image"'
        if caption:
            yield f'>\n'
            yield f'        <media:description type="plain">{caption}</media:description>\n'
            yield f'      </media:content>\n'
        else:
            yield f'/>\n'

Pros

  1. Maximum compatibility - Works with all modern readers
  2. Multiple images supported - Media RSS handles arrays naturally
  3. Rich metadata - Captions, dimensions, alt text possible
  4. Prominent display - Readers using media:thumbnail show images well
  5. Graceful degradation - Falls back to enclosure for older readers

Cons

  1. More complexity - Multiple elements to generate
  2. Namespace required - Adds xmlns declaration
  3. feedgen limitations - May need streaming approach for full control
  4. Spec sprawl - Using RSS 2.0 + MRSS together

Complexity Score: 5/10


Option 3: Full Standardization (All Formats)

Description

Comprehensive update to all three feed formats ensuring consistent media handling with both structured elements AND HTML content, plus adding the image field to JSON Feed items.

Implementation Changes

RSS (same as Option 2):

  • Add <enclosure> for first image
  • Add <media:content> for all images
  • Add <media:thumbnail> for first image
  • Keep HTML images in description

ATOM (already mostly correct):

  • Current implementation is good
  • Consider adding <media:thumbnail> via MRSS namespace

JSON Feed:

# In _build_item_object()
def _build_item_object(site_url: str, note: Note) -> Dict[str, Any]:
    # ... existing code ...

    # Add featured image (first image) at item level
    if hasattr(note, 'media') and note.media:
        first_media = note.media[0]
        media_url = f"{site_url}/media/{first_media['path']}"
        item["image"] = media_url  # Top-level image field

        # Attachments array (existing code)
        attachments = []
        for media_item in note.media:
            # ... existing attachment building ...
        item["attachments"] = attachments

Content Strategy Decision

Should HTML content include images?

Yes, always include images in HTML content (description, content_html) as well as in structured elements. Rationale:

  1. Some readers only render HTML, ignoring enclosures
  2. Ensures consistent display across all reader types
  3. ADR-057 and Q24 already mandate this approach
  4. IndieWeb convention supports redundant markup

Pros

  1. Complete solution - All formats fully supported
  2. Maximum reader compatibility - Covers all reader behaviors
  3. Consistent experience - Users see images regardless of reader
  4. Future-proof - Handles any new reader implementations

Cons

  1. Most complex - Changes to all three feed generators
  2. Redundant data - Images in multiple places (intentional)
  3. Larger feed size - More XML/JSON to transmit
  4. Testing burden - Must validate all three formats

Complexity Score: 7/10


Recommendation

I recommend Option 2: RSS + Media RSS Extension for the following reasons:

Rationale

  1. Addresses the actual problem: The user reported RSS as the problem format; ATOM and JSON Feed are working acceptably.

  2. Best compatibility/complexity ratio: Media RSS is widely supported by Feedly, Inoreader, and other major readers without excessive implementation burden.

  3. Multiple image support: Unlike Option 1, this handles the 2-4 image case that ADR-057 designed for.

  4. Caption preservation: Media RSS supports <media:description> which preserves alt text/captions.

  5. Minimal JSON Feed changes: JSON Feed only needs the image field addition (small change with good impact).

Implementation Priority

  1. Phase 1: Add <enclosure> to RSS (Option 1) - Immediate fix, 1 hour
  2. Phase 2: Add Media RSS namespace and elements - Enhanced fix, 2-3 hours
  3. Phase 3: Add image field to JSON Feed items - Polish, 30 minutes

Testing Validation

After implementation, validate with:

  1. W3C Feed Validator - RSS/ATOM compliance
  2. JSON Feed Validator - JSON Feed compliance
  3. Manual testing in: Feedly, NetNewsWire, Reeder, Inoreader, FreshRSS

Decision Required

The architect recommends Option 2 but requests stakeholder input on:

  1. Is multiple image support in RSS essential, or is first-image-only acceptable?
  2. Are there specific feed readers that must be supported?
  3. What is the timeline for this fix?

References