Skip to content

Real Murmur run · SaaS

Tidyroom PRD, brand kit & board

Working name. The run gave the idea this name; the idea's author hasn't named it, and no trademark or domain check has been done (see the brand kit's name notes).

A web-based branded client portal for freelancers and tiny agencies that puts files, payable invoices, messages and project status in one place for $19-$49/month, narrower than all-in-one suites like SuiteDash and Clientish, with branding and payable invoices on every paid plan where Baiivo gates its portal behind Pro.

Typed idea
91 words
Run time
9m 14s
Competitors
9
PRD features
13
Epics
11
Stories
43

Typed Nov 13, 2026 · run Nov 13, 2026 · published with the owner's permission. Raw search results are removed. This run didn't use Atlassian: no Jira project or Confluence space was created, so the board and docs below stayed in Murmur. Curated 14 Nov 2026: the competitor map leaves out three tools the run's checks couldn't reach or confirm as operating, and competitor prices come only from the run's checked competitor table.

Run your own idea. The first one's on us.

No card · 15 seconds is enough

Try free

At a glance

Built for

Solo freelancers and tiny agencies (roughly 1-5 people) who deliver work to clients and currently juggle email, Dropbox and PDF invoices; they use a web-based portal, and their clients access it in a browser.

Must-haves · 6

  • Branded client portal
  • Freelancer accounts and client invites
  • File sharing
+3 more in the PRD

Brand

  • Primary#1F6F5C
  • Secondary#F4EFE6
  • Accent#E8795A
  • Neutral#2B2F2E

Bricolage Grotesque/ DM Sans

01

The typed idea

91 words, typed in. This is the only input Murmur got.

Grok's reply to our own prompt on X (11 Nov 2026). We asked Grok for startup ideas, and one it listed was a client portal for freelancers to share files and invoices. We then asked it to describe the idea. Its reply was typed into Murmur word for word, and that reply is the whole input.91 words

Our own X account (AsumaCodes), asking Grok

“@grok share the idea with description of about 200-600 words.”

Grok's reply, typed into Murmur

This is for freelancers and tiny agencies. The pain is clients constantly asking “where’s that file?” or “did you get the invoice?” across email, Dropbox, and random PDFs. Today everything lives in different places so nothing feels professional or organized. The product is a clean branded portal where the freelancer can share files, show invoices that can be paid, send messages, and let the client track project status. They would pay $19 to $49 a month because it makes them look more established and cuts the back-and-forth that wastes their time.

Typed Nov 13, 2026Full text

02

Competitor map

Live web search for who's already building nearby, with a source link for every competitor.

  • 9 competitors
  • 5 table stakes
  • 4 ways to differentiate

Market summary

Many operating tools already offer a branded client portal. The closest to the full job are Baiivo (files, payable invoices, feedback, project view), Kitchen.co (files, messages, invoicing and payments, white-label), Arpixa (files, invoices and payments, messages, client-visible project status), Clientish (files, payable invoices, live chat, order tracking) and SuiteDash (white-label portal, file sharing, invoicing, projects). Adjacent operating tools cover most of it: Ahsuite (files, messaging, project progress), ManyRequests (requests, billing, branding), Wayfront (orders, files, invoices, messages) and OneSuite (files, documents, payable invoices). No claim is made that no tool does the job, because the phrase searches returned incomplete results. Several tools sit in the $12-$49/month range, so price alone does not separate this product. Searches that failed: none.

Competitors 9

  • Client work and payment workspace for freelancers and small studios, with a client portal covering projects, files, feedback, contracts and invoices.

    PriceFree $0/mo (3 invoices/month); Pro $39/mo (50 invoices/month); Growth $119/mo; Agency pricing listed separately. Stripe processing fees are extra. (baiivo.com), checked Nov 13, 2026

    +2 strengths /−1 weaknesses

    Strengths

    • Aimed at freelancers and small agencies
    • Files, invoices and payment in one portal

    Weaknesses

    • Portal access starts on the Pro plan; the Free plan does not include it
  • Kitchen.co

    kitchen.co

    Client portal software for agencies that centralizes projects, requests, tasks, messages, invoices and files in one window.

    PriceFree $0 (max 2 internal users); Agency White-label $29/month per internal user, billed monthly; extra 1TB storage $30/month. (kitchen.co), checked Nov 13, 2026

    +2 strengths /−1 weaknesses

    Strengths

    • Covers files, messages, invoicing and white-label in one portal
    • Claims 1,600+ creative agencies as users

    Weaknesses

    • Paid white-label plan is priced per internal user
  • Arpixa

    arpixa.io

    AI agency operating system whose branded client portal gives each client one place for projects, deliverables, documents, invoices, payments and messages.

    PriceFree $0 forever; Starter $12/mo; Pro $29/mo; Advanced $89/mo. Flat per workspace, not per user; yearly billing lowers the monthly rate. (arpixa.io), checked Nov 13, 2026

    +2 strengths /−0 weaknesses

    Strengths

    • Portal covers files, invoices, payments, messages and project status
    • Flat workspace pricing, not per user

    Weaknesses

    • Clientish

      clientish.co

      All-in-one client management software for agencies, freelancers and service teams, with a branded client portal.

      Pricenot found (tried clientish.co), checked Nov 13, 2026

      +2 strengths /−0 weaknesses

      Strengths

      • Portal lets clients pay invoices, exchange files and chat
      • Targets freelancers as well as agencies

      Weaknesses

      • SuiteDash

        suitedash.com

        All-in-one white-label business platform with CRM, client portals, projects, documents and billing, with unlimited staff and clients.

        PriceSTART $19/mo ($180/yr); THRIVE $49/mo ($480/yr); PINNACLE $99/mo ($960/yr); lifetime options listed. One price, not per user. Extra storage $5/month per 250GB. (suitedash.com), checked Nov 13, 2026

        +2 strengths /−0 weaknesses

        Strengths

        • Entry plan at $19/mo with unlimited users
        • Portal, files, billing and projects on one database

        Weaknesses

        • Client portal software for agencies and freelancers to share files, tasks, messages and projects with clients.

          Pricenot found (tried ahsuite.com), checked Nov 13, 2026

          +2 strengths /−0 weaknesses

          Strengths

          • Aimed at freelancers and agencies
          • Messaging, files and project progress in one portal

          Weaknesses

          • ManyRequests

            manyrequests.com

            White-label client portal for agencies and productized services covering requests, proofing, subscriptions and invoicing.

            PriceCore $39/mo ($27/mo billed annually); Pro $99/mo ($69/mo annually); Scale $559/mo annually. Extra seats on Core and Pro; unlimited clients on every plan. (manyrequests.com), checked Nov 13, 2026

            +2 strengths /−0 weaknesses

            Strengths

            • Request status visible to clients
            • Unlimited clients on every plan

            Weaknesses

            • Wayfront

              wayfront.com

              Client portal for agencies selling productized services, built around order forms, payment upfront, subscriptions and delivery.

              Priceno public pricing found (tried wayfront.com), checked Nov 13, 2026

              +1 strengths /−0 weaknesses

              Strengths

              • Portal shows orders, files, invoices and messages

              Weaknesses

              • OneSuite

                onesuite.io

                Time tracking, invoicing and client portal software for agencies, consultants and freelancers.

                Price$9/user/month, or $7/user/month billed annually; unlimited clients, projects and invoices; add-ons such as leads, storage and inboxes priced separately. (onesuite.io), checked Nov 13, 2026

                +2 strengths /−0 weaknesses

                Strengths

                • Portal with files and payable invoices
                • Unlimited clients, projects and invoices

                Weaknesses

                Table stakes 5

                • Branded or white-label client portal
                • File sharing between freelancer and client
                • Invoicing with online payment
                • Client messaging or communication in the portal
                • Client-visible project or request tracking

                Where to differentiate 4

                • Focus on a deliberately narrow freelancer setup (files, payable invoices, messages, status) while several competitors bundle CRM, proposals, time tracking and team tools; whether buyers want less is not established from the sources reviewed.
                • Answer the 'where's that file / did you get the invoice' question directly with a single client-facing view of waiting items; only Arpixa's Action Needed panel is stated in the sources reviewed.
                • Offer branding and payable invoices on the entry plan; Baiivo's portal starts on its Pro plan, and several tools price per internal user.
                • Messaging plus project status for solo freelancers is not clearly stated for every tool; gaps from silent sources are not established from the sources reviewed.
                Positioning

                A clean branded portal for freelancers and tiny agencies that puts files, payable invoices, messages and project status in one place at $19-$49/month, so clients stop asking where things are. It positions as simpler and more focused than all-in-one agency suites.

                03

                Product requirements document

                Every feature carries a rationale tied back to the memo or the research.

                • 6 must
                • 4 should
                • 3 could
                • 8 won't
                • 6 success metrics

                One-liner

                A web-based branded client portal for freelancers and tiny agencies that puts files, payable invoices, messages and project status in one place for $19-$49/month, narrower than all-in-one suites like SuiteDash and Clientish, with branding and payable invoices on every paid plan where Baiivo gates its portal behind Pro.

                Problem

                Freelancers and tiny agencies are constantly asked 'where's that file?' and 'did you get the invoice?' because deliverables, invoices and messages are scattered across email, Dropbox and loose PDFs. Today they cope by answering each question by hand from whichever tool holds the answer, which wastes time and makes the business look less established than it is. Existing tools already cover the basics: Baiivo, Kitchen.co, Arpixa, Clientish and SuiteDash all offer branded portals with files, payable invoices and messaging. The gaps are that many bundle CRM, proposals, time tracking and team tools a solo freelancer does not need, Baiivo's portal starts on its Pro plan, Kitchen.co prices white-label per internal user, and only Arpixa is stated to show a client a single list of what is waiting on them. SuiteDash's $19/mo entry plan means price alone does not separate a new entrant, so the pitch is a deliberately narrow portal that answers those two questions directly.

                Target user

                Solo freelancers and tiny agencies (roughly 1-5 people) who deliver work to clients and currently juggle email, Dropbox and PDF invoices; they use a web-based portal, and their clients access it in a browser.

                Features

                Open a card for the full description and rationale

                Must have 6

                1. Each freelancer configures a portal with their logo and colours, and each client gets a private portal of their own.

                  WhyFounder: the product is a 'clean branded portal' that makes the freelancer look more established; Research: branded portal is table stakes.

                2. Freelancers sign up and invite clients by email, and clients get passwordless or simple-login access to only their own portal.

                  WhyRecommended: every other must_have needs authenticated, per-client access to work at all.

                3. Freelancer and client can upload and download files in a per-client or per-project area with upload dates visible.

                  WhyFounder: the portal must let the freelancer share files; Research: file sharing is table stakes.

                4. Freelancer creates a simple invoice, the client views it in the portal and pays by card, and the invoice shows sent, viewed and paid status.

                  WhyFounder: the portal must 'show invoices that can be paid', and the pain is clients asking 'did you get the invoice?'; Research: online invoice payment is table stakes.

                5. A message thread per client or project inside the portal, with email notifications so each side knows when a reply arrives.

                  WhyFounder: the portal must let the freelancer send messages; Research: in-portal messaging is table stakes.

                6. Freelancer sets a simple status and stage per project that the client can view at any time, read-only for the client.

                  WhyFounder: the client should be able to 'track project status'; Research: client-visible project or request tracking is table stakes.

                Should have 4

                1. The portal home shows the client everything outstanding in one list: unpaid invoices, new files and unanswered messages. This goes beyond Arpixa's Action Needed panel by also surfacing the answer to 'where's that file'.

                  WhyResearch: only Arpixa is stated to have an Action Needed panel, and this view answers the founder's core pain directly.

                2. Freelancer sees when a client opened an invoice and can send a reminder from the portal, where Baiivo offers payment follow-up reminders.

                  WhyFounder: 'did you get the invoice?' is a named pain; Research: Baiivo offers payment follow-up reminders.

                3. Branding and payable invoices are included on every paid tier, with flat per-workspace pricing in the $19-$49/month range rather than per internal user.

                  WhyResearch: Baiivo gates its portal behind Pro and Kitchen.co charges per internal user, while the founder named $19-$49/month.

                4. Freelancers start a trial and subscribe on a monthly plan within the $19-$49 range.

                  WhyFounder: customers would pay $19 to $49 a month; Recommended: the build needs a way to collect it.

                Could have 3

                1. The freelancer serves the portal on their own domain or a branded subdomain.

                  WhyResearch: Arpixa, Clientish, ManyRequests and Baiivo offer custom domains or subdomains, which strengthens the 'established' look.

                2. Clients can approve a file or leave a comment on it.

                  WhyResearch: Baiivo and Ahsuite offer file feedback or approvals, and it reduces back-and-forth.

                3. The client receives an email when the project status or stage changes.

                  WhyRecommended: it reinforces the founder's goal of cutting status questions without adding a new tool.

                Won't have 8

                1. No lead pipeline, client database features or onboarding automation.

                  WhyResearch: SuiteDash, Clientish and Wayfront bundle CRM, and the first version competes on being narrower.

                2. No quote builder or contract signing.

                  WhyResearch: Baiivo bundles quotes and contracts, but the founder's scope is files, invoices, messages and status.

                3. No timers, timesheets or hour-based invoicing.

                  WhyResearch: OneSuite competes on time tracking, which is outside the founder's described portal.

                4. No iOS or Android app; the portal is responsive in the browser only.

                  WhyRecommended: a web portal proves the idea fastest, and SuiteDash's branded mobile experience is not a gap to chase first.

                5. No order forms, add-ons, retainers or failed-payment recovery for the freelancer's clients.

                  WhyResearch: Wayfront and ManyRequests target productized-service agencies, a different customer from the founder's freelancers.

                6. No programme to let others resell the platform under their own brand.

                  WhyResearch: SuiteDash's SU1TE Partner Program is a separate business line the founder has not asked for.

                7. No QuickBooks or Xero sync.

                  WhyResearch: OneSuite offers accounting sync, but it is not part of the founder's described scope.

                8. The first version ships in one language for one launch region.

                  WhyRecommended: localisation and tax variations would delay proving the core portal.

                Success metrics 6

                • Activation: share of new freelancer accounts that invite at least one client and share one file or send one invoice

                  ≥60% within 14 days of signup [assumption]

                • Core promise: share of freelancers with ≥3 active clients who rate 4 or 5 on a 1-5 survey question 'Client back-and-forth about files and invoices has dropped'

                  ≥60% of respondents at day 30, measured over the first 90 days after launch [assumption]

                • Invoice completion: share of invoices sent through the portal that clients pay through the portal

                  ≥50% paid within 14 days of sending, measured over the first 90 days [assumption]

                • Trial-to-paid conversion: share of trial starts that become paying subscribers

                  ≥20% within 30 days of trial start [assumption]

                • Subscription revenue: MRR from paying freelancers at a blended $29/month, reconciling to 100 paid users from ~500 trial starts at 20% conversion

                  $2,900 MRR by month 6 [assumption]

                • Paid-subscriber retention: share of paying freelancers still subscribed after 30 days of payment

                  ≥90% monthly retention across months 3-6 [assumption]

                Non-goals 5

                • Not an all-in-one agency suite: no CRM, proposals, contracts or time tracking.
                • Not an internal project management or team task tool; project status is a simple client-facing view.
                • Not an accounting system; invoices exist to be viewed and paid in the portal.
                • Not a platform for productized-service sellers or white-label resellers.
                • Not a native mobile app; it is a web portal.

                Risks 4

                • Crowded market: nine operating competitors already offer branded portals with files, payable invoices and messaging, SuiteDash starts at $19/mo and Arpixa at $12/mo, so price and features alone won't differentiate and the 'narrower is better' bet is unproven.
                • Card payment for invoices needs a payment processor and freelancer payout onboarding; none has been chosen and no processor docs were reviewed, so fees, onboarding friction and compliance are unknown.
                • Clients may ignore the portal and keep emailing, which would undercut the core promise of fewer 'where's that file?' questions.
                • Freelancers may find switching from existing Dropbox and email habits too costly unless setup takes minutes.

                Open questions 6

                • Product name: Tidyroom is a working name; the founder has not named the product — founder to confirm
                • Which payment processor should handle client card payments, and do funds go straight to the freelancer's own account? Recommended: a processor with direct payouts to the freelancer so Tidyroom never holds client money; founder to confirm.
                • What separates the $19 and $49 tiers (number of clients, storage, custom domain)? Founder to define.
                • Free trial or free plan, and is branding or payable invoices gated by plan? Recommended: 14-day trial with every core feature included; founder to confirm.
                • Which single launch region's currency and invoice/tax rules does the first version cover? Founder to choose.
                • Does 'tiny agencies' mean multiple freelancer seats per workspace in the first version, or one login per workspace? Recommended: one login per workspace first; founder to confirm.

                Want this structure for your own PRD? Start from the free product requirements document template, with a guide to each section.

                04

                Brand kit

                Name notes, tagline, values, palette, type and a logo direction, written from the PRD.

                • 4 core colors
                • 5 brand values
                • Bricolage Grotesque headings
                AaTidyroom

                Files, invoices and updates, all in one room.

                • Primary#1F6F5C
                • Secondary#F4EFE6
                • Accent#E8795A
                • Neutral#2B2F2E

                Bricolage Grotesque / DM Sans / JetBrains Mono5 brand values

                Brand values 5

                • Clarity over clutter
                • Professional without being corporate
                • Respect for the client's time
                • Calm, dependable service
                • Simplicity by design

                Semantic colors

                • error #BF3F36
                • success #247A47
                • warning #8A6514

                Name notes

                • The PRD's open questions mark Tidyroom as a working name that the founder has not confirmed, so treat it as provisional rather than final.
                • Possible alternatives to explore: Tidydesk, Clearroom.
                • No trademark or domain check has been done for Tidyroom or the alternatives; run both before committing.

                Logo direction

                Form
                Lowercase wordmark paired with a compact rounded-square symbol that works as an app icon and favicon
                Style
                Friendly, minimal geometric wordmark with a simple symbol, warm and tidy rather than techy
                Symbol concept
                An open doorway or room outline with a small tick or neatly placed block inside, suggesting an orderly space where everything has its place
                Iconography
                Rounded 1.75px line icons with soft corners and open terminals, drawn on a 24px grid in deep green with occasional coral accent fills for key actions like paid or waiting. Simple, friendly, consistent stroke weight, no heavy shadows.
                AvoidDark navy or indigo corporate lookGeneric cloud, folder or paper-plane iconsBroom, mop or cleaning-product imageryGradients and glossy effectsOverly playful mascots

                05

                Tech design

                The engineering brief: architecture, data flow, data models, schema, stack and the first tasks.

                • 10 components
                • 18 data models
                • 12 first tasks

                Overview

                Tidyroom is a multi-tenant web app. Each freelancer workspace has branded, private portals for its clients, covering files, payable invoices, messages and project status. The product lives or dies on the client's portal home, the 'Waiting on you' list. It must answer 'where's that file?' and 'did you get the invoice?' accurately, from one deterministic query, without ever leaking another client's data. Invoice payments run through a swappable payment-provider adapter with direct payouts to the freelancer [assumption], so Tidyroom never holds client money. Tidyroom's own subscription billing sits behind a separate BillingProvider interface. No LLM is used. Everything is rules, templates and SQL. The processor, launch region and plan tiers are still open, so they are kept as configuration and adapters, not hard-coded.

                Data flow

                Freelancer signs up, so Supabase Auth creates a user, then a workspace and subscription (trialing) are created and branding is set. The freelancer adds a client, so clients and client_invites rows are written (hash only) and an invite email goes to notification_outbox. The client opens the link, completes passwordless login and gets a portal_members row. Files: the browser requests a signed upload URL, uploads straight to Storage, then calls complete, so the file becomes 'ready' and a notification is queued. Invoices: the freelancer creates and sends one, so status becomes 'sent' and an invoice_events 'sent' row plus an email are written. The client opens it, so status becomes 'viewed'. Pay: the server creates a payment_attempts row with an idempotency key and a provider checkout, and the client pays on the provider's hosted page. The provider's webhook hits the receiver, which verifies the signature, dedupes via provider_events, validates amount and currency, and sets invoices.status='paid', paid_at, and an invoice_events 'payment_succeeded' row. The portal home recomputes the waiting list on every load, so resolved items vanish. Messages: posting writes messages, updates last_message_at and queues a debounced email. The scheduler drains the outbox.

                Tech stack 7

                frontend
                Next.js (App Router), responsive web; no native app.
                backend
                Next.js route handlers and server actions. Webhook receiver and outbox worker are short, idempotent jobs, so no separate worker service is needed. Serverless time and request-size limits apply, so uploads go direct to Storage.
                database
                Supabase Postgres with row-level security on every table. Storage in a private bucket.
                auth
                Supabase Auth: email and password or magic link for freelancers, passwordless magic link or email code for clients (clients are asked for no password).
                llm
                Not required. The waiting list, invoice totals, status rules and all user-facing summaries are deterministic queries and fixed templates; no feature needs generated text.
                infra
                Vercel hosting, Supabase Storage and a scheduled-jobs runner (Vercel Cron or Supabase scheduled jobs). Third parties that receive user data: Supabase, Vercel, a payment processor (client name, email and invoice amount), and a transactional email provider (recipient email and message notification content). Processor default is Stripe with direct payouts [assumption], behind a swappable adapter because the founder has not chosen one. The email provider is chosen at build time.
                devtools
                TypeScript, Supabase CLI migrations, Vitest, Playwright for end-to-end tests, and GitHub Actions.

                Components 10

                • Portal Home & Waiting-on-you Engine (core)

                  Inputs: the signed-in client's client_id (resolved from portal_members, never from a URL parameter), plus rows from invoices, files, file_receipts, message_threads and messages. Output: one ordered list of outstanding items for that client. Approach: three deterministic queries merged in application code. (1) Unpaid invoices: invoices.status IN ('sent','viewed'); overdue is derived from due_date and never stored; drafts and void invoices never appear. (2) New files: files.uploaded_by_role='freelancer', upload_status='ready', and no file_receipts row for this client's user. (3) Unanswered messages: threads whose latest message has sender_role='freelancer' and needs_reply=true, with no later client message. Sort order: overdue invoices, then other unpaid invoices by due_date, then unanswered messages, then new files by created_at. Each row links straight to the invoice, file or thread. Summary lines such as '2 invoices unpaid, total 840.00 USD' come from fixed templates filled with computed values, never free text. The freelancer gets the mirror view: what each client has not opened or paid. Constraints it must never violate: never show another client's items, never show an invoice as paid before a verified provider event, never include draft or void invoices, never show an item the client already resolved, and compute money only in integer minor units. Cross-tenant isolation is enforced twice: Postgres row-level security keyed on portal_members and workspaces.owner_user_id, and server-side checks on every route.

                • Tenant Access & Invites

                  Freelancer signup, and client invites by email. Invite tokens are random, and only token_hash is stored in client_invites. The raw token appears once in the emailed link. Invites expire (expires_at) and can be revoked. Accepting an invite creates or links a passwordless (magic-link or email-code) auth user and inserts a portal_members row. Resolves each session to a role (owner or client member) and a workspace. One login per workspace in v1 [assumption].

                • Branding & Portal Shell

                  Renders the client portal under /p/[workspace slug] with the workspace's logo (a Storage path) and validated hex colours from workspaces. Branding is applied on every paid plan, with no plan gate. Custom domains are out of scope for v1.

                • File Service

                  Creates files rows as 'pending' and issues signed upload URLs, so large uploads go straight to Supabase Storage and bypass serverless request limits. A completion call verifies the object exists and flips upload_status to 'ready'. Downloads and previews go through short-lived signed URLs issued only after an access check. The first open by a client writes file_receipts. Upload dates come from created_at. Files live in a private bucket with per-workspace and per-client path prefixes.

                • Messaging & Notification Outbox

                  Per-client or per-project threads (message_threads, messages). Freelancers can clear needs_reply on a message. Every email (invite, new message, invoice sent, reminder, invoice paid) is written to notification_outbox with a unique dedupe_key. A worker sends pending rows via a transactional email provider with retries, so a retry or double-click can't send twice. New-message emails are debounced per thread [assumption].

                • Project Status

                  Freelancer sets projects.status from a fixed list plus a free-text stage. The client sees it read-only. Writes update status_updated_at. Client-side writes are blocked by row-level security.

                • Invoice Service

                  Creates invoices with invoice_line_items. Totals are computed deterministically in integer minor units: subtotal is the sum of quantity times unit_amount_cents per line, plus tax_cents, which defaults to 0 because tax rules for the launch region are undecided. Numbers are allocated atomically from workspaces.next_invoice_number. State machine: draft to sent to viewed to paid, with void allowed from draft, sent or viewed. Viewed is set on the client's first open (first_viewed_at). Reminders are freelancer-triggered, rate-limited [assumption], and recorded in invoice_events and last_reminder_at. Every transition writes an invoice_events row. A paid invoice can't be edited or voided.

                • Payment Provider Adapter & Webhook Receiver

                  A PaymentProvider interface (create hosted checkout for an invoice, parse and verify webhook events) with one implementation, so the processor decision stays replaceable. Default implementation is Stripe with Connect-style direct payouts [assumption]. The provider's exact objects, events, fees and onboarding steps are verified against its docs at build time. Starting a payment creates a payment_attempts row with a deterministic idempotency_key and passes the same key to the provider. The webhook endpoint verifies the signature, inserts into provider_events (unique provider + provider_event_id) and processes each event once. Only a verified event can mark an invoice paid, never the browser redirect. It also checks that the paid amount and currency match invoices.total_cents and invoices.currency. Webhook signing secrets and API keys live only in server-side environment variables on the host, never in tables or client bundles. Payout onboarding state is tracked in payout_accounts.

                • Subscription Billing (BillingProvider)

                  Trial and monthly subscription for Tidyroom itself, behind a BillingProvider interface so the processor can change. State lives in subscriptions and plans. Plan limits (max_clients, max_storage_bytes, trial_days) are data, so the tier split can be decided later without code changes. Branding and payable invoices are never gated by plan. Provider events share the provider_events dedupe table.

                • Scheduler

                  Periodic jobs: drain notification_outbox, expire stale invites, expire stale payment_attempts, and reconcile 'pending' payment_attempts against the provider in case a webhook was missed. Runs on Vercel Cron or Supabase scheduled jobs, with the minimum interval depending on plan tier.

                Data models 18

                Open a model for its fields

                • plans10 fields
                  • iduuid
                  • codetext
                  • nametext
                  • price_centsinteger
                  • currencytext
                  • trial_daysinteger
                  • max_clientsinteger
                  • max_storage_bytesbigint
                  • is_activeboolean
                  • created_attimestamptz
                  • plans 1:N subscriptions
                • workspaces11 fields
                  • iduuid
                  • owner_user_iduuid
                  • nametext
                  • slugtext
                  • logo_pathtext
                  • primary_colortext
                  • accent_colortext
                  • currencytext
                  • next_invoice_numberinteger
                  • created_attimestamptz
                  • updated_attimestamptz
                  • workspaces 1:1 subscriptions
                  • workspaces 1:1 payout_accounts
                  • workspaces 1:N clients, projects, files, invoices, message_threads
                • subscriptions11 fields
                  • iduuid
                  • workspace_iduuid
                  • plan_iduuid
                  • statustext
                  • trial_ends_attimestamptz
                  • current_period_endtimestamptz
                  • providertext
                  • provider_customer_reftext
                  • provider_subscription_reftext
                  • created_attimestamptz
                  • updated_attimestamptz
                  • belongs to workspaces and plans
                • payout_accounts7 fields
                  • iduuid
                  • workspace_iduuid
                  • providertext
                  • provider_account_reftext
                  • statustext
                  • created_attimestamptz
                  • updated_attimestamptz
                  • belongs to workspaces
                • clients7 fields
                  • iduuid
                  • workspace_iduuid
                  • nametext
                  • emailtext
                  • companytext
                  • statustext
                  • created_attimestamptz
                  • clients 1:N client_invites, portal_members, projects, files, message_threads, invoices
                • client_invites8 fields
                  • iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • token_hashtext
                  • expires_attimestamptz
                  • accepted_attimestamptz
                  • revoked_attimestamptz
                  • created_attimestamptz
                  • belongs to clients
                • portal_members4 fields
                  • iduuid
                  • client_iduuid
                  • user_iduuid
                  • created_attimestamptz
                  • Links auth users to the one client portal they may read; basis of row-level security
                • projects9 fields
                  • iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • nametext
                  • statustext
                  • stagetext
                  • status_updated_attimestamptz
                  • created_attimestamptz
                  • archived_attimestamptz
                  • belongs to clients; optionally referenced by files, message_threads, invoices
                • files13 fields
                  • iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • project_iduuid
                  • uploaded_by_user_iduuid
                  • uploaded_by_roletext
                  • storage_pathtext
                  • file_nametext
                  • mime_typetext
                  • size_bytesbigint
                  • upload_statustext
                  • created_attimestamptz
                  • ready_attimestamptz
                  • files 1:N file_receipts
                • file_receipts5 fields
                  • iduuid
                  • file_iduuid
                  • client_iduuid
                  • user_iduuid
                  • first_opened_attimestamptz
                  • Drives 'new file' items in the waiting list
                • message_threads7 fields
                  • iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • project_iduuid
                  • subjecttext
                  • created_attimestamptz
                  • last_message_attimestamptz
                  • message_threads 1:N messages
                • messages10 fields
                  • iduuid
                  • thread_iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • sender_user_iduuid
                  • sender_roletext
                  • bodytext
                  • needs_replyboolean
                  • client_request_iduuid
                  • created_attimestamptz
                  • belongs to message_threads
                • invoices18 fields
                  • iduuid
                  • workspace_iduuid
                  • client_iduuid
                  • project_iduuid
                  • numberinteger
                  • statustext
                  • currencytext
                  • subtotal_centsbigint
                  • tax_centsbigint
                  • total_centsbigint
                  • due_datedate
                  • notetext
                  • sent_attimestamptz
                  • first_viewed_attimestamptz
                  • last_reminder_attimestamptz
                  • paid_attimestamptz
                  • voided_attimestamptz
                  • created_attimestamptz
                  • invoices 1:N invoice_line_items, invoice_events, payment_attempts
                • invoice_line_items6 fields
                  • iduuid
                  • invoice_iduuid
                  • positioninteger
                  • descriptiontext
                  • quantitynumeric(10,2)
                  • unit_amount_centsbigint
                  • belongs to invoices
                • invoice_events8 fields
                  • iduuid
                  • invoice_iduuid
                  • workspace_iduuid
                  • kindtext
                  • actor_roletext
                  • actor_user_iduuid
                  • metajsonb
                  • created_attimestamptz
                  • Audit trail and status history for invoices
                • payment_attempts12 fields
                  • iduuid
                  • invoice_iduuid
                  • workspace_iduuid
                  • providertext
                  • provider_checkout_reftext
                  • idempotency_keytext
                  • amount_centsbigint
                  • currencytext
                  • statustext
                  • failure_reasontext
                  • created_attimestamptz
                  • updated_attimestamptz
                  • belongs to invoices
                • provider_events8 fields
                  • iduuid
                  • providertext
                  • provider_event_idtext
                  • event_typetext
                  • payloadjsonb
                  • received_attimestamptz
                  • processed_attimestamptz
                  • processing_errortext
                  • Shared by invoice payments and Tidyroom subscription billing
                • notification_outbox12 fields
                  • iduuid
                  • workspace_iduuid
                  • recipient_emailtext
                  • kindtext
                  • payloadjsonb
                  • dedupe_keytext
                  • statustext
                  • attemptsinteger
                  • send_aftertimestamptz
                  • sent_attimestamptz
                  • last_errortext
                  • created_attimestamptz
                  • belongs to workspaces

                SQL schema (excerpt)

                View the SQL18 lines
                CREATE TABLE plans (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), code text NOT NULL UNIQUE, name text NOT NULL, price_cents integer NOT NULL CHECK (price_cents >= 0), currency text NOT NULL, trial_days integer NOT NULL DEFAULT 14 CHECK (trial_days >= 0), max_clients integer, max_storage_bytes bigint, is_active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE workspaces (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), owner_user_id uuid NOT NULL REFERENCES auth.users(id), name text NOT NULL, slug text NOT NULL UNIQUE, logo_path text, primary_color text NOT NULL DEFAULT '#1F2937' CHECK (primary_color ~ '^#[0-9A-Fa-f]{6}$'), accent_color text NOT NULL DEFAULT '#2563EB' CHECK (accent_color ~ '^#[0-9A-Fa-f]{6}$'), currency text NOT NULL, next_invoice_number integer NOT NULL DEFAULT 1, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE subscriptions (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL UNIQUE REFERENCES workspaces(id) ON DELETE CASCADE, plan_id uuid NOT NULL REFERENCES plans(id), status text NOT NULL CHECK (status IN ('trialing','active','past_due','canceled','expired')), trial_ends_at timestamptz, current_period_end timestamptz, provider text NOT NULL, provider_customer_ref text, provider_subscription_ref text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE payout_accounts (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL UNIQUE REFERENCES workspaces(id) ON DELETE CASCADE, provider text NOT NULL, provider_account_ref text, status text NOT NULL DEFAULT 'not_connected' CHECK (status IN ('not_connected','pending','active','restricted')), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE clients (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, name text NOT NULL, email text NOT NULL, company text, status text NOT NULL DEFAULT 'invited' CHECK (status IN ('invited','active','archived')), created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (workspace_id, email));
                CREATE TABLE client_invites (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, expires_at timestamptz NOT NULL, accepted_at timestamptz, revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE portal_members (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (client_id, user_id));
                CREATE TABLE projects (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, name text NOT NULL, status text NOT NULL DEFAULT 'not_started' CHECK (status IN ('not_started','in_progress','waiting_on_client','in_review','completed','on_hold')), stage text, status_updated_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now(), archived_at timestamptz);
                CREATE TABLE files (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, project_id uuid REFERENCES projects(id) ON DELETE SET NULL, uploaded_by_user_id uuid NOT NULL REFERENCES auth.users(id), uploaded_by_role text NOT NULL CHECK (uploaded_by_role IN ('freelancer','client')), storage_path text NOT NULL UNIQUE, file_name text NOT NULL, mime_type text, size_bytes bigint CHECK (size_bytes >= 0), upload_status text NOT NULL DEFAULT 'pending' CHECK (upload_status IN ('pending','ready')), created_at timestamptz NOT NULL DEFAULT now(), ready_at timestamptz);
                CREATE TABLE file_receipts (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), file_id uuid NOT NULL REFERENCES files(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, first_opened_at timestamptz NOT NULL DEFAULT now(), UNIQUE (file_id, user_id));
                CREATE TABLE message_threads (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, project_id uuid REFERENCES projects(id) ON DELETE SET NULL, subject text, created_at timestamptz NOT NULL DEFAULT now(), last_message_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE messages (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), thread_id uuid NOT NULL REFERENCES message_threads(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, sender_user_id uuid NOT NULL REFERENCES auth.users(id), sender_role text NOT NULL CHECK (sender_role IN ('freelancer','client')), body text NOT NULL, needs_reply boolean NOT NULL DEFAULT true, client_request_id uuid, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (thread_id, client_request_id));
                CREATE TABLE invoices (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, client_id uuid NOT NULL REFERENCES clients(id) ON DELETE CASCADE, project_id uuid REFERENCES projects(id) ON DELETE SET NULL, number integer NOT NULL, status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','sent','viewed','paid','void')), currency text NOT NULL, subtotal_cents bigint NOT NULL CHECK (subtotal_cents >= 0), tax_cents bigint NOT NULL DEFAULT 0 CHECK (tax_cents >= 0), total_cents bigint NOT NULL CHECK (total_cents > 0), due_date date, note text, sent_at timestamptz, first_viewed_at timestamptz, last_reminder_at timestamptz, paid_at timestamptz, voided_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (workspace_id, number), CHECK (total_cents = subtotal_cents + tax_cents));
                CREATE TABLE invoice_line_items (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id uuid NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, position integer NOT NULL, description text NOT NULL, quantity numeric(10,2) NOT NULL CHECK (quantity > 0), unit_amount_cents bigint NOT NULL CHECK (unit_amount_cents >= 0), UNIQUE (invoice_id, position));
                CREATE TABLE invoice_events (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id uuid NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, kind text NOT NULL CHECK (kind IN ('created','sent','viewed','reminder_sent','payment_started','payment_succeeded','payment_failed','voided')), actor_role text NOT NULL CHECK (actor_role IN ('freelancer','client','system')), actor_user_id uuid, meta jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE payment_attempts (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id uuid NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, provider text NOT NULL, provider_checkout_ref text, idempotency_key text NOT NULL UNIQUE, amount_cents bigint NOT NULL CHECK (amount_cents > 0), currency text NOT NULL, status text NOT NULL DEFAULT 'created' CHECK (status IN ('created','pending','succeeded','failed','expired')), failure_reason text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now());
                CREATE TABLE provider_events (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), provider text NOT NULL, provider_event_id text NOT NULL, event_type text NOT NULL, payload jsonb NOT NULL, received_at timestamptz NOT NULL DEFAULT now(), processed_at timestamptz, processing_error text, UNIQUE (provider, provider_event_id));
                CREATE TABLE notification_outbox (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, recipient_email text NOT NULL, kind text NOT NULL CHECK (kind IN ('client_invite','new_message','invoice_sent','invoice_reminder','invoice_paid')), payload jsonb NOT NULL DEFAULT '{}'::jsonb, dedupe_key text NOT NULL UNIQUE, status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent','failed')), attempts integer NOT NULL DEFAULT 0, send_after timestamptz NOT NULL DEFAULT now(), sent_at timestamptz, last_error text, created_at timestamptz NOT NULL DEFAULT now());

                First engineering tasks 12

                1. Apply schemaSql as migrations. Seed plans. Enable row-level security on all tables. Owner policies are keyed on workspaces.owner_user_id. Client policies are keyed on portal_members.user_id and allow read-only access to that client's invoices (status not 'draft'), files, projects, message_threads and messages, plus insert on messages and files with uploaded_by_role='client'. Make provider_events, payment_attempts and notification_outbox server-only (service role). Add a test that a client of workspace A can read none of workspace B's rows, and none of a sibling client's rows in the same workspace.

                2. Freelancer signup creates workspaces and subscriptions (status 'trialing', trial_ends_at from plans.trial_days). Inviting a client writes clients and client_invites (token_hash only, expires_at) and queues a notification_outbox 'client_invite' row. Accepting an invite validates the hash, expiry, accepted_at and revoked_at, creates or links the auth user, inserts portal_members, sets clients.status='active' and sets accepted_at. Re-sending revokes the old invite. Tests: expired, revoked and reused tokens fail.

                3. Vercel project with preview and production environments. Supabase projects per environment. Server-only environment variables for the service role key, payment provider keys, webhook signing secret and email provider key. Configure scheduled jobs for the outbox and reconciliation and confirm their minimum interval on the chosen plan. Make the webhook route publicly reachable but signature-verified. CI runs migrations and tests.

                4. Implement the three queries (invoices.status IN ('sent','viewed'); files with uploaded_by_role='freelancer', upload_status='ready' and no file_receipts row; threads whose latest messages.sender_role='freelancer' with needs_reply=true and no later client message). Merge and sort them, render fixed-template summary lines, and build the freelancer mirror view. Tests catch: swapped sender_role, draft or void invoices leaking, a paid invoice still shown, an item from another client, a currency or cents-versus-dollars mismatch in totals, a stale list after a webhook marks an invoice paid, and the case where a client has no items.

                5. Build invoice creation with invoice_line_items, deterministic totals (subtotal_cents, tax_cents, total_cents), atomic number allocation from workspaces.next_invoice_number, and the draft, sent, viewed, paid and void state machine with an invoice_events row per transition. The PaymentProvider adapter creates a hosted checkout, writing payment_attempts with idempotency_key and reusing an open attempt on retry. The webhook receiver verifies the signature, inserts provider_events (ignore duplicates), checks amount and currency against the invoice, and sets paid_at. Tests: duplicate event, out-of-order event, redirect without webhook leaves the invoice unpaid, amount mismatch is rejected, double-click on Pay creates one charge, and payment for a void invoice is flagged for manual review. Add a payout_accounts onboarding flow and block 'sent' until status is 'active'.

                6. Create a files row with upload_status 'pending' and a signed upload URL. A completion endpoint verifies the object, then sets 'ready' and ready_at. Downloads issue short-lived signed URLs after an access check and write file_receipts on the first client open. Enforce a max file size [assumption: set per plan, value TBD] and per-plan max_storage_bytes. Show upload dates from created_at.

                7. Build threads and messages for client-level and project-level conversations, with needs_reply toggling by the freelancer and client_request_id for retries. Write notification_outbox rows with deterministic dedupe_keys (for example new_message:thread:window) and a debounce via send_after. The worker sends pending rows, retries with attempts and last_error, and marks status 'sent' or 'failed'. Emails contain only a deep link plus a short preview.

                8. Branding settings write logo_path, primary_color and accent_color on workspaces, with hex validation, applied on the portal shell for every plan. Project status editor sets projects.status and projects.stage and updates status_updated_at; the client view is read-only.

                9. On the client's first invoice open, set first_viewed_at, move status 'sent' to 'viewed' and write an invoice_events 'viewed' row. The freelancer's reminder action is allowed only for status 'sent' or 'viewed', is rate-limited via last_reminder_at (3 days [assumption]), and queues an 'invoice_reminder' outbox row.

                10. Define a BillingProvider interface and one implementation. Handle trial start, checkout, and events (via provider_events) that update subscriptions.status and current_period_end. Enforce plans.max_clients and max_storage_bytes only where non-null, and never gate branding or payable invoices. Handle trial expiry and past_due with a read-only grace state.

                11. Jobs: drain notification_outbox, expire stale client_invites, mark old 'created' payment_attempts as 'expired', and reconcile 'pending' attempts against the provider for missed webhooks. All jobs must be safe to run twice.

                12. End-to-end tests for each promise: tenant isolation (client A cannot reach client B, workspace A cannot reach workspace B, including via signed file URLs and guessed IDs), invoice paid status only from verified events, no double charge on retries, waiting list matches stored state after every transition, invite token hash-only storage, and passwordless login works on mobile browsers.

                Open engineering questions 12

                • Which payment processor handles client card payments, and does it support direct payouts to the freelancer's own account? Stripe with direct payouts is the default [assumption]; its onboarding steps, webhook events, fees and refund or dispute behaviour must be checked against its current docs at build time.
                • What separates the $19 and $49 tiers: client count, storage, or something else? The plans table supports max_clients and max_storage_bytes, so this is a data decision.
                • Which launch region's currency and tax rules apply? tax_cents defaults to 0 until decided, and workspaces.currency is a single value per workspace.
                • What counts as 'unanswered' for a message? The assumed rule is that the latest message is from the freelancer with needs_reply=true and the client hasn't replied since. Is that right, or should every freelancer message count? Should a courtesy message like 'thanks!' be able to clear itself?
                • What counts as a 'new file' being seen: opening or downloading it (assumed), or merely viewing the list? Should client-uploaded files appear in the freelancer's mirror view?
                • Does the invite expiry (7 days [assumption]) apply to the invite link only, or also to the client's login session? It is stored per invite in client_invites.expires_at so it can be changed.
                • Is one login per workspace enough for 'tiny agencies' in v1? Multi-seat would need a workspace members table.
                • What is the maximum file size and storage per workspace? Large uploads depend on the storage limits of the chosen Supabase plan.
                • Are refunds, chargebacks and partial payments in scope? The current schema supports full payment only; a disputed or refunded invoice would need a new status.
                • Is a card required to start the trial? The schema supports both, but it changes the processor integration and conversion.
                • How often can scheduled jobs run on the chosen hosting plan? If the interval is too coarse, notification delays may need a different trigger.
                • Which data region should Supabase use for the launch region, and is there any contractual need to name sub-processors to freelancers' clients?

                06

                Docs

                The run's six docs, written from the artifacts above. This run didn't use Atlassian, so they stayed in Murmur and the run's zip; no Confluence space was created.

                Docs

                In Murmur and the run's zip · 6 docs

                • 01 - Product
                • 02 - Brand & Identity
                • 03 - Engineering
                • 04 - Competitor Analysis
                • 05 - Roadmap
                • 06 - Research Notes

                07

                Roadmap

                Every story is placed in one of three phases.

                • 3 phases
                • 43 stories placed
                1. Phase 1 Now

                  19stories

                  4 epics

                  • Engineering & Infrastructure
                  • Branded client portal
                  • Freelancer accounts and client invites
                  • Payable invoices
                2. Phase 2 Next

                  20stories

                  9 epics

                  • Engineering & Infrastructure
                  • Branded client portal
                  • File sharing
                  • Project messages
                  • Client-visible project status
                  • 'Waiting on you' client summary
                  • Invoice reminders and viewed receipts
                  • Entry-plan branding and payable invoices
                  • Subscription billing for Tidyroom
                3. Phase 3 Later

                  4stories

                  3 epics

                  • Engineering & Infrastructure
                  • 'Waiting on you' client summary
                  • Subscription billing for Tidyroom

                08

                Board

                The board Murmur built from the PRD: 11 epics, 43 stories, each with its Murmur story id (S for a feature story, E for engineering). This run didn't use Atlassian, so no Jira project was created.

                Murmur board11 epics · 43 stories

                • P1 Now
                • P2 Next
                • P3 Later

                Epic

                Engineering & Infrastructure

                13
                • Apply schema migrations and row-level security with isolation tests

                  E-01P1

                • Configure Supabase Auth providers, redirects and session middleware

                  E-02P1

                • Set up Vercel and Supabase environments, secrets and CI

                  E-03P1

                • Spike: Founder decisions on payments and launch region

                  E-04P1

                • Spike: Founder decisions on plans, pricing and trial

                  E-05P1

                • Spike: Founder decisions on waiting-on-you rules

                  E-06P1

                • Spike: Founder decisions on project status values and timing rules

                  E-07P1

                • Spike: Payment processor onboarding, webhooks and direct payouts

                  E-08P1

                • Spike: Scheduled job minimum interval on the chosen hosting plan

                  E-09P1

                • Spike: Supabase Storage upload size limits on the chosen plan

                  E-10P1

                • Spike: Transactional email provider and sender setup

                  E-11P1

                • Build the scheduler job routes for invite expiry, payment attempt expiry and reconciliation

                  E-12P2

                • Build the end-to-end guarantee test suite

                  E-13P3

                Epic

                Branded client portal

                2
                • Build the client portal shell route scoped to the signed-in client

                  S-01P1

                • Build the branding settings form with hex validation and logo upload

                  S-02P2

                Epic

                Freelancer accounts and client invites

                3
                • Build the freelancer signup and login pages that create a workspace and trial subscription

                  S-03P1

                • Build the invite-client form that stores a hashed token and queues the invite email

                  S-04P1

                • Build the invite acceptance page with passwordless client login

                  S-05P1

                Epic

                File sharing

                4
                • Build the signed direct-to-Storage upload flow with pending and ready states

                  S-06P2

                • Enforce per-plan max_storage_bytes on upload requests

                  S-07P2

                • Build the file list with upload dates and short-lived signed download links

                  S-08P2

                • Record a file_receipts row on the client's first open of a file

                  S-09P2

                Epic

                Payable invoices

                4
                • Build invoice creation with line items, deterministic totals, numbering and the status state machine

                  S-10P1

                • Build the payout account onboarding flow and block sending until it is active

                  S-11P1

                • Build the client invoice page and hosted checkout through the PaymentProvider adapter

                  S-12P1

                • Build the verified webhook receiver that marks invoices paid

                  S-13P1

                Epic

                Project messages

                4
                • Build threads and messages with retry-safe sending and needs_reply

                  S-14P2

                • Build the message thread UI in the freelancer dashboard and client portal

                  S-15P2

                • Queue deduplicated, debounced notification_outbox rows for new messages

                  S-16P2

                • Build the outbox worker route that sends pending emails with retries

                  S-17P2

                Epic

                Client-visible project status

                2
                • Build project creation and the status and stage editor for the freelancer

                  S-18P2

                • Build the read-only project status view in the client portal

                  S-19P2

                Epic

                'Waiting on you' client summary

                3
                • Build the three waiting-on-you queries with merge and sort

                  S-20P2

                • Render the portal home waiting list with fixed-template summary lines

                  S-21P2

                • Build the freelancer mirror view of what each client is waiting on

                  S-22P3

                Epic

                Invoice reminders and viewed receipts

                2
                • Record the client's first invoice open as viewed

                  S-23P2

                • Build the send-reminder action with rate limiting

                  S-24P2

                Epic

                Entry-plan branding and payable invoices

                2
                • Seed plan rows with flat per-workspace prices in the $19-$49 range

                  S-25P2

                • Guard against plan-gating of branding and invoice payments

                  S-26P2

                Epic

                Subscription billing for Tidyroom

                4
                • Define the BillingProvider interface and its first implementation

                  S-27P2

                • Build the subscribe checkout in workspace settings

                  S-28P2

                • Handle subscription events, trial expiry and the read-only grace state

                  S-29P3

                • Enforce max_clients on client invites only where the plan sets a limit

                  S-30P3