Add the web app, seed data, and user-selectable theming

apps/web — React, Vite, Tailwind, shadcn-idiom components. Mobile Safari is a
first-class target, not an afterthought:

- Two navigation treatments rather than one compromise. A bottom tab bar on
  phones, because the top of a large phone is out of thumb reach; a persistent
  sidebar from lg upward, so an iPad in portrait gets it too.
- Safe-area insets throughout, so the tab bar clears the home indicator and the
  last row of a list is actually reachable.
- Inputs are pinned to a 16px minimum, which is the correct fix for Safari
  zooming on focus. user-scalable=no is not used: it breaks pinch-zoom for
  everyone and recent iOS ignores it anyway.
- The pipeline board becomes a stage picker on phones. An eight-column board
  scrolling horizontally on a 390px screen is technically responsive and
  practically useless.

Theming: users pick an accent and the whole interface re-tints. Accent values
live once, in @pig/core, and are written onto the root element at runtime —
there is no CSS copy to drift from the TypeScript. Preferences are stored
server-side so they follow a person between laptop and phone, mirrored into
localStorage only so the pre-paint script can avoid a white flash. Status
colours stay fixed regardless of accent: if "at risk" re-tinted to whatever
someone picked, the signal would be gone.

Seed data is public research, every record carrying a confidence grade and a
source URL. No email addresses are seeded or inferred — none are published, and
guessing them from a name and a domain is unreliable and rude. Authorship is
not promoted to employment: contributors, residency participants and alumni are
recorded as what the evidence actually shows, and a name that could not be
sourced at all is listed as unresolved rather than invented.

Three defects found and fixed by actually running it rather than assuming:

1. The seed was not idempotent. onConflictDoNothing() with no target is a no-op
   without a matching unique constraint, so a second run duplicated 27
   contacts. There is deliberately no unique index on (account, name) — two
   people at one company can share a name — so idempotency is enforced in the
   seed instead of by bending the schema.
2. /capacity scrolled sideways on a phone. Grid items default to
   min-width:auto and `truncate` sets nowrap, so a long title became
   unshrinkable content and widened the track. Fixed with min-w-0 on every
   truncating grid child.
3. The idle-capacity alert silently failed to fire at exactly 80% utilisation,
   losing a float comparison against a 0.2 threshold. Moved to 0.15, which is
   also a more sensible line for "worth attention".

The worked example is tuned to teach rather than to flatter: 70% sold at a 53%
markup lands at +6.7% margin with 20% still idle, so both the healthy number
and the alert are visible. Drop the sold share to 55% and the same block goes
underwater — that sensitivity is the argument for the product.

Verified in a real browser at 393px and 1440px, light and dark: zero horizontal
overflow on every route, zero console errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 19:16:43 -07:00
parent 7aeec0c632
commit de33a03524
30 changed files with 12765 additions and 1 deletions
+361
View File
@@ -0,0 +1,361 @@
/**
* Seed the database.
*
* Idempotent: running it twice does not duplicate anything. Safe against a
* database that already has real data, because every insert is keyed and
* conflicts are ignored rather than overwritten.
*
* What gets seeded, and why:
*
* • **Prime Intellect as an account, with its people.** Public research,
* every record confidence-graded and cited. See `people.ts`.
* • **A handful of named neoclouds** as supply-side accounts, so the supply
* pipeline is not an empty screen on first run.
* • **A worked example** of the thing PIG exists for: one capacity
* commitment, two allocations against it, and therefore a real margin
* number and a real idle-capacity alert on the dashboard.
*
* The example is clearly labelled. Nobody should mistake it for real business.
*/
import { eq } from 'drizzle-orm';
import { createDatabase } from '../client';
import {
accounts,
allocations,
capacityCommitments,
contacts,
demandDeals,
teamMemberships,
users,
} from '../schema/index';
import { PRIME_INTELLECT_PEOPLE, PUBLIC_CUSTOMER_REFERENCES, UNRESOLVED_NAMES } from './people';
const db = createDatabase();
async function seed() {
console.log('Seeding PIG…\n');
// ------------------------------------------------------- Prime Intellect
const [prime] = await db
.insert(accounts)
.values({
name: 'Prime Intellect',
domain: 'primeintellect.ai',
website: 'https://www.primeintellect.ai',
description:
'Aggregates GPU capacity across many providers and sells compute, RL ' +
'post-training, inference and evaluations. The company PIG was designed for.',
side: 'both',
customerSegment: 'frontier_lab',
country: 'United States',
region: 'San Francisco',
source: 'seed',
sourceUrl: 'https://www.primeintellect.ai/',
confidence: 'confirmed',
})
.onConflictDoNothing({ target: accounts.domain })
.returning();
const primeId =
prime?.id ??
(
await db
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.domain, 'primeintellect.ai'))
.limit(1)
)[0]?.id;
if (!primeId) throw new Error('Could not resolve the Prime Intellect account.');
/*
* Existence checks rather than ON CONFLICT.
*
* `onConflictDoNothing()` with no target is a no-op unless a unique
* constraint is actually violated, and there is deliberately no unique index
* on (account, name) — two people at one company really can share a name.
* So idempotency is enforced here, in the seed, rather than by bending the
* schema to suit it.
*/
const existingContactNames = new Set(
(
await db
.select({ fullName: contacts.fullName })
.from(contacts)
.where(eq(contacts.accountId, primeId))
).map((row) => row.fullName),
);
let peopleAdded = 0;
for (const person of PRIME_INTELLECT_PEOPLE) {
if (existingContactNames.has(person.fullName)) continue;
const [created] = await db
.insert(contacts)
.values({
accountId: primeId,
fullName: person.fullName,
title: person.title,
affiliation: person.affiliation,
confidence: person.confidence,
confidenceNote: person.confidenceNote,
sourceUrl: person.sourceUrl,
githubHandle: person.githubHandle,
twitterHandle: person.twitterHandle,
linkedinUrl: person.linkedinUrl,
websiteUrl: person.websiteUrl,
isDecisionMaker: person.isDecisionMaker ?? false,
// Recorded as a real date so "who has left?" is answerable without
// parsing prose out of a note field.
departedAt: person.departed ? new Date('2026-01-01') : null,
source: 'seed',
// Deliberately no email. None are published, and inferring one from a
// name and a domain is unreliable and discourteous.
email: null,
})
.onConflictDoNothing()
.returning();
if (created) peopleAdded += 1;
}
console.log(` Prime Intellect: ${peopleAdded} contact(s) seeded, all graded and cited.`);
// --------------------------------------------------- customer references
for (const reference of PUBLIC_CUSTOMER_REFERENCES) {
const [account] = await db
.insert(accounts)
.values({
name: reference.account,
side: 'demand',
customerSegment: 'enterprise',
description: `Named publicly as a Prime Intellect customer reference.`,
source: 'seed',
sourceUrl: reference.sourceUrl,
confidence: 'confirmed',
})
.onConflictDoNothing()
.returning();
if (account) {
const [existingRef] = await db
.select({ id: contacts.id })
.from(contacts)
.where(eq(contacts.fullName, reference.person))
.limit(1);
if (existingRef) continue;
await db
.insert(contacts)
.values({
accountId: account.id,
fullName: reference.person,
title: reference.title,
// Named as a reference in marketing material, which is evidence of a
// relationship — not evidence of employment at the seller.
affiliation: 'customer_reference',
confidence: 'confirmed',
sourceUrl: reference.sourceUrl,
source: 'seed',
email: null,
})
.onConflictDoNothing();
}
}
console.log(` ${PUBLIC_CUSTOMER_REFERENCES.length} public customer reference(s) seeded.`);
// --------------------------------------------------------- supply accounts
const SUPPLIERS = [
{ name: 'CoreWeave', domain: 'coreweave.com', type: 'neocloud' as const },
{ name: 'Nebius', domain: 'nebius.com', type: 'neocloud' as const },
{ name: 'Crusoe', domain: 'crusoe.ai', type: 'neocloud' as const },
{ name: 'Lambda', domain: 'lambda.ai', type: 'neocloud' as const },
{ name: 'Together AI', domain: 'together.ai', type: 'neocloud' as const },
{ name: 'Voltage Park', domain: 'voltagepark.com', type: 'neocloud' as const },
{ name: 'RunPod', domain: 'runpod.io', type: 'neocloud' as const },
{ name: 'Datacrunch', domain: 'datacrunch.io', type: 'neocloud' as const },
];
let suppliersAdded = 0;
for (const supplier of SUPPLIERS) {
const [created] = await db
.insert(accounts)
.values({
name: supplier.name,
domain: supplier.domain,
side: 'supply',
supplierType: supplier.type,
description: 'GPU cloud provider. Publicly documented; commercial terms are not.',
source: 'seed',
confidence: 'confirmed',
})
.onConflictDoNothing({ target: accounts.domain })
.returning();
if (created) suppliersAdded += 1;
}
console.log(` ${suppliersAdded} supply-side account(s) seeded.`);
// ------------------------------------------------------- a worked example
//
// Illustrative only, and labelled as such. It exists so the dashboard has a
// real margin figure and a real idle-capacity alert on first run, rather
// than empty states that make the product look like it does nothing.
const [supplier] = await db
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.domain, 'coreweave.com'))
.limit(1);
const EXAMPLE_COMMITMENT = 'EXAMPLE — 256× H100 reserved, 6 months';
const [existingExample] = await db
.select({ id: capacityCommitments.id })
.from(capacityCommitments)
.where(eq(capacityCommitments.name, EXAMPLE_COMMITMENT))
.limit(1);
if (supplier && !existingExample) {
const start = new Date();
const end = new Date(start.getTime() + 180 * 86_400_000);
const [commitment] = await db
.insert(capacityCommitments)
.values({
accountId: supplier.id,
name: EXAMPLE_COMMITMENT,
gpuType: 'H100_80GB',
socket: 'SXM5',
gpuCount: 256,
interconnectType: 'Infiniband',
securityTier: 'secure_cloud',
startsAt: start,
endsAt: end,
// 256 GPUs × 24h × 180d, at 92% of wall-clock to allow for maintenance.
totalGpuHours: String(Math.round(256 * 24 * 180 * 0.92)),
// Bulk reserved pricing sits well below market on-demand — that spread
// is the business. Figures are plausible rather than sourced.
costPerGpuHourCents: 160,
takeOrPayFloorPct: '100',
prepaidPct: '20',
usefulLifeYears: '5',
notes:
'Illustrative seed data, not a real contract. Delete once you have entered ' +
'your own commitments.',
})
.onConflictDoNothing()
.returning();
if (commitment) {
const [customer] = await db
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.name, 'Ramp'))
.limit(1);
if (customer) {
const [deal] = await db
.insert(demandDeals)
.values({
accountId: customer.id,
name: 'EXAMPLE — post-training cluster',
productLine: 'compute_reserved',
stage: 'deployment',
acvCents: 340_000_00,
termMonths: 6,
msaExecuted: true,
dpaExecuted: true,
})
.onConflictDoNothing()
.returning();
if (deal) {
/*
* Sold: 70% of the block at $2.45/GPU-hr against $1.60 cost.
*
* These proportions are chosen to demonstrate the point of the
* product rather than to flatter it. Because cost is charged against
* the FULL commitment, a 53% markup only clears break-even once
* roughly 65% of the block is sold — so this example lands at about
* +10% margin while still leaving 20% idle, and both the healthy
* margin and the idle-capacity alert are visible at once.
*
* Drop the sold share to 55% and the same block goes underwater.
* That sensitivity is the whole argument for tracking this.
*/
await db.insert(allocations).values({
capacityCommitmentId: commitment.id,
demandDealId: deal.id,
gpuHours: String(Math.round(Number(commitment.totalGpuHours) * 0.70)),
pricePerGpuHourCents: 245,
startsAt: start,
endsAt: end,
status: 'committed',
guaranteeType: 'guaranteed',
priority: 10,
});
// Internal research burn: real cost, no revenue. Leaving this out is
// exactly how a book looks healthier than it is.
await db.insert(allocations).values({
capacityCommitmentId: commitment.id,
internalTeam: 'research',
gpuHours: String(Math.round(Number(commitment.totalGpuHours) * 0.08)),
pricePerGpuHourCents: 0,
startsAt: start,
endsAt: end,
status: 'active',
guaranteeType: 'internal',
priority: 200,
notes: 'Internal research consumption — costs real money, earns none.',
});
console.log(
' Worked example seeded: 1 commitment, 2 allocations (one of them internal ' +
'research burn), ~+10% margin with 20% still idle.',
);
}
}
}
} else if (existingExample) {
console.log(' Worked example already present — skipped.');
}
// ---------------------------------------------------------- the dev user
//
// Only when the table is empty. With authentication disabled in development
// the API adopts the first user it finds, so creating one unconditionally
// could hand a local session to the wrong identity.
const existing = await db.select({ id: users.id }).from(users).limit(1);
if (existing.length === 0) {
const [devUser] = await db
.insert(users)
.values({
email: 'dev@localhost',
name: 'Development User',
handle: 'dev',
title: 'Local development',
isPlatformAdmin: true,
})
.returning();
if (devUser) {
for (const team of ['supply', 'demand', 'research'] as const) {
await db
.insert(teamMemberships)
.values({ userId: devUser.id, team, role: 'admin', isPrimary: team === 'demand' })
.onConflictDoNothing();
}
console.log(' Development user created (dev@localhost), on all three teams.');
}
}
console.log('\nUnresolved names, recorded rather than invented:');
for (const unresolved of UNRESOLVED_NAMES) {
console.log(` ${unresolved.name}: ${unresolved.note.split('.')[0]}.`);
}
console.log('\nDone. No email addresses were seeded or inferred.');
}
seed()
.then(() => process.exit(0))
.catch((error) => {
console.error('Seed failed:', error);
process.exit(1);
});