Skip to content

feat: add Cap Pro Loom concierge migration - #2326

Merged
richiemcilroy merged 14 commits into
mainfrom
feature/loom-concierge-migration-20260921
Oct 20, 2026
Merged

richiemcilroy merged 14 commits into
mainfrom
feature/loom-concierge-migration-20260921

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Cap Pro customers can request a free concierge Loom migration from the dashboard. The page covers the self-service CSV steps and the Loom invite, and shows pending, in-progress, information-needed and complete states.
  • A staff-only queue (/dashboard/admin/loom-migrations) imports customer videos through the existing Loom workflow, with a restricted MP4 upload path for videos Loom won't serve.
  • Completion requires a verified agreed source count, matching Cap count, and no unfinished imports for the request. An expiring import lease lets an interrupted action recover.

Update for current main

  • Merged main (about 1,350 commits). Regenerated the schema as a single additive migration, 0050_loom_concierge, chained from main's 0049 snapshot. It only creates loom_migration_requests and loom_migration_imports.
  • Dropped the emails/ catalog edits, since that directory moved to the marketing repo. The transactional templates stay in packages/database/emails.
  • Restricted MP4 uploads now use getNewVideoPublic, so they respect an organization's private-by-default sharing.
  • Concierge actions return expected failures as { ok: false, error } instead of throwing. Production builds hide thrown server-action messages, so operators and customers would otherwise see a generic error instead of "imports are still processing" or "reconcile the count".
  • Starting an import moves only pending requests to in_progress. It no longer clears an open "Information needed" question.
  • Customers are emailed only when the status or message changes, not on count-only edits.
  • Status colours now use palette tokens that exist. The operator message resets when the status changes, so completion doesn't resend an old question.

Validation

  • Typecheck, Biome on the changed files, and all 63 Loom tests (52 unit, 11 integration).
  • New __tests__/integration/loom-concierge-database.test.ts runs against real MySQL and S3-compatible storage when CAP_LOOM_CONCIERGE_TEST_DATABASE_URL is set, and skips otherwise. It covers Pro, role and operator access, the one-active-request rule, the information round trip, CSV mapping and duplicates, lease contention and expiry, completion gating, presigned MP4 upload to MinIO, private default visibility, and cross-request isolation.
  • Applied every migration, 0000 through 0050, to a fresh MySQL 8 database.
  • Headless browser walkthrough against a local dev server, 23/23 checks:
    • Customer request, invite and answer.
    • Operator validation, CSV failure reporting and MP4 upload. The local media server produced result.mp4, a screenshot and a preview, and the video landed in its Cap space.
    • Completion is blocked while processing and succeeds afterwards. The queue empties and the customer sees "complete".
    • Free owners see the Pro requirement, members don't see the concierge card, and customers get a 404 on the operator queue.
  • The share-stage-identity unit failures also happen on main and are unrelated.

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with no outstanding correctness or repository-rule issues identified in the latest changes.

Summary

The PR adds a Cap Pro Loom concierge migration workflow for customers and staff, including request tracking, CSV-based imports, restricted MP4 uploads, status notifications, import reservations, and database-backed reconciliation.

  • Adds customer-facing migration request and status pages.
  • Adds a restricted operator queue for mapped CSV and MP4 imports.
  • Tracks imported videos per migration request and prevents completion while mapped work remains unfinished.
  • Adds renewable import leases to recover safely from interrupted actions.
  • Adds schema migrations, email templates, documentation, and focused unit tests.

Reviews (7) · Last reviewed commit: "fix: account for long Loom concierge imp..."

Comment thread apps/web/actions/loom-concierge.tsx Outdated
Comment thread apps/web/actions/loom-concierge.tsx Outdated
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

Comment thread apps/web/lib/loom-concierge.ts Outdated
Comment thread apps/web/__tests__/unit/loom-csv.test.ts Outdated
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

Comment thread apps/web/lib/loom-concierge.ts
@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Oct 20, 2026 5:22pm UTC

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 3 potential issues (3 medium).

Open in Capy

Comment thread apps/web/actions/loom-concierge.tsx Outdated
Comment on lines +325 to +331
eq(loomMigrationRequests.id, requestId),
eq(loomMigrationRequests.status, request.status),
isNotNull(loomMigrationRequests.activeOrganizationId),
nextStatus === "completed"
? or(
eq(loomMigrationRequests.activeImportCount, 0),
lte(loomMigrationRequests.activeImportLeaseUntil, new Date()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completion can miss imports created after its unfinished-work check

Medium Severity

For an in-progress request, the unfinished-import query can return empty, then a concurrent CSV import or restricted-upload preparation can reserve the request, insert a mapped uploading video, and release its reservation before this UPDATE runs. The status remains in_progress and activeImportCount is back to zero, so both guards pass despite the newly unfinished work. The customer receives a completion notice and the request leaves the queue; a prepared MP4 upload can no longer be finalized because its request is inactive. Lock the request across the unfinished-work check and completion update, or use a version guard that also detects intervening imports.

Open in Capy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5efae8d. Completion now locks the request row (FOR UPDATE), re-checks status and the import lease, and checks unfinished imports with a locking read, all in one transaction. Import paths take the same row lock before they insert mapped work, so they serialize. The new integration test holds an import transaction open while completion runs. It completes on the old code and is blocked on the new code.

Comment thread apps/web/actions/loom.ts Outdated
!existing.ownerId ||
!existing.ownerEmail ||
normalizeImportEmail(existing.ownerEmail) !== normalizedEmail ||
!existing.rawFileKey ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed automatic imports cannot use MP4 recovery

Medium Severity

The automatic-import path in importLoomVideoForOwner creates videoUploads without rawFileKey, and importLoomVideoWorkflow never persists that key. If a concierge CSV import later reaches phase error, this new recovery path rejects it because existing.rawFileKey is null, even with the correct owner and an authorized MP4. Retrying the CSV also rejects the existing video, while completion rejects its mapped error record. Persist or safely derive the automatic import's raw-file key so the operator can recover it without deleting the video or editing database records.

Open in Capy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5efae8d. For an errored automatic import with no stored rawFileKey, the restricted-upload path derives the same key the import workflow writes ({ownerId}/{videoId}/raw-upload.mp4). It saves the key on the upload row inside the lease-checked transaction, so finishing the upload works. An integration test covers recovering a failed CSV import through the MP4 path end to end.

}),
);
started += result.importedCount;
failures.push(...result.results.filter((row) => !row.success));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operator queue discards space-mapping failures

Medium Severity

When space creation, video assignment, or owner membership fails after an import starts, processLoomCsvRows returns success: true together with an error explaining the failed mapping. This filter discards those rows, so the new operator queue reports zero rows needing review and never displays the warning. The video can finish outside its requested space or without the required membership, and retrying the CSV refuses it as already imported. Retain and display rows with errors even when their video import started, distinguishing mapping failures from failed starts.

Open in Capy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5efae8d. The queue now keeps rows that started but returned an error, counts them separately ("started rows need space mapping"), and lists them with their requested space.

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

@richiemcilroy
richiemcilroy merged commit 4e400a9 into main Oct 20, 2026
39 checks passed

This branch was successfully deployed

2 active (1 outdated) and 1 inactive deployments
Preview – cap-web — bead7365 Deployed Oct 20, 2026 by vercel[bot]
Preview – cap-status — bead7365 Deployed Oct 20, 2026 by vercel[bot]
Preview — 5b2bc8ad Deployed Sep 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant