Issue: chrishannah/miniship#40
Status: Planning
Estimated Time: 4-6 hours
Complexity: Medium
Risk: Medium (data migration for reordering, cascading deletes)
Add a comprehensive entry types management UI to allow users to view, edit, delete, and reorder entry types. This addresses the issue of duplicate types and provides long-term flexibility for type management.
Page: app/dashboard/[id]/settings/types/page.tsx
- Server component for authentication + changelog ownership check
- Renders
EntryTypesManagerclient component - Breadcrumb: Dashboard → Changelog → Settings → Entry Types
- List all entry types for a changelog (defaults + custom)
- Return usage count for each type
- Return ordering information
- Response format:
{
types: [
{
id: string,
name: string,
color: string,
icon: string,
changelogId: string | null, // null = default type
order: number,
usageCount: number,
isDefault: boolean
}
]
}- Update entry type (name, color, icon)
- Validation:
- User owns the changelog (for custom types)
- Name is not empty
- Color is valid hex
- Icon is valid (1-2 characters)
- Cannot edit default types
- Return updated type
- Delete custom entry type
- Validation:
- User owns the changelog
- Cannot delete default types
- Check if type is in use
- Two modes:
- Strict: Return 400 if type is in use (with usage count)
- Cascade: Accept
replaceWithquery param to migrate items to another type
- Response: Success or error with usage count
- Bulk update order field for all types
- Body:
{ typeIds: string[] }(ordered array) - Validation:
- User owns the changelog
- All typeIds belong to this changelog
- Update order field for each type based on array position
Migration: Add order field to entryTypes table
ALTER TABLE entry_types ADD COLUMN "order" INTEGER DEFAULT 0;Data migration script:
- Assign order based on current ID (defaults first, then customs)
- Defaults: order = 0-5 (based on hardcoded order)
- Customs: order = 1000+ (based on creation date)
Schema update:
export const entryTypes = pgTable('entry_types', {
// ... existing fields
order: integer('order').default(0).notNull(),
});Main container:
- Fetch types from GET endpoint
- Manage drag-and-drop state
- Handle edit/delete actions
- Show loading/error states
Individual type card:
- Display: icon, name, color badge
- Show "Default" badge for default types
- Show usage count
- Actions: Edit, Delete (custom types only)
- Drag handle for reordering
Modal for editing type:
- Fields: name, color (color picker), icon (emoji picker or text input)
- Preview of type appearance
- Save/Cancel buttons
- Validation feedback
Confirmation dialog:
- Show usage count if > 0
- If in use: require selecting replacement type
- Warning message
- Confirm/Cancel buttons
- Grid layout (2-3 columns on desktop, 1 on mobile)
- Default types at top (grayed out, non-editable)
- Custom types below
- Each card shows:
- Icon + name
- Color badge
- "Default" badge or "Custom" badge
- Usage count (e.g., "Used in 12 items")
- Edit/Delete buttons (custom types only)
- Drag handle
Approach: Simple up/down arrows (simpler than drag-and-drop)
- Each custom type card has ↑↓ buttons
- Click to move type up/down in list
- Debounced API call to update order
- Optimistic UI update
Alternative: react-beautiful-dnd for drag-and-drop
- More complex but better UX
- Consider for v2
- Click "Edit" on custom type
- Modal opens with current values
- Color picker (simple input or library like react-color)
- Icon input (text field, 1-2 char limit)
- Name input
- Preview shows how type will appear in releases
- Save → PATCH API → Update UI
- Click "Delete" on custom type
- Check usage count
- If usage = 0: simple confirmation
- If usage > 0:
- Show warning: "This type is used in X items"
- Dropdown: "Replace with:" (list of other types)
- Confirm button disabled until replacement selected
- DELETE API with replaceWith param if needed
- Update UI on success
Phase 1: Database + API (2-3 hours)
- Add
ordercolumn to entryTypes table - Write data migration script
- Implement GET /api/changelogs/[id]/types
- Implement PATCH /api/types/[id]
- Implement DELETE /api/types/[id]
- Implement PATCH /api/changelogs/[id]/types/reorder
- Test all endpoints
Phase 2: UI Components (2-3 hours)
- Create EntryTypesManager component
- Create EntryTypeCard component
- Create EditTypeModal component
- Create DeleteTypeModal component
- Wire up API calls
- Add loading/error states
- Test UI flows
Phase 3: Polish (30-60 min)
- Add breadcrumb navigation
- Style consistency with rest of app
- Mobile responsiveness
- Accessibility (keyboard nav, ARIA labels)
- Error handling UI
- Decision: Use
orderinteger field instead of linked list - Rationale: Simpler to query, easier to implement
- Trade-off: Need to handle gaps/duplicates (resolve by reassigning order on conflicts)
- Decision: Require replacement type if in use
- Rationale: Safer than cascade delete, user controls migration
- Alternative considered: Soft delete (keep type but hide it) — rejected as too complex
- Decision: Store both in same table, use
changelogId = nullfor defaults - Rationale: Already implemented this way, no migration needed
- UI: Gray out defaults, disable edit/delete actions
- Decision: Simple hex input with color preview (native input type="color")
- Rationale: Lightweight, works everywhere
- Alternative: react-color library if users want more control
- Decision: Text input with 1-2 character limit
- Rationale: Simple, supports any emoji
- Alternative: Emoji picker library (future enhancement)
-
Duplicate order values
- Migration script assigns unique orders
- Reorder API reassigns all orders sequentially
- No duplicates possible after save
-
Deleting type that's in use
- API returns 400 with usage count
- UI forces replacement type selection
- Migration happens in transaction
-
Editing default type
- API returns 403
- UI disables edit button
- Clear "Default" badge shown
-
Network errors during reorder
- Optimistic update reverts on error
- Toast notification shows error
- User can retry
-
Changelog with no custom types
- Show only default types
- "No custom types yet" message
- Encourage creation (link to add type flow if it exists)
- List shows all default + custom types
- Usage counts are accurate
- Cannot edit default types
- Cannot delete default types
- Can edit custom type (name, color, icon)
- Can delete custom type (usage = 0)
- Cannot delete type in use without replacement
- Reordering updates order correctly
- Reordering persists after page reload
- Mobile responsive layout works
- Loading states show correctly
- Error states show correctly
- Toast notifications work
- Drag-and-drop reordering (react-beautiful-dnd)
- Emoji picker for icon selection
- Advanced color picker (gradients, presets)
- Bulk actions (delete multiple, reorder multiple)
- Type templates (quick create common types)
- Export/import type configurations
- Type usage analytics (most/least used)
Database migration:
-- Add order column
ALTER TABLE entry_types ADD COLUMN "order" INTEGER DEFAULT 0 NOT NULL;
-- Assign default type orders (hardcoded based on current defaults)
UPDATE entry_types SET "order" = 0 WHERE name = 'Added' AND changelog_id IS NULL;
UPDATE entry_types SET "order" = 1 WHERE name = 'Changed' AND changelog_id IS NULL;
UPDATE entry_types SET "order" = 2 WHERE name = 'Deprecated' AND changelog_id IS NULL;
UPDATE entry_types SET "order" = 3 WHERE name = 'Removed' AND changelog_id IS NULL;
UPDATE entry_types SET "order" = 4 WHERE name = 'Fixed' AND changelog_id IS NULL;
UPDATE entry_types SET "order" = 5 WHERE name = 'Security' AND changelog_id IS NULL;
-- Assign custom type orders (1000+ based on creation date)
WITH ordered_customs AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY created_at) + 999 as new_order
FROM entry_types
WHERE changelog_id IS NOT NULL
)
UPDATE entry_types
SET "order" = ordered_customs.new_order
FROM ordered_customs
WHERE entry_types.id = ordered_customs.id;- Similar feature in Miniroll: N/A (Miniroll doesn't have custom types)
- GitHub issue: chrishannah/miniship#40
- Related issues: None
- Design inspiration: Linear (issue labels management), Notion (database properties)
Next Steps:
- Review plan with Chris
- Create database migration
- Implement API endpoints
- Build UI components
- Test thoroughly
- Ship 🚀