Skip to content

Instantly share code, notes, and snippets.

@matthew-gerstman
Created February 12, 2026 01:14
Show Gist options
  • Select an option

  • Save matthew-gerstman/2f9351c281c65944d55b72aaa6197770 to your computer and use it in GitHub Desktop.

Select an option

Save matthew-gerstman/2f9351c281c65944d55b72aaa6197770 to your computer and use it in GitHub Desktop.
Project Folders Cleanup Plan

Project Folders Cleanup Plan

Context

The project folders feature (workspace-scoped folders that organize projects) shipped as an MVP and has accumulated technical debt. The three main areas needing attention are: incomplete error handling in dashboard components, service-layer code quality issues, and gaps in test coverage. This plan covers a full cleanup pass.


Commits

  1. fix: add error handling and rollback to project folder rename operations
  2. refactor: clean up project-folder service validation and remove wrapper methods
  3. fix: add query param validation and consolidate route access checks
  4. fix: align frontend ProjectFolder types with API contract
  5. test: add missing project-folder service tests for edge cases
  6. docs: update project folder spec files with error handling and edge cases

1. Fix Dashboard Error Handling (Commit 1)

Problem

  • project-folder-list-row.tsx — rename has no try/catch at all. If updateProjectFolder fails, the error propagates uncaught and mirror state diverges from server.
  • project-folder-card.tsx — rename catches errors but doesn't roll back the optimistic mirror update on failure.

Changes

dashboard/src/features/projects-grid/project-folder-list-row.tsx

  • Wrap the rename handler in try/catch
  • Add toast.error() notification on failure (matching the card component pattern)
  • Roll back mirror state on API failure by restoring previous name

dashboard/src/features/projects-grid/project-folder-card.tsx

  • Add mirror rollback in the catch block — restore previous folder name if API call fails
  • Ensure toast error message is user-friendly

Pattern

// Before (list row - no error handling)
const handleRename = async (newName: string) => {
  mirror.update(folder.id, { name: newName })
  await updateProjectFolder(folder.id, { name: newName })
}

// After (with rollback)
const handleRename = async (newName: string) => {
  const previousName = folder.name
  mirror.update(folder.id, { name: newName })
  try {
    await updateProjectFolder(folder.id, { name: newName })
  } catch (error) {
    mirror.update(folder.id, { name: previousName })
    toast.error('Failed to rename folder')
  }
}

Files

  • dashboard/src/features/projects-grid/project-folder-list-row.tsx
  • dashboard/src/features/projects-grid/project-folder-card.tsx

2. Clean Up Service Layer (Commit 2)

Problem

  • hasSiblingNameConflict is a wrapper that exists in both project-folder.service.ts and folder.service.ts with similar logic
  • checkWouldCreateCycle is a trivial wrapper around the shared utility
  • createFolder doesn't check if the parent folder is soft-deleted
  • moveProjectsToFolder silently succeeds with empty array — should validate
  • deleteFolder event publishing errors are logged but could leave dashboard out of sync

Changes

apps/api/src/services/project-folder.service.ts

  1. Remove checkWouldCreateCycle wrapper — call wouldCreateCycle() from folder-utils.ts directly in updateFolder()

  2. Inline hasSiblingNameConflict — simplify by querying directly where used (createFolder, updateFolder). The logic is straightforward: query for sibling with normalized name match.

  3. Add soft-delete validation to createFolder — when parentFolderId is provided, check deletedAt IS NULL on the parent

  4. Validate moveProjectsToFolder input — throw if projectIds array is empty

  5. Improve deleteFolder event error handling — log at warn level instead of silently catching, and include folder ID in log context

Files

  • apps/api/src/services/project-folder.service.ts

3. Tighten Route Validation (Commit 3)

Problem

  • Every endpoint individually checks workspace access with similar boilerplate
  • Query params limit and offset accept any string (including negative numbers)
  • parentFolderId === 'null' string comparison is fragile

Changes

apps/api/src/routes/project-folders.ts

  1. Add numeric validation to query params — use Elysia's t.Numeric() with minimum: 0 for limit/offset

  2. Fix parentFolderId null handling — accept parentFolderId as t.Optional(t.Union([t.String(), t.Null()])) and handle properly instead of string comparison

  3. Extract workspace access check — create a resolveAndAuthorize helper within the route file that handles the common pattern of: get folder -> check workspace access -> return folder. Reduces ~10 lines per endpoint to ~1 line.

Files

  • apps/api/src/routes/project-folders.ts

4. Align Frontend Types (Commit 4)

Problem

  • ProjectFolder interface in dashboard declares parentFolderId?: string | null (optional) but API always sends the field (it's string | null, not optional)
  • moveProjectsToFolder converts null to 'root' on the client, then API converts back — unnecessary round-trip

Changes

dashboard/src/api/project-folders.ts

  • Fix ProjectFolder.parentFolderId type to string | null (not optional)
  • Fix ProjectFolder.description type to string | null (not optional)
  • Remove client-side null -> 'root' conversion in moveProjectsToFolder — send null directly and let API handle it
  • Use ky's searchParams option instead of manual URLSearchParams construction

Files

  • dashboard/src/api/project-folders.ts

5. Add Missing Tests (Commit 5)

Problem

Test file (project-folder.service.test.ts, 314 lines) only covers basic happy paths. Missing coverage for:

  • Name conflict detection (case-insensitive matching)
  • Cycle detection when moving folders
  • Soft-deleted parent validation
  • Event publishing verification
  • moveProjectsToFolder with empty array
  • deleteFolder cascading behavior

Changes

apps/api/src/services/project-folder.service.test.ts

Add test cases:

  1. createFolder — rejects when parent is soft-deleted
  2. createFolder — rejects when sibling has same normalized name
  3. updateFolder — rejects when move would create cycle
  4. updateFolder — rejects name conflict with existing sibling
  5. moveProjectsToFolder — rejects empty projectIds array
  6. deleteFolder — verifies event is published to workspace
  7. deleteFolder — succeeds even when event publishing fails

Files

  • apps/api/src/services/project-folder.service.test.ts

6. Update Spec Files (Commit 6)

Problem

Existing spec files are minimal (11-17 lines each) and don't document error handling, edge cases, or the rename rollback pattern.

Changes

dashboard/src/features/projects-grid/project-folder-card.spec.md

  • Document optimistic update + rollback pattern
  • Document error states and toast notifications
  • Add edge cases (empty name, network failure)

dashboard/src/features/projects-grid/project-folder-list-row.spec.md

  • Document rename behavior matching card pattern
  • Document loading states during API calls

Files

  • dashboard/src/features/projects-grid/project-folder-card.spec.md
  • dashboard/src/features/projects-grid/project-folder-list-row.spec.md

Verification

  1. Lint & typecheck: bun obvious check --changed
  2. Run project folder tests: bun obvious test --files apps/api/src/services/project-folder.service.test.ts
  3. Run dashboard tests: bun obvious test --changed (for any affected dashboard test files)
  4. Manual verification: Start dashboard with bun obvious up --dashboard-only, navigate to a workspace with project folders enabled, and test:
    • Rename a folder, verify success
    • Trigger a rename failure (e.g., disconnect network), verify rollback + toast
    • Create nested folders, move them around
    • Delete a folder with contents

Files Summary

File Action
dashboard/src/features/projects-grid/project-folder-list-row.tsx Fix error handling
dashboard/src/features/projects-grid/project-folder-card.tsx Fix rollback
apps/api/src/services/project-folder.service.ts Refactor validation, remove wrappers
apps/api/src/routes/project-folders.ts Tighten validation, extract helper
dashboard/src/api/project-folders.ts Fix types, simplify client
apps/api/src/services/project-folder.service.test.ts Add edge case tests
dashboard/src/features/projects-grid/project-folder-card.spec.md Update docs
dashboard/src/features/projects-grid/project-folder-list-row.spec.md Update docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment