- ADR-062: Timestamp-based slug format (supersedes ADR-007) - Updated v1.5.0 RELEASE.md with 6-phase plan - Updated BACKLOG.md with deferred N+1 query locations - Developer questions and architect responses for Phase 1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
198 lines
7.0 KiB
Markdown
198 lines
7.0 KiB
Markdown
# 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)
|