Skip to content

Commit 1ab60cf

Browse files
fix: isolate $data demo-seed imports from CP build graph
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 523d310 commit 1ab60cf

3 files changed

Lines changed: 76 additions & 26 deletions

File tree

‎app/src/lib/stores/contract.svelte.ts‎

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -524,18 +524,13 @@ function lsSaveAll(models: Record<string, ContractModel>): void {
524524
localStorage.setItem(LS_KEY, JSON.stringify(models));
525525
}
526526

527-
// Seed JSONs imported at build time so the demo starts populated. The publish
528-
// flow copies these files from `../data/*.json` into the public repo (see
529-
// `scripts/publish-app.sh`'s data-file copy step) so the imports resolve at
530-
// build time both in the monorepo and in the standalone clone.
531-
import stripeCustomersSeed from '$data/stripe-customers-contract.json';
532-
import stripeSubscriptionsSeed from '$data/stripe-subscriptions-contract.json';
533-
import stripeInvoicesSeed from '$data/stripe-invoices-contract.json';
534-
const DEMO_SEED_MODELS: ContractModel[] = [
535-
stripeCustomersSeed as unknown as ContractModel,
536-
stripeSubscriptionsSeed as unknown as ContractModel,
537-
stripeInvoicesSeed as unknown as ContractModel
538-
];
527+
// NOTE: demo-mode seed imports live in `./demo-seed.ts`, NOT here. That file
528+
// uses the `$data` Vite alias (only defined in the standalone app's own
529+
// vite.config.ts) and is imported only by the SA `+page.svelte`. The Context
530+
// Plane frontend transitively imports this store through the data-contract
531+
// package's converter exports, so any `$data/...` imports added at module
532+
// scope here would break the CP dev build with "Failed to resolve import".
533+
// Keep that aliased path confined to demo-seed.ts.
539534

540535
// --- API helpers -----------------------------------------------------------
541536

@@ -738,19 +733,13 @@ function makeExampleModel(): ContractModel {
738733

739734
export async function initStore() {
740735
if (store.loaded) return;
741-
let models = await apiListModels();
742-
743-
// In demo mode, if localStorage is empty seed all four example contracts
744-
// (3 Stripe + 1 Receipts) so visitors land on a populated app instead of
745-
// a single example. Normal SA install seeds just the example contract via
746-
// the existing `models.length === 0` branch below.
747-
if (DEMO_MODE && models.length === 0) {
748-
for (const m of DEMO_SEED_MODELS) {
749-
const migrated = migrateModel(JSON.parse(JSON.stringify(m)) as Record<string, unknown>);
750-
await apiCreateModel(migrated);
751-
}
752-
models = await apiListModels();
753-
}
736+
const models = await apiListModels();
737+
738+
// Demo-mode seeding (when localStorage is empty) is handled by the SA
739+
// `+page.svelte` calling `seedDemoIfEmpty()` from `./demo-seed.ts` BEFORE
740+
// initStore runs. The store stays generic so the CP frontend can import
741+
// this module via the converter without pulling in the SA-only `$data`
742+
// alias.
754743

755744
if (models.length === 0) {
756745
const example = makeExampleModel();

‎app/src/lib/stores/demo-seed.ts‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Demo-mode seed for the standalone GitHub Pages build.
2+
//
3+
// This file imports the example contract JSONs via the `$data` Vite alias
4+
// (defined in `apps/data-contract/app/vite.config.ts`). It is therefore
5+
// only safe to import from the standalone app's own entrypoints (e.g.
6+
// `+page.svelte`). The Context Plane frontend transitively imports
7+
// `contract.svelte.ts` through the data-contract package's converter
8+
// exports, but does NOT import this file — keeping the `$data`-resolved
9+
// imports out of CP's build graph.
10+
//
11+
// `seedDemoIfEmpty()` runs once at SA app startup when
12+
// `VITE_DEMO_MODE=true`. It checks localStorage; if the demo store is
13+
// empty, it migrates and writes each example contract under the same
14+
// localStorage key the store uses.
15+
16+
import type { ContractModel } from '../types';
17+
import { migrateModel } from './contract.svelte';
18+
19+
import stripeCustomersSeed from '$data/stripe-customers-contract.json';
20+
import stripeSubscriptionsSeed from '$data/stripe-subscriptions-contract.json';
21+
import stripeInvoicesSeed from '$data/stripe-invoices-contract.json';
22+
23+
const LS_KEY = 'data-contract-demo-models';
24+
25+
const SEEDS: ContractModel[] = [
26+
stripeCustomersSeed as unknown as ContractModel,
27+
stripeSubscriptionsSeed as unknown as ContractModel,
28+
stripeInvoicesSeed as unknown as ContractModel
29+
];
30+
31+
/**
32+
* Seed the demo localStorage with the bundled example contracts when empty.
33+
* Call this from the standalone `+page.svelte` `onMount` BEFORE `initStore()`,
34+
* gated by `VITE_DEMO_MODE === 'true'`. No-op if localStorage already has
35+
* any contracts (so user-modified state wins after the first visit).
36+
*/
37+
export function seedDemoIfEmpty(): void {
38+
if (typeof window === 'undefined') return;
39+
let existing: Record<string, unknown> = {};
40+
try {
41+
const raw = localStorage.getItem(LS_KEY);
42+
existing = raw ? JSON.parse(raw) : {};
43+
} catch {
44+
existing = {};
45+
}
46+
if (Object.keys(existing).length > 0) return;
47+
const seeded: Record<string, ContractModel> = {};
48+
for (const m of SEEDS) {
49+
const migrated = migrateModel(JSON.parse(JSON.stringify(m)) as Record<string, unknown>);
50+
seeded[migrated.id] = migrated;
51+
}
52+
localStorage.setItem(LS_KEY, JSON.stringify(seeded));
53+
}

‎app/src/routes/+page.svelte‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
deletePatternOverride
2222
} from '$lib/stores/contract.svelte';
2323
import { contractToContextPlane, contextPlaneToContract } from '$lib/converters/context-plane';
24+
import { seedDemoIfEmpty } from '$lib/stores/demo-seed';
2425
import { contractToRtf } from '$lib/converters/rtf';
2526
import { contractToHtml } from '$lib/converters/word';
2627
import { createStandaloneAdapter } from '$lib/adapters/standalone-adapter';
@@ -111,7 +112,14 @@
111112
});
112113
setContext('dataAdapter', adapter);
113114
114-
onMount(() => {
115+
onMount(async () => {
116+
// In demo mode (GitHub Pages static build), seed the example contracts
117+
// into localStorage on first visit so the app starts populated. The
118+
// seed module is imported only here in the SA entrypoint — not in the
119+
// shared store — so the `$data/...` build-time imports stay out of the
120+
// CP frontend's dependency graph.
121+
const demo = (import.meta as { env?: { VITE_DEMO_MODE?: string } }).env?.VITE_DEMO_MODE === 'true';
122+
if (demo) seedDemoIfEmpty();
115123
initStore();
116124
});
117125

0 commit comments

Comments
 (0)