# 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)
```xml
<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)
```xml
<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)
```json
{
  "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](https://www.rssboard.org/rss-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](https://www.rssboard.org/media-rss):
- 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](https://jsonfeed.org/version/1.1):
- `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`

```python
# 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)

```python
# 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

---

## Option 2: RSS + Media RSS Extension (Recommended)

### 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:
```python
# Register Media RSS namespace
fg.register_extension('media', 'http://search.yahoo.com/mrss/')
```

Add media elements per item:
```python
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)

```python
# 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**:
```python
# 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](https://validator.w3.org/feed/) - RSS/ATOM compliance
2. [JSON Feed Validator](https://validator.jsonfeed.org/) - 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

- [RSS 2.0 Specification](https://www.rssboard.org/rss-specification)
- [Media RSS Specification](https://www.rssboard.org/media-rss)
- [JSON Feed 1.1](https://www.jsonfeed.org/version/1.1/)
- [ATOM RFC 4287](https://tools.ietf.org/html/rfc4287)
- ADR-057: Media Attachment Model
- Q24-Q28: v1.2.0 Developer Q&A (Feed Integration)
