Supreme Suite is thirteen products — a courier CRM, a movers CRM, a salon booking system, a restaurant back office, a school portal, a property manager, a tours desk, a warehouse tracker, an appointments engine, a loyalty program, dashboards, an AI chatbot, and an AI voice agent. There is one codebase. Not thirteen forks, not thirteen repos, not thirteen versions of the same login page slowly drifting apart. One engine, and thirteen pack files that tell it what to become. This is how that works, and why building it any other way quietly kills a small studio.
The promise we sell on Supreme Suite is that a business owner picks a vertical, brands it, and is live in minutes on a 3-day free trial. Behind that promise is an architectural decision that had to be made on day one and could not be undone cheaply later: do we copy the code per vertical, or do we configure it? We configured it. Every vertical is data, not a branch.
The fork-and-duplicate trap
The obvious way to ship a second vertical is to copy the first. The courier app works, the movers business wants something similar, so you duplicate the repo, rename a few things, and ship. It feels fast because for one weekend it is fast. Then you do it a third time, and a fourth, and now you have four codebases that started identical and are diverging by the commit.
The bill comes due on the first cross-cutting change. A bug in the auth flow now needs fixing in four places. A new payment provider — say wiring WiPay for Jamaican card payments — has to be ported, re-tested, and re-deployed four times, and by the third copy the surrounding code has drifted enough that the patch no longer applies cleanly. Multiply that by thirteen verticals and every improvement becomes a migration project. The studio stops shipping features and starts maintaining clones.
For a team that ships, that math is fatal. The whole reason to have a product line is leverage: build once, sell many. Forking inverts it — you build once and then maintain many, forever. So the engine had to be singular, and everything that differs between a salon and a courier had to live outside the code that runs them both.
What a pack actually is
A pack is a declarative file. It does not contain logic — it contains answers. The engine asks a fixed set of questions on every render and every request: what is this product called, what does its dashboard track, what fields does a record have, what does the AI staff member know, what colors and copy does the marketing site use. The pack answers. Swap the pack, and the same engine renders a different product.
Concretely, a pack declares four things: branding (name, logo, palette, copy), schema (the entities this vertical tracks and their fields), AI staff (the persona, knowledge, and tools the agent ships with), and navigation/feature flags (which modules are on). Everything else — auth, billing, the rendering shell, the data layer, the agent runtime — is engine, and the engine never knows or cares which vertical it is serving.
// packs/courier.ts — a vertical expressed as data, not code.
import type { Pack } from "@/engine/types";
export const courierPack: Pack = {
id: "courier",
brand: {
name: "Courier",
noun: "delivery",
nounPlural: "deliveries",
palette: { ink: "#0a0a0a", bg: "#ffffff" }, // mono by default
tagline: "Dispatch. Track. Deliver.",
},
// The vertical's data model — drives forms, tables, and DB tables.
entities: {
job: {
label: "Delivery",
fields: [
{ key: "pickup", type: "address", required: true },
{ key: "dropoff", type: "address", required: true },
{ key: "weight", type: "number", unit: "kg" },
{ key: "status", type: "enum",
options: ["pending", "picked_up", "in_transit", "delivered"] },
],
},
},
// The AI staff member this vertical ships with.
staff: {
name: "Dispatch",
role: "dispatch coordinator",
knows: ["delivery zones", "rate cards", "driver availability"],
tools: ["quote", "book", "track"],
},
dashboard: {
metrics: ["deliveries_today", "in_transit", "on_time_rate"],
},
};Now compare what changes for a salon. The engine is byte-for-byte identical. Only the pack differs — and it differs in data, which means a non-engineer could read it and a reviewer can diff it without re-reading the whole system.
// packs/salon.ts — same engine, different answers.
export const salonPack: Pack = {
id: "salon",
brand: {
name: "Salon",
noun: "appointment",
nounPlural: "appointments",
palette: { ink: "#0a0a0a", bg: "#ffffff" },
tagline: "Book. Style. Rebook.",
},
entities: {
job: {
label: "Appointment",
fields: [
{ key: "client", type: "relation", to: "contact", required: true },
{ key: "service", type: "enum",
options: ["cut", "color", "treatment", "styling"] },
{ key: "stylist", type: "relation", to: "staff" },
{ key: "start", type: "datetime", required: true },
],
},
},
staff: {
name: "Front Desk",
role: "booking assistant",
knows: ["service menu", "stylist schedules", "deposit policy"],
tools: ["check_availability", "book", "remind"],
},
dashboard: { metrics: ["booked_today", "chair_utilization", "rebook_rate"] },
};How the engine consumes a pack
The engine is a Next.js application — App Router, React 19, Tailwind v4, TypeScript, Supabase underneath. At its core is a resolver: given a tenant, load its pack, and make that pack available to every layer through context. Pages do not hardcode a vertical; they read from the active pack. A form is generated from pack.entities, a dashboard from pack.dashboard.metrics, the agent's system prompt from pack.staff. The same <RecordForm> component renders a delivery for a courier and an appointment for a salon because it is driven by the schema in the pack, not by a hand-written form per vertical.
// engine/resolve.ts — one entry point, any vertical.
import { packs } from "@/packs";
export async function resolveTenant(host: string) {
// Map a tenant's domain to its row, then to its pack id.
const tenant = await db.tenants.byHost(host);
const pack = packs[tenant.packId];
if (!pack) throw new Error(`Unknown pack: ${tenant.packId}`);
return {
tenant, // row-level identity + branding overrides
pack, // the vertical's declared shape
schema: pack.entities, // drives forms + tables
agent: buildAgent(pack.staff), // AI staff, configured from the pack
};
}
// A form component that never names a vertical.
export function RecordForm({ entity }: { entity: EntityDef }) {
return entity.fields.map((f) => <Field key={f.key} def={f} />);
}White-labeling "in minutes" falls out of this for free. A new tenant is a row, not a deployment. Choosing a vertical sets packId; uploading a logo and picking copy writes branding overrides onto the tenant row. There is no build step per customer, no new project on Vercel, no engineer in the loop. The engine is already deployed; onboarding a tenant is data entry that the customer does themselves during the trial.
Tenant isolation: keeping verticals apart at runtime
Sharing code must never mean sharing data. Two salons on the same engine, or a salon and a courier, can never see each other's records. The pack pattern handles the shape of a product; tenant isolation handles the security of it, and they are separate concerns that both have to be right.
Isolation lives in the data layer, not the application layer — because application-layer checks are one forgotten where clause away from a leak. Every tenant-owned table carries a tenant_id, and Supabase Row Level Security enforces that a request can only ever touch rows belonging to the caller's tenant. The engine resolves the tenant once at the edge of the request; the database refuses to return anything outside it, even if the application code is wrong.
-- Isolation enforced in Postgres, not trusted to app code.
alter table records enable row level security;
create policy tenant_isolation on records
using (tenant_id = current_setting('app.tenant_id')::uuid);
-- Set once per request, from the resolved tenant, before any query.
-- select set_config('app.tenant_id', $1, true);Why this scales to thirteen — and beyond
The payoff of one engine is that improvements compound across the whole line instead of being paid for per product. Fix the booking flow once, and every booking-shaped vertical — salon, appointments, tours, property viewings — gets the fix on the next deploy. Add a payment provider once, and all thirteen can charge cards. Harden the AI staff runtime once, and thirteen agents get sharper at the same moment. The shared spine is the leverage; the packs are the surface.
- Build once, sell many — a thirteenth vertical is a new pack file, not a new codebase. The marginal cost of a product collapses toward the cost of describing it.
- Fixes compound — one patch to the engine reaches every vertical at once, so quality moves up for the whole line on every deploy.
- Onboarding is data entry — a new customer is a tenant row plus branding overrides, which is why white-labeling takes minutes and runs during the free trial with no engineer involved.
- Reviews stay readable — adding a vertical is a diff to a declarative file, not a sprawling code change, so a pack can be reviewed and reasoned about quickly.
- The brand can't drift — the mono house style and shared shell live in the engine, so no vertical can wander off-brand without editing the thing they all share.
The same instinct runs through everything the studio ships. The AI staff member in each pack is itself an instance of a shared agent pipeline we reuse across products — covered in Shipping AI Staff — and the broader pattern of one operating system behind thirty-plus platforms is the studio's whole thesis. Supreme Suite is just the most literal expression of it: thirteen products that are, underneath, one program told thirteen different things.
Don't build the same product thirteen times. Build it once, and write down thirteen times what it should become.
— Studio principle
Want a product line that grows by configuration instead of duplication? That is the architecture we build into every platform we ship.
Start a projectQuestions, answered
- What is a 'pack' in Supreme Suite?
- A pack is a declarative file that describes a single vertical — its branding, its data schema (the entities and fields it tracks), its AI staff persona and tools, and which features are on. It contains answers, not logic. The shared engine reads the active tenant's pack and renders that vertical. Swapping the pack turns the same engine into a different product, which is how thirteen verticals run on one codebase.
- How is this different from just forking the code per vertical?
- A fork copies the entire codebase, so every shared change — a bug fix, a new payment provider, a security patch — has to be redone in every copy, and the copies drift apart over time. The pack pattern keeps one engine and expresses each vertical as data. One fix reaches all verticals at once, and a new vertical is a new file rather than a new repo. Forks tax you on every future change; packs do not.
- How do you keep one tenant's data from leaking to another?
- Isolation lives in the database, not the application code. Every tenant-owned table carries a tenant_id, and Supabase Row Level Security enforces that a request can only read rows belonging to the caller's tenant. The engine resolves the tenant once at the edge of each request; Postgres refuses anything outside it even if the application logic is wrong. The pack defines what a product is; RLS defines what a tenant can see.
The stack we actually use
The tools below run our own systems and the ones we build for clients. A few are affiliate links — they support The Signal at no cost to you.
Some links on this site are affiliate links. If you click through and sign up or buy, J Supreme Tech may earn a commission at no extra cost to you. We only recommend tools we actually use to build and run our own systems. Full disclosure.