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 +96 -57
- package/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/search/server/AnswerEngine.d.ts.map +1 -1
- package/dist/search/server/AnswerEngine.js +10 -4
- package/dist/search/server/AnswerEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/version.ts +1 -1
- package/src/search/server/AnswerEngine.ts +10 -3
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
88
|
-
- `pushSend({ userId, page, query, ...notification })` — send a push notification whose click-through target is a route
|
|
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
|
|
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
|
|
99
|
-
| `setUserHelper(helper)` | Customize how the framework reads
|
|
100
|
-
| `setOnUserCreate(handler)` | Called on first login with `(userId, { email?, phone? }, db)
|
|
101
|
-
| `setAuth(provider)` | Replace the default
|
|
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)` |
|
|
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
|
|
111
|
-
| `setOnEmail(handler)` | Handle inbound emails routed to `{domain}@ugly.bot
|
|
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
|
|
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,
|
|
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
|
|
145
|
-
| `feedbackReportCreateNoAuth` / `errorLogCaptureNoAuth` | Same-origin, public endpoints used by the built-in browser telemetry
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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`
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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`
|
|
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,
|
|
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
|
|
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
|
|
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 () => {
|
|
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
|
|
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()`
|
|
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`
|
|
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
|
|
460
|
-
4. Apex apps (`silentSso: true`
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
583
|
-
//
|
|
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.
|
|
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 (
|
|
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
|
|
906
|
+
Inside a scaffolded project, the same commands are available via `npm run …` scripts.
|
|
868
907
|
|
|
869
908
|
---
|
|
870
909
|
|
package/dist/cli/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.1.
|
|
1
|
+
export declare const CLI_VERSION = "0.1.871";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cli/version.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
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
|
|
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":"AAAA,8EAA8E;AAC9E,EAAE;AACF,2DAA2D;AAC3D,kFAAkF;AAClF,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,4EAA4E;AA8D5E;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IACpC,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpD,OAAO,CACL,KAAK,GAAG,EAAE;QACV,aAAa,GAAG,CAAC;QACjB,6KAA6K,CAAC,IAAI,CAChL,CAAC,CACF,CACF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,KAAK,CAAC,GAAG,CACP,IAAgB,EAChB,GAAkB;QAElB,sEAAsE;QACtE,iFAAiF;QACjF,MAAM,IAAI,GAA2B,GAAG,CAAC,IAAI,CAAC;QAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEpD,0EAA0E;QAC1E,uEAAuE;QACvE,0EAA0E;QAC1E,gBAAgB;QAChB,MAAM,aAAa,GACjB,UAAU,CAAC,MAAM,GAAG,CAAC;YACrB,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,aAAa;YAC9B,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YACnC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEhB,sBAAsB;QACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"AnswerEngine.js","sourceRoot":"","sources":["../../../src/search/server/AnswerEngine.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,2DAA2D;AAC3D,kFAAkF;AAClF,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,4EAA4E;AA8D5E;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IACpC,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpD,OAAO,CACL,KAAK,GAAG,EAAE;QACV,aAAa,GAAG,CAAC;QACjB,6KAA6K,CAAC,IAAI,CAChL,CAAC,CACF,CACF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,KAAK,CAAC,GAAG,CACP,IAAgB,EAChB,GAAkB;QAElB,sEAAsE;QACtE,iFAAiF;QACjF,MAAM,IAAI,GAA2B,GAAG,CAAC,IAAI,CAAC;QAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEpD,0EAA0E;QAC1E,uEAAuE;QACvE,0EAA0E;QAC1E,gBAAgB;QAChB,MAAM,aAAa,GACjB,UAAU,CAAC,MAAM,GAAG,CAAC;YACrB,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,aAAa;YAC9B,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YACnC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEhB,sBAAsB;QACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;QAEzD,2EAA2E;QAC3E,uEAAuE;QACvE,4EAA4E;QAC5E,6EAA6E;QAC7E,mCAAmC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACrC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAE5E,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI;gBAC3B,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC7D,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;YAClB,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QACxD,CAAC,CAAC,CACH,CAAC;QAEF,qCAAqC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE3D,sEAAsE;QACtE,IACE,IAAI,KAAK,MAAM;YACf,IAAI,CAAC,MAAM,CAAC,YAAY,KAAK,KAAK;YAClC,QAAQ,CAAC,MAAM,GAAG,CAAC;YACnB,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EACtB,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;YACrE,IAAI,QAAQ,IAAI,QAAQ,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC7C,OAAO;oBACL,SAAS,EAAE,QAAQ;oBACnB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,MAAM,CAClB,KAAa,EACb,QAAkB,EAClB,KAAa;QAEb,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAChD,KAAK;gBACL,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,QAAQ;wBACd,OAAO,EACL,kEAAkE;4BAClE,iEAAiE;4BACjE,oEAAoE;4BACpE,mEAAmE;4BACnE,sDAAsD;qBACzD;oBACD;wBACE,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,aAAa,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,EAAE;qBAClE;iBACF;gBACD,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAgB;QACvC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;QAChE,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,KAAa;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAChD,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY;gBACxD,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,QAAQ;wBACd,OAAO,EACL,mEAAmE;4BACnE,yCAAyC,GAAG,iBAAiB;qBAChE;oBACD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE;iBACjC;gBACD,SAAS,EAAE,GAAG;aACf,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;YAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,EAAE,GAAG,MAAM;qBACd,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;qBACjD,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBACjB,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,WAAW,CACvB,UAAoB,EACpB,UAAuB;QAEvB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACvB,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;YACjC,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5D,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC,CAAC,CACH,CACF,CAAC;QACF,kEAAkE;QAClE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,CAAC;IAEO,sBAAsB,CAC5B,GAAkB,EAClB,QAAkB;QAElB,MAAM,OAAO,GACX,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC;QACrE,MAAM,MAAM,GACV,uEAAuE;YACvE,qEAAqE;YACrE,0EAA0E;YAC1E,0EAA0E;YAC1E,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,0EAA0E;YAC1E,yEAAyE;YACzE,2EAA2E;YAC3E,yEAAyE;YACzE,gBAAgB;YAChB,OAAO,CAAC;QACV,OAAO;YACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE;YACnC,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;YACtB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,EAAE;SACrC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,UAAU,CACtB,KAAa,EACb,QAAwB,EACxB,IAAgB;QAEhB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CACxD,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,EACpC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAC7B,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAClE,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC3D,KAAK;YACL,QAAQ;YACR,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAClE,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ugly-app",
|
|
3
|
-
"version": "0.1.
|
|
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": [
|
package/src/cli/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by prebuild — do not edit manually
|
|
2
|
-
export const CLI_VERSION = "0.1.
|
|
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
|
-
//
|
|
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
|
-
|
|
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;
|