Back to Document Hub
Product Operations · Pre-launch

Summit Editing — User Journeys

Credit-based video editing for real-estate media companies. This document traces the actual flows in code — routes, server functions, and Supabase RPCs — for customers, editors, and the admin/operator, plus the order state machine, billing, and the gaps that matter for launch.

Target launch: July 6, 2026 Roles: customer · editor · admin Assignment: auto on submit (least-loaded editor) Compiled 2026-07-06
1

Personas

Four people touch the system. Three are DB roles (app_role: customer · editor · admin); the "premium editor" is an operational label, not a separate role.

customer

Real-estate media company

Owner of a small media business who shoots raw property footage (drone, walkthroughs, agent on-camera) but doesn't want to edit. Buys credits in bulk and submits jobs.

Goal: a finished, branded property video back in 48h (24h with the Rush add-on) — trackable, downloadable, revisable. Gets in via the invite-only waitlist; every order is paid (no free starter credits).

editor

Standard editor

Internal/contract editor invited by the admin. Handles the bread-and-butter Standard packages.

Goal: see assigned orders in a queue, work them before the due date, deliver the file, and turn revisions around fast.

editor

Premium editor

Same role, trusted with Premium/longer-form and advanced add-ons (on-camera advanced, AI effects, voiceover). editor_specialty exists but is display-only — auto-assignment routes by load, not specialty.

Goal: same as standard editor, plus premium-tier work. Specialty-aware routing is a planned enhancement.

admin

Admin / Operator

The launch operator and hub of the system: oversees the auto-assigned queue (and reassigns/ overrides when needed), manages the editor roster and customers, grants/reconciles credits, handles contact inquiries.

Goal: keep the queue moving — auto-assign puts every paid order on an editor's queue at submit; the admin catches the exceptions (no eligible editor, over-capacity, reassignment).

Naming note Product/UX says "client," but the DB enum value is customer. Roles live in a separate user_roles table, checked via the has_role(uid, role) SECURITY DEFINER function — never as a column on profiles.
2

End-to-End Journeys

2.1 — Customer Journey

Customer journey flowchart
flowchart TD A([Visit marketing pages]) --> B[Sign up with invite code] B --> C[Verify email] C --> D[Buy credits
Stripe embedded checkout] D --> E[Submit edit request
submit_edit_order RPC] E -->|Auto-assigned at submit
least-loaded active editor under capacity| F[Order: awaiting_footage
edit footage_link here only] F --> G[Editor works it
accepted → in_progress] G --> H[Order: in_progress
email: order-status-update] H --> I[Order: ready_for_delivery
email: delivery-file-added] I --> J[Download via signed URL] J --> K{Satisfied?} K -->|Yes| L([Delivered]) K -->|No| M[Request revision
request_revision RPC] M --> H F -->|Cancel while awaiting| N([Cancelled — credits restored])

Discover → Waitlist → Sign up (invite-only beta)

  1. Discover. Public marketing pages — /, /pricing, /how-it-works, /ai-examples, /faq. During the invite-only beta the primary CTA points to /waitlist.
  2. Join the waitlist. /waitlist embeds a Fillout form (WAITLIST_URL). This is the front door: prospects request access and the team reaches out to invite them when a spot opens. There is no invite-code field in the app — access is gated by who gets invited off the waitlist, not by a code typed at signup.
  3. Create the account at /signup once invited. The visitor must download the Upload Guide PDF and tick the confirmation box before the buttons enable. Required: full name, email, password (min 8) — or Continue with Google (OAuth signup is available). No invite code is entered here.
  4. Server-side account creation. signUp() calls supabase.auth.signUp. On auth.users INSERT the handle_new_user() trigger creates the profile + customer role and grants the current starter_credits from app_settingsset to 0 for launch, so new customers start with an empty balance and must buy credits before their first edit. (Comped testers are topped up by an admin via the customer credit-grant tool.)

Verify email

  1. Verification email (Supabase Auth via Lovable email infra, signup template). The page swaps to a "Verify your email" panel.
  2. Click the link → redirected to /dashboard. The _authenticated.tsx layout gates everything under it.

Buy credits

  1. Go to /buy-credits. Seven USD packs (100 → 10,000 credits) + Annual Partner plan, with volume discounts.
  2. Stripe embedded checkout → on completion redirects to /checkout/return?session_id=….
  3. Credits granted server-side. checkout.session.completedpayments/webhook: idempotency-guards via credit_transactions, maps price lookup_key → credits, inserts a transaction, increments profiles.credit_balance. No orders row is created for a credit buy. Balance is server-authoritative.
  4. Return page calls checkStatus to verify the real session status (no more false green check).

Submit an edit request (3-step checkout wizard)

  1. Go to /submit. The order form is a 3-step wizard (a StepIndicator at the top):
    • Step 1 — Your details. Business/contact details (a StandingDetailsCard prefills returning customers) and the required delivery email.
    • Step 2 — Order specifics. Package (Standard 30s / 60s, Premium 60s / 120s); clip count (extras bill at 5 credits each over the package's included clip limit); format (Reel/Horizontal/Both); add-ons (intro-outro, on-cam advanced, captions, voiceover, Rush — 24h, AI effects 1×/3×/5×, property lines); footage link, branding link, reference video link, music, on-screen text, notes.
    • Step 3 — Review & confirm. A line-item credits summary shows package + add-ons + clip overage vs. the current balance before the customer confirms.
  2. Confirm → submit_edit_order RPC. Client sends labels and canonical slugs; costing is 100% server-side from package_prices / addon_prices (unknown slugs rejected). The RPC deducts credits, sets due_at = now + 24h if the Rush add-on is present, else 48h, inserts the order at awaiting_footage / payment_status='paid' / currency='USD', writes a debit transaction, and then calls auto_assign_order() (see the Editor journey). Returns the order id → navigates to /dashboard/orders/:id.

Track → Deliver → Download

  1. Order starts at awaiting_footage, already assigned. Auto-assignment normally picks an editor at submit; the order lands on that editor's queue immediately (it only stays unassigned if no active editor has capacity). The customer may edit only footage_link / special_instructions here (enforced by the orders_restrict_customer_updates trigger).
  2. Track at /dashboard (credit balance, order list, StatusGauge) and /dashboard/orders/:id. Status emails fire on in_progress and ready_for_delivery (order-status-update).
  3. Receive delivery at /dashboard/orders/:id/delivery when status hits ready_for_delivery; each file also fires delivery-file-added.
  4. Download via a 10-minute signed URL from the deliveries bucket; logged to order_delivery_downloads.

Request a revision → revision loop

  1. Request a revisionrequest_revision RPC (up to 20 attachments → order_revision_attachments). Moves the order delivered → in_progress and bumps revision_count — the only customer-driven status change RLS whitelists.
  2. Editor notified (revision-requested). The editor/admin drives sub-stages via set_revision_stage (queued → in_progress → complete), notifying both parties (revision-progress). The loop can repeat.

2.2 — Editor Journey

Editor journey flowchart
flowchart TD A([Invited by admin
inviteEditor RPC]) --> B[Set password via invite link] B --> C[Log in at /login] C --> D[Set availability
set_editor_availability RPC] D --> E[Auto-assigned an order at submit
auto_assign_order: least-loaded, in-app notification] E --> F[View queue at /editor/index
getEditorQueue: my orders by due_at] F --> G[Open order workspace
/editor/orders/:id] G --> H[Move: accepted] H --> I[Move: uploading_footage] I --> J[Move: in_progress
email: order-status-update to customer] J --> K[Upload delivery files
deliveries bucket + notifyDeliveryUploaded] K --> L[Mark: ready_for_delivery
email: delivery-file-added] L --> M[Mark: delivered] M --> N{Revision requested?} N -->|Yes — customer triggers| O[Work revision
set_revision_stage] O --> L N -->|No| P([Done]) E -->|Due-soon cron ~6h| Q[Due-soon email alert]
  1. Invited (no self-signup). Admin runs inviteEditorauth.admin.inviteUserByEmail, grants the editor role, sets is_editor_active=true. Editor sets a password via the invite link, logs in at /login.
  2. Set availability at /editor/availabilitysetEditorAvailability. The two privileged fields (is_editor_active, max_concurrent_orders, default 5) are written through the set_editor_availability SECURITY DEFINER RPC (a trigger + column REVOKE block direct writes); specialty/bio go through the normal user update.
  3. Receive an assignment (usually automatic). When a customer submits, the submit_edit_order RPC calls auto_assign_order(), which — while the auto_assign flag in app_settings is on (it is) — picks the least-loaded active editor still under max_concurrent_orders (row-locked with SKIP LOCKED to avoid double-booking), sets assigned_to, and inserts an order_assigned in-app notification. An admin can still run assign_order manually to (re)assign; that path also enqueues the editor-assignment email.
  4. See the queue at /editor/indexgetEditorQueue: orders where assigned_to = me and not cancelled, sorted by due_at. Overdue = due_at < now.
  5. Work the order at /editor/orders/:id, which redirects to the shared, role-aware /admin/orders/:id workspace. RLS scopes editors to their assigned order.
  6. Move status forward (accepted → uploading_footage → in_progress) via updateOrderStatus; in_progress emails the customer. assertCanEditOrder allows only the assigned editor or an admin.
  7. Upload the delivery to the deliveries bucket (rows in order_deliveries); notifyDeliveryUploaded emails the customer per file.
  8. Mark ready / delivered. ready_for_delivery unlocks the delivery page; delivered stamps delivered_at.
  9. Handle revisionsrevision-requested email, work it, drive set_revision_stage.
  10. Due-soon alerts. A Bearer-protected cron hook (api/public/hooks/editor-due-soon) scans orders due within ~6h and emails the assigned editor (editor-due-soon), de-duped via due_soon_alerted_at.
Auto-assign routes by load, not specialty Auto-assignment (auto_assign_order) and the manual assign_order RPC both pick on current load + capacity among active editors — neither matches on editor_specialty. Premium vs. standard remains an operational label; specialty-aware routing is a planned enhancement.

2.3 — Admin / Operator Journey

Admin / operator journey flowchart
flowchart TD A([New paid order arrives
auto-assigned at submit]) --> B[/admin/index orders board
all orders, filterable/] B --> C{Needs action?} C -->|Reassign / no editor free| D[Open /admin/orders/:id] D --> E[(Re)assign editor
assign_order RPC — manual override] E --> F[Adjust due date
updateOrderDueDate] F --> G[Monitor progress
order_status_history audit trail] G --> H{Issue?} H -->|Notes / override| I[Write internal notes
Change status if needed] H -->|No issue| J([Order delivered]) C -->|Inbox| K[Triage contact submissions
/admin/inbox — Gmail threads] C -->|Editors| L[Manage editor roster
/admin/editors] L --> M[Invite / remove editors
inviteEditor, removeEditorRole] L --> N[Check workload
editor_workload_summary RPC] C -->|Customers| O[Customer detail
/admin/customers/:id] O --> P[Grant / deduct credits
adminAdjustCredits] C -->|Payment issue| Q[Reconcile a Stripe session
/admin/reconcile]
  1. Orders board at /admin/index — all orders, filterable by status / "mine" / overdue. Links to inbox and the due-soon console.
  2. Triage the inbox at /admin/inbox — reads Gmail threads via inbox.functions. The contact form writes a contact_submissions row and relays an email to info@summitediting.co.
  3. Open an order at /admin/orders/:id — the central workspace (shared with editors, role-aware; admin-only actions are hidden from editors). From here: (re)assign, set due date, change status, upload deliveries, drive revision stages, write internal notes (order_internal_notes), and cancel & refund.
  4. Reassign / handle the exceptions. Auto-assign already put most orders on an editor's queue at submit, so the admin's job here is the exceptions: reassign via updateOrderAssignmentassign_order RPC (requires admin, validates an active editor, soft-warns on over-capacity but still assigns, sets assigned_to, notifies the editor), and pick up any order that auto-assign left unassigned because no editor had capacity.
  5. Manage editors at /admin/editors (Editors tab): inviteEditor, removeEditorRole (refused while the editor has active orders), and editor_workload_summary(). Editor max_concurrent_orders here is what auto-assign's capacity cap reads.
  6. Manage customers at /admin/customers (Customers tab) → customer detail (/admin/customers/:id): view a customer's orders and balance, and grant or deduct credits (adminAdjustCredits) — this is how comped testers are topped up now that starter credits are 0.
  7. Reconcile a payment at /admin/reconcile (adminReconcileSession): paste a Stripe session_id to credit a purchase that the webhook missed (idempotent — a session already processed is a no-op).
  8. Monitor due-soon emails at /admin/due-soon-emails (Due-soon emails tab). Note: customers self-cancel only while awaiting_footage; admins can cancel & refund from the order workspace.
3

Order Lifecycle State Machine

States are the order_status enum. Orders enter at awaiting_footage and are auto-assigned to an editor at submit (the status is unchanged — assignment just sets assigned_to). Customers can only move delivered → in_progress (revision) and awaiting_footage → cancelled (self-cancel); an admin can also cancel & refund from the order workspace. Everything else is admin/editor-driven. There is no illegal-transition guard yet.

Order lifecycle state diagram
stateDiagram-v2 direction TB [*] --> awaiting_footage : submit_edit_order (customer)
auto_assign_order sets assigned_to awaiting_footage --> cancelled : cancel_order (customer / admin) awaiting_footage --> accepted : admin / editor accepted --> uploading_footage : admin / editor uploading_footage --> in_progress : admin / editor
email: order-status-update in_progress --> ready_for_delivery : admin / editor
email: delivery-file-added ready_for_delivery --> delivered : admin / editor
email: order-status-update delivered --> in_progress : request_revision (customer)
revision_count++ cancelled --> [*] delivered --> [*]
customer-notified status internal status customer-triggered transition terminal: cancelled

Transition table — who triggers what, which email fires

FromToWhoMechanismEmail(s)
awaiting_footageCustomersubmit_edit_order (→ auto_assign_order)— (credits deducted; editor gets in-app order_assigned)
awaiting_footagecancelledCustomercancel_orderorder-cancelled; order-cancelled-editor if assigned
awaiting_footageacceptedAdmin/EditorupdateOrderStatus
accepteduploading_footageAdmin/EditorupdateOrderStatus
uploading_footagein_progressAdmin/EditorupdateOrderStatusorder-status-update
in_progressready_for_deliveryAdmin/EditorupdateOrderStatus + uploadorder-status-update; delivery-file-added/file
ready_for_deliverydeliveredAdmin/EditorupdateOrderStatus— (stamps delivered_at)
deliveredin_progressCustomerrequest_revisionrevision-requested (editor)
(revision)sub-stageAdmin/Editorset_revision_stagerevision-progress (cust + editor)
(assignment)unchangedAuto (submit) / Admin (override)auto_assign_order / assign_orderin-app order_assigned (auto); editor-assignment email (manual)

Audit: every status change is auto-logged to order_status_history by the log_order_status_change trigger (from/to/by); notes attach to the freshly logged row.

4

Credit & Billing Journey

Credits are a unit-less internal currency. The balance is server-authoritative — a column guard raises if a non-admin tries to change credit_balance.

BUY BALANCE SPEND REFUND /buy-credits profiles. /submit cancel_order Stripe checkout ----> credit_balance -----> submit_edit_order -----> (awaiting_footage only) | (authoritative) deducts cost restores credits v ^ pkg + addons + overage | checkout.session | | v .completed webhook: | v credit_transactions * idempotency check +--------------- credit_transactions delta > 0 * +credit_transactions delta < 0 reason=order_cancelled * +credit_balance reason=edit_request * NO orders row
  • Currency: credit packs are priced in USD, and new orders now stamp currency='USD' too (the earlier CAD default was fixed; already-charged rows keep the currency they were charged in). Edit orders record amount_cents=0 — they're paid in credits, not dollars — and credits are redeemable across all edit types.
  • Pricing source of truth: package_prices (slug → base_credits, clip_limit) and addon_prices (slug → credits, is_rush). Overage = 5 credits/extra clip. The client sends slugs; the RPC never trusts client-supplied amounts.
  • Refund path: the only automated refund is cancel_order, and only while awaiting_footage — it restores credits_spent and writes an order_cancelled transaction. No partial/post-work refund and no in-app Stripe-dollar refund (a dollar refund is done manually in Stripe; credits are the in-app currency).
5

Edge Cases & Gaps

Auto-assign resolved the admin bottleneck The pre-launch "admin must manually assign every order" bottleneck is resolved. Orders now auto-assign at submit (auto_assign_order, flag on in app_settings) to the least-loaded active editor under capacity, with an in-app notification. The remaining edge is saturation: if every active editor is at max_concurrent_orders, auto-assign leaves the order awaiting_footage and unassigned until an admin reassigns or an editor frees up — there's no queue-overflow alert yet, so the admin should watch the unassigned filter on the orders board.
ScenarioWhat happens todayState
Failed / abandoned paymentNo webhook → no credits, no order. Return page now verifies real session status (old version showed a false green check).fixed
Underpayment via tampered priceWas possible (regex on a client string). Now costed server-side from price tables; unknown slugs rejected.fixed
RLS-denied accessQuery returns empty/denied — but most routes lack isError handling, so it hangs on "Loading…" or shows a false "No edits yet."open
New order needs an editorAuto-assigned at submit to the least-loaded active editor under capacity (auto_assign_order). No admin action needed on the happy path.works
All editors at capacityAuto-assign finds no eligible editor and leaves the order awaiting_footage/unassigned until an editor frees up or an admin reassigns. No overflow alert yet.watch
Revision after deliverySupported & safe — the one customer-driven status change RLS whitelists. Editor notified; sub-stages tracked.works
Uninvited visitor tries to sign upAccess is controlled by the invite-only waitlist (the public CTA points to /waitlist), not an in-app invite code. /signup itself has no code gate; gating is operational (who gets invited off the waitlist).by design
New customer with no creditsStarter credits = 0, so a fresh account has an empty balance and submit_edit_order raises "Insufficient credits" until they buy. Comped testers are topped up via adminAdjustCredits.by design
Cancel after awaiting_footageCustomers can only self-cancel while still awaiting footage. Once work starts, an admin can cancel & refund from the order workspace (refunds credits, emails both parties).works
No-credit submitsubmit_edit_order raises "Insufficient credits" before creating the order; UI also disables submit.works
Over-capacity editor (manual assign)Manual assign_order still assigns but returns over_capacity:true as a soft warning. The capacity count now includes accepted/uploading_footage (under-count fixed), so auto-assign's hard cap and this warning reflect true load.fixed
Admin in editor dropdownAdmins can appear in the dropdown; assigning one then errors. Filter to role='editor'.open
Editor availability writeWas silently failing (trigger + REVOKE). Fixed via the set_editor_availability SECURITY DEFINER RPC.fixed
Duplicate status emailsServer fn and admin UI both send on status change. Remove the client-side send.open
Unauthenticated cron hookeditor-due-soon now requires a Bearer secret (constant-time compare). Was open to anyone.fixed
Brand inconsistencyAuth emails branded "summit-edit-flow"; contact email domain updated to info@summitediting.co to match sender notify.summitediting.co. Auth email branding still needs alignment.open
Illegal status jumpsNo transition guard — admin/editor can set any status (e.g. skip stages, delivered→in_progress outside revision).post-launch
6

Touchpoint Matrix

Each journey step → route/screen → server function or RPC → email fired.

Customer

StepRoute / ScreenServer fn / RPC / TriggerEmail
Discover/, /pricing, /how-it-works, /ai-examples, /faq
Join waitlist/waitlistFillout embed (WAITLIST_URL)
Sign up (invited)/signupsignUphandle_new_user (grants starter_credits=0)signup (verify)
Verify emaillink → /dashboardSupabase Auth + lovable/email/auth/webhook(verify link)
Buy credits/buy-creditsStripeEmbeddedCheckout
Payment confirm/checkout/returncheckStatus; payments/webhook
Submit edit/submitsubmit_edit_order RPC
Track status/dashboard, /dashboard/orders/:idorder select (RLS); StatusGaugeorder-status-update
Receive delivery/dashboard/orders/:id/deliveryorder_deliveries; signed URLdelivery-file-added
Download(same)logs order_delivery_downloads
Request revision/dashboard/orders/:idrequestRevisionrequest_revisionrevision-requested (editor)
Revision progress/dashboard/orders/:id(editor's set_revision_stage)revision-progress
Cancel (early only)/dashboard/orders/:idcancelOrdercancel_orderorder-cancelled
Contact/contactapi/public/contact (+Gmail relay)contact-confirmation

Editor

StepRoute / ScreenServer fn / RPCEmail (received)
Invitedlink → /logininviteEditorinviteUserByEmailSupabase invite
Set availability/editor/availabilitysetEditorAvailabilityset_editor_availability
Get assigned(in-app notification)auto_assign_order (at submit) / assign_order (admin override)editor-assignment (manual only)
View queue/editor/indexgetEditorQueue
Work order/editor/orders/:id/admin/orders/:idupdateOrderStatus (RLS: assigned only)
Upload delivery/admin/orders/:idupload → order_deliveries; notifyDeliveryUploaded
Mark ready/delivered/admin/orders/:idupdateOrderStatus
Revision work/admin/orders/:idsetRevisionStageset_revision_stagerevision-requested, revision-progress
Due-soon alert(cron)api/public/hooks/editor-due-sooneditor-due-soon
Order cancelled(notification)cancel_order (customer)order-cancelled-editor

Admin

StepRoute / ScreenServer fn / RPCEmail
Orders board/admin/indexorder select (full access)
Inbox/admin/inboxinbox.functions (Gmail)
Reassign order (override)/admin/orders/:idupdateOrderAssignmentassign_order (auto-assign already ran at submit)editor-assignment
Set due date/admin/orders/:idupdateOrderDueDate
Change status/admin/orders/:idupdateOrderStatusorder-status-update
Internal notes/admin/orders/:idsaveAdminNotes (order_internal_notes)
Cancel & refund/admin/orders/:idcancelOrdercancel_orderorder-cancelled (+ editor)
Manage editors/admin/editorsinviteEditor, removeEditorRole, editor_workload_summary()(invite)
Manage customers/admin/customers, /admin/customers/:idgetAdminCustomerDetail, adminAdjustCredits (grant/deduct)
Reconcile payment/admin/reconcileadminReconcileSession (credit a missed Stripe session)
Due-soon console/admin/due-soon-emailsdue-soon-emails.functions