# ADR-039: Micropub URL Construction Fix

## Status
Accepted

## Context
After the v1.0.0 release, a bug was discovered in the Micropub implementation where the Location header returned after creating a post contains a double slash:

- **Expected**: `https://starpunk.thesatelliteoflove.com/notes/so-starpunk-v100-is-complete`
- **Actual**: `https://starpunk.thesatelliteoflove.com//notes/so-starpunk-v100-is-complete`

### Root Cause Analysis
The issue occurs due to a mismatch between how SITE_URL is stored and used:

1. **Configuration Storage** (`starpunk/config.py`):
   - SITE_URL is normalized to always end with a trailing slash (lines 26, 92)
   - This is required for IndieAuth/OAuth specs where root URLs must have trailing slashes
   - Example: `https://starpunk.thesatelliteoflove.com/`

2. **URL Construction** (`starpunk/micropub.py`):
   - Constructs URLs using: `f"{site_url}/notes/{note.slug}"` (lines 311, 381)
   - This adds a leading slash to the path segment
   - Results in: `https://starpunk.thesatelliteoflove.com/` + `/notes/...` = double slash

3. **Inconsistent Handling**:
   - RSS feed module (`starpunk/feed.py`) correctly strips trailing slash before use (line 77)
   - Micropub module doesn't handle this, causing the bug

## Decision
Fix the URL construction in the Micropub module by removing the leading slash from the path segment. This maintains the trailing slash convention in SITE_URL while ensuring correct URL construction.

### Implementation Approach
Change the URL construction pattern from:
```python
permalink = f"{site_url}/notes/{note.slug}"
```

To:
```python
permalink = f"{site_url}notes/{note.slug}"
```

This works because SITE_URL is guaranteed to have a trailing slash.

### Affected Code Locations
1. `starpunk/micropub.py` line 311 - Location header in `handle_create`
2. `starpunk/micropub.py` line 381 - URL in Microformats2 response in `handle_query`

## Rationale

### Why Not Strip the Trailing Slash?
We could follow the RSS feed approach and strip the trailing slash:
```python
site_url = site_url.rstrip("/")
permalink = f"{site_url}/notes/{note.slug}"
```

However, this approach has downsides:
- Adds unnecessary processing to every request
- Creates inconsistency with how SITE_URL is used elsewhere
- The trailing slash is intentionally added for IndieAuth compliance

### Why This Solution?
- **Minimal change**: Only modifies the string literal, not the logic
- **Consistent**: SITE_URL remains normalized with trailing slash throughout
- **Efficient**: No runtime string manipulation needed
- **Clear intent**: The code explicitly shows we expect SITE_URL to end with `/`

## Consequences

### Positive
- Fixes the immediate bug with minimal code changes
- No configuration changes required
- No database migrations needed
- Backward compatible - doesn't break existing data
- Fast to implement and test

### Negative
- Developers must remember that SITE_URL has a trailing slash
- Could be confusing without documentation
- Potential for similar bugs if pattern isn't followed elsewhere

### Mitigation
- Add a comment at each URL construction site explaining the trailing slash convention
- Consider adding a utility function in future versions for URL construction
- Document the SITE_URL trailing slash convention clearly

## Alternatives Considered

### 1. Strip Trailing Slash at Usage Site
```python
site_url = current_app.config.get("SITE_URL", "http://localhost:5000").rstrip("/")
permalink = f"{site_url}/notes/{note.slug}"
```
- **Pros**: More explicit, follows RSS feed pattern
- **Cons**: Extra processing, inconsistent with config intention

### 2. Remove Trailing Slash from Configuration
Modify `config.py` to not add trailing slashes to SITE_URL.
- **Pros**: Simpler URL construction
- **Cons**: Breaks IndieAuth spec compliance, requires migration for existing deployments

### 3. Create URL Builder Utility
```python
def build_url(base, *segments):
    """Build URL from base and path segments"""
    return "/".join([base.rstrip("/")] + list(segments))
```
- **Pros**: Centralized URL construction, prevents future bugs
- **Cons**: Over-engineering for a simple fix, adds unnecessary abstraction for v1.0.1

### 4. Use urllib.parse.urljoin
```python
from urllib.parse import urljoin
permalink = urljoin(site_url, f"notes/{note.slug}")
```
- **Pros**: Standard library solution, handles edge cases
- **Cons**: Adds import, slightly less readable, overkill for this use case

## Implementation Notes

### Version Impact
- Current version: v1.0.0
- Fix version: v1.0.1 (PATCH increment - backward-compatible bug fix)

### Testing Requirements
1. Verify Location header has single slash
2. Test with various SITE_URL configurations (with/without trailing slash)
3. Ensure RSS feed still works correctly
4. Check all other URL constructions in the codebase

### Release Type
This qualifies as a **hotfix** because:
- It fixes a bug in production (v1.0.0)
- The fix is isolated and low-risk
- No new features or breaking changes
- Critical for proper Micropub client operation

## References
- [Issue Report]: Malformed redirect URL in Micropub implementation
- [W3C Micropub Spec](https://www.w3.org/TR/micropub/): Location header requirements
- [IndieAuth Spec](https://indieauth.spec.indieweb.org/): Client ID URL requirements
- ADR-028: Micropub Implementation Strategy
- docs/standards/versioning-strategy.md: Version increment guidelines