# ADR-062: Timestamp-Based Slug Format

## Status
Accepted (Supersedes ADR-007)

## Context

ADR-007 established a content-based slug generation algorithm that extracts the first 5 words from note content to create URL slugs. While this approach provides readable, SEO-friendly URLs, it has drawbacks for a personal note-taking system:

1. **Privacy Concerns**: The slug reveals note content in the URL. Private thoughts or draft content become visible in URLs that may be shared or logged.

2. **SEO Irrelevance**: StarPunk is designed for personal IndieWeb notes, not public blogs. Notes are typically short-form content (similar to tweets or status updates) where SEO optimization provides no meaningful benefit.

3. **Unpredictable Slugs**: Users cannot predict what slug will be generated without examining their content carefully.

4. **Edge Case Handling**: Content-based slugs require complex fallback logic for short content, unicode-only content, or special characters.

5. **Collision Complexity**: Similar notes require random suffix generation (e.g., `hello-world-a7c9`), which undermines the readability goal.

The user has explicitly stated: "Notes don't need SEO."

## Decision

Change the default slug format from content-based to timestamp-based:

**New Default Format**: `YYYYMMDDHHMMSS`
- Example: `20251216143052`
- Compact, sortable, predictable
- 14 characters total

**Collision Handling**: Sequential numeric suffix
- First collision: `20251216143052-1`
- Second collision: `20251216143052-2`
- Simple, predictable, no randomness

**Custom Slugs**: Continue to support user-specified slugs via `mp-slug` Micropub property and the web UI custom slug field. When provided, custom slugs take precedence.

### Algorithm Specification

```python
def generate_slug(custom_slug: str = None, created_at: datetime = None) -> str:
    """Generate a URL-safe slug for a note.

    Args:
        custom_slug: User-provided custom slug (takes precedence if provided)
        created_at: Note creation timestamp (defaults to now)

    Returns:
        URL-safe slug string
    """
    if custom_slug:
        return sanitize_slug(custom_slug)

    # Default: timestamp-based
    timestamp = (created_at or datetime.now()).strftime("%Y%m%d%H%M%S")
    return ensure_unique_slug(timestamp)

def ensure_unique_slug(base_slug: str) -> str:
    """Ensure slug is unique, adding numeric suffix if needed."""
    if not slug_exists(base_slug):
        return base_slug

    suffix = 1
    while slug_exists(f"{base_slug}-{suffix}"):
        suffix += 1
    return f"{base_slug}-{suffix}"
```

### Examples

| Scenario | Generated Slug |
|----------|----------------|
| Normal note at 2:30:52 PM on Dec 16, 2025 | `20251216143052` |
| Second note in same second | `20251216143052-1` |
| Third note in same second | `20251216143052-2` |
| Note with custom slug "my-custom-slug" | `my-custom-slug` |

## Rationale

### Timestamp Format (Score: 9/10)

**Pros**:
- **Privacy**: URL reveals nothing about content
- **Predictability**: User knows exactly what slug format to expect
- **Sortability**: Chronological sorting by URL is possible
- **Simplicity**: No complex word extraction or normalization
- **Collision Rarity**: Same-second creation is rare; handled cleanly when it occurs
- **Compact**: 14 characters vs potentially 50+ for content-based

**Cons**:
- **Not Memorable**: `20251216143052` is harder to remember than `hello-world`
- **No SEO Value**: Search engines prefer descriptive URLs

**Why Cons Don't Matter**:
- StarPunk is for personal notes, not public SEO-optimized content
- Notes are accessed via UI, feeds, or bookmarks, not memorized URLs
- Users wanting memorable URLs can use custom slugs

### Sequential Suffix (Score: 10/10)

**Pros**:
- **Deterministic**: No randomness; same collision always gets same suffix
- **Simple**: No cryptographic random generation needed
- **Readable**: `-1`, `-2` are clear and obvious
- **Debuggable**: Easy to understand collision resolution

**Cons**:
- **Enumerable**: Sequential numbers could be probed
- Not a real security concern for note slugs

### Comparison with ADR-007 Approach

| Aspect | ADR-007 (Content-Based) | ADR-062 (Timestamp) |
|--------|------------------------|---------------------|
| Privacy | Reveals content | Reveals only time |
| Complexity | High (word extraction, normalization, unicode handling) | Low (strftime) |
| SEO | Good | None |
| Predictability | Low | High |
| Collision handling | Random suffix | Sequential suffix |
| Fallback cases | Many (short content, unicode, etc.) | None |
| Code lines | ~50 | ~15 |

## Consequences

### Positive

1. **Simplified Code**: Remove complex word extraction, unicode normalization, and multiple fallback paths
2. **Better Privacy**: Note content never appears in URLs
3. **Predictable Output**: Users always know what slug format to expect
4. **Fewer Edge Cases**: No special handling for short content, unicode, or special characters
5. **Cleaner Collisions**: Sequential suffixes are more intuitive than random strings

### Negative

1. **Migration**: Existing notes keep their content-based slugs (no migration needed)
2. **Not Human-Readable**: URLs don't describe content
3. **No SEO**: Search engines won't benefit from descriptive URLs

### Mitigations

**Human-Readable URLs**:
- Users wanting descriptive URLs can use custom slugs via `mp-slug`
- The web UI custom slug field remains available
- This is opt-in rather than default

**SEO**:
- IndieWeb notes are typically not SEO targets
- Content is in the page body where search engines can index it
- Microformats2 markup provides semantic meaning

## Backward Compatibility

- Existing notes retain their slugs (no data migration)
- New notes use timestamp format by default
- Custom slug functionality unchanged
- All existing URLs remain valid

## Testing Requirements

- Test default slug generation produces timestamp format
- Test collision handling with sequential suffixes
- Test custom slugs still take precedence
- Test edge case: multiple notes in same second
- Test reserved slug rejection still works
- Verify existing tests for custom slugs pass

## Implementation Notes

Changes required in:
- `starpunk/slug_utils.py`: Update `generate_slug()` function
- `starpunk/notes.py`: Remove content parameter from slug generation call
- Tests: Update expected slug formats

Estimated effort: Small (1-2 hours implementation, 1 hour testing)

## References

- ADR-007: Slug Generation Algorithm (Superseded by this ADR)
- ADR-035: Custom Slugs (Unchanged; complements this decision)
- IndieWeb Permalink Best Practices: https://indieweb.org/permalink

## Acceptance Criteria

- [ ] Default slugs use `YYYYMMDDHHMMSS` format
- [ ] Collision handling uses sequential suffix (`-1`, `-2`, etc.)
- [ ] Custom slugs via `mp-slug` continue to work
- [ ] Custom slugs via web UI continue to work
- [ ] Reserved slug validation unchanged
- [ ] Existing notes unaffected
- [ ] All tests pass
- [ ] Code complexity reduced

---

**Approved**: 2025-12-16
**Architect**: StarPunk Architect Agent
**Supersedes**: ADR-007 (Slug Generation Algorithm)
