@pylonsync/create-pylon 0.11.0 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-pylon.js +1 -1
- package/package.json +1 -1
- package/templates/_root/AGENTS.md +31 -1
- package/templates/agency/AGENTS.md +1 -1
- package/templates/agency/app/(marketing)/contact-form.tsx +22 -32
- package/templates/agency/app/(marketing)/layout.tsx +36 -64
- package/templates/agency/app/(marketing)/not-found.tsx +6 -8
- package/templates/agency/app/(marketing)/page.tsx +93 -144
- package/templates/agency/app/(marketing)/work/[slug]/page.tsx +69 -71
- package/templates/agency/app/(marketing)/work/page.tsx +20 -17
- package/templates/agency/app/globals.css +15 -6
- package/templates/agency/app.ts +10 -0
- package/templates/agency/components/marketing.tsx +40 -104
- package/templates/agency/lib/site.config.ts +34 -52
- package/templates/agency/public/images/hero.jpg +0 -0
- package/templates/agency/public/images/team/claire-donovan.jpg +0 -0
- package/templates/agency/public/images/team/dana-okafor.jpg +0 -0
- package/templates/agency/public/images/team/marcus-lee.jpg +0 -0
- package/templates/agency/public/images/work/atlas-health.jpg +0 -0
- package/templates/agency/public/images/work/cohort.jpg +0 -0
- package/templates/agency/public/images/work/ledger.jpg +0 -0
- package/templates/agency/public/images/work/vela.jpg +0 -0
- package/templates/ai-chat/AGENTS.md +1 -1
- package/templates/ai-studio/AGENTS.md +1 -1
- package/templates/barebones/AGENTS.md +1 -1
- package/templates/chat/AGENTS.md +1 -1
- package/templates/consumer/AGENTS.md +1 -1
- package/templates/consumer/README.md +39 -23
- package/templates/consumer/app/(app)/explore/page.tsx +13 -0
- package/templates/consumer/app/(app)/layout.tsx +17 -0
- package/templates/consumer/app/(app)/p/[id]/page.tsx +13 -0
- package/templates/consumer/app/(app)/page.tsx +15 -0
- package/templates/consumer/app/(app)/u/[username]/page.tsx +14 -0
- package/templates/consumer/app/error.tsx +1 -1
- package/templates/consumer/app/globals.css +25 -0
- package/templates/consumer/app/layout.tsx +3 -10
- package/templates/consumer/app/llms.ts +10 -10
- package/templates/consumer/app/not-found.tsx +9 -22
- package/templates/consumer/app/robots.ts +3 -4
- package/templates/consumer/app/sitemap.ts +7 -21
- package/templates/consumer/app.ts +152 -33
- package/templates/consumer/components/social/app-chrome.tsx +109 -0
- package/templates/consumer/components/social/avatar.tsx +50 -0
- package/templates/consumer/components/social/comment-form.tsx +51 -0
- package/templates/consumer/components/social/create-post.tsx +155 -0
- package/templates/consumer/components/social/edit-profile.tsx +103 -0
- package/templates/consumer/components/social/explore-screen.tsx +23 -0
- package/templates/consumer/components/social/home-screen.tsx +161 -0
- package/templates/consumer/components/social/post-card.tsx +77 -0
- package/templates/consumer/components/social/post-grid.tsx +46 -0
- package/templates/consumer/components/social/post-parts.tsx +146 -0
- package/templates/consumer/components/social/post-screen.tsx +173 -0
- package/templates/consumer/components/social/profile-screen.tsx +146 -0
- package/templates/consumer/components/social/rich-text.tsx +22 -0
- package/templates/consumer/components/social/session.tsx +43 -0
- package/templates/consumer/components/social/use-social.ts +86 -0
- package/templates/consumer/functions/addComment.ts +27 -0
- package/templates/consumer/functions/createPost.ts +39 -0
- package/templates/consumer/functions/ensureProfile.ts +39 -0
- package/templates/consumer/functions/seedFeed.ts +38 -0
- package/templates/consumer/functions/updateProfile.ts +61 -0
- package/templates/consumer/lib/seed.ts +192 -0
- package/templates/consumer/lib/site.ts +6 -0
- package/templates/consumer/lib/social.ts +245 -0
- package/templates/consumer/tests/social.test.ts +157 -0
- package/templates/creator/AGENTS.md +1 -1
- package/templates/creator/app/(marketing)/layout.tsx +35 -69
- package/templates/creator/app/(marketing)/newsletter-signup.tsx +15 -26
- package/templates/creator/app/(marketing)/not-found.tsx +8 -6
- package/templates/creator/app/(marketing)/page.tsx +90 -106
- package/templates/creator/app/globals.css +14 -0
- package/templates/creator/app/layout.tsx +1 -0
- package/templates/creator/app.ts +10 -0
- package/templates/creator/components/marketing.tsx +28 -89
- package/templates/creator/lib/site.config.ts +51 -29
- package/templates/creator/public/images/headshot.jpg +0 -0
- package/templates/crm/AGENTS.md +1 -1
- package/templates/crm/lib/seed.ts +153 -21
- package/templates/directory/AGENTS.md +1 -1
- package/templates/directory/app/(marketing)/directory-browse.tsx +99 -137
- package/templates/directory/app/(marketing)/layout.tsx +37 -88
- package/templates/directory/app/(marketing)/not-found.tsx +11 -10
- package/templates/directory/app/(marketing)/page.tsx +12 -33
- package/templates/directory/app/(marketing)/submit/page.tsx +9 -10
- package/templates/directory/app/(marketing)/submit-form.tsx +26 -26
- package/templates/directory/app/globals.css +15 -4
- package/templates/directory/app.ts +10 -0
- package/templates/directory/components/marketing.tsx +27 -91
- package/templates/directory/lib/site.config.ts +13 -24
- package/templates/expo/mobile/apps/expo/README.md +1 -1
- package/templates/expo/mobile/apps/expo/package.json +55 -54
- package/templates/expo/mobile/apps/expo/src/entitlements.ts +24 -6
- package/templates/helpdesk/AGENTS.md +1 -1
- package/templates/helpdesk/lib/seed.ts +188 -104
- package/templates/inventory/AGENTS.md +1 -1
- package/templates/inventory/lib/seed.ts +175 -44
- package/templates/invoices/AGENTS.md +1 -1
- package/templates/invoices/lib/seed.ts +74 -89
- package/templates/local-service/AGENTS.md +1 -1
- package/templates/local-service/app/(marketing)/booking-widget.tsx +31 -31
- package/templates/local-service/app/(marketing)/layout.tsx +25 -37
- package/templates/local-service/app/(marketing)/not-found.tsx +4 -4
- package/templates/local-service/app/(marketing)/page.tsx +145 -139
- package/templates/local-service/app/globals.css +11 -0
- package/templates/local-service/app/layout.tsx +2 -0
- package/templates/local-service/app.ts +9 -0
- package/templates/local-service/components/marketing.tsx +14 -39
- package/templates/local-service/lib/site.config.ts +16 -23
- package/templates/local-service/public/images/hero.jpg +0 -0
- package/templates/marketplace/AGENTS.md +1 -1
- package/templates/marketplace/client/SellForm.tsx +2 -0
- package/templates/projects/AGENTS.md +1 -1
- package/templates/projects/lib/seed.ts +103 -32
- package/templates/restaurant/AGENTS.md +1 -1
- package/templates/restaurant/app/(marketing)/layout.tsx +26 -48
- package/templates/restaurant/app/(marketing)/page.tsx +146 -123
- package/templates/restaurant/app/globals.css +11 -0
- package/templates/restaurant/app/layout.tsx +1 -0
- package/templates/restaurant/app.ts +10 -0
- package/templates/restaurant/components/marketing.tsx +10 -0
- package/templates/restaurant/lib/site.config.ts +16 -14
- package/templates/restaurant/public/images/bar.jpg +0 -0
- package/templates/restaurant/public/images/branzino.jpg +0 -0
- package/templates/restaurant/public/images/hearth.jpg +0 -0
- package/templates/restaurant/public/images/hero.jpg +0 -0
- package/templates/saas/AGENTS.md +1 -1
- package/templates/shop/AGENTS.md +1 -1
- package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
- package/templates/shop/public/images/products/linen.jpg +0 -0
- package/templates/todo/AGENTS.md +1 -1
- package/templates/waitlist/AGENTS.md +1 -1
- package/templates/waitlist/app/(marketing)/layout.tsx +39 -85
- package/templates/waitlist/app/(marketing)/not-found.tsx +8 -9
- package/templates/waitlist/app/(marketing)/page.tsx +51 -86
- package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +43 -83
- package/templates/waitlist/app/globals.css +23 -0
- package/templates/waitlist/app/layout.tsx +1 -0
- package/templates/waitlist/app.ts +18 -0
- package/templates/waitlist/components/marketing.tsx +67 -81
- package/templates/waitlist/lib/site.config.ts +52 -59
- package/templates/agency/app/not-found.tsx +0 -44
- package/templates/ai-chat/app/not-found.tsx +0 -44
- package/templates/ai-studio/app/not-found.tsx +0 -44
- package/templates/consumer/app/feed-client.tsx +0 -159
- package/templates/consumer/app/page.tsx +0 -27
- package/templates/consumer/functions/_keep.ts +0 -13
- package/templates/consumer/functions/seedPosts.ts +0 -56
- package/templates/consumer/tests/example.test.ts +0 -12
- package/templates/creator/app/not-found.tsx +0 -44
- package/templates/directory/app/not-found.tsx +0 -44
- package/templates/local-service/app/not-found.tsx +0 -44
- package/templates/restaurant/app/not-found.tsx +0 -44
- package/templates/shop/app/not-found.tsx +0 -44
- package/templates/waitlist/app/not-found.tsx +0 -44
|
@@ -18,28 +18,31 @@ export type BaseConfig = {
|
|
|
18
18
|
copyrightName: string;
|
|
19
19
|
socials: Social[];
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
// brand: the one accent. paper: page background. ink: running text.
|
|
22
|
+
colors: { brand: string; brandSoft: string; paper: string; ink: string };
|
|
22
23
|
seo: { title: string; description: string };
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
export type Offering = { title: string; body: string; price?: string };
|
|
26
27
|
export type Testimonial = { quote: string; name: string; role: string };
|
|
27
28
|
export type LinkItem = { label: string; href: string; note?: string };
|
|
29
|
+
export type Issue = { title: string; date: string; href: string; summary?: string };
|
|
28
30
|
|
|
29
31
|
export type CreatorConfig = BaseConfig & {
|
|
30
32
|
hero: {
|
|
31
|
-
badge: string;
|
|
32
33
|
name: string; // the person / personal brand
|
|
33
34
|
tagline: string; // one-line "what you do"
|
|
34
|
-
intro: string; //
|
|
35
|
+
intro: string; // two sentences of running text under the name
|
|
35
36
|
};
|
|
36
|
-
about: {
|
|
37
|
-
offerings: {
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
about: { headline: string; paragraphs: string[] };
|
|
38
|
+
offerings: { headline: string; items: Offering[] };
|
|
39
|
+
// Recent newsletter issues or posts. Omit to hide the section.
|
|
40
|
+
writing?: { headline: string; items: Issue[] };
|
|
41
|
+
testimonials?: { headline: string; items: Testimonial[] };
|
|
42
|
+
// The realtime feature: a live newsletter subscriber count. The form sits in
|
|
43
|
+
// the hero, under the introduction.
|
|
40
44
|
newsletter: {
|
|
41
|
-
|
|
42
|
-
headline: string;
|
|
45
|
+
name: string; // the newsletter's name, e.g. "The Studio Notes"
|
|
43
46
|
subcopy: string;
|
|
44
47
|
emailPlaceholder: string;
|
|
45
48
|
ctaLabel: string;
|
|
@@ -47,7 +50,7 @@ export type CreatorConfig = BaseConfig & {
|
|
|
47
50
|
counterLabel: string; // e.g. "readers subscribed"
|
|
48
51
|
seedCount?: number; // vanity baseline added to the real live count
|
|
49
52
|
};
|
|
50
|
-
links?: {
|
|
53
|
+
links?: { headline: string; body: string; items: LinkItem[] };
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
/* ----------------------------- config ---------------------------- */
|
|
@@ -75,7 +78,7 @@ export const siteConfig: CreatorConfig = {
|
|
|
75
78
|
],
|
|
76
79
|
},
|
|
77
80
|
|
|
78
|
-
colors: { brand: "#
|
|
81
|
+
colors: { brand: "#4a6b3f", brandSoft: "#e6ecdf", paper: "#f7f3ec", ink: "#2a2622" },
|
|
79
82
|
|
|
80
83
|
seo: {
|
|
81
84
|
title: "Maya Rivera — product design coach & writer",
|
|
@@ -84,16 +87,14 @@ export const siteConfig: CreatorConfig = {
|
|
|
84
87
|
},
|
|
85
88
|
|
|
86
89
|
hero: {
|
|
87
|
-
badge: "Now coaching · 2 spots open",
|
|
88
90
|
name: "Maya Rivera",
|
|
89
91
|
tagline: "Product design coach & writer.",
|
|
90
92
|
intro:
|
|
91
|
-
"I help product designers get unstuck, sharpen their portfolios, and do the bravest work of their careers.
|
|
93
|
+
"I help product designers get unstuck, sharpen their portfolios, and do the bravest work of their careers. I spent fifteen years leading design teams, and now I spend that time with you.",
|
|
92
94
|
},
|
|
93
95
|
|
|
94
96
|
about: {
|
|
95
|
-
|
|
96
|
-
headline: "Hi, I'm Maya.",
|
|
97
|
+
headline: "About",
|
|
97
98
|
paragraphs: [
|
|
98
99
|
"I've led design at two startups and a public company, shipped products to millions, and mentored designers who now lead teams of their own.",
|
|
99
100
|
"These days I coach one-on-one, review portfolios, and write a weekly newsletter about the craft and the career. Every issue focuses on practical ways to move the work forward.",
|
|
@@ -101,8 +102,7 @@ export const siteConfig: CreatorConfig = {
|
|
|
101
102
|
},
|
|
102
103
|
|
|
103
104
|
offerings: {
|
|
104
|
-
|
|
105
|
-
headline: "Three ways I can help.",
|
|
105
|
+
headline: "What I do",
|
|
106
106
|
items: [
|
|
107
107
|
{
|
|
108
108
|
title: "1:1 coaching",
|
|
@@ -117,14 +117,37 @@ export const siteConfig: CreatorConfig = {
|
|
|
117
117
|
{
|
|
118
118
|
title: "Team workshops",
|
|
119
119
|
body: "Half-day workshops on critique, design systems, and shipping faster without lowering the bar.",
|
|
120
|
-
price: "
|
|
120
|
+
price: "priced per team",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
writing: {
|
|
126
|
+
headline: "Recent issues",
|
|
127
|
+
items: [
|
|
128
|
+
{
|
|
129
|
+
title: "Critique is a skill, not a meeting",
|
|
130
|
+
date: "Sep 7, 2026",
|
|
131
|
+
href: "#",
|
|
132
|
+
summary: "How to run a critique that ends with decisions instead of notes.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
title: "Your portfolio has too many projects",
|
|
136
|
+
date: "Aug 31, 2026",
|
|
137
|
+
href: "#",
|
|
138
|
+
summary: "Three case studies, told well, beat twelve told badly.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "What senior designers stop asking permission for",
|
|
142
|
+
date: "Aug 24, 2026",
|
|
143
|
+
href: "#",
|
|
144
|
+
summary: "The small decisions that mark the change from mid-level to senior.",
|
|
121
145
|
},
|
|
122
146
|
],
|
|
123
147
|
},
|
|
124
148
|
|
|
125
149
|
testimonials: {
|
|
126
|
-
|
|
127
|
-
headline: "From people I've worked with.",
|
|
150
|
+
headline: "From people I have worked with",
|
|
128
151
|
items: [
|
|
129
152
|
{
|
|
130
153
|
quote:
|
|
@@ -148,24 +171,23 @@ export const siteConfig: CreatorConfig = {
|
|
|
148
171
|
},
|
|
149
172
|
|
|
150
173
|
newsletter: {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
subcopy:
|
|
154
|
-
"One short, useful email every Sunday — on design, taste, and doing brave work. No spam, unsubscribe anytime.",
|
|
174
|
+
name: "The Studio Notes",
|
|
175
|
+
subcopy: "One short email every Sunday on design, taste, and doing brave work.",
|
|
155
176
|
emailPlaceholder: "you@email.com",
|
|
156
177
|
ctaLabel: "Subscribe",
|
|
157
|
-
successMessage: "You
|
|
178
|
+
successMessage: "You are in. The next issue arrives on Sunday.",
|
|
158
179
|
counterLabel: "designers reading",
|
|
159
180
|
seedCount: 2400,
|
|
160
181
|
},
|
|
161
182
|
|
|
162
183
|
links: {
|
|
163
|
-
|
|
164
|
-
|
|
184
|
+
headline: "Work with me",
|
|
185
|
+
body:
|
|
186
|
+
"If you want coaching or a portfolio review, book a short intro call first. We talk through what you need, and I tell you whether I can help.",
|
|
165
187
|
items: [
|
|
166
|
-
{ label: "Portfolio", href: "#", note: "Selected work, 2010–today" },
|
|
167
|
-
{ label: "Read the archive", href: "#", note: "Every past issue of The Studio Notes" },
|
|
168
188
|
{ label: "Book a free intro call", href: "#", note: "15 minutes, no pitch" },
|
|
189
|
+
{ label: "Read the archive", href: "#", note: "Every past issue of The Studio Notes" },
|
|
190
|
+
{ label: "Portfolio", href: "#", note: "Selected work, 2010 to today" },
|
|
169
191
|
],
|
|
170
192
|
},
|
|
171
193
|
};
|
|
Binary file
|
package/templates/crm/AGENTS.md
CHANGED
|
@@ -65,7 +65,7 @@ this pass before the user has to ask.
|
|
|
65
65
|
- **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. Streaming does NOT extend the call deadline (`PYLON_FN_CALL_TIMEOUT`, 30s default) — set `timeout: <secs>` on the def for a long run.
|
|
66
66
|
- **`ctx.stream.write(text)` streams to the caller — and every fn stream is RESUMABLE**: the server buffers frames under a stream id (`streamFn`'s `onStreamId`), so a dropped connection or closed tab catches up via `resumeStream(id)`, including the final result after the handler finished. `ctx.rooms.broadcast(room, topic, data)` fans out to every CURRENTLY-CONNECTED subscriber (second device, second tab) but does not replay missed messages — use the stream id for anything that must survive a gap. Broadcast resolves `{ delivered: false }` when the room has no members — a no-op, not an error. Clients receive with `useRoomMessages(room, cb)` from `@pylonsync/react`; `useRoom` is the send side.
|
|
67
67
|
- **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
|
|
68
|
-
- **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.
|
|
68
|
+
- **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; an upload is readable by its uploader only, so pass `visibility: "public"` to `uploadFile` or `<FileUpload>` for files every visitor sees; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.
|
|
69
69
|
|
|
70
70
|
## Testing
|
|
71
71
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Demo data for a brand-new workspace.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// with something to look at, then never touches the data again.
|
|
3
|
+
// The first sign-in seeds a working pipeline: two dozen companies, a contact or
|
|
4
|
+
// two at each, deals in every stage, and a few weeks of activity. The app
|
|
5
|
+
// opens with something to look at, then never touches the data again.
|
|
6
6
|
//
|
|
7
7
|
// Pure data + pure shaping, so the seeding function stays a thin wrapper and
|
|
8
8
|
// this file can be edited (or emptied) without reading any server code.
|
|
@@ -30,6 +30,8 @@ export interface SeedDeal {
|
|
|
30
30
|
stage: string;
|
|
31
31
|
/** Days from today; negative is in the past. */
|
|
32
32
|
closeInDays: number;
|
|
33
|
+
/** Days since the deal was created. */
|
|
34
|
+
age: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface SeedActivity {
|
|
@@ -47,6 +49,24 @@ export const SEED_COMPANIES: SeedCompany[] = [
|
|
|
47
49
|
{ key: "atlas", name: "Atlas Fitness", domain: "atlasfitness.io", industry: "Fitness", size: "10-50" },
|
|
48
50
|
{ key: "quarry", name: "Quarry Design Co", domain: "quarry.design", industry: "Agency", size: "1-10" },
|
|
49
51
|
{ key: "beacon", name: "Beacon Property", domain: "beaconproperty.com", industry: "Real Estate", size: "50-200" },
|
|
52
|
+
{ key: "larkspur", name: "Larkspur Clinics", domain: "larkspurclinics.com", industry: "Healthcare", size: "200-500" },
|
|
53
|
+
{ key: "meridian", name: "Meridian Freight", domain: "meridianfreight.com", industry: "Logistics", size: "500+" },
|
|
54
|
+
{ key: "copperline", name: "Copperline Brewing", domain: "copperline.beer", industry: "Food & Beverage", size: "10-50" },
|
|
55
|
+
{ key: "tidewater", name: "Tidewater Marine", domain: "tidewatermarine.com", industry: "Manufacturing", size: "50-200" },
|
|
56
|
+
{ key: "sable", name: "Sable & Finch", domain: "sableandfinch.com", industry: "Retail", size: "10-50" },
|
|
57
|
+
{ key: "orchard", name: "Orchard Learning", domain: "orchardlearning.org", industry: "Education", size: "50-200" },
|
|
58
|
+
{ key: "granite", name: "Granite Peak Outfitters", domain: "granitepeak.com", industry: "Retail", size: "10-50" },
|
|
59
|
+
{ key: "fernhill", name: "Fernhill Veterinary", domain: "fernhillvet.com", industry: "Healthcare", size: "10-50" },
|
|
60
|
+
{ key: "kestrel", name: "Kestrel Aviation Services", domain: "kestrelaviation.com", industry: "Aviation", size: "200-500" },
|
|
61
|
+
{ key: "bluefin", name: "Bluefin Analytics", domain: "bluefin.io", industry: "Software", size: "50-200" },
|
|
62
|
+
{ key: "harbor", name: "Harbor Point Hotels", domain: "harborpointhotels.com", industry: "Hospitality", size: "500+" },
|
|
63
|
+
{ key: "wren", name: "Wren Architecture", domain: "wren.studio", industry: "Agency", size: "10-50" },
|
|
64
|
+
{ key: "summit", name: "Summit Credit Union", domain: "summitcu.org", industry: "Finance", size: "200-500" },
|
|
65
|
+
{ key: "pinecrest", name: "Pinecrest Farms", domain: "pinecrestfarms.com", industry: "Agriculture", size: "50-200" },
|
|
66
|
+
{ key: "vantage", name: "Vantage Solar", domain: "vantagesolar.com", industry: "Energy", size: "50-200" },
|
|
67
|
+
{ key: "ironwood", name: "Ironwood Furniture", domain: "ironwoodfurniture.com", industry: "Manufacturing", size: "10-50" },
|
|
68
|
+
{ key: "cobalt", name: "Cobalt Staffing", domain: "cobaltstaffing.com", industry: "Recruiting", size: "50-200" },
|
|
69
|
+
{ key: "halcyon", name: "Halcyon Senior Living", domain: "halcyonliving.com", industry: "Healthcare", size: "200-500" },
|
|
50
70
|
];
|
|
51
71
|
|
|
52
72
|
export const SEED_CONTACTS: SeedContact[] = [
|
|
@@ -54,29 +74,135 @@ export const SEED_CONTACTS: SeedContact[] = [
|
|
|
54
74
|
{ company: "northwind", name: "Marcus Iyer", email: "marcus@northwind.co", title: "Head of IT" },
|
|
55
75
|
{ company: "hallmark", name: "Priya Raman", email: "priya@hallmarkdental.com", title: "Practice Manager" },
|
|
56
76
|
{ company: "riverbed", name: "Tom Alvarez", email: "tom@riverbed.coffee", title: "Founder" },
|
|
77
|
+
{ company: "riverbed", name: "Jess Nakamura", email: "jess@riverbed.coffee", title: "Operations Lead" },
|
|
57
78
|
{ company: "atlas", name: "Simone Clarke", email: "simone@atlasfitness.io", title: "Owner" },
|
|
58
79
|
{ company: "quarry", name: "Ben Osei", email: "ben@quarry.design", title: "Principal" },
|
|
59
80
|
{ company: "beacon", name: "Lena Fischer", email: "lena@beaconproperty.com", title: "Director of Sales" },
|
|
81
|
+
{ company: "beacon", name: "Owen Hart", email: "owen@beaconproperty.com", title: "IT Manager" },
|
|
82
|
+
{ company: "larkspur", name: "Dr. Amara Bello", email: "abello@larkspurclinics.com", title: "Medical Director" },
|
|
83
|
+
{ company: "larkspur", name: "Kevin Doyle", email: "kdoyle@larkspurclinics.com", title: "COO" },
|
|
84
|
+
{ company: "meridian", name: "Rachel Stone", email: "rstone@meridianfreight.com", title: "VP Technology" },
|
|
85
|
+
{ company: "meridian", name: "Luis Herrera", email: "lherrera@meridianfreight.com", title: "Procurement Manager" },
|
|
86
|
+
{ company: "copperline", name: "Mike Brennan", email: "mike@copperline.beer", title: "Co-founder" },
|
|
87
|
+
{ company: "tidewater", name: "Sarah Lindqvist", email: "sarah@tidewatermarine.com", title: "Plant Manager" },
|
|
88
|
+
{ company: "sable", name: "Isabel Moreau", email: "isabel@sableandfinch.com", title: "Owner" },
|
|
89
|
+
{ company: "orchard", name: "David Park", email: "dpark@orchardlearning.org", title: "Director of Technology" },
|
|
90
|
+
{ company: "granite", name: "Hannah Weiss", email: "hannah@granitepeak.com", title: "General Manager" },
|
|
91
|
+
{ company: "fernhill", name: "Dr. Chris Okafor", email: "chris@fernhillvet.com", title: "Owner" },
|
|
92
|
+
{ company: "kestrel", name: "James Whitaker", email: "jwhitaker@kestrelaviation.com", title: "Director of Maintenance" },
|
|
93
|
+
{ company: "kestrel", name: "Nadia Petrov", email: "npetrov@kestrelaviation.com", title: "Systems Analyst" },
|
|
94
|
+
{ company: "bluefin", name: "Elena Vasquez", email: "elena@bluefin.io", title: "Head of Product" },
|
|
95
|
+
{ company: "harbor", name: "Robert Chen", email: "rchen@harborpointhotels.com", title: "VP Guest Experience" },
|
|
96
|
+
{ company: "harbor", name: "Monica Reyes", email: "mreyes@harborpointhotels.com", title: "Regional Manager" },
|
|
97
|
+
{ company: "wren", name: "Alex Duffy", email: "alex@wren.studio", title: "Partner" },
|
|
98
|
+
{ company: "summit", name: "Patricia Nguyen", email: "pnguyen@summitcu.org", title: "SVP Member Services" },
|
|
99
|
+
{ company: "summit", name: "Greg Holloway", email: "gholloway@summitcu.org", title: "Information Security" },
|
|
100
|
+
{ company: "pinecrest", name: "Walt Jensen", email: "walt@pinecrestfarms.com", title: "Owner" },
|
|
101
|
+
{ company: "vantage", name: "Anita Rao", email: "anita@vantagesolar.com", title: "Head of Installations" },
|
|
102
|
+
{ company: "ironwood", name: "Paul Marchetti", email: "paul@ironwoodfurniture.com", title: "Owner" },
|
|
103
|
+
{ company: "cobalt", name: "Tara Singh", email: "tara@cobaltstaffing.com", title: "Managing Director" },
|
|
104
|
+
{ company: "cobalt", name: "Derek Lowe", email: "derek@cobaltstaffing.com", title: "Operations Manager" },
|
|
105
|
+
{ company: "halcyon", name: "Margaret O'Neill", email: "moneill@halcyonliving.com", title: "Executive Director" },
|
|
106
|
+
{ company: "halcyon", name: "Sam Kowalski", email: "skowalski@halcyonliving.com", title: "IT Lead" },
|
|
60
107
|
];
|
|
61
108
|
|
|
62
109
|
export const SEED_DEALS: SeedDeal[] = [
|
|
63
|
-
|
|
64
|
-
{ company: "
|
|
65
|
-
{ company: "
|
|
66
|
-
{ company: "
|
|
67
|
-
{ company: "
|
|
68
|
-
{ company: "
|
|
69
|
-
{ company: "
|
|
70
|
-
{ company: "
|
|
110
|
+
// Lead
|
|
111
|
+
{ company: "atlas", contact: "Simone Clarke", title: "Class booking system", value: 7400, stage: "lead", closeInDays: 40, age: 6 },
|
|
112
|
+
{ company: "beacon", contact: "Lena Fischer", title: "Listing sync integration", value: 64000, stage: "lead", closeInDays: 55, age: 4 },
|
|
113
|
+
{ company: "copperline", contact: "Mike Brennan", title: "Taproom POS integration", value: 12500, stage: "lead", closeInDays: 45, age: 3 },
|
|
114
|
+
{ company: "granite", contact: "Hannah Weiss", title: "Rental fleet tracking", value: 9800, stage: "lead", closeInDays: 60, age: 2 },
|
|
115
|
+
{ company: "pinecrest", contact: "Walt Jensen", title: "CSA subscription portal", value: 16000, stage: "lead", closeInDays: 50, age: 8 },
|
|
116
|
+
{ company: "ironwood", contact: "Paul Marchetti", title: "Custom order configurator", value: 21000, stage: "lead", closeInDays: 70, age: 1 },
|
|
117
|
+
{ company: "wren", contact: "Alex Duffy", title: "Project archive search", value: 8200, stage: "lead", closeInDays: 35, age: 5 },
|
|
118
|
+
{ company: "fernhill", contact: "Dr. Chris Okafor", title: "Appointment reminders", value: 4600, stage: "lead", closeInDays: 30, age: 9 },
|
|
119
|
+
{ company: "harbor", contact: "Monica Reyes", title: "Housekeeping dispatch app", value: 38000, stage: "lead", closeInDays: 75, age: 2 },
|
|
120
|
+
{ company: "halcyon", contact: "Sam Kowalski", title: "Family portal", value: 27000, stage: "lead", closeInDays: 65, age: 7 },
|
|
121
|
+
|
|
122
|
+
// Qualified
|
|
123
|
+
{ company: "northwind", contact: "Marcus Iyer", title: "Warehouse scanner pilot", value: 9500, stage: "qualified", closeInDays: 26, age: 18 },
|
|
124
|
+
{ company: "quarry", contact: "Ben Osei", title: "Client portal build", value: 31000, stage: "qualified", closeInDays: 18, age: 22 },
|
|
125
|
+
{ company: "larkspur", contact: "Kevin Doyle", title: "Multi-site scheduling", value: 72000, stage: "qualified", closeInDays: 34, age: 15 },
|
|
126
|
+
{ company: "meridian", contact: "Luis Herrera", title: "Carrier onboarding workflow", value: 44000, stage: "qualified", closeInDays: 28, age: 20 },
|
|
127
|
+
{ company: "orchard", contact: "David Park", title: "Parent communication hub", value: 19500, stage: "qualified", closeInDays: 21, age: 12 },
|
|
128
|
+
{ company: "kestrel", contact: "Nadia Petrov", title: "Maintenance log digitisation", value: 56000, stage: "qualified", closeInDays: 42, age: 25 },
|
|
129
|
+
{ company: "summit", contact: "Patricia Nguyen", title: "Member onboarding forms", value: 33500, stage: "qualified", closeInDays: 30, age: 16 },
|
|
130
|
+
{ company: "cobalt", contact: "Tara Singh", title: "Candidate pipeline dashboard", value: 14800, stage: "qualified", closeInDays: 24, age: 11 },
|
|
131
|
+
{ company: "vantage", contact: "Anita Rao", title: "Installer scheduling", value: 26500, stage: "qualified", closeInDays: 38, age: 14 },
|
|
132
|
+
{ company: "tidewater", contact: "Sarah Lindqvist", title: "Shop floor work orders", value: 41000, stage: "qualified", closeInDays: 33, age: 19 },
|
|
133
|
+
|
|
134
|
+
// Proposal
|
|
135
|
+
{ company: "northwind", contact: "Dana Whitfield", title: "Fleet dispatch rollout", value: 48000, stage: "proposal", closeInDays: 12, age: 34 },
|
|
136
|
+
{ company: "riverbed", contact: "Tom Alvarez", title: "Multi-store inventory", value: 22000, stage: "proposal", closeInDays: 5, age: 29 },
|
|
137
|
+
{ company: "meridian", contact: "Rachel Stone", title: "Customer tracking portal", value: 88000, stage: "proposal", closeInDays: 9, age: 41 },
|
|
138
|
+
{ company: "bluefin", contact: "Elena Vasquez", title: "Embedded reporting module", value: 36000, stage: "proposal", closeInDays: 14, age: 27 },
|
|
139
|
+
{ company: "harbor", contact: "Robert Chen", title: "Guest messaging platform", value: 67000, stage: "proposal", closeInDays: 7, age: 38 },
|
|
140
|
+
{ company: "halcyon", contact: "Margaret O'Neill", title: "Care plan tracking", value: 52000, stage: "proposal", closeInDays: 16, age: 31 },
|
|
141
|
+
{ company: "summit", contact: "Greg Holloway", title: "Security review tooling", value: 18000, stage: "proposal", closeInDays: 3, age: 24 },
|
|
142
|
+
{ company: "sable", contact: "Isabel Moreau", title: "Loyalty programme", value: 11200, stage: "proposal", closeInDays: 10, age: 26 },
|
|
143
|
+
|
|
144
|
+
// Won
|
|
145
|
+
{ company: "hallmark", contact: "Priya Raman", title: "Patient intake portal", value: 15200, stage: "won", closeInDays: -8, age: 52 },
|
|
146
|
+
{ company: "riverbed", contact: "Jess Nakamura", title: "Roastery ordering app", value: 13400, stage: "won", closeInDays: -30, age: 74 },
|
|
147
|
+
{ company: "larkspur", contact: "Dr. Amara Bello", title: "Referral tracking", value: 29000, stage: "won", closeInDays: -19, age: 63 },
|
|
148
|
+
{ company: "kestrel", contact: "James Whitaker", title: "Parts inventory system", value: 47500, stage: "won", closeInDays: -44, age: 90 },
|
|
149
|
+
{ company: "orchard", contact: "David Park", title: "Enrolment forms", value: 8900, stage: "won", closeInDays: -12, age: 48 },
|
|
150
|
+
{ company: "cobalt", contact: "Derek Lowe", title: "Timesheet approvals", value: 12600, stage: "won", closeInDays: -61, age: 102 },
|
|
151
|
+
{ company: "vantage", contact: "Anita Rao", title: "Site survey app", value: 19800, stage: "won", closeInDays: -25, age: 70 },
|
|
152
|
+
{ company: "beacon", contact: "Owen Hart", title: "Tenant maintenance requests", value: 23000, stage: "won", closeInDays: -3, age: 45 },
|
|
153
|
+
{ company: "wren", contact: "Alex Duffy", title: "Proposal templates", value: 6400, stage: "won", closeInDays: -37, age: 66 },
|
|
154
|
+
|
|
155
|
+
// Lost
|
|
156
|
+
{ company: "beacon", contact: "Lena Fischer", title: "Agent CRM migration", value: 18500, stage: "lost", closeInDays: -21, age: 58 },
|
|
157
|
+
{ company: "tidewater", contact: "Sarah Lindqvist", title: "Quality inspection app", value: 28000, stage: "lost", closeInDays: -40, age: 81 },
|
|
158
|
+
{ company: "granite", contact: "Hannah Weiss", title: "Online store rebuild", value: 15500, stage: "lost", closeInDays: -14, age: 49 },
|
|
159
|
+
{ company: "bluefin", contact: "Elena Vasquez", title: "Data warehouse sync", value: 42000, stage: "lost", closeInDays: -55, age: 95 },
|
|
160
|
+
{ company: "ironwood", contact: "Paul Marchetti", title: "Showroom kiosk", value: 9200, stage: "lost", closeInDays: -9, age: 40 },
|
|
71
161
|
];
|
|
72
162
|
|
|
73
163
|
export const SEED_ACTIVITIES: SeedActivity[] = [
|
|
74
|
-
{ deal: "Fleet dispatch rollout", kind: "call", body: "Walked Dana through the dispatch flow. Wants a security review before signing
|
|
164
|
+
{ deal: "Fleet dispatch rollout", kind: "call", body: "Walked Dana through the dispatch flow. Wants a security review before signing. Sending the SOC 2 summary.", age: 5 },
|
|
75
165
|
{ deal: "Fleet dispatch rollout", kind: "email", body: "Sent revised proposal with the 3-year term and onboarding included.", age: 30 },
|
|
76
|
-
{ deal: "
|
|
166
|
+
{ deal: "Fleet dispatch rollout", kind: "meeting", body: "Pricing workshop with Dana and Marcus. They asked for a per-vehicle price instead of per-seat.", age: 120 },
|
|
167
|
+
{ deal: "Warehouse scanner pilot", kind: "note", body: "Marcus has 12 Zebra scanners on hand. Pilot will run in the Rotterdam warehouse only.", age: 54 },
|
|
168
|
+
{ deal: "Patient intake portal", kind: "note", body: "Signed. Kickoff scheduled for the 14th. Priya is the day-to-day contact.", age: 190 },
|
|
77
169
|
{ deal: "Multi-store inventory", kind: "meeting", body: "Demo with Tom and the two store leads. Main question is offline mode at the market stall.", age: 48 },
|
|
78
|
-
{ deal: "
|
|
79
|
-
{ deal: "
|
|
170
|
+
{ deal: "Multi-store inventory", kind: "email", body: "Sent the offline sync write-up and the pricing for a third store.", age: 20 },
|
|
171
|
+
{ deal: "Client portal build", kind: "note", body: "Ben wants SSO with their Google Workspace. Confirmed that is in scope.", age: 72 },
|
|
172
|
+
{ deal: "Client portal build", kind: "call", body: "Ben is comparing us to a Webflow build. Sent three portal references.", age: 8 },
|
|
173
|
+
{ deal: "Listing sync integration", kind: "call", body: "Intro call. They are evaluating two other vendors. Decision in Q3.", age: 96 },
|
|
174
|
+
{ deal: "Multi-site scheduling", kind: "meeting", body: "Site visit at the Westgate clinic. 14 practitioners, 3 rooms, paper diary today.", age: 140 },
|
|
175
|
+
{ deal: "Multi-site scheduling", kind: "email", body: "Kevin asked for HIPAA documentation before the next meeting. Sent the BAA template.", age: 36 },
|
|
176
|
+
{ deal: "Customer tracking portal", kind: "meeting", body: "Executive review with Rachel and the CFO. Budget approved pending legal.", age: 26 },
|
|
177
|
+
{ deal: "Customer tracking portal", kind: "note", body: "Legal wants a data residency clause. Ours already covers EU, so this should be quick.", age: 12 },
|
|
178
|
+
{ deal: "Customer tracking portal", kind: "call", body: "Rachel confirmed the board wants this live before peak season.", age: 200 },
|
|
179
|
+
{ deal: "Carrier onboarding workflow", kind: "email", body: "Luis sent the current onboarding checklist. 47 steps, 6 systems.", age: 90 },
|
|
180
|
+
{ deal: "Embedded reporting module", kind: "meeting", body: "Technical review with Elena's engineers. They want the embed to support row-level permissions.", age: 44 },
|
|
181
|
+
{ deal: "Embedded reporting module", kind: "email", body: "Sent proposal v2 with the permissions work broken out as a phase 2 option.", age: 18 },
|
|
182
|
+
{ deal: "Guest messaging platform", kind: "call", body: "Robert wants SMS and WhatsApp on day one. Email can wait.", age: 60 },
|
|
183
|
+
{ deal: "Guest messaging platform", kind: "meeting", body: "Pilot proposal for the two Boston properties. Monica will be the operational sponsor.", age: 15 },
|
|
184
|
+
{ deal: "Care plan tracking", kind: "note", body: "Margaret needs sign-off from the clinical board. They meet on the first Tuesday.", age: 70 },
|
|
185
|
+
{ deal: "Care plan tracking", kind: "email", body: "Sent the audit log and access control write-up Sam asked for.", age: 33 },
|
|
186
|
+
{ deal: "Security review tooling", kind: "call", body: "Greg walked through their current spreadsheet process. Wants the pilot scoped to vendor reviews only.", age: 9 },
|
|
187
|
+
{ deal: "Loyalty programme", kind: "meeting", body: "Isabel liked the points model. Asked whether it can work with their Square terminals.", age: 52 },
|
|
188
|
+
{ deal: "Member onboarding forms", kind: "call", body: "Patricia confirmed they process about 900 applications a month. Paper today.", age: 80 },
|
|
189
|
+
{ deal: "Candidate pipeline dashboard", kind: "email", body: "Tara sent sample data. Their ATS export is a CSV with 23 columns.", age: 100 },
|
|
190
|
+
{ deal: "Installer scheduling", kind: "meeting", body: "Rode along with an install crew. Real pain is the morning dispatch call, not the calendar.", age: 150 },
|
|
191
|
+
{ deal: "Shop floor work orders", kind: "note", body: "Sarah wants tablets on the floor. Rugged cases are their cost, not ours.", age: 64 },
|
|
192
|
+
{ deal: "Maintenance log digitisation", kind: "meeting", body: "FAA compliance is the whole conversation. Nadia is pulling the relevant Part 145 sections.", age: 112 },
|
|
193
|
+
{ deal: "Parent communication hub", kind: "call", body: "David wants a pilot with two schools this term.", age: 30 },
|
|
194
|
+
{ deal: "Roastery ordering app", kind: "note", body: "Live since March. Jess mentioned a wholesale tier as a possible expansion.", age: 400 },
|
|
195
|
+
{ deal: "Referral tracking", kind: "note", body: "Went live across all four clinics. Amara is the reference for Halcyon.", age: 320 },
|
|
196
|
+
{ deal: "Parts inventory system", kind: "note", body: "Renewal due in eleven months. James is happy with the barcode workflow.", age: 700 },
|
|
197
|
+
{ deal: "Tenant maintenance requests", kind: "email", body: "Contract countersigned. Onboarding starts Monday with Owen's team.", age: 50 },
|
|
198
|
+
{ deal: "Agent CRM migration", kind: "note", body: "Lost to the incumbent. Lena said the switching cost for 80 agents was the blocker.", age: 500 },
|
|
199
|
+
{ deal: "Quality inspection app", kind: "note", body: "Lost. They built it in-house with their ERP vendor.", age: 960 },
|
|
200
|
+
{ deal: "Online store rebuild", kind: "note", body: "Lost to a Shopify agency. Budget was smaller than we scoped for.", age: 330 },
|
|
201
|
+
{ deal: "Data warehouse sync", kind: "note", body: "Lost. Elena's team chose Fivetran for the sync and kept us for reporting, which is the open proposal.", age: 1300 },
|
|
202
|
+
{ deal: "Taproom POS integration", kind: "email", body: "Mike asked which POS systems we already integrate with. Sent the list.", age: 40 },
|
|
203
|
+
{ deal: "CSA subscription portal", kind: "call", body: "Walt runs 340 CSA boxes a week on a spreadsheet. Wants pickup site management too.", age: 180 },
|
|
204
|
+
{ deal: "Housekeeping dispatch app", kind: "note", body: "Inbound from the Guest messaging conversation. Monica introduced us to her ops lead.", age: 44 },
|
|
205
|
+
{ deal: "Family portal", kind: "email", body: "Sam sent the current family newsletter process. Wants photo sharing with consent controls.", age: 160 },
|
|
80
206
|
];
|
|
81
207
|
|
|
82
208
|
export interface ShapedSeed {
|
|
@@ -94,20 +220,26 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
|
|
|
94
220
|
const at = (days: number) => new Date(now + days * 86_400_000).toISOString();
|
|
95
221
|
const hoursAgo = (hours: number) => new Date(now - hours * 3_600_000).toISOString();
|
|
96
222
|
|
|
223
|
+
const latestActivity = new Map<string, number>();
|
|
224
|
+
for (const a of SEED_ACTIVITIES) {
|
|
225
|
+
const current = latestActivity.get(a.deal);
|
|
226
|
+
if (current === undefined || a.age < current) latestActivity.set(a.deal, a.age);
|
|
227
|
+
}
|
|
228
|
+
|
|
97
229
|
return {
|
|
98
|
-
companies: SEED_COMPANIES.map((c) => ({
|
|
230
|
+
companies: SEED_COMPANIES.map((c, index) => ({
|
|
99
231
|
key: c.key,
|
|
100
232
|
row: {
|
|
101
233
|
name: c.name,
|
|
102
234
|
domain: c.domain,
|
|
103
235
|
industry: c.industry,
|
|
104
236
|
size: c.size,
|
|
105
|
-
createdAt:
|
|
237
|
+
createdAt: at(-120 + index * 3),
|
|
106
238
|
},
|
|
107
239
|
})),
|
|
108
|
-
contacts: SEED_CONTACTS.map((c) => ({
|
|
240
|
+
contacts: SEED_CONTACTS.map((c, index) => ({
|
|
109
241
|
company: c.company,
|
|
110
|
-
row: { name: c.name, email: c.email, title: c.title, createdAt:
|
|
242
|
+
row: { name: c.name, email: c.email, title: c.title, createdAt: at(-110 + index * 2) },
|
|
111
243
|
})),
|
|
112
244
|
deals: SEED_DEALS.map((d) => ({
|
|
113
245
|
company: d.company,
|
|
@@ -118,8 +250,8 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
|
|
|
118
250
|
value: d.value,
|
|
119
251
|
stage: d.stage,
|
|
120
252
|
closeDate: at(d.closeInDays),
|
|
121
|
-
createdAt:
|
|
122
|
-
updatedAt: hoursAgo(
|
|
253
|
+
createdAt: at(-d.age),
|
|
254
|
+
updatedAt: hoursAgo(Math.min(latestActivity.get(d.title) ?? d.age * 24, d.age * 24)),
|
|
123
255
|
},
|
|
124
256
|
})),
|
|
125
257
|
activities: SEED_ACTIVITIES.map((a) => ({
|
|
@@ -65,7 +65,7 @@ this pass before the user has to ask.
|
|
|
65
65
|
- **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. Streaming does NOT extend the call deadline (`PYLON_FN_CALL_TIMEOUT`, 30s default) — set `timeout: <secs>` on the def for a long run.
|
|
66
66
|
- **`ctx.stream.write(text)` streams to the caller — and every fn stream is RESUMABLE**: the server buffers frames under a stream id (`streamFn`'s `onStreamId`), so a dropped connection or closed tab catches up via `resumeStream(id)`, including the final result after the handler finished. `ctx.rooms.broadcast(room, topic, data)` fans out to every CURRENTLY-CONNECTED subscriber (second device, second tab) but does not replay missed messages — use the stream id for anything that must survive a gap. Broadcast resolves `{ delivered: false }` when the room has no members — a no-op, not an error. Clients receive with `useRoomMessages(room, cb)` from `@pylonsync/react`; `useRoom` is the send side.
|
|
67
67
|
- **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
|
|
68
|
-
- **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.
|
|
68
|
+
- **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; an upload is readable by its uploader only, so pass `visibility: "public"` to `uploadFile` or `<FileUpload>` for files every visitor sees; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.
|
|
69
69
|
|
|
70
70
|
## Testing
|
|
71
71
|
|