@tomorrowos/sdk 0.9.40 → 0.9.41
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/LOVABLE_SETUP.md +67 -67
- package/README.md +4 -3
- package/REPLIT_SETUP.md +115 -44
- package/VERCEL_QUESTIONS.md +22 -22
- package/VERCEL_SETUP.md +193 -193
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter-v0/package.json +2 -2
package/VERCEL_SETUP.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
>
|
|
6
6
|
> **On Vercel, this file is the setup questionnaire** (not `REPLIT_SETUP.md`, not `LLM_PROMPT.md`).
|
|
7
7
|
>
|
|
8
|
-
> Protocol id: `vercel-setup/1.9`
|
|
8
|
+
> Protocol id: `vercel-setup/1.9` �?pairs with `@tomorrowos/sdk` **0.9.41+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
|
|
9
9
|
>
|
|
10
|
-
> **Questions source of truth:** `VERCEL_QUESTIONS.md`
|
|
11
|
-
> **Copy questions verbatim from `VERCEL_QUESTIONS.md`.** Do not invent alternate Q2 options (no “Supabase Storage
|
|
12
|
-
> **`TOMORROWOS_STORE` and `DATABASE_SSL` are agent-only**
|
|
10
|
+
> **Questions source of truth:** `VERCEL_QUESTIONS.md` �?Agents must **open that file and paste each Q block verbatim**. Do not paraphrase.
|
|
11
|
+
> **Copy questions verbatim from `VERCEL_QUESTIONS.md`.** Do not invent alternate Q2 options (no “Supabase Storage�? no S3, no “disable media�? no making Blob the recommended option).
|
|
12
|
+
> **`TOMORROWOS_STORE` and `DATABASE_SSL` are agent-only** �?set them automatically after Q1; never put them in a user-facing Env form for the customer to type.
|
|
13
13
|
|
|
14
14
|
## NON-NEGOTIABLE VERCEL RUNTIME CONTRACT
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ The following rules are **mandatory acceptance criteria**, not suggestions. The
|
|
|
18
18
|
1. **One TomorrowOS instance only.** Use the shared instance exported through `api/index.ts`. **Never create a second `TomorrowOS` instance in `api/ws.ts`** or any other route.
|
|
19
19
|
2. **Never use `public/` as the Vercel Output Directory.** The dashboard Output Directory must be **empty / unset**. Serve the Control Panel at runtime from `cms-panel/` through TomorrowOS `staticRoot`.
|
|
20
20
|
3. **Preview may use Next only as an isolated reverse-proxy shell under `preview/`.** Preview must not replace TomorrowOS or determine the Production runtime.
|
|
21
|
-
4. **Publish must use the Fluid Function.** Production must run `api/index.ts`
|
|
21
|
+
4. **Publish must use the Fluid Function.** Production must run `api/index.ts` �?the shared exported `http.Server`, with Fluid compute enabled. It must not use `next start`, a static export, or a bare `(req, res)` handler.
|
|
22
22
|
5. **After every Production deployment, verify all three gates before saying setup is complete:**
|
|
23
23
|
- `GET /status` returns TomorrowOS **JSON**, not HTML.
|
|
24
24
|
- `GET /` returns the TomorrowOS **Control Panel**.
|
|
@@ -35,16 +35,16 @@ When a human pastes a prompt like this, **read this file and start Question 1 im
|
|
|
35
35
|
```text
|
|
36
36
|
Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
|
|
37
37
|
First open VERCEL_QUESTIONS.md. For Q1, Q2, Q3: paste each question block VERBATIM (character-for-character). Do not rephrase options.
|
|
38
|
-
Q2 must show Cloudinary (recommended) as option 1
|
|
39
|
-
After database choice, YOU set TOMORROWOS_STORE and DATABASE_SSL
|
|
38
|
+
Q2 must show Cloudinary (recommended) as option 1 �?never Blob-as-recommended, never S3, never Supabase Storage.
|
|
39
|
+
After database choice, YOU set TOMORROWOS_STORE and DATABASE_SSL �?do not ask me to fill those.
|
|
40
40
|
Do not use LLM_PROMPT.md or REPLIT_SETUP.md. No OpenAI/LLM API keys.
|
|
41
41
|
Apply the NON-NEGOTIABLE VERCEL RUNTIME CONTRACT exactly: one TomorrowOS instance; no public/ Output Directory; Preview Next only under preview/; Publish through the Fluid Function; verify /status JSON, / Control Panel, and WebSocket 101; pair devices only with the Production URL.
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Alternate triggers:
|
|
45
45
|
|
|
46
|
-
- “Set up TomorrowOS on Vercel using VERCEL_SETUP.md
|
|
47
|
-
- “Configure my TomorrowOS CMS (Vercel wizard)
|
|
46
|
+
- “Set up TomorrowOS on Vercel using VERCEL_SETUP.md�?
|
|
47
|
+
- “Configure my TomorrowOS CMS (Vercel wizard)�?
|
|
48
48
|
|
|
49
49
|
When any of these fire, **ignore `REPLIT_SETUP.md` and `LLM_PROMPT.md`** for the Q&A phase (you may skim Replit docs only for shared SDK semantics).
|
|
50
50
|
|
|
@@ -57,9 +57,9 @@ When any of these fire, **ignore `REPLIT_SETUP.md` and `LLM_PROMPT.md`** for the
|
|
|
57
57
|
TomorrowOS CMS is **one Node process** that serves:
|
|
58
58
|
|
|
59
59
|
- Browser Control Panel at `GET /` (`public/` via `staticRoot`)
|
|
60
|
-
- Device **WebSocket** on the same origin (`https`
|
|
60
|
+
- Device **WebSocket** on the same origin (`https` �?players use `wss`)
|
|
61
61
|
|
|
62
|
-
### Production / Publish
|
|
62
|
+
### Production / Publish �?Node only (no Next.js)
|
|
63
63
|
|
|
64
64
|
| Required | Forbidden |
|
|
65
65
|
|----------|-----------|
|
|
@@ -68,12 +68,12 @@ TomorrowOS CMS is **one Node process** that serves:
|
|
|
68
68
|
| **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
|
|
69
69
|
| Durable store = **Postgres** (Supabase or Neon, pooled URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
|
|
70
70
|
| Media = **Cloudinary** or **Vercel Blob** (durable HTTPS URLs) | Relying on local `public/uploads` alone for production fleets |
|
|
71
|
-
| Env vars in **Vercel Project
|
|
71
|
+
| Env vars in **Vercel Project �?Settings �?Environment Variables** | Committing passwords / API secrets / `.env` with secrets |
|
|
72
72
|
| `cms.hostingTarget`: `"vercel"` in `brand.json` | Creating `.replit` / Replit artifact files |
|
|
73
73
|
|
|
74
74
|
**Publish / Production Deploy** must run **`TomorrowOS.listen(...)`** directly. Do **not** put Next.js in front of Production traffic.
|
|
75
75
|
|
|
76
|
-
### Publish `404: NOT_FOUND`
|
|
76
|
+
### Publish `404: NOT_FOUND` �?mandatory prevention (read before every deploy)
|
|
77
77
|
|
|
78
78
|
The production URL showing:
|
|
79
79
|
|
|
@@ -86,7 +86,7 @@ almost always means **Vercel still thinks this is a Next.js / static deploy**, n
|
|
|
86
86
|
|
|
87
87
|
**Before every Production deploy, you MUST verify all of the following:**
|
|
88
88
|
|
|
89
|
-
1. **Project
|
|
89
|
+
1. **Project �?Settings �?General �?Framework Preset = Other** (or unset / null).
|
|
90
90
|
If it still says **Next.js**, patch it via Vercel API / CLI / dashboard **before** redeploying.
|
|
91
91
|
`"framework": null` in `vercel.json` alone is **not enough** if the dashboard still says Next.js.
|
|
92
92
|
2. **Install Command** = `npm install` / `pnpm install` / `yarn` (normal install).
|
|
@@ -101,7 +101,7 @@ almost always means **Vercel still thinks this is a Next.js / static deploy**, n
|
|
|
101
101
|
|
|
102
102
|
**Setup is NOT complete** while Production `GET /` returns `404: NOT_FOUND` or WebSocket upgrade returns 200 HTML.
|
|
103
103
|
|
|
104
|
-
### Publish static trap
|
|
104
|
+
### Publish static trap �?`public/` served without Node (read this)
|
|
105
105
|
|
|
106
106
|
A common **worse** failure mode than `404: NOT_FOUND`:
|
|
107
107
|
|
|
@@ -110,7 +110,7 @@ A common **worse** failure mode than `404: NOT_FOUND`:
|
|
|
110
110
|
- The panel shows **CMS unreachable** and console:
|
|
111
111
|
`Unexpected token 'T', "The page c"... is not valid JSON`
|
|
112
112
|
|
|
113
|
-
**Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *“public if it exists
|
|
113
|
+
**Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *“public if it exists�?). **No Function** is running TomorrowOS.
|
|
114
114
|
|
|
115
115
|
**Fix (Scheme A):** use `cms-panel/` (not `public/`), Fluid Function `api/index.ts` + rewrites, Output Directory empty. Static UI is served **at runtime** via `staticRoot` in `listen()`.
|
|
116
116
|
|
|
@@ -124,7 +124,7 @@ curl -s https://YOUR-PROJECT.vercel.app/status | head -c 80
|
|
|
124
124
|
|
|
125
125
|
**Setup is NOT complete** while `/status` returns HTML, the panel shows the JSON parse error, or devices cannot open `wss://`.
|
|
126
126
|
|
|
127
|
-
### Preview (v0 / Vercel Agent sandbox)
|
|
127
|
+
### Preview (v0 / Vercel Agent sandbox) �?Next.js shell allowed
|
|
128
128
|
|
|
129
129
|
v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx server.ts` may listen successfully while Preview still shows a blank page, because the sandbox intercepts the public port with Next before requests reach TomorrowOS.
|
|
130
130
|
|
|
@@ -134,10 +134,10 @@ v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx se
|
|
|
134
134
|
|---------------------|--------|
|
|
135
135
|
| Next.js remains the Preview host | Satisfies v0 / sandbox expectations |
|
|
136
136
|
| TomorrowOS on internal port | e.g. `TOMORROWOS_INTERNAL_PORT=3001` (not the public Preview port) |
|
|
137
|
-
| Transparent HTTP reverse proxy | Next route/middleware forwards `/` (and Control Panel paths)
|
|
137
|
+
| Transparent HTTP reverse proxy | Next route/middleware forwards `/` (and Control Panel paths) �?`http://127.0.0.1:3001` |
|
|
138
138
|
| Start both processes in Preview | Next + `tsx server.ts` (or a `dev:preview` script that starts both) |
|
|
139
139
|
|
|
140
|
-
**Preview WebSocket note:** HTTP proxy is enough for Control Panel HTML/API smoke checks. Device `wss` pairing may not work through a Next proxy in Preview
|
|
140
|
+
**Preview WebSocket note:** HTTP proxy is enough for Control Panel HTML/API smoke checks. Device `wss` pairing may not work through a Next proxy in Preview �?that is acceptable. **Pair devices against the Production Publish URL**, not v0 Preview.
|
|
141
141
|
|
|
142
142
|
**Do not** replace TomorrowOS with a Next-only CMS. Next is a **Preview adapter**, not the product.
|
|
143
143
|
|
|
@@ -145,43 +145,43 @@ v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx se
|
|
|
145
145
|
|
|
146
146
|
## Questionnaire scope (STRICT)
|
|
147
147
|
|
|
148
|
-
Ask **only** these questions, in **this exact order**. **Copy the “Ask exactly
|
|
148
|
+
Ask **only** these questions, in **this exact order**. **Copy the “Ask exactly�?blocks verbatim** �?do not paraphrase options.
|
|
149
149
|
|
|
150
150
|
| Step | Section | When |
|
|
151
151
|
|------|---------|------|
|
|
152
|
-
| 1 | **Question 1**
|
|
153
|
-
| 2 | **Question 2**
|
|
154
|
-
| 3 | **Question 3**
|
|
152
|
+
| 1 | **Question 1** �?Database (Supabase �?Neon �?SQLite) | Always |
|
|
153
|
+
| 2 | **Question 2** �?Media (**Cloudinary recommended** �?Vercel Blob �?local) | Always (after Q1) |
|
|
154
|
+
| 3 | **Question 3** �?Brand / TomorrowOS look (`brand.json` only) | Always last |
|
|
155
155
|
|
|
156
156
|
**That is the complete list.** Do **not** ask how many screens / devices.
|
|
157
157
|
|
|
158
158
|
**Forbidden invented Question 2 options (never offer these in chat or UI):**
|
|
159
159
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
160
|
+
- �?“Vercel Blob **(recommended)**�?as option 1 �?Blob is option **2**, not recommended over Cloudinary
|
|
161
|
+
- �?“Supabase Storage�?/ S3 bucket / `SUPABASE_SERVICE_ROLE_KEY` for media
|
|
162
|
+
- �?“No media storage�?/ “disable media uploads�?/ “remote URLs only�?as a numbered choice
|
|
163
|
+
- �?Any media list that does not start with **Cloudinary (recommended)**
|
|
164
164
|
|
|
165
165
|
**Forbidden Env UX after Question 1:**
|
|
166
166
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
167
|
+
- �?Showing the user empty fields for `TOMORROWOS_STORE` or `DATABASE_SSL` to fill
|
|
168
|
+
- �?Asking in chat “what should TOMORROWOS_STORE be?�?
|
|
169
|
+
- �?After DB choice + URL paste: **you** write those two vars silently (see Q1 Steps B/C/D)
|
|
170
170
|
|
|
171
171
|
**Forbidden extra questions (never ask, even if the user skips a step):**
|
|
172
172
|
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
173
|
+
- �?OpenAI / Anthropic / Gemini / any LLM API key (`sk-…`, etc.)
|
|
174
|
+
- �?“AI pairing�? “AI feature logic�? or inventing auth that needs an LLM key
|
|
175
|
+
- �?Asking the user to type `TOMORROWOS_STORE` or `DATABASE_SSL`
|
|
176
|
+
- �?Any secret not listed in Questions 1�? (DB URL, media credentials, brand)
|
|
177
177
|
|
|
178
178
|
TomorrowOS pairing and Control Panel logic live in `@tomorrowos/sdk`. They do **not** call OpenAI.
|
|
179
179
|
|
|
180
|
-
If the user **skips** a Question 2 option, do **not** invent a substitute question. Re-state Question 2 choices **verbatim**, or proceed with local uploads + warning
|
|
180
|
+
If the user **skips** a Question 2 option, do **not** invent a substitute question. Re-state Question 2 choices **verbatim**, or proceed with local uploads + warning �?never pivot to LLM keys or Supabase Storage.
|
|
181
181
|
|
|
182
|
-
If the user says “just set it up, don’t ask questions
|
|
182
|
+
If the user says “just set it up, don’t ask questions,�?respond:
|
|
183
183
|
|
|
184
|
-
> I need a few quick answers from VERCEL_SETUP.md (database
|
|
184
|
+
> I need a few quick answers from VERCEL_SETUP.md (database �?Supabase or Neon preferred �?media storage with Cloudinary recommended, and branding). It takes about a minute and ensures the CMS works on Vercel.
|
|
185
185
|
|
|
186
186
|
Then ask **Question 1**.
|
|
187
187
|
|
|
@@ -189,10 +189,10 @@ Then ask **Question 1**.
|
|
|
189
189
|
|
|
190
190
|
## Hard rules
|
|
191
191
|
|
|
192
|
-
1. **Open `VERCEL_QUESTIONS.md` first.** Paste Q1
|
|
192
|
+
1. **Open `VERCEL_QUESTIONS.md` first.** Paste Q1 �?configure �?paste Q2 �?configure �?paste Q3. Never paraphrase question options. Never use `LLM_PROMPT.md` or `REPLIT_SETUP.md` as the questionnaire. **Never** ask for OpenAI or other LLM API keys.
|
|
193
193
|
2. **Question 2 options are fixed:** (1) Cloudinary **(recommended)**, (2) Vercel Blob, (3) local disk. Do not reorder; do not invent Supabase Storage / disable-uploads.
|
|
194
194
|
3. **Cloudinary:** open **one** Vercel Env popup with **all** Cloudinary fields together. Do **not** quiz one credential at a time.
|
|
195
|
-
4. **`TOMORROWOS_STORE` + `DATABASE_SSL` = agent-owned.** After Q1 choice (and URL if needed), set them yourself in Vercel Env Vars. **Never** include them as blank fields for the customer. Mapping: Supabase
|
|
195
|
+
4. **`TOMORROWOS_STORE` + `DATABASE_SSL` = agent-owned.** After Q1 choice (and URL if needed), set them yourself in Vercel Env Vars. **Never** include them as blank fields for the customer. Mapping: Supabase �?`TOMORROWOS_STORE=supabase` + `DATABASE_SSL=true`; Neon �?`TOMORROWOS_STORE=postgres` + `DATABASE_SSL=true`; SQLite �?`TOMORROWOS_STORE=sqlite` (no `DATABASE_SSL` required).
|
|
196
196
|
5. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
|
|
197
197
|
6. **Postgres env naming:** prefer **`SUPABASE_URL`** for Supabase; prefer **`DATABASE_URL`** for Neon.
|
|
198
198
|
7. **Always prefer pooled connection strings** (Supabase Session pooler **6543**; Neon pooled host).
|
|
@@ -203,63 +203,63 @@ Then ask **Question 1**.
|
|
|
203
203
|
12. **Enable Fluid compute** for Production WebSockets.
|
|
204
204
|
13. **Production:** `api/index.ts` exports `http.Server` from `TomorrowOS.listen`. Not `next start`. No second TomorrowOS in `api/ws.ts`.
|
|
205
205
|
14. **Preview:** Next reverse-proxy shell when v0 is Next-hardwired.
|
|
206
|
-
15. After Q&A: **configure
|
|
206
|
+
15. After Q&A: **configure �?install �?deploy �?minimal verify**.
|
|
207
207
|
16. **No inventing CMS login** from a branding URL.
|
|
208
|
-
17. **Skip
|
|
208
|
+
17. **Skip �?invent.** Stay on Q1–Q3 only.
|
|
209
209
|
18. **Publish gate:** `/status` JSON + WebSocket 101; no static `public/` trap.
|
|
210
210
|
|
|
211
|
-
**Question order:** Q1
|
|
211
|
+
**Question order:** Q1 �?(auto-set store env) �?Q2 �?Q3 �?execution checklist.
|
|
212
212
|
|
|
213
213
|
---
|
|
214
214
|
|
|
215
|
-
## Scheme A
|
|
215
|
+
## Scheme A �?Vercel Functions + WebSockets (mandatory for Production)
|
|
216
216
|
|
|
217
217
|
> **Goal:** Preview may use Next + proxy; **Publish** runs TomorrowOS as a **Vercel Function** (`api/index.ts`) on **Fluid**, with WebSocket support per [Vercel WebSockets docs](https://vercel.com/docs/functions/websockets).
|
|
218
218
|
> **Scope:** Layout + `vercel.json` + dashboard. Prefer `npx @tomorrowos/sdk init --hosting v0` (`cms-starter-v0`).
|
|
219
|
-
> **Do not** break Replit/Railway: those keep using default `init`
|
|
219
|
+
> **Do not** break Replit/Railway: those keep using default `init` �?`cms-starter` (root `server.ts` + `server.listen`, no `api/`).
|
|
220
220
|
|
|
221
221
|
### Why Scheme A exists
|
|
222
222
|
|
|
223
223
|
| Layer | Preview (v0) | Publish (Production) |
|
|
224
224
|
|-------|----------------|----------------------|
|
|
225
|
-
| Host | Next.js shell + HTTP proxy | **Vercel Function** `api/index.ts`
|
|
226
|
-
| Static UI | Proxied from TomorrowOS | Served by **`staticRoot`** (`cms-panel/`) inside `listen()`
|
|
227
|
-
| WebSocket | Optional / may fail in Preview | **Required**
|
|
225
|
+
| Host | Next.js shell + HTTP proxy | **Vercel Function** `api/index.ts` �?`export default server` |
|
|
226
|
+
| Static UI | Proxied from TomorrowOS | Served by **`staticRoot`** (`cms-panel/`) inside `listen()` �?**not** Vercel Output Directory |
|
|
227
|
+
| WebSocket | Optional / may fail in Preview | **Required** �?Fluid Function upgrade (`wss://` �?`/` rewritten to `/api`, or direct `/api`) |
|
|
228
228
|
| Next files | Allowed under `preview/` | **Must not** live at project root |
|
|
229
229
|
|
|
230
|
-
**Root cause of “Control Panel works, TV cannot connect
|
|
230
|
+
**Root cause of “Control Panel works, TV cannot connect�?** HTTP can be served without WebSocket upgrades. Classic root-only `server.ts` / plain serverless handlers often return **200 HTML** on `Upgrade` instead of **101**. Official pattern is a Function that exports `http.Server` with `ws` attached ([docs](https://vercel.com/docs/functions/websockets)).
|
|
231
231
|
|
|
232
|
-
### A1
|
|
232
|
+
### A1 �?Project layout (Agent must enforce)
|
|
233
233
|
|
|
234
234
|
**Production (Vercel / v0 starter):**
|
|
235
235
|
|
|
236
236
|
```
|
|
237
|
-
cms-app.ts
|
|
238
|
-
api/index.ts
|
|
239
|
-
server.ts
|
|
240
|
-
vercel.json
|
|
237
|
+
cms-app.ts �?shared TomorrowOS.listen + export const server
|
|
238
|
+
api/index.ts �?export { server as default } from "../cms-app.js"
|
|
239
|
+
server.ts �?local/Preview: same default export (npm start / internal port)
|
|
240
|
+
vercel.json �?fluid + rewrites + functions.maxDuration
|
|
241
241
|
package.json
|
|
242
242
|
brand.json
|
|
243
|
-
cms-panel/
|
|
244
|
-
preview/
|
|
243
|
+
cms-panel/ �?CMS static UI (NOT named public/ �?avoids static-output trap)
|
|
244
|
+
preview/ �?Next proxy for v0 Preview only
|
|
245
245
|
```
|
|
246
246
|
|
|
247
|
-
**Preview only
|
|
247
|
+
**Preview only �?keep ALL Next under `preview/`:**
|
|
248
248
|
|
|
249
249
|
```
|
|
250
250
|
preview/
|
|
251
251
|
next.config.mjs
|
|
252
252
|
app/
|
|
253
|
-
|
|
253
|
+
�?
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
**Do NOT:**
|
|
257
257
|
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
258
|
+
- �?Leave `next.config.mjs` + `app/` at project root
|
|
259
|
+
- �?Set Output Directory to `public`, `cms-panel`, or `.`
|
|
260
|
+
- �?Create a **second** TomorrowOS instance in `api/ws.ts` (separate isolate = broken pairing memory)
|
|
261
|
+
- �?Use `(req, res) =>` serverless handlers that drop `upgrade`
|
|
262
|
+
- �?Put `startCommand` / `processes` in `vercel.json` (v0 schema rejects them)
|
|
263
263
|
|
|
264
264
|
**`package.json` scripts:**
|
|
265
265
|
|
|
@@ -276,10 +276,10 @@ preview/
|
|
|
276
276
|
}
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
- **Local / Railway-style:** `npm start`
|
|
279
|
+
- **Local / Railway-style:** `npm start` �?`tsx server.ts` �?SDK calls `server.listen` (no `VERCEL` env).
|
|
280
280
|
- **Vercel Production:** Function loads `api/index.ts`; SDK skips `listen` when `process.env.VERCEL` is set and Vercel owns the socket.
|
|
281
281
|
|
|
282
|
-
### A2
|
|
282
|
+
### A2 �?Function entry (matches Vercel WebSockets docs)
|
|
283
283
|
|
|
284
284
|
```ts
|
|
285
285
|
// api/index.ts
|
|
@@ -299,10 +299,10 @@ export { server, tomorrowos };
|
|
|
299
299
|
SDK behaviour (0.9.32+):
|
|
300
300
|
|
|
301
301
|
- Accepts WebSocket upgrades on `/`, `/api`, `/api/ws` (players may use either)
|
|
302
|
-
- When `VERCEL` is set: **`autoListen` defaults to false**
|
|
302
|
+
- When `VERCEL` is set: **`autoListen` defaults to false** �?do not bind a port yourself
|
|
303
303
|
- Pattern aligns with docs: create `http.Server`, attach `ws`, **`export default server`**
|
|
304
304
|
|
|
305
|
-
### A3
|
|
305
|
+
### A3 �?`vercel.json` (Production)
|
|
306
306
|
|
|
307
307
|
```json
|
|
308
308
|
{
|
|
@@ -323,7 +323,7 @@ SDK behaviour (0.9.32+):
|
|
|
323
323
|
}
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
**`maxDuration` plan limits:** Hobby / many default plans allow **1
|
|
326
|
+
**`maxDuration` plan limits:** Hobby / many default plans allow **1�?00** seconds. **800** requires a higher Vercel plan. Prefer **`300`** unless the account already allows more �?otherwise Publish fails with *“maxDuration must be between 1 and 300�?upgrade your plan�?.
|
|
327
327
|
|
|
328
328
|
**Why rewrites:** Public URLs stay `https://app.vercel.app/` and `/status` while the Function mount is `/api`. Device `wss://app.vercel.app/` is rewritten to the same Function so one isolate handles HTTP + WS.
|
|
329
329
|
|
|
@@ -333,24 +333,24 @@ SDK behaviour (0.9.32+):
|
|
|
333
333
|
|
|
334
334
|
- `"fluid": true` is required for WebSockets
|
|
335
335
|
- **Do not** set `outputDirectory` to `cms-panel` / `public`
|
|
336
|
-
- `"buildCommand"` = no-op
|
|
336
|
+
- `"buildCommand"` = no-op �?**never** `next build` for Production
|
|
337
337
|
- Dashboard Output Directory must stay **empty**
|
|
338
338
|
|
|
339
|
-
### A4
|
|
339
|
+
### A4 �?Vercel Project Settings (dashboard �?mandatory)
|
|
340
340
|
|
|
341
341
|
| Setting | Required value |
|
|
342
342
|
|---------|----------------|
|
|
343
|
-
| Framework Preset | **Other** / null
|
|
343
|
+
| Framework Preset | **Other** / null �?**not Next.js** |
|
|
344
344
|
| Install Command | `npm install` |
|
|
345
345
|
| Build Command | `npm run build` (no-op) |
|
|
346
346
|
| **Output Directory** | **empty** |
|
|
347
347
|
| Fluid compute | **On** |
|
|
348
|
-
| WebSockets | Account must allow Functions WebSockets (see docs “Permissions Required
|
|
348
|
+
| WebSockets | Account must allow Functions WebSockets (see docs “Permissions Required�? |
|
|
349
349
|
|
|
350
|
-
### A5
|
|
350
|
+
### A5 �?Publish acceptance (all must pass)
|
|
351
351
|
|
|
352
|
-
1. `curl -s https://PROD/status`
|
|
353
|
-
2. `curl -s https://PROD/`
|
|
352
|
+
1. `curl -s https://PROD/status` �?JSON (`{"status":...}`)
|
|
353
|
+
2. `curl -s https://PROD/` �?Control Panel HTML
|
|
354
354
|
3. **WebSocket upgrade returns 101**, not 200 HTML:
|
|
355
355
|
|
|
356
356
|
```bash
|
|
@@ -365,28 +365,28 @@ curl.exe -i --http1.1 ^
|
|
|
365
365
|
4. Control Panel does **not** show CMS unreachable / `Unexpected token 'T'`
|
|
366
366
|
5. Fluid on; Framework = Other
|
|
367
367
|
|
|
368
|
-
If (1)+(2) pass but (3) fails
|
|
368
|
+
If (1)+(2) pass but (3) fails �?Function is serving HTTP only. Re-check Fluid, `api/index.ts` default export, rewrites, and WebSockets permission. **Do not** invent a second `api/ws.ts` TomorrowOS instance.
|
|
369
369
|
|
|
370
|
-
### A6
|
|
370
|
+
### A6 �?What Scheme A does **not** require
|
|
371
371
|
|
|
372
|
-
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
372
|
+
- �?No Next.js as Production CMS
|
|
373
|
+
- �?No replacing SDK transport with hand-rolled pairing
|
|
374
|
+
- �?No SQLite as production store on Vercel
|
|
375
|
+
- �?No changes to Replit `cms-starter` layout for Vercel-only fixes
|
|
376
376
|
|
|
377
|
-
### A7
|
|
377
|
+
### A7 �?If v0 Publish still fails (Path B / C)
|
|
378
378
|
|
|
379
379
|
**Path B:** Deploy with `vercel --prod` or Git integration (full Vercel), same Scheme A layout.
|
|
380
380
|
|
|
381
381
|
**Path C:** Host CMS on **Railway / Fly.io / Replit** (long-lived Node). Use Vercel only for Preview if needed.
|
|
382
382
|
|
|
383
|
-
### A8
|
|
383
|
+
### A8 �?Migrate an existing broken Vercel project
|
|
384
384
|
|
|
385
385
|
1. Scaffold/compare with `cms-starter-v0` (`init --hosting v0`) or copy:
|
|
386
386
|
- `cms-app.ts`, `api/index.ts`, `server.ts`, `vercel.json`
|
|
387
|
-
- rename `public/`
|
|
387
|
+
- rename `public/` �?`cms-panel/` if still present
|
|
388
388
|
2. Ensure **one** Function exports the **same** `server` from `cms-app.ts`
|
|
389
|
-
3. Fluid on
|
|
389
|
+
3. Fluid on �?Redeploy
|
|
390
390
|
4. Verify A5 (especially WebSocket 101)
|
|
391
391
|
5. On TVs: enter `https://YOUR.vercel.app/` (players also try `/api` automatically on vercel.app hosts)
|
|
392
392
|
|
|
@@ -396,7 +396,7 @@ If (1)+(2) pass but (3) fails → Function is serving HTTP only. Re-check Fluid,
|
|
|
396
396
|
|
|
397
397
|
### Production runtime (Publish)
|
|
398
398
|
|
|
399
|
-
- Serve TomorrowOS via **Vercel Function** `api/index.ts`
|
|
399
|
+
- Serve TomorrowOS via **Vercel Function** `api/index.ts` �?`export default server` (Fluid + WebSockets).
|
|
400
400
|
- Keep root **`server.ts`** for **local** `npm start` / Preview internal port only (same `cms-app.ts`).
|
|
401
401
|
- Keep **`@tomorrowos/sdk`** (0.9.32+) and put **`tsx` in `dependencies`** for local start.
|
|
402
402
|
- Node **20+**.
|
|
@@ -456,7 +456,7 @@ Add `@vercel/blob` only when the user chooses **Vercel Blob** in Question 2.
|
|
|
456
456
|
|
|
457
457
|
For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a process runner (`concurrently` or equivalent). These must **not** become the Production CMS.
|
|
458
458
|
|
|
459
|
-
### `server.ts` pattern (Vercel-adapted
|
|
459
|
+
### `server.ts` pattern (Vercel-adapted �?Production)
|
|
460
460
|
|
|
461
461
|
Keep the server at **project root** (e.g. `server.ts` / `server.mts`). Do **not** move Production into `api/` solely to satisfy Vercel routing myths.
|
|
462
462
|
|
|
@@ -496,14 +496,14 @@ const server = tomorrowos.listen({
|
|
|
496
496
|
export default server;
|
|
497
497
|
```
|
|
498
498
|
|
|
499
|
-
- **Production:** `PORT` from Vercel
|
|
499
|
+
- **Production:** `PORT` from Vercel �?TomorrowOS listens on that port (no Next).
|
|
500
500
|
- **Preview:** set `TOMORROWOS_INTERNAL_PORT=3001` (or similar) so Next can own `:3000` and proxy inward.
|
|
501
501
|
|
|
502
502
|
Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
|
|
503
503
|
|
|
504
|
-
### Preview adapter (v0 / Next reverse proxy)
|
|
504
|
+
### Preview adapter (v0 / Next reverse proxy) �?required when Preview is Next-hardwired
|
|
505
505
|
|
|
506
|
-
Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening
|
|
506
|
+
Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening�?. Then:
|
|
507
507
|
|
|
508
508
|
1. Keep a **minimal** Next app **isolated when possible** (e.g. under `preview/`) so Production Framework detection does not latch onto root `next.config.*`.
|
|
509
509
|
2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
|
|
@@ -526,9 +526,9 @@ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Contr
|
|
|
526
526
|
- Deleting TomorrowOS `server.ts` because Preview needed Next.
|
|
527
527
|
- Claiming setup complete if Production entrypoint is still `next start`.
|
|
528
528
|
- Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
|
|
529
|
-
- Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404
|
|
529
|
+
- Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404�?
|
|
530
530
|
|
|
531
|
-
### `vercel.json` (Production
|
|
531
|
+
### `vercel.json` (Production �?explicit Node, not Next)
|
|
532
532
|
|
|
533
533
|
Write `vercel.json` so Production cannot be mistaken for Next. Minimum:
|
|
534
534
|
|
|
@@ -545,23 +545,23 @@ Use `pnpm` / `yarn` variants of install/build if that is the project’s package
|
|
|
545
545
|
|
|
546
546
|
**Do not** add `startCommand`, `processes`, or `outputDirectory` to `vercel.json` when v0 schema rejects them. Start = `package.json` `"start": "tsx server.ts"`. Output Directory = **empty in dashboard**.
|
|
547
547
|
|
|
548
|
-
**Also force Project Settings (dashboard or Vercel API)
|
|
548
|
+
**Also force Project Settings (dashboard or Vercel API) �?this is mandatory (see Scheme A4):**
|
|
549
549
|
|
|
550
550
|
| Setting | Required value |
|
|
551
551
|
|---------|----------------|
|
|
552
|
-
| Framework Preset | **Other** / null
|
|
552
|
+
| Framework Preset | **Other** / null �?**not Next.js** |
|
|
553
553
|
| Install Command | `npm install` (or pnpm/yarn) |
|
|
554
554
|
| Build Command | `npm run build` (no-op exit 0) |
|
|
555
|
-
| Output Directory | **empty / null**
|
|
556
|
-
| Start Command | `npm run start`
|
|
555
|
+
| Output Directory | **empty / null** �?**NOT `public`** |
|
|
556
|
+
| Start Command | `npm run start` �?`tsx server.ts` (or `.mts`) �?set in **dashboard**, not `vercel.json` |
|
|
557
557
|
| Fluid compute | **On** |
|
|
558
558
|
|
|
559
559
|
If `next` / `next.config.*` exist for Preview and the dashboard still auto-selects Next.js:
|
|
560
560
|
|
|
561
561
|
1. Patch Framework Preset to **Other** via API/CLI/dashboard.
|
|
562
562
|
2. Redeploy.
|
|
563
|
-
3. Hit Production `GET /status`
|
|
564
|
-
4. Hit Production `GET /`
|
|
563
|
+
3. Hit Production `GET /status` �?must be **JSON**, not HTML.
|
|
564
|
+
4. Hit Production `GET /` �?must be Control Panel HTML, not `404: NOT_FOUND`.
|
|
565
565
|
|
|
566
566
|
If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/` for Production**. Document the adaptation in the final summary.
|
|
567
567
|
|
|
@@ -574,15 +574,15 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
|
|
|
574
574
|
|
|
575
575
|
### What not to create
|
|
576
576
|
|
|
577
|
-
-
|
|
578
|
-
-
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
-
|
|
577
|
+
- �?`.replit`, `.replit-artifact`
|
|
578
|
+
- �?Next.js as the **Production** CMS (App Router replacing TomorrowOS)
|
|
579
|
+
- �?Root `api/` catch-all exporting raw `http.Server` to “fix�?Publish 404
|
|
580
|
+
- �?Separate “API project�?without the Control Panel
|
|
581
|
+
- �?Committing database passwords, Cloudinary secrets, or Blob tokens
|
|
582
582
|
|
|
583
583
|
---
|
|
584
584
|
|
|
585
|
-
## Question 1
|
|
585
|
+
## Question 1 �?Database (always)
|
|
586
586
|
|
|
587
587
|
> **Do not** ask screen counts. Present all three options; recommend **Supabase** for Vercel fleets.
|
|
588
588
|
> **After the user picks a database:** collect the URL if needed, then **you** set `TOMORROWOS_STORE` + `DATABASE_SSL` (see mapping below). The customer must **never** be asked to type those two names or values.
|
|
@@ -595,71 +595,71 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
|
|
|
595
595
|
| 2 Neon | `TOMORROWOS_STORE=postgres`, `DATABASE_SSL=true` | `DATABASE_URL` (pooled string) |
|
|
596
596
|
| 3 SQLite | `TOMORROWOS_STORE=sqlite` | Confirmation only |
|
|
597
597
|
|
|
598
|
-
If you open an Env configuration UI for Q1, fields visible to the user may include **only** `SUPABASE_URL` or `DATABASE_URL`. **Do not** show blank `TOMORROWOS_STORE` / `DATABASE_SSL` inputs
|
|
598
|
+
If you open an Env configuration UI for Q1, fields visible to the user may include **only** `SUPABASE_URL` or `DATABASE_URL`. **Do not** show blank `TOMORROWOS_STORE` / `DATABASE_SSL` inputs �?write those yourself in the same step.
|
|
599
599
|
|
|
600
|
-
### Step A
|
|
600
|
+
### Step A �?Ask storage choice
|
|
601
601
|
|
|
602
602
|
**Ask exactly:**
|
|
603
603
|
|
|
604
604
|
> Which database should TomorrowOS use on Vercel?
|
|
605
605
|
>
|
|
606
|
-
> **1. Supabase Postgres (recommended)**
|
|
606
|
+
> **1. Supabase Postgres (recommended)** �?durable pairing/playlists; use the **Session pooler** URL (`*.pooler.supabase.com:6543`).
|
|
607
607
|
>
|
|
608
|
-
> **2. Neon Postgres**
|
|
608
|
+
> **2. Neon Postgres** �?serverless Postgres native to Vercel; use Neon’s **pooled** connection string from the Neon dashboard (not the direct un-pooled host for serverless).
|
|
609
609
|
>
|
|
610
|
-
> **3. SQLite (`data/tomorrowos.db`)**
|
|
610
|
+
> **3. SQLite (`data/tomorrowos.db`)** �?**demo / local only on Vercel.** The filesystem is ephemeral; pairings and playlists will not survive redeploys. Only choose this if you understand the limitation.
|
|
611
611
|
>
|
|
612
|
-
> Reply with **1**, **2**, or **3** (or “Supabase
|
|
612
|
+
> Reply with **1**, **2**, or **3** (or “Supabase�?/ “Neon�?/ “SQLite�?.
|
|
613
613
|
|
|
614
|
-
### Step B
|
|
614
|
+
### Step B �?If **1 / Supabase**
|
|
615
615
|
|
|
616
|
-
**Ask exactly (connection string only
|
|
616
|
+
**Ask exactly (connection string only �?not TOMORROWOS_STORE / DATABASE_SSL):**
|
|
617
617
|
|
|
618
618
|
> Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**. I will set **`TOMORROWOS_STORE`** and **`DATABASE_SSL`** for you automatically.
|
|
619
619
|
>
|
|
620
|
-
> In Supabase: **Project Settings
|
|
620
|
+
> In Supabase: **Project Settings �?Database �?Connection string �?Connection pooling** (Session mode).
|
|
621
621
|
> Preferred shape:
|
|
622
622
|
> `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
|
|
623
623
|
>
|
|
624
624
|
> **Do not** use the direct host `db.*.supabase.co:5432` for Vercel.
|
|
625
625
|
|
|
626
|
-
**You must then (automatic
|
|
626
|
+
**You must then (automatic �?do NOT ask the user to type store/ssl vars):**
|
|
627
627
|
|
|
628
628
|
1. Set Vercel Env Vars in one step:
|
|
629
|
-
- `SUPABASE_URL=<user pooler string>`
|
|
630
|
-
- `TOMORROWOS_STORE=supabase`
|
|
631
|
-
- `DATABASE_SSL=true`
|
|
629
|
+
- `SUPABASE_URL=<user pooler string>` �?from user
|
|
630
|
+
- `TOMORROWOS_STORE=supabase` �?**you set; never ask**
|
|
631
|
+
- `DATABASE_SSL=true` �?**you set; never ask**
|
|
632
632
|
2. Wire `cms-app.ts` / `server.ts` store as in **Runtime & Vercel deploy rules**.
|
|
633
633
|
3. Optional committed `.env.example` with **placeholders only**.
|
|
634
634
|
4. **Do not** commit the real connection string.
|
|
635
635
|
5. **Do not** open a form whose blank fields include `TOMORROWOS_STORE` or `DATABASE_SSL`.
|
|
636
636
|
|
|
637
|
-
### Step C
|
|
637
|
+
### Step C �?If **2 / Neon**
|
|
638
638
|
|
|
639
639
|
**Ask exactly (connection string only):**
|
|
640
640
|
|
|
641
641
|
> Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**. I will set **`TOMORROWOS_STORE`** and **`DATABASE_SSL`** for you automatically.
|
|
642
642
|
>
|
|
643
|
-
> In Neon: **Dashboard
|
|
643
|
+
> In Neon: **Dashboard �?Connection details �?Pooled connection**.
|
|
644
644
|
> Typical shape:
|
|
645
645
|
> `postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
|
|
646
646
|
|
|
647
647
|
**You must then (automatic):**
|
|
648
648
|
|
|
649
649
|
1. Set Vercel Env Vars:
|
|
650
|
-
- `DATABASE_URL=<user pooled string>`
|
|
651
|
-
- `TOMORROWOS_STORE=postgres`
|
|
652
|
-
- `DATABASE_SSL=true`
|
|
650
|
+
- `DATABASE_URL=<user pooled string>` �?from user
|
|
651
|
+
- `TOMORROWOS_STORE=postgres` �?**you set; never ask**
|
|
652
|
+
- `DATABASE_SSL=true` �?**you set; never ask**
|
|
653
653
|
2. Wire `createTomorrowOSStore` for Postgres.
|
|
654
654
|
3. **Do not** commit the real connection string.
|
|
655
655
|
4. **Do not** ask the user to fill `TOMORROWOS_STORE` or `DATABASE_SSL` in chat or Env UI.
|
|
656
656
|
5. If you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
|
|
657
657
|
|
|
658
|
-
### Step D
|
|
658
|
+
### Step D �?If **3 / SQLite**
|
|
659
659
|
|
|
660
660
|
**Warn exactly before continuing:**
|
|
661
661
|
|
|
662
|
-
> SQLite on Vercel is **not** suitable for production fleets
|
|
662
|
+
> SQLite on Vercel is **not** suitable for production fleets �?data in `data/tomorrowos.db` is lost when the instance is recycled or redeployed. Continue only for a quick demo.
|
|
663
663
|
|
|
664
664
|
Only proceed after the user explicitly confirms.
|
|
665
665
|
|
|
@@ -673,29 +673,29 @@ Only proceed after the user explicitly confirms.
|
|
|
673
673
|
|
|
674
674
|
---
|
|
675
675
|
|
|
676
|
-
## Question 2
|
|
676
|
+
## Question 2 �?Media storage
|
|
677
677
|
|
|
678
678
|
> **This question is only about media files (images/videos).**
|
|
679
|
-
> It is **not** about OpenAI, AI pairing, or LLM keys. If you are about to ask for an `sk-` key, **stop**
|
|
680
|
-
> **Copy the three options below verbatim.** Wrong examples that must **never** appear: “Vercel Blob (recommended)
|
|
679
|
+
> It is **not** about OpenAI, AI pairing, or LLM keys. If you are about to ask for an `sk-` key, **stop** �?you are off-protocol.
|
|
680
|
+
> **Copy the three options below verbatim.** Wrong examples that must **never** appear: “Vercel Blob (recommended)�? “Supabase Storage�? “No media storage / disable uploads�?
|
|
681
681
|
|
|
682
|
-
### Step A
|
|
682
|
+
### Step A �?Ask storage choice
|
|
683
683
|
|
|
684
|
-
**Ask exactly (wording must match
|
|
684
|
+
**Ask exactly (wording must match �?Cloudinary is recommended):**
|
|
685
685
|
|
|
686
686
|
> How should playlist media (images/videos) be stored?
|
|
687
687
|
>
|
|
688
|
-
> **1. Cloudinary (recommended)**
|
|
688
|
+
> **1. Cloudinary (recommended)** �?durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection. **Prefer this on Vercel.**
|
|
689
689
|
>
|
|
690
|
-
> **2. Vercel Blob**
|
|
690
|
+
> **2. Vercel Blob** �?Vercel-native object storage; durable `https://*.public.blob.vercel-storage.com/...` URLs. Use when you want media on the same Vercel project without a Cloudinary account.
|
|
691
691
|
>
|
|
692
|
-
> **3. Local disk only**
|
|
692
|
+
> **3. Local disk only** �?`cms-panel/uploads` or `public/uploads` (OK for quick local tests; **not** for production Vercel fleets �?files are ephemeral).
|
|
693
693
|
>
|
|
694
|
-
> Reply with **1**, **2**, or **3** (or “Cloudinary
|
|
694
|
+
> Reply with **1**, **2**, or **3** (or “Cloudinary�?/ “Vercel Blob�?/ “local�?.
|
|
695
695
|
|
|
696
|
-
If you catch yourself about to offer Blob-as-recommended, Supabase Storage, or “no media
|
|
696
|
+
If you catch yourself about to offer Blob-as-recommended, Supabase Storage, or “no media�? **stop and paste the block above instead**.
|
|
697
697
|
|
|
698
|
-
### Step B
|
|
698
|
+
### Step B �?If **1 / Cloudinary** (same Question 2 �?**one Env popup**)
|
|
699
699
|
|
|
700
700
|
> **Critical UX rule:** Immediately open the **Vercel Environment Variables configuration popup / form** with **all Cloudinary fields in one UI**. Do **not** ask for cloud name, then key, then secret in separate chat messages.
|
|
701
701
|
|
|
@@ -726,13 +726,13 @@ If you catch yourself about to offer Blob-as-recommended, Supabase Storage, or
|
|
|
726
726
|
|
|
727
727
|
The SDK auto-detects these env vars.
|
|
728
728
|
|
|
729
|
-
### Step C
|
|
729
|
+
### Step C �?If **2 / Vercel Blob** (same Question 2)
|
|
730
730
|
|
|
731
731
|
**Ask exactly:**
|
|
732
732
|
|
|
733
733
|
> I will enable **Vercel Blob** for media uploads.
|
|
734
734
|
>
|
|
735
|
-
> 1. In the Vercel project: **Storage
|
|
735
|
+
> 1. In the Vercel project: **Storage �?Create �?Blob** (or link an existing Blob store to this project).
|
|
736
736
|
> 2. Confirm **`BLOB_READ_WRITE_TOKEN`** is available (Vercel usually injects it when Blob is linked).
|
|
737
737
|
>
|
|
738
738
|
> Paste the token only if it is not already set in your project Env Vars. Do you already have Blob linked on this Vercel project? (yes / no)
|
|
@@ -749,57 +749,57 @@ The SDK auto-detects these env vars.
|
|
|
749
749
|
import { put } from "@vercel/blob";
|
|
750
750
|
|
|
751
751
|
// On upload: const blob = await put(filename, body, { access: "public", token: process.env.BLOB_READ_WRITE_TOKEN });
|
|
752
|
-
// Store blob.url in uploaded_assets
|
|
752
|
+
// Store blob.url in uploaded_assets �?same shape as Cloudinary secure URLs.
|
|
753
753
|
```
|
|
754
754
|
|
|
755
|
-
4. Warn: until the project’s Blob bridge is wired, Control Panel **Media** status may show local/ephemeral
|
|
755
|
+
4. Warn: until the project’s Blob bridge is wired, Control Panel **Media** status may show local/ephemeral �?verify uploads return `https://*.blob.vercel-storage.com/...` before calling setup complete.
|
|
756
756
|
5. Do **not** proceed to Question 3 until `BLOB_READ_WRITE_TOKEN` exists (or Blob is linked and token is confirmed in the Vercel dashboard).
|
|
757
757
|
|
|
758
|
-
### Step D
|
|
758
|
+
### Step D �?If **3 / local uploads**
|
|
759
759
|
|
|
760
760
|
1. `mkdir -p public/uploads`
|
|
761
|
-
2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs
|
|
761
|
+
2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs �?Cloudinary or Vercel Blob is strongly preferred for Vercel.
|
|
762
762
|
|
|
763
763
|
---
|
|
764
764
|
|
|
765
|
-
## Question 3
|
|
765
|
+
## Question 3 �?Brand (`brand.json` only)
|
|
766
766
|
|
|
767
767
|
> Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or `server.ts` store wiring beyond what Q1–Q2 already required.
|
|
768
768
|
>
|
|
769
|
-
> **IRON RULE
|
|
770
|
-
> If the user pastes a URL (or says “make it look like this site
|
|
771
|
-
> **Do not** scaffold a login page, signup, OAuth, gated dashboard, or copy the reference site’s IA/pages
|
|
769
|
+
> **IRON RULE �?website URL �?build a website / login / auth CMS.**
|
|
770
|
+
> If the user pastes a URL (or says “make it look like this site�?, that input is **reference material for `brand.json` only** (name, colours, fonts, logo, tagline).
|
|
771
|
+
> **Do not** scaffold a login page, signup, OAuth, gated dashboard, or copy the reference site’s IA/pages �?**unless the user explicitly asks for CMS login / auth**.
|
|
772
772
|
> Default TomorrowOS Control Panel has **no login**. Keep it that way.
|
|
773
773
|
|
|
774
|
-
### Step A
|
|
774
|
+
### Step A �?Ask branding input
|
|
775
775
|
|
|
776
776
|
**Ask exactly:**
|
|
777
777
|
|
|
778
778
|
> Let’s brand your TomorrowOS experience. You can answer in either way:
|
|
779
779
|
>
|
|
780
|
-
> **Option A
|
|
780
|
+
> **Option A �?Website URL only (brand reference)**
|
|
781
781
|
> Paste **one public website URL**. I will use it **only** to infer colours, fonts, name, tagline, and logo for **`brand.json`**.
|
|
782
|
-
> I will **not** rebuild that website, add a login page, or change Control Panel features
|
|
782
|
+
> I will **not** rebuild that website, add a login page, or change Control Panel features �?unless you explicitly ask for login/auth later.
|
|
783
783
|
>
|
|
784
|
-
> **Option B
|
|
784
|
+
> **Option B �?Manual fields**
|
|
785
785
|
> Provide:
|
|
786
786
|
> 1. **Product / venue name**
|
|
787
787
|
> 2. **Tagline** (optional)
|
|
788
788
|
> 3. **Primary colour** (hex, e.g. `#FF8A3D`)
|
|
789
|
-
> 4. **Background colour** (hex, optional
|
|
790
|
-
> 5. **Text colour** (hex, optional
|
|
789
|
+
> 4. **Background colour** (hex, optional �?default `#FAFAF9`)
|
|
790
|
+
> 5. **Text colour** (hex, optional �?default `#0A0908`)
|
|
791
791
|
> 6. **Secondary / accent colour** (hex, optional)
|
|
792
|
-
> 7. **Logo**
|
|
792
|
+
> 7. **Logo** �?upload SVG/PNG into the project, or a URL I can fetch into `./assets/`
|
|
793
793
|
|
|
794
|
-
### Step B
|
|
794
|
+
### Step B �?If the user gives **only a website URL** (Option A)
|
|
795
795
|
|
|
796
796
|
**You must:**
|
|
797
797
|
|
|
798
798
|
1. **Fetch and inspect** the page (HTTP GET the URL; follow one redirect if needed). Do not invent colours.
|
|
799
799
|
2. **Write / update `brand.json` only** from what you infer. Touch nothing else for this step (no new pages, no auth, no Next marketing site, no login UI).
|
|
800
800
|
3. **Derive branding** using this priority order:
|
|
801
|
-
- **Name:** `<title>`, `og:site_name`, or prominent header / logo `alt` text (trim to
|
|
802
|
-
- **Tagline:** `meta[name="description"]`, `og:description`, or first hero subtitle (
|
|
801
|
+
- **Name:** `<title>`, `og:site_name`, or prominent header / logo `alt` text (trim to �?60 chars).
|
|
802
|
+
- **Tagline:** `meta[name="description"]`, `og:description`, or first hero subtitle (�?120 chars).
|
|
803
803
|
- **Primary colour:** `meta[name="theme-color"]`, CSS `--primary` / `--brand` variables, or dominant accent from linked stylesheets / inline styles (convert to `#RRGGBB`).
|
|
804
804
|
- **Background colour:** `body` / `:root` background (default `#FAFAF9` if light site).
|
|
805
805
|
- **Text colour:** main body text colour (default `#0A0908` if light site).
|
|
@@ -808,20 +808,20 @@ import { put } from "@vercel/blob";
|
|
|
808
808
|
- **Logo:** prefer `og:image`, then `link[rel="icon"]` / apple-touch-icon, then header `<img>` logo. Download into `./assets/logo.png` or `./assets/logo.svg` and set `logoPath` accordingly. If only favicon exists, use it and note size in the summary.
|
|
809
809
|
4. **Contrast check:** if background is dark, set `activationScreen.theme` to `"dark"` and add `logoPathOnDark` when a light logo variant exists.
|
|
810
810
|
5. **Infer `cms.useCase`** from page content when obvious (restaurant / retail / corporate / etc.); otherwise `"other"`.
|
|
811
|
-
6. **Show a one-line summary** to the user (name + primary hex + logo path) before or while writing the file
|
|
811
|
+
6. **Show a one-line summary** to the user (name + primary hex + logo path) before or while writing the file �?do not ask a second branding question unless fetch failed.
|
|
812
812
|
7. If the URL is unreachable, blocked, or has no usable styles, say so and fall back to **Option B** manual questions for missing fields only.
|
|
813
813
|
|
|
814
814
|
**Explicitly forbidden when the user only gave a URL (no login request):**
|
|
815
815
|
|
|
816
|
-
-
|
|
817
|
-
-
|
|
818
|
-
-
|
|
819
|
-
-
|
|
820
|
-
-
|
|
816
|
+
- �?Login / signup / “Sign in to continue�?screens
|
|
817
|
+
- �?Auth middleware, sessions, cookies, OAuth, password forms
|
|
818
|
+
- �?Cloning the reference site as a marketing landing or multi-page CMS
|
|
819
|
+
- �?Gating the Control Panel behind authentication
|
|
820
|
+
- �?Treating “make it like this site�?as “rebuild this site�?
|
|
821
821
|
|
|
822
|
-
If the reference site itself is a login page, **still only extract visual brand tokens** (colours/logo/name) into `brand.json`
|
|
822
|
+
If the reference site itself is a login page, **still only extract visual brand tokens** (colours/logo/name) into `brand.json` �?do **not** recreate that login flow.
|
|
823
823
|
|
|
824
|
-
### Step C
|
|
824
|
+
### Step C �?Write `brand.json`
|
|
825
825
|
|
|
826
826
|
Validate mentally against `brand.schema.json`. Minimum:
|
|
827
827
|
|
|
@@ -851,11 +851,11 @@ Validate mentally against `brand.schema.json`. Minimum:
|
|
|
851
851
|
}
|
|
852
852
|
```
|
|
853
853
|
|
|
854
|
-
Always set **`cms.hostingTarget`: `"vercel"`** without asking. Default `expectedScreens` to `5` unless the user already volunteered a number. Set `cmsEndpoint` only if they already know the public `https://…vercel.app` URL; otherwise tell them to pair TVs with the **Published** HTTPS origin (player maps `https://`
|
|
854
|
+
Always set **`cms.hostingTarget`: `"vercel"`** without asking. Default `expectedScreens` to `5` unless the user already volunteered a number. Set `cmsEndpoint` only if they already know the public `https://…vercel.app` URL; otherwise tell them to pair TVs with the **Published** HTTPS origin (player maps `https://` �?`wss://`).
|
|
855
855
|
|
|
856
856
|
---
|
|
857
857
|
|
|
858
|
-
## After all answers
|
|
858
|
+
## After all answers �?execution checklist
|
|
859
859
|
|
|
860
860
|
### A. Seed the project
|
|
861
861
|
|
|
@@ -865,11 +865,11 @@ npx @tomorrowos/sdk@latest init .
|
|
|
865
865
|
# npx @tomorrowos/sdk@latest init . --force
|
|
866
866
|
```
|
|
867
867
|
|
|
868
|
-
Then (**Scheme A
|
|
868
|
+
Then (**Scheme A �?mandatory for Publish**):
|
|
869
869
|
|
|
870
870
|
1. Keep **`server.ts`** at **project root** with top-level `listen()` + `export default server`.
|
|
871
|
-
2. If Preview needs Next: **move** `next.config.*`, `app/`, Next `tsconfig` / `postcss` / `next-env.d.ts` into **`preview/`**
|
|
872
|
-
3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, no-op `buildCommand`
|
|
871
|
+
2. If Preview needs Next: **move** `next.config.*`, `app/`, Next `tsconfig` / `postcss` / `next-env.d.ts` into **`preview/`** �?remove them from root so Production is not mistaken for Next/static.
|
|
872
|
+
3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, no-op `buildCommand` �?**no** `startCommand`).
|
|
873
873
|
4. **Force Project Settings** per **Scheme A4** (Framework = Other, Output Directory empty, Start = `npm run start`, Fluid on). Patch dashboard via API if it still says Next.js.
|
|
874
874
|
5. Do **not** add root `api/` catch-all for raw `http.Server`.
|
|
875
875
|
6. Do **not** add `.replit*`.
|
|
@@ -881,8 +881,8 @@ Then (**Scheme A — mandatory for Publish**):
|
|
|
881
881
|
|------|----------|--------------|
|
|
882
882
|
| `SUPABASE_URL` | If Q1 = Supabase | User pastes URL |
|
|
883
883
|
| `DATABASE_URL` | If Q1 = Neon | User pastes URL |
|
|
884
|
-
| `TOMORROWOS_STORE` | Yes | **Agent auto-sets only**
|
|
885
|
-
| `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets only**
|
|
884
|
+
| `TOMORROWOS_STORE` | Yes | **Agent auto-sets only** �?never a blank field for the user (`supabase` / `postgres` / `sqlite`) |
|
|
885
|
+
| `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets only** �?never a blank field for the user (`true`) |
|
|
886
886
|
| `CLOUDINARY_*` | If Q2 = Cloudinary | User via **one Env popup** (all fields) |
|
|
887
887
|
| `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | User / Blob link |
|
|
888
888
|
| `TOMORROWOS_INTERNAL_PORT` | Preview only | Agent (e.g. `3001`) |
|
|
@@ -904,40 +904,40 @@ Confirm browser:
|
|
|
904
904
|
- **Preview:** Control Panel visible through the Next proxy at `/`
|
|
905
905
|
- **Production start:** Control Panel served directly by TomorrowOS at `/`
|
|
906
906
|
|
|
907
|
-
### D. Deploy (Publish
|
|
907
|
+
### D. Deploy (Publish �?Node only)
|
|
908
908
|
|
|
909
909
|
```bash
|
|
910
910
|
vercel --prod
|
|
911
|
-
# or git push
|
|
911
|
+
# or git push �?Vercel Git integration
|
|
912
912
|
```
|
|
913
913
|
|
|
914
|
-
**Publish acceptance checklist (all must pass
|
|
914
|
+
**Publish acceptance checklist (all must pass �?Scheme A5):**
|
|
915
915
|
|
|
916
|
-
1. Framework Preset = **Other** / null (dashboard confirms
|
|
916
|
+
1. Framework Preset = **Other** / null (dashboard confirms �?not Next.js)
|
|
917
917
|
2. Output Directory = **empty** (not `public`)
|
|
918
918
|
3. Start Command = Node `TomorrowOS.listen` (`npm run start` / `tsx server.ts`)
|
|
919
919
|
4. Fluid compute on
|
|
920
920
|
5. Env Vars present for Production
|
|
921
|
-
6. Production `GET /status` returns **JSON**
|
|
922
|
-
7. Production `GET /` returns Control Panel HTML
|
|
921
|
+
6. Production `GET /status` returns **JSON** �?**not** HTML (`Unexpected token 'T'` means this failed)
|
|
922
|
+
7. Production `GET /` returns Control Panel HTML �?**not** `404: NOT_FOUND`
|
|
923
923
|
|
|
924
|
-
If step 6 fails but step 7 passes: **static `public/` trap**
|
|
924
|
+
If step 6 fails but step 7 passes: **static `public/` trap** �?Vercel is not running Node. Re-apply Scheme A3–A4; try **Path B** (Git / `vercel --prod`); do **not** invent `api/` + `rewrites`.
|
|
925
925
|
|
|
926
926
|
Tell the user:
|
|
927
927
|
|
|
928
928
|
- **Control Panel (Publish):** `https://YOUR-PROJECT.vercel.app`
|
|
929
929
|
- **TV CMS endpoint:** same HTTPS origin (players use `wss://`)
|
|
930
|
-
- **Preview:** Next shell is for v0 only
|
|
930
|
+
- **Preview:** Next shell is for v0 only �?do not pair devices to Preview
|
|
931
931
|
|
|
932
932
|
### E. Minimal verification only
|
|
933
933
|
|
|
934
934
|
**Do:**
|
|
935
935
|
|
|
936
936
|
1. Preview: Control Panel HTML at `/` (via Next proxy in `preview/` if applicable)
|
|
937
|
-
2. Publish: `GET /status`
|
|
937
|
+
2. Publish: `GET /status` �?JSON; `GET /` �?Control Panel from **Node** TomorrowOS
|
|
938
938
|
3. Publish: panel does **not** show `Unexpected token 'T'` / CMS unreachable
|
|
939
939
|
4. Server status: Media OK when Cloudinary or Blob configured
|
|
940
|
-
5. Database: if ERROR shows **`ENETUNREACH`
|
|
940
|
+
5. Database: if ERROR shows **`ENETUNREACH` �?IPv6 �?`:5432`**, switch to **pooled** URL (Supabase `:6543` or Neon pooler) and redeploy �?call this out explicitly to the user
|
|
941
941
|
6. Fluid compute on for Production
|
|
942
942
|
|
|
943
943
|
**Do not block** on long WebSocket/device pairing tests unless the user asks. Prefer pairing against **Publish**, not Preview.
|
|
@@ -949,25 +949,25 @@ Tell the user:
|
|
|
949
949
|
| Symptom | Likely cause | Fix |
|
|
950
950
|
|---------|--------------|-----|
|
|
951
951
|
| Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Function | Scheme A: Framework = **Other**, Fluid on, `api/index.ts` + rewrites; redeploy |
|
|
952
|
-
| Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site**
|
|
953
|
-
| Only `/api/status` returns JSON, `/status` is 404 | Missing **rewrites** to `/api` | Add Scheme A3 rewrite `/(.*)
|
|
952
|
+
| Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** �?no Function | Rename to `cms-panel/`; Output empty; Scheme A Function entry; **Path B** if needed |
|
|
953
|
+
| Only `/api/status` returns JSON, `/status` is 404 | Missing **rewrites** to `/api` | Add Scheme A3 rewrite `/(.*) �?/api` (exclude existing `/api/`); redeploy |
|
|
954
954
|
| Control Panel OK, TV **could not connect** | WebSocket upgrade not reaching Function (200 HTML) | Fluid on; `export default server`; verify 101 on `/` and `/api`; check WebSockets permission |
|
|
955
955
|
| `vercel.json` error: invalid `startCommand` | v0 schema rejects it | Remove from `vercel.json`; Function entry does not need startCommand |
|
|
956
|
-
| Separate `api/ws.ts` with a second `new TomorrowOS` | Two isolates
|
|
956
|
+
| Separate `api/ws.ts` with a second `new TomorrowOS` | Two isolates �?pairing memory split | **One** Function only (`api/index.ts` �?shared `cms-app.ts`) |
|
|
957
957
|
| `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty**; use `cms-panel/`; Path B redeploy |
|
|
958
958
|
| Build runs `next build` / missing routes-manifest | Next auto-detected from root `next.config` / `app/` | Move Next to `preview/`; Framework null/Other; `build` = no-op |
|
|
959
|
-
| Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy
|
|
959
|
+
| Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy �?internal TomorrowOS port |
|
|
960
960
|
| Preview works, Publish broken / Next-only | Production still on Next | Switch Production to Fluid Function `api/index.ts` |
|
|
961
961
|
| `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
|
|
962
962
|
| Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
|
|
963
963
|
| Uploads break / broken thumbs in prod | Local uploads only | Cloudinary or Vercel Blob |
|
|
964
|
-
| Blob uploads 401 / missing token | Blob not linked | Vercel Storage
|
|
964
|
+
| Blob uploads 401 / missing token | Blob not linked | Vercel Storage �?Blob + `BLOB_READ_WRITE_TOKEN` |
|
|
965
965
|
| WebSocket fails on Publish | Fluid off / static-only / Next as Production / no Function WS | Enable Fluid; Scheme A; test 101 upgrade |
|
|
966
|
-
| Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected
|
|
966
|
+
| Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected �?pair on Publish URL |
|
|
967
967
|
| Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set after DB choice; remove those fields from user Env forms |
|
|
968
|
-
| Agent offered Blob-as-recommended / Supabase Storage / “no media
|
|
968
|
+
| Agent offered Blob-as-recommended / Supabase Storage / “no media�?for Q2 | Invented options | Re-ask Q2 verbatim: **1 Cloudinary (recommended)**, 2 Blob, 3 local |
|
|
969
969
|
| Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
|
|
970
|
-
| Agent asked for OpenAI / `sk-` key during setup | Hallucinated “AI pairing
|
|
970
|
+
| Agent asked for OpenAI / `sk-` key during setup | Hallucinated “AI pairing�?requirement | **Refuse.** TomorrowOS does not need LLM keys. Return to Q2 media choices only |
|
|
971
971
|
| Agent built a login page after user pasted a URL | Misread brand reference as product scope | Remove login; keep starter Control Panel; apply URL only to `brand.json` |
|
|
972
972
|
| Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
|
|
973
973
|
| Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
|
|
@@ -985,22 +985,22 @@ Tell the user:
|
|
|
985
985
|
| Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
|
|
986
986
|
| Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
|
|
987
987
|
| Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
|
|
988
|
-
| Brand from URL | Not in Replit protocol | **Option A**
|
|
988
|
+
| Brand from URL | Not in Replit protocol | **Option A** �?infer **`brand.json` only**; never invent login |
|
|
989
989
|
|
|
990
990
|
---
|
|
991
991
|
|
|
992
|
-
## Final summary template (Agent
|
|
992
|
+
## Final summary template (Agent �?human)
|
|
993
993
|
|
|
994
994
|
After setup, report:
|
|
995
995
|
|
|
996
996
|
1. **Live URL** (Production Publish)
|
|
997
|
-
2. **Publish health:** Framework = Other; Output
|
|
997
|
+
2. **Publish health:** Framework = Other; Output �?`public`; `GET /status` �?JSON; `GET /` OK
|
|
998
998
|
3. **Preview note:** Next proxy shell used / not needed
|
|
999
|
-
4. **Env Vars set** (names only
|
|
999
|
+
4. **Env Vars set** (names only �?never values); note which were auto-set (`TOMORROWOS_STORE`, `DATABASE_SSL`)
|
|
1000
1000
|
5. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
|
|
1001
1001
|
6. **Media:** Cloudinary, Vercel Blob, or local (with warning)
|
|
1002
1002
|
7. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
|
|
1003
|
-
8. **Runtime split:** Preview =
|
|
1003
|
+
8. **Runtime split:** Preview = �?; Publish = Node TomorrowOS (confirm **not** Next)
|
|
1004
1004
|
9. **Open action:** if Database still ERROR, paste the pooler instruction again
|
|
1005
1005
|
|
|
1006
1006
|
Protocol complete when:
|
|
@@ -1009,4 +1009,4 @@ Protocol complete when:
|
|
|
1009
1009
|
- **Publish** returns Control Panel at `/` **and** `/status` JSON from pure Node `TomorrowOS.listen` with Fluid (not static `public/`, not `404: NOT_FOUND`, not `Unexpected token 'T'`), **and**
|
|
1010
1010
|
- Q1–Q3 configuration is saved
|
|
1011
1011
|
|
|
1012
|
-
|
|
1012
|
+
�?even if a sandbox DB probe still flakes **after** the pooled URL is correctly set for Production.
|