Updated 2026-11-12 · rebuilt from the repo on every deploy

C1 Platform Spec

The build spec for C1: a multi-tenant platform for membership portals, courses, sessions, retreats and their public websites, built as a layer on the Blackline codebase. Any agent can pick up a phase from this file and build it.

How to use this file

  1. Read Vocabulary, Rules and Ruled out before writing any code.
  2. Pick the next phase in Build phases. Each phase lists its modules and its done-criteria.
  3. Build each module from its entry in Modules. Every entry uses the same template.
  4. A decision marked Recommended or Open needs Nick's confirmation before it becomes code. A decision marked Decided is settled; reopen it only with new information.
  5. When you change a decision, update this file in the same change and add a line to Changelog.

Repo conventions still apply: CLAUDE.md, docs/coding-standards.md, docs/testing.md, docs/file-structure.md, docs/query-engine.md.


Stack

Every spec change, design and build in this file assumes C1's stack. Check a proposal against it before writing it down.

Layer Technology Notes
Frontend Vue 3.5.14 with the Composition API, Quasar 2.18, built by the Quasar CLI (@quasar/app-vite 2.2.0) A single-page app today. Quasar's SSR, SSG, PWA and Capacitor modes exist but are not set up.
Frontend logic ivue: class-based reactivity on top of Vue 3 v2 code uses the upstream package (infinite-system/ivue, npm ivue 2.6.0), not the vendored app/src/utils/ivue2.ts; its hot-reload engine is dropped (Evgeny, 2026-11-11). Six Post Player and Virtual Scroller files still import the vendored copy until the switch lands. app/src/utils/ivue.ts (v1) stays as it is. The repo's ivue skill is the manual. ivue adds no limits of its own.
State Pinia
Backend Express on Node 22, TypeScript, Objection and Knex, the C1 query engine
Data PostgreSQL 17, Redis, OpenSearch
Shared shared/: types, Zod schemas, enums, utilities used by app and api
Auth Better Auth (Keycloak in older Blackline parts)
Hosting C1's own servers behind Cloudflare tunnels; blue-green deploys See the c1-servers skill.

Rule 0: no feature may need a second frontend framework inside C1. Other frameworks (Astro, Next, Nuxt) are allowed only in custom-tier brand repos that live outside C1 and talk to its API.


Vocabulary

One word per meaning. Use these words in code, UI labels and docs.

Word Meaning In code
Platform C1 itself. Super admins work here. Super user area
Account Whoever signs up for C1 and pays C1: one brand or an agency. Groups businesses. organization
Business One tenant: a brand with its own legal entity, money, staff, domain, members and data. project
Module A feature set a business can have switched on, such as Academy or Retreats. Module registry
Offering Anything a person can enrol in or buy: a retreat, session, course, workshop, membership, event. c1_offering
Kind The type of an offering. Each kind has its own details table. c1_offering.kind
Enrolment One person on one offering, with a stage. c1_enrolment
Entitlement What a person may see or use, with a key and a time window. Access reads this, never payment state. c1_entitlement
Role What a person may do, granted with a scope. c1_role_grant
Scope Where a role applies: platform, account, business, module or offering. scope_type
Link A connection between two businesses that shares identity, catalogue or access. Never money. c1_business_link
Connection A link to a system outside C1: Chatwoot, Google, an external website, an old platform. c1_connection
Site A domain plus what it shows: public pages, a member portal, or both. c1_site
Page An ordered list of blocks on a site. c1_page
Block A typed section of a page or post, rendered by one Vue component in the site engine. c1_block_type
Payee A person or vendor who receives a share of a sale through a Stripe connected account. c1_payee
Split Who gets what share of an item sold, and when it is released. c1_split
Support grant Time-limited, audited access for an agency or C1 staff to a business they don't own. c1_support_grant

Rules

These hold for every change. A change that breaks one is a bug even if the tests pass.

Codebase

  1. C1 is a layer on Blackline, not a fork to strip. C1 regularly merges upstream Blackline (see the merge-main skill). Never delete a Blackline file. Hide real-estate features; do not remove them.
  2. C1 code lives in its own folders: api/src/modules/c1_*, shared/modules/c1_*, app/src/modules/c1/. Blackline files change only at the hook points listed in Hook points. A script compares changed Blackline paths against that list (Phase 1).
  3. New tables use the c1_ prefix in the public schema. Every business-owned table carries project_id.
  4. New reads go through the query engine (entities and queries), per docs/query-engine.md. Admin lists are query declarations, not new routes.
  5. Project settings follow the blackline-settings-convention skill. C1 settings are declared once in the C1 settings catalog (see Settings catalog).
  6. Post Player is untouched. The Blog module is separate from app/src/ui/post-player/ and its invariants. They share only the TipTap dependency.

Tenancy and data

  1. No tenant word in code. Brand names, colours, copy and section labels live in seed data and admin records. Test: seed a made-up tenant and grep for "Tranquillity" outside seeds.
  2. Every screen a member sees reads from an admin record.
  3. Businesses never read each other's tables. Cross-business data flows only through a link, using the other business's published queries and services.
  4. A module writes only its own tables. Other modules call its service functions.
  5. No kind-specific column on c1_offering. Kind-specific fields live in that kind's details table.
  6. Account-level modules hold relationships, never money. Every money record belongs to exactly one business.

Money

  1. Money stays with the business that earns it. Each business charges through its own payment provider account. No money moves through C1. (Decided)
  2. Money only moves against an order. Access reads entitlements, never payments. Provider messages are verified, stored once, then applied. A new provider never touches the core. (From the billing plan.)
  3. Payout routes are frozen onto the order when it is paid. Changing a payee's share later never changes money already taken.

People and access

  1. One person, one identity, everywhere. Products and businesses link to the person; they never create their own user table.
  2. Seeing and doing are separate. Entitlements decide what a person can see. Role grants decide what they can do. Climbing the ladder adds rows; nothing is replaced.
  3. Every super admin and support grant action is written to the audit log.
  4. Special-category data (health, diet, brief history) is readable only by facilitator roles on that offering, and every read is logged.
  5. An agent acts with the permissions of the person who connected it. Agents never create payments, refunds or payouts without a person confirming.

Websites

  1. Code never holds content, and content never holds layout. Block components are code. Block content is data in C1.
  2. Brands never hand C1 their infrastructure credentials. Domains point at C1 with a DNS record.

Ruled out

Each of these was considered and rejected. Don't re-propose one without new information.

Option Why not
Fork Blackline and delete the real-estate code Every upstream merge becomes a fight.
A separate admin app on the same API Two frontends, two sign-ins, two looks.
Put every Blackline setting in the new admin, hidden by role Overwhelming. Use curated screens with Advanced links instead.
One project for all of Tranquillity with sections inside Can't separate legal entities, Stripe accounts or staff.
Money moving through C1, or C1 as the Stripe Connect platform for brands now Nick decided money stays per brand. C1 as platform also carries refund, chargeback and tax risk (see Phase 9).
A single cart across two businesses now Needs C1 as the payment platform. Later phase, behind a tax review.
Payload CMS as the back end (from the retreat spec v2.0; the Astro research was an enterprise WordPress-migration reference, not a C1 plan) Two back ends would split users, contacts and money. C1 replaces it.
EmDash as the CMS for each brand Single-site, with its own database and users. Used as the reference design instead.
Astro as C1's site engine C1 is Vue 3, Quasar and ivue. Astro can't reuse C1's components (Quasar renders on the server only through its own CLI), so it would be a second stack. Allowed only in custom-tier repos outside C1.
Shop items as a kind of offering Offerings have enrolments, stages and access; physical goods have stock, variants and fulfilment. Forcing one model breaks rule 11.
Coolify as the agency hub It's a server panel, not business onboarding. C1 has its own servers and deploy pipeline.
One site repo per brand as the default Breaks "new brands without new code". Custom repos are a paid tier.
Rebuilding a site on every publish Server-rendered pages with caching are simpler across many tenants. Publishing clears the cache.

Tenancy

Platform (C1 super admins)
└─ Account: an organization that pays C1. A brand, or an agency.
   ├─ Business: a project. One legal entity, its Stripe account, staff, members, domain.
   │    └─ Modules switched on for it (Academy, Sessions, Meditation, ...)
   ├─ Business: ...
   ├─ Links between its businesses (identity, catalogue, access)
   └─ Connections to outside systems

What lives at the account, and what stays with the business

The test: does the thing create an obligation to a customer? Money, consent, contracts and personal data held as a data controller stay with the business, because the business is the legal entity. Tools, assets, credentials and read-only views can rise to the account.

At the account Stays with each business
Overview (a home page the owner arranges, with traffic across every business) Members, contacts and their consent
Channels: social accounts (YouTube, Instagram, Facebook, TikTok) connected once and given to the businesses that use them Campaigns, email automations, sender and unsubscribe list
Audiences that span businesses (consent must name both entities) Funnels, forms and their responses
Team and Roles, Sites, Plan and Billing, Agency Access Stripe account, orders, payouts, tax
Themes and brand assets that several businesses share Website, legal pages, member app

A link grants each of these separately:

  1. Identity: the same person is the same member in both businesses.
  2. Catalogue: a hub business may show offerings from the linked business, read through that business's published queries.
  3. Access: a hub portal may check an entitlement held in the linked business.

Money never crosses a link. A member buying from two businesses uses two checkouts.

Marketing consent belongs to a legal entity. A shared list across two legal entities needs consent that names both, or each business emails only its own consenting contacts. One list is fine within one legal entity.


Roles and the ladder

c1_role_grant(person_id, role, scope_type, scope_id, granted_by, granted_at, expires_at, revoked_at)

Step Role Typical scope Example
Visitor none Reads public pages
Free member none (entitlements only) Claimed a free gift
Paid member none (entitlements only) Meditation Membership
Contributor contributor offering or module A teacher drafts posts, can't publish
Teacher teacher offering (course) Teaches the Remote Viewing Intensive
Facilitator facilitator offering (retreat) Runs Nepal 2027
Practitioner practitioner offering (session) or module Offers readings
Moderator moderator module Looks after Academy comments
Author author module (Blog) Publishes own posts
Editor editor module or business Manages all content
Admin admin module or business Runs the Retreats business
Owner owner account Nikki: every business
Support support business, via support grant Agency or C1 staff, time-limited
Super admin super_admin platform C1 staff

Modules

Module registry

Each module declares a manifest:

// app/src/modules/c1/<module>/module.ts and api/src/modules/c1_<module>/module.ts
{
  key: 'academy',
  scope: 'business',            // 'business' | 'account'
  sellable: true,               // a C1 tier an account can buy
  dependsOn: ['offerings', 'video'],
  adminNav: [...],              // menu entries, labels in Title Case
  portalTabs: [...],            // member portal tabs
  entitlementPrefixes: ['academy:'],
  events: ['enrolment.created', ...],
  settings: [...],              // keys in the C1 settings catalog
}

Module template

Every module below uses these headings: Scope, Records, Admin, Portal, Roles, Events, Depends on, Done when.

Offerings (core)

Billing (core)

Bundles and Tiers

Plans and Tiers (tier builder)

C1 Website and Sign-Up

Payees and Splits

Meditation (video library)

Live

Academy

Sessions

Retreats

Shop

Library

Blog

Pages and Sites

Inbox (Chatwoot)

Analytics and SEO

Sponsorships

Audiences

Channels

Account Overview

Email (Automations and Templates)

Forms

Funnels

Member App (portal)

Website Import

Contacts and Campaigns (curated C1 screens)

Live Preview panel

Website transfer (The Tranquillity)

C1 as a business (tenant zero)

Agent (MCP and CLI)

Reused Blackline modules

Contacts, Form Responses, Emails, Campaigns, Portals, Reporting, the query engine, the audit log and the media library are used as they are. C1 screens link to them through Advanced links until a curated C1 screen earns its place.


Websites (site engine)

Constraint (from Evgeny): C1's frontend is Vue 3 with Quasar and ivue (class-based reactivity, imported from src/utils/ivue2), built by the Quasar CLI as a single-page app. Astro cannot run inside it. Astro can render plain Vue 3 components through @astrojs/vue, but C1's components depend on Quasar, whose server rendering is supported only through its own CLI, and on Pinia stores, which Astro islands do not share. So an Astro site could not reuse C1's components; it would be a second frontend stack.


Admin experience

Scope switcher

The top of the sidebar is a switcher listing Platform (super admins only), then each account the person can reach with its businesses underneath.

Scope Menu
Super Admin Overview; Tenants (Accounts, Businesses, People, Modules); Operate (Onboarding, Imports, Email Delivery, Storage, Health, Support); Build (Templates, Data, Audit Log); Settings
C1 Business The Business menu below, for C1 itself (Members are called Customers), plus Pricing Page, Sign-Up Flow and Leads and Trials under Website and Sales. Plans are edited in Sales › Plans and Prices.
Account Overview (arranged by the owner, with Where Traffic Goes), Businesses, Sites, Channels, Team and Roles, Sponsorships, Audiences, Plan and Billing, Agency Access, Settings. Business cards show each business's cover, logo, domain, legal entity, members, this month's revenue (from its own Stripe), health and modules, with Open and Settings.
Business Overview; Audience (Members, Contacts, Inbox); Catalogue (Offerings, then one entry per enabled module: Video Library, Live, Academy, Sessions, Retreats, Shop, Library); Website and App (Website, Blog, Member App, Import Website); Marketing (Campaigns, Funnels, Email, Links, Forms); Money (Sales, Payouts); Insights (Analytics and SEO); Settings. Tabs: Sales (Subscriptions, Orders, Declines, Plans and Prices, Bundles, Events), Payouts (Transfers, Payees), Email (Automations, Templates, Sender), Links (Links, Attribution), Website (one editor: Pages, Styles, Blocks, Legal and Templates panels), Member App (Layout, Home Screen, App Stores), Live (Sessions, Recordings, Zoom Connection, Settings), Settings (Brand, Sections, Domains and Sites, Payment Providers, Team, Developers, Connect Agent, Security, Labs).

Platform screens pulled from Blackline

The Platform scope reuses existing super user screens instead of rebuilding them. Each one is restyled into the C1 shell, and nothing is deleted.

Existing screen (route) C1 Platform screen Notes
Projects (super_user.projects), Custom Domain card (configurations.app_domain), portal domain (settings.domain) Businesses A business is a project.
organization module (create, assign projects, Postmark server per organization) Accounts An account is an organization.
Users (super_user.users), impersonation (bl.impersonate) People Better Auth admin tools sit here too.
Provision Infra: SES Domains, Postmark Servers, Postmark Domains (super_user.provision_infra.*); Emails log (super_user.emails) Email: Delivery Log, Sender Domains, Servers Onboarding's Email Sender step calls the same services.
Generic Import: Contact Import, Document Import (super_user.data_import.*) Imports, beside Website Import The contact-import and document-import skills drive them.
Media (super_user.media) Storage With video hosting usage per business against its plan limit.
System Templates (super_user.system_templates) Templates Beside site and page templates.
Run Job (super_user.run_job) Health › Jobs Beside the billing jobs (apply, renew, retry, expire grace, reconcile).
Activity (super_user.activity), Audit Log (super_user.audit_log) Audit Log: Audit Log, Activity
Query Workbench (project.query_workbench) Data Super users only, as today.
Tags (super_user.tags), Entity Types (super_user.lookups) Settings › Advanced Rarely changed.
Receivers, PDF Markup, DocuSign Fields, Agreement Diagnostics, Book Converter, Posts, V1 imports, Inventory, Transaction, Project Items, Lot Topology and POI imports Hidden from the C1 menu Real-estate or legacy tools. Still reachable through Advanced for Blackline work.

Business-level reuse follows the same idea: Contacts and Interactions feed Members; Forms and Form Responses become the Forms module; Campaigns, Audiences and the Email Builder feed Email; Admin (Members, Roles, Teams) becomes Settings › Team; API Keys, Webhooks and Webhook History go to Settings › Developers; Reporting (Dashboard, Query, Reports, Schedules, Exports) feeds Analytics; Portals and Present › Onboarding (Branding, Configuration, Portal tabs) feed the Member App. Grid, Siteplan, Siting, Sales, Transactions, Inventory, Lot Rules, Pricing History and Cashflow are real-estate modules, hidden for membership businesses.

Built for 100+ businesses

C1 will hold hundreds of accounts and businesses. Every Platform and Account screen is designed for that from the start.

Curated screens, then Advanced, then Labs

Settings catalog

Each C1 setting is declared once: key, label, description, scope (platform | account | business), group, level (basic | advanced | labs), type, default. Screens render from the catalog. Promoting a setting is a one-word change.

Look

The ObsidianUI pieces Nick chose: the dashboard shell (frame), the analytics dashboard (overview pages), status bars (integration health), active sessions (Security) and the discover button (Open Member Portal). Neutrals from Obsidian, the tenant's accent colour, Instrument Serif for page titles. Tabs are tinted text, not pills. Labels in Title Case. The back button names the previous screen.


Market and gaps

Researched 2026-11-11 (vendor pages unless noted).

Hook points

The only Blackline files C1 may change. Keep this list current.

Where Why
app/src/router/routes.ts One import that registers C1 routes
app/src/components/layout/AppRoutesList.vue Filter the menu by enabled modules
app/src/stores/user.store.ts Expose C1 role grants and the current scope
api/src/routes (router mount) One mount for C1 module routers
shared/modules/query_catalog/query_catalog.ts List C1 entities and queries
Project layout selection Use the C1 layout for C1 routes

Add a row before touching any other Blackline file, with the reason.


Build phases

Each phase ends with focused tests, the validation-pipeline gate, and a manual-test checklist.

# Phase Delivers Done when
0 Decide This spec reviewed by Nick and Evgeny; open decisions answered Both sign off
1 Hook points and gating Module registry, enabled modules per business, C1 role grants, scope switcher, hook-point check script A business with only Academy sees no real-estate or retreat pages; the script passes
2 Billing MVP Billing core and Stripe adapter; Meditation Membership Buy, renew, fail and recover in Stripe test mode
3 Offerings core Offerings, details per kind, enrolments A session, course and retreat come from one editor
4 Payees and Splits Port the Norway transfer engine Practitioner and teacher shares paid and reversed correctly
5 Blog and shared site engine Blog native and synced modes, Website Import (WordPress), site engine per the chosen option, first ten blocks Tranquillity's 61 articles render on the hub site
6 Academy and Sessions Both modules on Offerings EOC and a paid session run end to end
7 Platform and Account consoles Accounts, Businesses, Modules, Plans (tier builder), C1 Website and Sign-Up, Onboarding, Health, Support grants, Team and Roles, Sites A made-up agency onboards a client business without code
8 Retreats and Sponsorships Retreats module, then Operations; Sponsorships at account level Nepal 2027 runs in C1; a sponsor deal spans two businesses
9 Inbox, Analytics and SEO, Agent Email automations, Links and Attribution, Chatwoot connection, Google connections, MCP Each works for one business with role-based access
10 Later custom-repo tier, translations, C1 as Stripe platform with a single cart across brands (only after a tax review)

Open decisions

# Question Recommendation
1 Is the business the project (tenant) and the account the organization? Yes
2 Is Retreats built inside C1, or kept as the separate Payload build connected through identity links? Inside C1
3 Does Evgeny agree with the hook-point approach, the module registry and the C1 role grants? Ask him
4 Where is Chatwoot installed (staging or production), and which features need its enterprise edition? Ask Evgeny
5 Which Cloudflare plan covers custom hostnames, and at what price? Ask Cloudflare
6 How far has Blackline's MCP and CLI work got? Ask Evgeny
7 How do Blackline project roles and C1 role grants meet? Decide in Phase 1
8 Which billing options from the billing plan are chosen? Nick
9 Site engine: a separate Quasar SSR app (site/), or SSR mode on the existing app with signed-in routes client-only (needs @quasar/app-vite 3.1+)? Evgeny decides; A is safer to start, B simpler long term
10 Mobile apps through Quasar's Capacitor mode? Decided (Nick): yes, Capacitor.
11 One store listing per business, or one C1 app for many? Decided (Nick): one listing per business. A business can show linked businesses' modules inside its own app; a standalone app for a second business is an add-on.
12 C1 video hosting: which storage and streaming service, and how large is each plan's allowance? Decide with Evgeny
13 Who switches the six Post Player and Virtual Scroller files from src/utils/ivue2 to the ivue npm package (drop five import.meta.hot.accept(... ivueHotUpdate ...) calls, add one dependency, then delete ivue2.ts)? These files have invariants specs and no unit tests. Evgeny, or a session he reviews
14 Do campaigns stay with each business, with a later account-level fan-out send? Yes (recommended); Nick to confirm
15 Funnel allowance per plan (Core 1, Studio 5, Events 15, Agency unlimited)? Nick
16 Mobile apps and Apple's rules: in-app purchase for digital memberships and courses, and each business under its own developer account, or one C1 app listing businesses? Decide before the first store submission
17 Add a Community module (discussion, spaces) and one member calendar? Recommended; Nick

Sources

What Where
Member portal mockups (round 22) Open Design project tq-member-home-2026-11-06: member-portal-round-22.html
Admin mockup round 1 (Tranquillity business) Same project: admin-dashboard-round-1.html, built by src/admin1build.py
Admin mockup round 2 (platform, account, business) Same project: admin-dashboard-round-2.html, built by src/admin2build.py
Admin data map Same project: data-contract.md
Billing plan docs/c1-billing-plan.md (repo copy; the Open Design artifact c1-billing-plan.html is its origin)
Open Design project folder ~/Library/Application Support/Open Design/namespaces/release-stable/data/projects/tq-member-home-2026-11-06/
Retreat platform spec v2.0 ~/Claude/Projects/Tranquillity Retreats/retreat-management-system/Specs/tranquillity-retreat-platform-spec-v2.0.md
Sponsorship pipeline spec ~/Claude/Projects/Tranquillity Retreats/specs/sponsorship-pipeline.md
Norway shop transfer engine ~/Claude/Projects/Tranquillity Retreats/2026 Norway/2026-norway-retreat-welcome-portal, lib/shop/transfers.js. Not on main: it lives on the shop branches (claude/cart-followups-flake, wt/r11-*), last changed in commit 8ffd687. The deployed copy runs on the Tranquillity Retreats Cloudflare account.
Astro research ~/Desktop/Migrating From WordPress to Astro.pdf
ObsidianUI components https://www.obsidianui.dev/r/<slug>.json for dashboard-shell, analytics-dashboard, active-sessions, status-bars, discover-button
EmDash (reference CMS) https://emdashcms.com/, https://docs.emdashcms.com/concepts/architecture/, https://github.com/emdash-cms/emdash
Stripe Connect https://docs.stripe.com/connect/charges, https://docs.stripe.com/connect/marketplace/tasks/accept-payment/separate-charges-and-transfers
Cloudflare for SaaS https://developers.cloudflare.com/cloudflare-for-saas/start/getting-started/
Chatwoot Platform API https://developers.chatwoot.com/api-reference/users/get-user-sso-link
Market research (platforms, Apple rules, VAT) https://kajabi.com/pricing, https://www.gohighlevel.com/pricing, https://simplero.com/pricing, https://circle.so/pricing, https://www.mightynetworks.com/pricing, https://www.uscreen.tv/pricing/, https://help.wetravel.com/en/articles/1270486-payment-plans-how-they-work-setup, https://developer.apple.com/app-store/review/guidelines/, https://mha.co.uk/insights/eu-vat-changes-for-virtual-events-and-online-courses
Kajabi funnels and landing pages https://help.kajabi.com/articles/marketing/funnels/funnels-in-kajabi, https://docs.kajabi.com/articles/website/pages/what-is-the-difference-between-a-landing-page-and-a-website
HubSpot landing pages and website pages https://www.blendb2b.com/blog/landing-pages-vs-website-pages-hubspot, https://developers.hubspot.com/docs/cms/key-concepts
Admin mockup round 7 and the mockup hub Open Design project: admin-dashboard-round-7.html; index.html is the hub (rebuilt by src/hubbuild.py)
C1 servers and domains .claude/skills/c1-servers/SKILL.md

Copy an outside source into docs/ when the phase that needs it starts, so the repo stays self-contained.


Changelog