Skip to content

Instantly share code, notes, and snippets.

@chrishannah
Created March 23, 2026 22:00
Show Gist options
  • Select an option

  • Save chrishannah/cfda0140e4d78d945c439bdcb2a7ecf1 to your computer and use it in GitHub Desktop.

Select an option

Save chrishannah/cfda0140e4d78d945c439bdcb2a7ecf1 to your computer and use it in GitHub Desktop.
Implementation Plan: Entry Types Management UI (miniship#40)

Implementation Plan: Entry Types Management UI

Issue: chrishannah/miniship#40
Status: Planning
Estimated Time: 4-6 hours
Complexity: Medium
Risk: Medium (data migration for reordering, cascading deletes)

Overview

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.

Architecture

1. New Routes

Page: app/dashboard/[id]/settings/types/page.tsx

  • Server component for authentication + changelog ownership check
  • Renders EntryTypesManager client component
  • Breadcrumb: Dashboard → Changelog → Settings → Entry Types

2. API Endpoints

GET /api/changelogs/[id]/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
    }
  ]
}

PATCH /api/types/[id]

  • 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 /api/types/[id]

  • 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 replaceWith query param to migrate items to another type
  • Response: Success or error with usage count

PATCH /api/changelogs/[id]/types/reorder

  • 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

3. Database Changes

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(),
});

4. UI Components

EntryTypesManager (Client Component)

Main container:

  • Fetch types from GET endpoint
  • Manage drag-and-drop state
  • Handle edit/delete actions
  • Show loading/error states

EntryTypeCard (Sub-component)

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

EditTypeModal (Dialog)

Modal for editing type:

  • Fields: name, color (color picker), icon (emoji picker or text input)
  • Preview of type appearance
  • Save/Cancel buttons
  • Validation feedback

DeleteTypeModal (Dialog)

Confirmation dialog:

  • Show usage count if > 0
  • If in use: require selecting replacement type
  • Warning message
  • Confirm/Cancel buttons

5. Features

List View

  • 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

Reordering

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

Editing

  • 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

Deleting

  • 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

6. Implementation Steps

Phase 1: Database + API (2-3 hours)

  1. Add order column to entryTypes table
  2. Write data migration script
  3. Implement GET /api/changelogs/[id]/types
  4. Implement PATCH /api/types/[id]
  5. Implement DELETE /api/types/[id]
  6. Implement PATCH /api/changelogs/[id]/types/reorder
  7. Test all endpoints

Phase 2: UI Components (2-3 hours)

  1. Create EntryTypesManager component
  2. Create EntryTypeCard component
  3. Create EditTypeModal component
  4. Create DeleteTypeModal component
  5. Wire up API calls
  6. Add loading/error states
  7. Test UI flows

Phase 3: Polish (30-60 min)

  1. Add breadcrumb navigation
  2. Style consistency with rest of app
  3. Mobile responsiveness
  4. Accessibility (keyboard nav, ARIA labels)
  5. Error handling UI

Technical Decisions

Reordering Strategy

  • Decision: Use order integer 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)

Delete Strategy

  • 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

Default vs Custom Types

  • Decision: Store both in same table, use changelogId = null for defaults
  • Rationale: Already implemented this way, no migration needed
  • UI: Gray out defaults, disable edit/delete actions

Color Picker

  • Decision: Simple hex input with color preview (native input type="color")
  • Rationale: Lightweight, works everywhere
  • Alternative: react-color library if users want more control

Icon Picker

  • Decision: Text input with 1-2 character limit
  • Rationale: Simple, supports any emoji
  • Alternative: Emoji picker library (future enhancement)

Edge Cases

  1. Duplicate order values

    • Migration script assigns unique orders
    • Reorder API reassigns all orders sequentially
    • No duplicates possible after save
  2. Deleting type that's in use

    • API returns 400 with usage count
    • UI forces replacement type selection
    • Migration happens in transaction
  3. Editing default type

    • API returns 403
    • UI disables edit button
    • Clear "Default" badge shown
  4. Network errors during reorder

    • Optimistic update reverts on error
    • Toast notification shows error
    • User can retry
  5. Changelog with no custom types

    • Show only default types
    • "No custom types yet" message
    • Encourage creation (link to add type flow if it exists)

Testing Checklist

  • 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

Future Enhancements

  • 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)

Migration Notes

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;

References

  • 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:

  1. Review plan with Chris
  2. Create database migration
  3. Implement API endpoints
  4. Build UI components
  5. Test thoroughly
  6. Ship 🚀
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment