Files
sim/packages/db
Waleed 89ae738745 feat(folders): soft-delete folders and show in Recently Deleted (#4001)
* feat(folders): soft-delete folders and show in Recently Deleted

Folders are now soft-deleted (archived) instead of permanently removed,
matching the existing pattern for workflows, tables, and knowledge bases.
Users can restore folders from Settings > Recently Deleted.

- Add `archivedAt` column to `workflowFolder` schema with index
- Change folder deletion to set `archivedAt` instead of hard-delete
- Add folder restore endpoint (POST /api/folders/[id]/restore)
- Batch-restore all workflows inside restored folders in one transaction
- Add scope filter to GET /api/folders (active/archived)
- Add Folders tab to Recently Deleted settings page
- Update delete modal messaging for restorable items
- Change "This action cannot be undone" styling to muted text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(testing): add FOLDER_RESTORED to audit mock

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): atomic restore transaction and scope to folder-deleted workflows

Address two review findings:
- Wrap entire folder restore in a single DB transaction to prevent
  partial state if any step fails
- Only restore workflows archived within 5s of the folder's archivedAt,
  so individually-deleted workflows are not silently un-deleted
- Add folder_restored to PostHog event map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(folders): simplify restore to remove hacky 5s time window

The 5-second time window for scoping which workflows to restore was
a fragile heuristic (magic number, race-prone, non-deterministic).
Restoring a folder now restores all archived workflows in it, matching
standard trash/recycle-bin behavior. Users can re-delete any workflow
they don't want after restore.

The single-transaction wrapping from the prior commit is kept — that
was a legitimate atomicity fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): regenerate folder soft-delete migration with drizzle-kit

Replace manually created migration with proper drizzle-kit generated
one that includes the snapshot file, fixing CI schema sync check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(db): fix migration metadata formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): scope restore to folder-deleted workflows via shared timestamp

Use a single timestamp across the entire folder deletion — folders,
workflows, schedules, webhooks, etc. all get the exact same archivedAt.
On restore, match workflows by exact archivedAt equality with the
folder's timestamp, so individually-deleted workflows are not
silently un-deleted.

- Add optional archivedAt to ArchiveWorkflowOptions (backwards-compatible)
- Pass shared timestamp through deleteFolderRecursively → archiveWorkflowsByIdsInWorkspace
- Filter restore with eq(workflow.archivedAt, folderArchivedAt) instead of isNotNull

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(workflows): clear folderId on restore when folder is archived or missing

When individually restoring a workflow from Recently Deleted, check if
its folder still exists and is active. If the folder is archived or
missing, clear folderId so the workflow appears at root instead of
being orphaned (invisible in sidebar).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): format restoreFolderRecursively call to satisfy biome

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): close remaining restore edge cases

Three issues caught by audit:

1. Child folder restore used isNotNull instead of timestamp matching,
   so individually-deleted child folders would be incorrectly restored.
   Now uses eq(archivedAt, folderArchivedAt) for both workflows AND
   child folders — consistent and deterministic.

2. No workspace archived check — could restore a folder into an
   archived workspace. Now checks getWorkspaceWithOwner, matching
   the existing restoreWorkflow pattern.

3. Re-restoring an already-restored folder returned an error. Now
   returns success with zero counts (idempotent).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): add archivedAt to optimistic folder creation objects

Ensures optimistic folder objects include archivedAt: null for
consistency with the database schema shape.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(folders): handle missing parent folder during restore reparenting

If the parent folder row no longer exists (not just archived), the
restored folder now correctly gets reparented to root instead of
retaining a dangling parentId reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 20:06:04 -07:00
..
2026-02-19 13:11:35 -08:00