Stickies
Stickies are rich text notes with tagging, version history, and soft-delete support.
Features
Rich Text Editing
- Markdown support
- Code blocks with syntax highlighting
- Inline formatting (bold, italic, links)
Version History
Every edit creates a version:
- View previous versions
- Restore any version
- Compare changes
Organization
- Tags: Organize with custom tags
- Tag Constellation: Visual tag browser
- Search: Full-text search across all stickies
Soft Delete
Deleted stickies go to trash:
- Tag-based system (
trashtag) - Restore from trash
- Permanent delete available
Usage
Creating a Sticky
- Navigate to
/protected/stickies - Click "New Sticky"
- Enter content and tags
- Save
Tagging System
Available tags:
- work, personal, urgent
- Custom tags created on-the-fly
- Special tag: "trash" (soft delete)
Viewing Trash
The trash filter appears when deleted items exist:
- Click "trash" tag to view deleted items
- Restore or permanently delete
Data Model
-- Stickies table
CREATE TABLE aicync.stickies (
id UUID PRIMARY KEY,
user_id UUID REFERENCES auth.users,
content TEXT,
tags TEXT[],
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Version history
CREATE TABLE aicync.sticky_versions (
id UUID PRIMARY KEY,
sticky_id UUID REFERENCES stickies,
content TEXT,
version_number INT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/stickies | List all stickies |
| POST | /api/stickies | Create sticky |
| PATCH | /api/stickies/[id] | Update sticky |
| DELETE | /api/stickies/[id] | Soft delete |
| GET | /api/stickies/[id]/versions | Get versions |
| POST | /api/stickies/[id]/restore | Restore from trash |
Soft Delete Pattern
The tag-based soft delete pattern:
// Delete: Add trash tag
await supabase.from('stickies').update({
tags: [...existing, 'trash'],
deleted_at: new Date()
}).eq('id', id);
// Restore: Remove trash tag
await supabase.from('stickies').update({
tags: tags.filter(t => t !== 'trash'),
deleted_at: null
}).eq('id', id);
// Query non-deleted
const { data } = await supabase
.from('stickies')
.select()
.not('tags', 'cs', '{trash}')
.is('deleted_at', null);
Legacy Compatibility
Old items may have deleted_at set without trash tag:
// Check both for trash items
const isTrash = sticky.tags.includes('trash') ||
sticky.deleted_at !== null;