- 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>
7.0 KiB
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:
-
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.
-
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.
-
Unpredictable Slugs: Users cannot predict what slug will be generated without examining their content carefully.
-
Edge Case Handling: Content-based slugs require complex fallback logic for short content, unicode-only content, or special characters.
-
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
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:
20251216143052is harder to remember thanhello-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,-2are 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
- Simplified Code: Remove complex word extraction, unicode normalization, and multiple fallback paths
- Better Privacy: Note content never appears in URLs
- Predictable Output: Users always know what slug format to expect
- Fewer Edge Cases: No special handling for short content, unicode, or special characters
- Cleaner Collisions: Sequential suffixes are more intuitive than random strings
Negative
- Migration: Existing notes keep their content-based slugs (no migration needed)
- Not Human-Readable: URLs don't describe content
- 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: Updategenerate_slug()functionstarpunk/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
YYYYMMDDHHMMSSformat - Collision handling uses sequential suffix (
-1,-2, etc.) - Custom slugs via
mp-slugcontinue 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)