ugly-app 0.1.870 → 0.1.871

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/README.md CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  A full-stack TypeScript framework for shipping production web apps with one CLI. Scaffold with `npx ugly-app init my-app` and get an opinionated Express + React + PostgreSQL stack with built-in auth, type-safe RPC over WebSocket and HTTP, real-time document tracking, AI generation, storage, and a CLI for every workflow.
4
4
 
5
- ugly-app is designed to be deployed and operated through [ugly.bot](https://ugly.bot) — the platform handles auth, infra (PostgreSQL, Qdrant, NATS, S3-compatible object storage), AI provider keys, and deployment. Your app talks to all of this through the project's dev tunnel and the per-app `UGLY_BOT_TOKEN`.
5
+ ugly-app is designed to be deployed and operated through [ugly.bot](https://ugly.bot) — the platform handles auth, infra (PostgreSQL, Qdrant, NATS, S3-compatible object storage), AI provider keys, and deployment. Your app talks to all of it through the project's dev tunnel and the per-app `UGLY_BOT_TOKEN`.
6
6
 
7
7
  ## What's included
8
8
 
9
9
  - **Server**: Express + WebSocket with type-safe RPC and Zod validation
10
10
  - **Client**: React + Vite with typed routing, lazy pages, animated transitions, popup management
11
11
  - **Database**: PostgreSQL (JSONB) via the data proxy, with full-text search (`search`) and vector search (Qdrant `vector`)
12
- - **Auth**: HttpOnly cookies + JWT, ugly.bot OAuth out of the box, extensible via `AuthProvider`
12
+ - **Auth**: HttpOnly cookies + JWT, ugly.bot OAuth out of the box, magic-link + Google when self-hosting sessions
13
13
  - **AI**: Text, image, embeddings, web search — all proxied through ugly.bot (no per-provider keys in your app)
14
- - **Realtime**: NATS pub/sub and document change subscriptions (`trackDoc` / `trackDocs`)
14
+ - **Realtime**: NATS pub/sub and live document subscriptions (`trackDoc` / `trackDocs`)
15
15
  - **Storage**: S3-compatible buckets with presigned uploads
16
- - **Workers & cron**: `setWorkers()` registers named async tasks with optional Zod input schemas and cron schedules
16
+ - **Workers & cron**: `setWorkers()` registers named async tasks with optional Zod schemas and cron schedules
17
17
  - **Localization**: Strings tables with critical-string SSR injection
18
18
  - **Experiments**: Deterministic A/B bucketing tied to event logging
19
19
  - **CLI**: `ugly-app` commands for dev, build, deploy, migrations, logs, AI, and auth
@@ -26,7 +26,7 @@ cd my-app
26
26
  npm run dev
27
27
  ```
28
28
 
29
- The scaffold gives you a working app at `http://localhost:4321` with todo CRUD, AI chat, file upload, auth demo, collab editing, and ~20 other test pages wired up.
29
+ The scaffold gives you a working app at `http://localhost:4321` with todo CRUD, AI chat, file upload, auth demo, collab editing, and ~20 other test pages.
30
30
 
31
31
  ---
32
32
 
@@ -70,24 +70,24 @@ await app.start(parseInt(process.env['PORT'] ?? '4321'));
70
70
 
71
71
  ```typescript
72
72
  function createApp<R extends AppRegistryBase, Defs extends CollectionDefRegistry>(
73
- registry: R, // { requests, messages }
73
+ registry: R, // { requests, messages, pages? }
74
74
  requests: Partial<RequestHandlers<R['requests']>>, // handler implementations
75
75
  appDefs: Defs, // collections from defineCollections()
76
76
  configure?: (c: AppConfigurator) => void,
77
77
  deleteHandlers?: DeleteHandlers<Defs>, // per-collection onDelete hooks
78
- ): App<CollectionMap<typeof BUILTIN_DEFS & Defs>>;
78
+ ): App<CollectionMap<typeof BUILTIN_DEFS & Defs>, RegistryPages<R>>;
79
79
  ```
80
80
 
81
- The returned `App` object has:
81
+ The returned `App`:
82
82
  - `start(port?)` — start the server (default port 3000; templates use 4321)
83
83
  - `db` — the `TypedDB` instance
84
84
  - `httpServer` — the underlying Node `http.Server`
85
85
  - `wss` — the main `WebSocketServer` (path set by `setWsPath`, default `/rpc`)
86
86
  - `dispatch(name, input, userId)` — invoke an RPC handler programmatically
87
- - `registerRoutes(fn)` — mount more Express routes after creation
88
- - `pushSend({ userId, page, query, ...notification })` — send a push notification whose click-through target is a route from this app's `pages` table. `page` is type-checked against route keys (when `pages` is provided on the createApp registry), `query` against that route's params. The framework builds the absolute URL and stamps project identity so the client can open the dock app.
87
+ - `registerRoutes(fn)` — mount additional Express routes after creation
88
+ - `pushSend({ userId, page, query, ...notification })` — send a push notification whose click-through target is a route in this app's `pages` table. `page` is type-checked against route keys (when `pages` is on the createApp registry), `query` against that route's params.
89
89
 
90
- Framework-managed background services start automatically: schema drift check, NATS connection + KV buckets (`TTS`, `RATELIMIT`), data-proxy connection, event counter flush, TTL cleanup for log tables, console / error capture, and ugly.bot log forwarding.
90
+ Framework-managed background services start automatically: schema drift check, NATS connection + KV buckets (`TTS`, `RATELIMIT`), data-proxy connection, event counter flush, TTL cleanup for log tables, console/error capture, and ugly.bot log forwarding.
91
91
 
92
92
  ### `AppConfigurator`
93
93
 
@@ -95,23 +95,23 @@ Passed to the optional fourth argument of `createApp`. Every method is optional.
95
95
 
96
96
  | Method | Description |
97
97
  |--------|-------------|
98
- | `setPages({ pages, renderPage?, clientDistPath? })` | Mount the SPA. In dev, runs Vite in middleware mode; in prod, serves `dist/client`. Provide `renderPage` for SSR on pages with `ssr: true`. |
99
- | `setUserHelper(helper)` | Customize how the framework reads / writes the `user` collection during WebSocket auth (default looks up by id in a generic `user` collection). |
100
- | `setOnUserCreate(handler)` | Called on first login with `(userId, { email?, phone? }, db)` — your chance to create the user record. |
101
- | `setAuth(provider)` | Replace the default ugly.bot OAuth provider. Must implement `verify(code)` and `authUrl(origin)`. |
98
+ | `setPages({ pages, renderPage?, clientDistPath? })` | Mount the SPA. In dev runs Vite in middleware mode; in prod serves `dist/client`. Provide `renderPage` to SSR routes with `ssr: true`. |
99
+ | `setUserHelper(helper)` | Customize how the framework reads/writes the `user` collection during WebSocket auth. |
100
+ | `setOnUserCreate(handler)` | Called on first login with `(userId, { email?, phone? }, db)`. |
101
+ | `setAuth(provider)` | Replace the default auth provider (must implement `verify(code)` and `authUrl(origin)`). |
102
102
  | `setOnSocketMessage(handler)` | Single raw-WebSocket message handler. Return `true` to consume, `false` to fall through. |
103
103
  | `addSocketMessageHandler(handler)` | Append to the handler chain; first to return `true` wins. |
104
104
  | `setWsPath(path)` | Override the WebSocket path (default `/rpc`). |
105
105
  | `setOnWsAuth(handler)` | `(ws, userId, req) => void` — fires after a socket session authenticates. |
106
106
  | `setOnAfterStart(handler)` | `(db) => Promise<void>` — called once after data-proxy + NATS are ready. |
107
- | `setOnMinuteTick(fn)` / `setOnHourlyTick(fn)` | Framework-managed periodic callbacks. Only fire when `CLOCK_ENABLED=true`. |
107
+ | `setOnMinuteTick(fn)` / `setOnHourlyTick(fn)` | Periodic callbacks. Only fire when `CLOCK_ENABLED=true`. |
108
108
  | `setHealthHandler(fn)` | Override the default `GET /health` response. |
109
109
  | `setExperiments(experiments)` | Register `Experiment` definitions for `initSession` / `captureEvent` bucketing. |
110
- | `setIsAdmin(fn)` | `(userId, db) => boolean \| Promise<boolean>` — gate for admin-only framework requests (`adminCreateTestUser` / `list` / `delete`). Defaults to matching `MAINTAIN_BOT_USER_ID`. |
111
- | `setOnEmail(handler)` | Handle inbound emails routed to `{domain}@ugly.bot` (called via internal HTTP). |
110
+ | `setIsAdmin(fn)` | `(userId, db) => boolean \| Promise<boolean>` — gate for admin-only framework requests. Defaults to matching `MAINTAIN_BOT_USER_ID`. |
111
+ | `setOnEmail(handler)` | Handle inbound emails routed to `{domain}@ugly.bot`. |
112
112
  | `setCronTasks(tasks, handlers)` | Legacy cron-only registry. Prefer `setWorkers()`. |
113
113
  | `setWorkers(workers, handlers)` | Register named async tasks with optional Zod input schema and cron schedule. Powers `/_workers/manifest`, `POST /_workers/run`, and the cron orchestrator. |
114
- | `setStrings(config)` | Localization config — framework injects language + critical strings into SSR HTML and exposes `resolveLanguage` / `getCriticalStrings`. |
114
+ | `setStrings(config)` | Localization config — framework injects language + critical strings into SSR HTML. |
115
115
  | `registerRoutes(fn)` | Mount custom Express routes. |
116
116
  | `setWorkerQueue(queue)` | Register a `WorkerQueue` with `start()` / `stop()` for app lifecycle management. |
117
117
 
@@ -131,7 +131,7 @@ Inside a handler, access state via captured imports — `app.db`, `storage`, `pg
131
131
 
132
132
  ### Built-in framework requests
133
133
 
134
- `createApp` automatically registers several framework handlers, accessible from any client via the normal RPC pipeline:
134
+ `createApp` automatically registers several framework handlers, reachable from any client via the normal RPC pipeline:
135
135
 
136
136
  | Name | Purpose |
137
137
  |------|---------|
@@ -141,10 +141,10 @@ Inside a handler, access state via captured imports — `app.db`, `storage`, `pg
141
141
  | `textGen` / `imageGen` | AI proxies — server-validated, billed through ugly.bot. |
142
142
  | `kagiSearch` / `kagiSummarize` / `kagiEnrichWeb` / `kagiEnrichNews` | Web search via ugly.bot. |
143
143
  | `uploadUrl` | Issues a presigned PUT for the `temp` bucket. |
144
- | `shareLink` | Mint a `https://ugly.bot/l/<code>` short link with OG metadata (see the "sharing links" rule in `CLAUDE.md`). |
145
- | `feedbackReportCreateNoAuth` / `errorLogCaptureNoAuth` | Same-origin, public endpoints used by the built-in browser telemetry (feedback + client-side error capture) to write into the project's own Postgres. |
144
+ | `shareLink` | Mint a `https://ugly.bot/l/<code>` short link with OpenGraph metadata. |
145
+ | `feedbackReportCreateNoAuth` / `errorLogCaptureNoAuth` | Same-origin, public endpoints used by the built-in browser telemetry. |
146
146
  | `submitFeedbackBot` | Forwards `db.captureFeedback` writes for the maintain-bot persona. |
147
- | `feedbackReportResolve` | Admin-only — resolve or decline a feedback report (Workers-only via D1 telemetry). |
147
+ | `feedbackReportResolve` | Admin-only — resolve or decline a feedback report. |
148
148
  | `adminCreateTestUser` / `adminListTestUsers` / `adminDeleteTestUser` | Admin-only — synthetic-user management. Gated by `setIsAdmin()` (or `MAINTAIN_BOT_USER_ID`). |
149
149
 
150
150
  App-provided handlers with the same name override the framework's defaults.
@@ -161,7 +161,7 @@ App-provided handlers with the same name override the framework's defaults.
161
161
  import { authReq, defineRequests, req, z } from 'ugly-app/shared';
162
162
 
163
163
  export const requests = defineRequests({
164
- // Public — handler signature: (userId: string | null, input) => Promise<output>
164
+ // Public — handler: (userId: string | null, input) => Promise<output>
165
165
  getPublicData: req({
166
166
  input: z.object({ id: z.string() }),
167
167
  output: z.object({ data: z.string() }),
@@ -173,7 +173,7 @@ export const requests = defineRequests({
173
173
  output: z.object({ userId: z.string(), email: z.string().optional() }),
174
174
  }),
175
175
 
176
- // With per-endpoint rate limiting (enforced before handler runs)
176
+ // Per-endpoint rate limiting (enforced before handler runs)
177
177
  submitFeedback: authReq({
178
178
  input: z.object({ type: z.enum(['bug', 'design', 'feature']), message: z.string() }),
179
179
  output: z.object({ id: z.string() }),
@@ -214,9 +214,9 @@ export const collections = defineCollections({
214
214
  - `search?: { fields, language? }` — PostgreSQL full-text search columns.
215
215
  - `vector?: { dimensions, source }` — Qdrant vector index over the named JSONB path.
216
216
 
217
- All documents extend `DBObject`: `{ _id, version, created, updated }`. Use `dbDefaults()` to stamp the latter three on inserts.
217
+ All documents extend `DBObject`: `{ _id, version, created, updated }`. Use `dbDefaults()` to stamp `version`/`created`/`updated` on inserts, and `nanoid()` for `_id` — never `crypto.randomUUID()`, `Date.now()`, or a counter.
218
218
 
219
- After schema changes, run `npm run db:schema-gen` and then `npm run db:migrate`. The app refuses to start when drift is detected (set `SCHEMA_CHECK_SKIP=true` only as a last resort).
219
+ After schema changes, run `npm run db:schema-gen` then `npm run db:migrate`. The app refuses to start when drift is detected (set `SCHEMA_CHECK_SKIP=true` only as a last resort).
220
220
 
221
221
  ### Pages (`shared/pages.ts`)
222
222
 
@@ -233,7 +233,7 @@ export type AppPages = typeof pages;
233
233
  ```
234
234
 
235
235
  - `:param` matches a single path segment; `*param` is greedy (captures slashes).
236
- - The generic on `definePage<Params>()` is **phantom** — never set at runtime, used for client-side type inference.
236
+ - The generic on `definePage<Params>()` is **phantom** — never set at runtime, used purely for client-side type inference.
237
237
  - `auth` defaults to `true`. `ssr` defaults to `false`. `{ auth: true, ssr: true }` is dropped to `ssr: false` with a warning — SSR output is served from a shared edge cache and must not depend on the viewer.
238
238
  - `cacheQuery?: string[]` — query params that participate in the SSR edge-cache key. Unlisted params bypass the cache rather than poison it (e.g. `cacheQuery: ['q']` on a search page).
239
239
  - `ssrCacheTimeout?: number` — override the default 1-year edge TTL for pages whose content drifts between deploys.
@@ -270,12 +270,12 @@ bootstrapApp({
270
270
  | `messages?` | Your `MessageRegistry` (merged with framework messages). |
271
271
  | `RouterProvider` | The `RouterProvider` returned from `createRouter()`. |
272
272
  | `render` | Callback returning the app's UI tree (typically `<RouterView />`). |
273
- | `root?` | Root element / selector (default `'#root'`). |
274
- | `fallback?` | UI for unmatched routes (default: tiny "404"). |
273
+ | `root?` | Root element / CSS selector (default `'#root'`). |
274
+ | `fallback?` | UI for unmatched routes (default: a tiny 404). |
275
275
  | `socketUrl?` | Override the WebSocket path (default `/rpc`). |
276
- | `strings?` | Localization config — when present, wraps the tree with `<StringsProvider>`. |
276
+ | `strings?` | Localization config — wraps the tree with `<StringsProvider>` when present. |
277
277
  | `keyboard?: false` | Disable the framework `<KeyboardProvider>` wrapper. |
278
- | `silentSso?` | Apex-domain apps (ugly.chat, ugly.press, …) that use ugly.bot as their auth authority but don't share its cookie. When true, a logged-out boot attempts a non-blocking top-level SSO redirect to adopt an existing ugly.bot session. Leave unset for `*.ugly.bot` subdomains and Mode B (self-auth) apps. |
278
+ | `silentSso?` | Apex-domain apps (ugly.chat, ugly.press, …) that use ugly.bot as their auth authority but don't share its cookie. When true, a logged-out boot attempts a non-blocking top-level SSO redirect to adopt an existing ugly.bot session. Leave unset for `*.ugly.bot` subdomains and Mode B apps. |
279
279
 
280
280
  `bootstrapApp` reads `window.__AUTH_TOKEN__` (injected by the server after cookie verification). If absent, it renders unauthenticated immediately — the router's per-route auth guard surfaces the framework's `<AuthRoot>` (Mode A → `<LoginPopup>`; Mode B → magic-link form + optional Google button) when a user opens a protected route. If the token is present, it connects the socket, mounts `<AppProvider>`, and renders.
281
281
 
@@ -291,7 +291,7 @@ import { createRouter } from 'ugly-app/client';
291
291
  import { pages } from '../shared/pages';
292
292
  import { allPages } from './allPages';
293
293
 
294
- export const { RouterProvider, RouterView, useRouter } = createRouter({
294
+ export const { RouterProvider, RouterView, useRouter, Link, setAllPages } = createRouter({
295
295
  pages,
296
296
  allPages,
297
297
  });
@@ -301,12 +301,27 @@ Config: `pages` (required), `allPages?` (lazy route → element map; may be regi
301
301
 
302
302
  `createRouter` returns:
303
303
 
304
- - **`RouterProvider`** — props: `children`, `fallback?`, `isAuthenticated?`, `initialUrl?` (`{ pathname, search }`; required for SSR, used on the client during hydration so the first render matches the server's). Manages route state, browser history, and the popup layer. When `isAuthenticated()` returns false and the current route declares `auth: true`, the framework's `<AuthRoot>` renders in place of the page synchronously — apps cannot override this fallback. (Silent SSO happens in `bootstrapApp`, not in the router — the previous first-paint-blocking `<AutoLoginGate>` iframe was removed so logged-out landing pages paint immediately.)
304
+ - **`RouterProvider`** — props: `children`, `fallback?`, `isAuthenticated?`, `initialUrl?` (`{ pathname, search }`; required for SSR, used on the client during hydration so the first render matches the server's). Manages route state, browser history, and the popup layer. When `isAuthenticated()` returns false and the current route declares `auth: true`, the framework's `<AuthRoot>` renders synchronously in place of the page — apps cannot override this fallback.
305
305
  - **`RouterView`** — renders the active page with animated transitions. Props: `durationMs?`, `easing?`, `transitionComponent?` (replaces `ViewFlipper`), `renderPage?` (sync alternative to `allPages` loaders, called with `RouterStateRaw`).
306
306
  - **`useRouter()`** — returns the router context (see below).
307
307
  - **`Link`** — typed SPA link bound to this router's pages. Renders a real `<a>` (right-click / cmd-click / SEO keep working) but intercepts a plain left-click and routes through `push` / `replace`. Props: `to`, `params` (both type-checked against `pages`), `replace?`, `children`, plus the usual anchor attrs.
308
308
  - **`setAllPages(map)`** — register the lazy `PageMap<Pages>` after construction (typically from the browser entry). Lets the Worker bundle skip every lazy page's import graph while the client still gets code-split routes.
309
309
 
310
+ ### `definePage` — page metadata
311
+
312
+ Re-exported from `ugly-app/shared`. Runtime shape:
313
+
314
+ ```typescript
315
+ interface PageMeta {
316
+ auth: boolean; // default: true
317
+ ssr: boolean; // default: false
318
+ cacheQuery?: string[]; // edge-cache key participants
319
+ ssrCacheTimeout?: number; // seconds; default 31_536_000 (1yr)
320
+ }
321
+ ```
322
+
323
+ The `<Params>` generic on `definePage<Params>()` is a phantom type — used only by TypeScript for route-param inference in `useRouter()`, `Link`, and `pushSend`.
324
+
310
325
  ### Page map — `lazyPage` / `lazyPageLoader`
311
326
 
312
327
  ```typescript
@@ -325,6 +340,8 @@ export const allPages = {
325
340
  - **`lazyPage(factory)`** — lazy-imports a default-exported `React.ComponentType<Params>`. The page receives route params as props.
326
341
  - **`lazyPageLoader(factory)`** — lazy-imports an async loader `(params) => Promise<ReactElement>`. Use when a route needs data fetching before render. The loader file is the chunk boundary, so it can statically import its page component.
327
342
 
343
+ Both wrap the `import()` in a one-shot reload guard: on a stale-deploy chunk 404 the framework reloads the page once (guarded by sessionStorage) so cached `index.html` picks up the new chunk hashes instead of wedging the SPA.
344
+
328
345
  Example loader:
329
346
 
330
347
  ```typescript
@@ -358,11 +375,11 @@ current.routeName; // typed union of all route keys
358
375
  current.params; // typed params for the current route
359
376
  ```
360
377
 
361
- All route names and params are fully typed against `pages`. Internally `push` / `replace` are no-ops when `buildUrl()` produces a URL that doesn't match a registered route (and emit a `console.error`).
378
+ All route names and params are fully typed against `pages`. Internally `push` / `replace` log a `console.error` (no-op) when `buildUrl()` produces a URL that doesn't match a registered route.
362
379
 
363
380
  ### Popups — `openPopup()`
364
381
 
365
- Always use `useRouter().openPopup()` for modals, sheets, and menus. The router owns the popup layer, manages the spring animation, and stacks popups z-index-correctly.
382
+ Always use `useRouter().openPopup()` for modals, sheets, and menus. The router owns the popup layer, drives the spring animation, and stacks popups z-index-correctly.
366
383
 
367
384
  ```tsx
368
385
  const { openPopup } = useRouter();
@@ -374,7 +391,7 @@ const handle = openPopup(<MyContent />, {
374
391
  containerStyle: { /* CSS for the content wrapper */ },
375
392
  backgroundStyle: { /* CSS for the backdrop */ },
376
393
  animConfig: { duration: 300, easing: myEasingFn },
377
- renderLayer: (props) => <CustomLayer {...props} />, // fully replace the layer renderer
394
+ renderLayer: (props) => <CustomLayer {...props} />, // fully replace the layer
378
395
  });
379
396
 
380
397
  handle.hide(); // dismiss programmatically
@@ -389,7 +406,7 @@ Modes:
389
406
 
390
407
  ### `AppProvider` & `useApp()`
391
408
 
392
- `bootstrapApp` mounts `<AppProvider>` automatically after socket connect. Use `useApp()` inside any page to access the active user and socket.
409
+ `bootstrapApp` mounts `<AppProvider>` automatically after the socket connects. Use `useApp()` inside any page to reach the active user and socket:
393
410
 
394
411
  ```typescript
395
412
  const {
@@ -400,21 +417,20 @@ const {
400
417
  showPopup, // legacy popup API (prefer useRouter().openPopup)
401
418
  hidePopup,
402
419
  hideAllPopups,
403
- runAsync, // runAsync(label, async () => { ... }, options?) — shows loading overlay while pending
420
+ runAsync, // runAsync(label, async () => {...}, options?) — shows loading overlay while pending
404
421
  splashDone, // mark a splash-screen step complete
405
- localizer, // (key, params?) => string — alias for useLocalizer
422
+ localizer, // (key, params?) => string
406
423
  } = useApp();
407
424
  ```
408
425
 
409
- `useApp` is generic in `TAsyncOptions` so apps can pass an option type through to a custom `loadingOverlay` element. `useAppOptional()` returns `null` outside the provider; `useLocalizer()` returns a localizer that prefers `<StringsProvider>` data, falls back to the `AppProvider` localizer prop, and finally to identity.
426
+ `useApp` is generic in `TAsyncOptions` so apps can pass an option type through to a custom `loadingOverlay` element. `useAppOptional()` returns `null` outside the provider; `useLocalizer()` prefers `<StringsProvider>` data, falls back to the `AppProvider` localizer prop, and finally to identity.
410
427
 
411
428
  ### `Link` component
412
429
 
413
- Prefer the typed `Link` returned by `createRouter()` — no `router` prop, `to` / `params` are checked against your pages, and it uses the enclosing `<RouterProvider>` context automatically.
430
+ Prefer the typed `Link` returned by `createRouter()` — no `router` prop, `to` / `params` checked against your pages, uses the enclosing `<RouterProvider>` context automatically.
414
431
 
415
432
  ```tsx
416
433
  // Typed Link — from your `client/router.ts`
417
- const { Link } = createRouter({ pages, allPages });
418
434
  <Link to="user/:userId" params={{ userId: '123' }}>View profile</Link>
419
435
  ```
420
436
 
@@ -431,7 +447,7 @@ Never use a bare `<a href="/route">` for internal navigation — it triggers a f
431
447
  | `request(name, input)` | Invoke a typed RPC handler. |
432
448
  | `getDoc(collection, id)` | Server-mediated doc fetch. |
433
449
  | `getDocs(collection, filter?, opts?)` | Filtered query. |
434
- | `trackDoc(collection, id, cb)` | Live subscription — returns unsubscribe. |
450
+ | `trackDoc(collection, id, cb)` | Live subscription — returns unsubscribe. Sends an initial snapshot then relays mutations. |
435
451
  | `trackDocs(collection, params, cb)` | Live filtered subscription. |
436
452
  | `uploadFile(file, key)` | Presigned upload to the `temp` bucket. |
437
453
  | `connectionState` | `'connecting' \| 'connected' \| 'reconnecting' \| 'disconnected' \| 'idle-disconnected'`. |
@@ -456,16 +472,16 @@ Two modes, picked from the `auth` block in the project's `.uglyapp` config:
456
472
 
457
473
  1. Every page request: the server checks the `auth_token` cookie. If it verifies through `${UGLY_BOT_URL}/verify`, the server injects `window.__AUTH_TOKEN__` into the HTML.
458
474
  2. Client boot: if `__AUTH_TOKEN__` is present, `bootstrapApp` opens the WebSocket with the token. Otherwise it renders logged-out immediately (no first-paint block).
459
- 3. Background account-sync (always): `bootstrapApp` opens a hidden iframe to `${UGLY_BOT_URL}/oauth/silent` on every load. If it returns a one-time code, the client POSTs `/auth/verify` to refresh the cookie. If the refreshed account's `userId` differs from the current session, the client calls `/auth/logout` and reloads onto the live account (guarded once per from→to pair to prevent loops).
460
- 4. Apex apps (`silentSso: true` in `bootstrapApp`): if the hidden iframe can't read ugly.bot's cookie (Safari / 3p-cookie blocking), a **top-level** redirect to `/oauth/silent` (first-party, bypasses 3p blocking) runs instead — one-shot, marked done so it can't loop. Skipped for accounts already confirmed live.
461
- 5. Manual login: on protected routes, the per-route auth guard surfaces `<AuthRoot>` → `<LoginPopup>`, which opens `${UGLY_BOT_URL}/oauth` in a popup. On completion, the platform redirects back with either a `postMessage` code or `?ugly_oauth_code=…`; `bootstrapApp` redeems either at the top of boot.
475
+ 3. Background account-sync (always): `bootstrapApp` opens a hidden iframe to `${UGLY_BOT_URL}/oauth/silent`. If it returns a one-time code, the client POSTs `/auth/verify` to refresh the cookie. If the refreshed account's `userId` differs from the current session, the client calls `/auth/logout` and reloads onto the live account (guarded once per from→to pair to prevent loops).
476
+ 4. Apex apps (`silentSso: true`): if the hidden iframe can't read ugly.bot's cookie (Safari / third-party-cookie blocking), a **top-level** redirect to `/oauth/silent` (first-party, bypasses 3p blocking) runs instead — one-shot, marked done so it can't loop.
477
+ 5. Manual login: on protected routes, the per-route auth guard surfaces `<AuthRoot>` → `<LoginPopup>`, which opens `${UGLY_BOT_URL}/oauth` in a popup. On completion, the platform redirects back with either a `postMessage` code or `?ugly_oauth_code=…`.
462
478
 
463
479
  ### Mode B — Self-issued sessions
464
480
 
465
481
  When `.uglyapp` sets `auth.mode: 'self'`, `buildAuth()` wires the magic-link provider as primary; if `providers.google.clientId` is configured it adds Google as an extra provider on the same router.
466
482
 
467
483
  - `<AuthRoot>` renders `<MagicLinkForm>` (plus the Google button when configured) instead of `<LoginPopup>`.
468
- - Background ugly.bot silent SSO is a no-op — the ugly.bot iframe would either silently fail or set a cookie that won't verify against the local session.
484
+ - Background ugly.bot silent SSO is a no-op — the ugly.bot iframe would either silently fail or set a cookie that wouldn't verify against the local session.
469
485
  - `POST /auth/magic-link/request` accepts an email, mints a one-time token (default 15 min lifetime, configurable via `auth.magicLinkExpiresMin`), and emails a link.
470
486
  - `GET /auth/magic-link/verify?token=…` validates the token, calls `onUserCreate` on first login, sets the session cookie, and redirects.
471
487
  - Google login lives at `GET /auth/google/url` + `POST /auth/google/callback` when enabled.
@@ -507,7 +523,7 @@ configurator.setAuth({
507
523
 
508
524
  ## Database — `TypedDB`
509
525
 
510
- Access via `app.db` or via `import { app } from './your-app-module'`. All methods accept a `CollectionDef` (from `defineCollections`) or a plain collection name string.
526
+ Access via `app.db`. All methods accept a `CollectionDef` (from `defineCollections`) or a plain collection name string.
511
527
 
512
528
  ### Writing
513
529
 
@@ -578,9 +594,10 @@ const k = db.cacheKey('prefix', id);
578
594
 
579
595
  ```typescript
580
596
  import { createUserHelper, dbDefaults } from 'ugly-app';
597
+ import { nanoid } from 'nanoid';
581
598
 
582
- const newDoc = { _id: crypto.randomUUID(), ...dbDefaults(), title: 'Hi' };
583
- // ^^^^^^^^^^^^^^^ { version: 1, created, updated }
599
+ const newDoc = { _id: nanoid(), ...dbDefaults(), title: 'Hi' };
600
+ // ^^^^^^^^^^^^^^^ { version: 1, created, updated }
584
601
 
585
602
  const userHelper = createUserHelper<User>(collections.user);
586
603
  const user = await userHelper.get(db, userId);
@@ -600,7 +617,7 @@ Imports available from `ugly-app`:
600
617
 
601
618
  ## AI
602
619
 
603
- AI calls are proxied through ugly.bot — your app never holds an AI provider key. Pass `UGLY_BOT_TOKEN` in the environment and the framework handles routing, balance tracking, retries, and per-user billing.
620
+ AI calls are proxied through ugly.bot — your app never holds an AI provider key. Set `UGLY_BOT_TOKEN` in the environment and the framework handles routing, balance tracking, retries, and per-user billing.
604
621
 
605
622
  ### Server-side text generation
606
623
 
@@ -778,6 +795,28 @@ Bucketing is deterministic: `hash(experimentId + userId)` (or `sessionId` for un
778
795
 
779
796
  ---
780
797
 
798
+ ## Sharing links
799
+
800
+ Never hand-build outbound URLs. Mint through the central helper — the returned `https://ugly.bot/l/<code>` short link deep-links into native + dock apps, renders an OpenGraph card, and can optionally gate humans behind login:
801
+
802
+ ```typescript
803
+ import { shareLink } from 'ugly-app'; // server
804
+ const url = await shareLink({
805
+ target: `/article/${id}`,
806
+ og: { title, description, image },
807
+ requireAuth: false,
808
+ });
809
+ ```
810
+
811
+ ```typescript
812
+ import { shareLink } from 'ugly-app/client'; // client
813
+ const url = await shareLink({ target: location.pathname, og: { title } });
814
+ ```
815
+
816
+ Do **not** route unsubscribe or other single-use, token-bearing URLs through `shareLink()` — those must remain direct app URLs.
817
+
818
+ ---
819
+
781
820
  ## Built-in endpoints
782
821
 
783
822
  | Endpoint | Description |
@@ -804,14 +843,14 @@ Bucketing is deterministic: `hash(experimentId + userId)` (or `sessionId` for un
804
843
  |-------------|-------------|
805
844
  | `ugly-app` | Server: `createApp`, `TypedDB`, auth, AI clients, NATS, storage, email, push, workers. |
806
845
  | `ugly-app/shared` | Cross-tier: `defineRequests`, `defineCollections`, `definePage`, `defineWorkers`, Zod, experiments, time constants. |
807
- | `ugly-app/client` | React: `bootstrapApp`, `createRouter`, `lazyPage`, `AppProvider`, components, animations, audio, AI helpers. |
846
+ | `ugly-app/client` | React: `bootstrapApp`, `createRouter`, `lazyPage`, `lazyPageLoader`, `AppProvider`, components, animations, audio, AI helpers. |
808
847
  | `ugly-app/conversation/{shared,server,client}` | AI chat sessions with persisted history. |
809
848
  | `ugly-app/collab/{server,client}` | Yjs-based collaborative editing. |
810
849
  | `ugly-app/markdown/{shared,client}` | Markdown rendering + editor. |
811
850
  | `ugly-app/webrtc`, `ugly-app/webrtc/server` | WebRTC video rooms. |
812
851
  | `ugly-app/three/{server,client}` | Three.js scene helpers. |
813
852
  | `ugly-app/worker` | Worker queue runtime. |
814
- | `ugly-app/playwright` | Test utilities. |
853
+ | `ugly-app/playwright` | Test utilities (`inspectWindow`, `expectClean`, `simulateKeyboard`, `waitForApp`). |
815
854
  | `ugly-app/vite`, `ugly-app/eslint` | Build-tool plugins. |
816
855
 
817
856
  ---
@@ -831,7 +870,7 @@ Bucketing is deterministic: `hash(experimentId + userId)` (or `sessionId` for un
831
870
  | `NATS_PREFIX` / `COMPOSE_PROJECT_NAME` | NATS subject prefix for per-env isolation. |
832
871
  | `CLOCK_ENABLED` | `true` to enable `setOnMinuteTick` / `setOnHourlyTick`. |
833
872
  | `CRON_SECRET` | Bearer secret for `POST /api/_cron/:taskName` and prod `POST /_workers/run`. |
834
- | `MAINTAIN_BOT_USER_ID` | User id allowed to access admin-only handlers. |
873
+ | `MAINTAIN_BOT_USER_ID` | User id allowed to access admin-only handlers (fallback when `setIsAdmin` unset). |
835
874
  | `INTERNAL_EMAIL_SECRET` | Shared secret for `/internal/email-callback`. |
836
875
  | `JWT_SECRET` | Required when using `getRequestUser()` for the per-project session cookie. |
837
876
  | `APP_DOMAIN` | App domain; combined with `NATS_PREFIX` for `getRequestUser()` validation. |
@@ -862,9 +901,9 @@ Browser-visible variables must be prefixed `VITE_` and consumed via `import.meta
862
901
  | `ugly-app error:dev` / `error:prod` | Query error logs (your tunnel / production). |
863
902
  | `ugly-app perf:dev` / `perf:prod` | Query performance metrics. |
864
903
  | `ugly-app feedback:dev` / `feedback:prod` | Query user feedback. |
865
- | `ugly-app feedback:submit` / `feedback:resolve` | Manage feedback (run with `--help` for flags). |
904
+ | `ugly-app feedback:submit` / `feedback:resolve` | Manage feedback (`--help` for flags). |
866
905
 
867
- Inside a scaffolded project, the same commands are available via `npm run …` scripts — see `templates/CLAUDE.md` for the full list.
906
+ Inside a scaffolded project, the same commands are available via `npm run …` scripts.
868
907
 
869
908
  ---
870
909
 
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.870";
1
+ export declare const CLI_VERSION = "0.1.871";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.870";
2
+ export const CLI_VERSION = "0.1.871";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnswerEngine.d.ts","sourceRoot":"","sources":["../../../src/search/server/AnswerEngine.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iDAAiD,CAAC;AAClF,OAAO,KAAK,EACV,SAAS,EAET,UAAU,EACX,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACxD,wEAAwE;IACxE,MAAM,CAAC,CACL,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EACpE,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC3B,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AA0BD,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,kBAAkB;IAEjD,GAAG,CACP,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;YAwD7C,MAAM;IA+BpB,OAAO,CAAC,gBAAgB;YAKV,WAAW;YA6BX,WAAW;IAqBzB,OAAO,CAAC,sBAAsB;YA2BhB,UAAU;CAoBzB"}
1
+ {"version":3,"file":"AnswerEngine.d.ts","sourceRoot":"","sources":["../../../src/search/server/AnswerEngine.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iDAAiD,CAAC;AAClF,OAAO,KAAK,EACV,SAAS,EAET,UAAU,EACX,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACxD,wEAAwE;IACxE,MAAM,CAAC,CACL,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EACpE,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC3B,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AA0BD,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,kBAAkB;IAEjD,GAAG,CACP,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,YAAY,CAAA;KAAE,CAAC;YA+D7C,MAAM;IA+BpB,OAAO,CAAC,gBAAgB;YAKV,WAAW;YA6BX,WAAW;IAqBzB,OAAO,CAAC,sBAAsB;YA2BhB,UAAU;CAoBzB"}
@@ -45,11 +45,17 @@ export class AnswerEngine {
45
45
  // 2. Retrieve + merge
46
46
  const merged = await this.retrieveAll(subQueries, retrievers);
47
47
  const top = merged.slice(0, this.config.maxResults ?? 8);
48
- if (top.length > 0)
49
- emit.sources(top.map((t) => t.result.source));
50
- // 3. Read top sources
48
+ // Only the sources we actually READ can be cited (`[1]..[readN]`), so only
49
+ // those are shown. Emitting all `maxResults` padded the card list with
50
+ // retrieved-but-unread results often off-topic or the wrong entity — that
51
+ // inflated apparent rigor and never backed the answer. Now the visible cards
52
+ // map 1:1 to the citation numbers.
51
53
  const readN = this.config.readTopN ?? 4;
52
- const contexts = await Promise.all(top.slice(0, readN).map(async (t, i) => {
54
+ const readable = top.slice(0, readN);
55
+ if (readable.length > 0)
56
+ emit.sources(readable.map((t) => t.result.source));
57
+ // 3. Read the shown sources
58
+ const contexts = await Promise.all(readable.map(async (t, i) => {
53
59
  const body = t.retriever.read
54
60
  ? await t.retriever.read(t.result).catch(() => t.result.text)
55
61
  : t.result.text;
@@ -1 +1 @@
1
- {"version":3,"file":"AnswerEngine.js","sourceRoot":"","sources":["../../../src/search/server/AnswerEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AnswerEngine.js","sourceRoot":"","sources":["../../../src/search/server/AnswerEngine.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.870",
3
+ "version": "0.1.871",
4
4
  "type": "module",
5
5
  "comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
6
6
  "files": [
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.870";
2
+ export const CLI_VERSION = "0.1.871";
@@ -115,12 +115,19 @@ export class AnswerEngine {
115
115
  // 2. Retrieve + merge
116
116
  const merged = await this.retrieveAll(subQueries, retrievers);
117
117
  const top = merged.slice(0, this.config.maxResults ?? 8);
118
- if (top.length > 0) emit.sources(top.map((t) => t.result.source));
119
118
 
120
- // 3. Read top sources
119
+ // Only the sources we actually READ can be cited (`[1]..[readN]`), so only
120
+ // those are shown. Emitting all `maxResults` padded the card list with
121
+ // retrieved-but-unread results — often off-topic or the wrong entity — that
122
+ // inflated apparent rigor and never backed the answer. Now the visible cards
123
+ // map 1:1 to the citation numbers.
121
124
  const readN = this.config.readTopN ?? 4;
125
+ const readable = top.slice(0, readN);
126
+ if (readable.length > 0) emit.sources(readable.map((t) => t.result.source));
127
+
128
+ // 3. Read the shown sources
122
129
  const contexts = await Promise.all(
123
- top.slice(0, readN).map(async (t, i) => {
130
+ readable.map(async (t, i) => {
124
131
  const body = t.retriever.read
125
132
  ? await t.retriever.read(t.result).catch(() => t.result.text)
126
133
  : t.result.text;