@polishd/next 0.1.0 → 0.2.0

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.
Files changed (68) hide show
  1. package/AGENTS.md +161 -46
  2. package/CHANGELOG.md +128 -0
  3. package/README.md +238 -48
  4. package/bin/doctor.mjs +374 -0
  5. package/bin/init.mjs +215 -188
  6. package/bin/polishd.mjs +85 -0
  7. package/bin/shared.mjs +114 -0
  8. package/dist/ai/actions.d.ts +7 -2
  9. package/dist/ai/actions.d.ts.map +1 -1
  10. package/dist/ai/actions.js +9 -1
  11. package/dist/ai/actions.js.map +1 -1
  12. package/dist/ai/guard.d.ts +44 -3
  13. package/dist/ai/guard.d.ts.map +1 -1
  14. package/dist/ai/guard.js +13 -2
  15. package/dist/ai/guard.js.map +1 -1
  16. package/dist/ai/providers.d.ts +2 -0
  17. package/dist/ai/providers.d.ts.map +1 -1
  18. package/dist/ai/providers.js +70 -0
  19. package/dist/ai/providers.js.map +1 -1
  20. package/dist/ai/settings.d.ts +15 -1
  21. package/dist/ai/settings.d.ts.map +1 -1
  22. package/dist/ai/settings.js +38 -1
  23. package/dist/ai/settings.js.map +1 -1
  24. package/dist/ai/types.d.ts +10 -0
  25. package/dist/ai/types.d.ts.map +1 -1
  26. package/dist/client/init.d.ts.map +1 -1
  27. package/dist/client/init.js +51 -2
  28. package/dist/client/init.js.map +1 -1
  29. package/dist/config.d.ts +57 -3
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +62 -3
  32. package/dist/config.js.map +1 -1
  33. package/dist/dashboard/index.d.ts +34 -5
  34. package/dist/dashboard/index.d.ts.map +1 -1
  35. package/dist/dashboard/index.js +145 -17
  36. package/dist/dashboard/index.js.map +1 -1
  37. package/dist/dashboard/summary.d.ts.map +1 -1
  38. package/dist/dashboard/summary.js +59 -5
  39. package/dist/dashboard/summary.js.map +1 -1
  40. package/dist/dashboard/token-auth.d.ts +56 -0
  41. package/dist/dashboard/token-auth.d.ts.map +1 -0
  42. package/dist/dashboard/token-auth.js +158 -0
  43. package/dist/dashboard/token-auth.js.map +1 -0
  44. package/dist/dashboard/unlock.d.ts +2 -0
  45. package/dist/dashboard/unlock.d.ts.map +1 -0
  46. package/dist/dashboard/unlock.js +54 -0
  47. package/dist/dashboard/unlock.js.map +1 -0
  48. package/dist/dashboard.css +2601 -0
  49. package/dist/proxy.d.ts +53 -5
  50. package/dist/proxy.d.ts.map +1 -1
  51. package/dist/proxy.js +73 -3
  52. package/dist/proxy.js.map +1 -1
  53. package/dist/route.d.ts.map +1 -1
  54. package/dist/route.js +11 -3
  55. package/dist/route.js.map +1 -1
  56. package/dist/server/ingest.d.ts +33 -2
  57. package/dist/server/ingest.d.ts.map +1 -1
  58. package/dist/server/ingest.js +61 -7
  59. package/dist/server/ingest.js.map +1 -1
  60. package/dist/server/queries.d.ts +16 -0
  61. package/dist/server/queries.d.ts.map +1 -1
  62. package/dist/server/queries.js +80 -16
  63. package/dist/server/queries.js.map +1 -1
  64. package/dist/session.d.ts.map +1 -1
  65. package/dist/session.js +4 -2
  66. package/dist/session.js.map +1 -1
  67. package/docs/SETUP.md +308 -51
  68. package/package.json +14 -7
package/AGENTS.md CHANGED
@@ -18,13 +18,19 @@ Read these from the target app before changing anything.
18
18
  | Next.js App Router | an `app/` or `src/app/` directory exists | Stop. Polishd has no Pages Router support. |
19
19
  | Next.js ≥ 15 | `next` version in `package.json` | Stop and report. |
20
20
  | Node ≥ 22.5 | `node -v`, plus `engines`/`.nvmrc`/host setting | Capture silently no-ops on older Node. Warn the user and continue only if they accept that. |
21
- | Tailwind CSS | `tailwindcss` in dependencies | The dashboard renders unstyled. See Phase 4. |
22
21
  | Package manager | lockfile name | Use it consistently; don't mix. |
23
22
 
24
23
  Record two facts you will need throughout:
25
24
 
26
25
  - **`BASE`** — `src` if `src/app/` exists, otherwise `` (repo root).
27
- - **`NEXT_MAJOR`** — 16+ uses `proxy.ts`; 15 uses `middleware.ts`.
26
+ - **`NEXT_MAJOR`** — 16+ uses `proxy.ts`; 15 uses `middleware.ts`. Read the
27
+ *installed* version from `node_modules/next/package.json`, not the range in
28
+ `package.json` — `^16`, `latest` and `canary` tell you nothing about what is
29
+ actually installed. The CLI does this for you; you need it only for Phase 3b.
30
+
31
+ Getting `NEXT_MAJOR` wrong is a silent total failure, not a build error: Next
32
+ simply never loads a file under the other name, so no session cookie is minted,
33
+ every beacon still returns 200, and nothing is ever stored.
28
34
 
29
35
  ## Phase 2 — Install
30
36
 
@@ -37,18 +43,25 @@ Postgres URL is configured. No separate install is needed.
37
43
 
38
44
  ## Phase 3 — Scaffold
39
45
 
40
- Prefer the CLI. It handles every branch in Phase 1 and Phase 4 for you:
46
+ Prefer the CLI. It detects `BASE` and `NEXT_MAJOR` itself, so you do not have
47
+ to branch on them:
41
48
 
42
49
  ```bash
43
- npx polishd init
50
+ npx @polishd/next init
44
51
  ```
45
52
 
46
- Flags: `--dry-run` (preview), `--force` (overwrite existing), `--js` (JavaScript
47
- output), `--config` (also emit `polishd.config.ts`).
53
+ Flags: `--dry-run` (preview), `--force` (regenerate files the CLI itself wrote,
54
+ backing each up to `.bak` first), `--js` (JavaScript output), `--config` (also
55
+ emit `polishd.config.ts`).
56
+
57
+ It writes four files and appends `.polishd/` to `.gitignore`.
58
+ It names the proxy file for the detected Next major and reports which it chose;
59
+ if it could not determine the version it says so — check that against Phase 1
60
+ and rename by hand if it guessed wrong.
48
61
 
49
- It writes four files, appends `.polishd/` to `.gitignore`, and wires Tailwind.
50
- If it reports that a file already exists, do not overwrite blindly — merge by
51
- hand following Phase 3b.
62
+ **`--force` will not overwrite a file the CLI did not write**, so it is safe to
63
+ re-run. When it finds a proxy it did not author it prints merge instructions
64
+ instead. Follow Phase 3c, not Phase 3b, in that case.
52
65
 
53
66
  ### Phase 3b — Manual scaffold (only if the CLI could not run)
54
67
 
@@ -70,9 +83,7 @@ export const config = {
70
83
  };
71
84
  ```
72
85
 
73
- If the app already has a proxy/middleware, do **not** replace it. Call
74
- `withPolishdSession(request, existingResponse)` in the existing handler and
75
- merge the matcher, keeping the `api` exclusion.
86
+ If the app already has a proxy/middleware, do **not** replace it — see Phase 3c.
76
87
 
77
88
  **2. `{BASE}/instrumentation-client.ts`** — starts browser capture.
78
89
 
@@ -96,6 +107,7 @@ export const POST = createPolishdRoute();
96
107
  **4. `{BASE}/app/polishd/page.tsx`** — the dashboard.
97
108
 
98
109
  ```tsx
110
+ import "@polishd/next/dashboard.css";
99
111
  import { createPolishdPage } from "@polishd/next/dashboard";
100
112
 
101
113
  export const runtime = "nodejs";
@@ -106,29 +118,62 @@ export default createPolishdPage();
106
118
 
107
119
  Then add `.polishd/` to `.gitignore`.
108
120
 
109
- ## Phase 4 — Wire Tailwind
121
+ ### Phase 3c — The app already has a proxy or middleware
122
+
123
+ Next loads exactly one proxy module, so Polishd goes **inside** the existing
124
+ one. Never replace it: that file typically carries auth redirects, i18n, A/B
125
+ routing, or rate limiting.
126
+
127
+ The trap is the matcher. One file means one `config.matcher`, so merging means
128
+ widening it to the union — and Polishd's matcher is near-total. A handler that
129
+ was scoped to two routes silently begins running on every request in the app.
130
+ Nothing errors; its redirects just start firing where they never did before.
131
+
132
+ Use `composePolishd` and declare the handler's original scope, which is
133
+ re-applied as an internal guard:
134
+
135
+ ```ts
136
+ import { composePolishd } from "@polishd/next/proxy";
110
137
 
111
- The dashboard is built from Tailwind utility classes and ships **no stylesheet**.
112
- Tailwind will not find it on its own — v4's content detection deliberately
113
- ignores `node_modules`, and v3 only scans the globs you give it. Skipping this
114
- step produces a dashboard that returns HTTP 200 and is completely unstyled.
138
+ // Existing handler, unchanged.
139
+ function myProxy(request: NextRequest) { /* … */ }
115
140
 
116
- **Tailwind v4** (no `tailwind.config.*`; CSS has `@import "tailwindcss"`) — add
117
- to that CSS file, with the path relative to the file itself:
141
+ // Keeps running only on /admin/*, though the matcher below is near-total.
142
+ export const proxy = composePolishd(myProxy, { matcher: "/admin/:path*" });
118
143
 
119
- ```css
120
- @import "tailwindcss";
121
- @source "../../node_modules/@polishd/next/dist";
144
+ export const config = {
145
+ matcher: ["/((?!api|_next/static|_next/image|favicon|assets).*)"],
146
+ };
122
147
  ```
123
148
 
124
- **Tailwind v3** — add to `content` in `tailwind.config.*`:
149
+ Read the existing `config.matcher` **before** you overwrite it and pass exactly
150
+ that value as `matcher`. On Next 15 the file is `middleware.ts` and the export
151
+ must be named `middleware`.
125
152
 
126
- ```ts
127
- content: ["./src/**/*.{ts,tsx}", "./node_modules/@polishd/next/dist/**/*.js"],
153
+ `matcher` accepts Next's matcher strings or a `RegExp`. The union literal itself
154
+ must still be written by hand — Next statically parses it (Invariant 1).
155
+
156
+ ## Phase 4 — Styling
157
+
158
+ One import in the dashboard page, which the CLI writes for you:
159
+
160
+ ```tsx
161
+ import "@polishd/next/dashboard.css";
128
162
  ```
129
163
 
130
- **No Tailwind** — tell the user the dashboard will be unstyled. Do not add
131
- Tailwind to their app to fix this unless they ask.
164
+ The stylesheet is compiled at publish time and self-contained. **Do not add
165
+ Tailwind to the app, and do not add an `@source` line** — neither is needed, and
166
+ the host's Tailwind version is irrelevant to how the dashboard renders.
167
+
168
+ It carries no global preflight and every rule is scoped to `.polishd-root`, so
169
+ it cannot restyle the host app.
170
+
171
+ **On an existing 0.1.x install**, a `@source ".../@polishd/next/dist"` line in
172
+ the host's Tailwind config is still doing the styling and the dashboard is not
173
+ broken. Do not remove it as a first step. Add the stylesheet import, verify the
174
+ dashboard still renders correctly, and only then remove the `@source` line —
175
+ removing it first is the one order that leaves the dashboard unstyled. If the
176
+ app has no Tailwind at all, the import is the only thing that styles it.
132
177
 
133
178
  ## Phase 5 — Invariants
134
179
 
@@ -145,10 +190,13 @@ style preferences.
145
190
  fails the build with *"Next.js can't recognize the exported `runtime` field
146
191
  in route. It mustn't be reexported."*
147
192
 
148
- 3. **Gate the dashboard in production.** With no `authenticate` callback, the
149
- dashboard *and its server actions* are public: anyone reaching the URL can
150
- read the analytics, change the configured model and API base URL, and spend
151
- the owner's model tokens. See Phase 6.
193
+ 3. **The dashboard refuses to serve in production until access is decided.**
194
+ It is not a read-only report: its settings change the configured model and
195
+ API base URL, so reaching it means being able to redirect the owner's
196
+ requests and spend their key. With no `POLISHD_DASHBOARD_TOKEN`, no
197
+ `authenticate` callback, and no `POLISHD_DASHBOARD_PUBLIC=true`, production
198
+ renders a locked screen and every server action denies. Development is
199
+ unaffected. See Phase 6.
152
200
 
153
201
  4. **Never import `@polishd/next/server` from a client component.** It pulls
154
202
  database drivers into the browser bundle. Use `@polishd/next/client` there.
@@ -158,8 +206,32 @@ style preferences.
158
206
 
159
207
  ## Phase 6 — Protect the dashboard
160
208
 
161
- If the app has any auth helper, wire it up. Ask the user which one rather than
162
- guessing.
209
+ Pick one of three. **If the app has no auth of its own, use the token** — do not
210
+ hand-roll a gate, and do not leave it for the user to figure out later.
211
+
212
+ **1. Built-in token (default choice for an app with no auth).** No code:
213
+
214
+ ```bash
215
+ POLISHD_DASHBOARD_TOKEN=$(openssl rand -hex 32)
216
+ ```
217
+
218
+ Add it to the app's `.env.example` and tell the user to set it in their
219
+ deployment environment. The locked screen renders a password form; the token is
220
+ exchanged for an httpOnly, path-scoped cookie by a server action, so it never
221
+ travels in a URL. Constant-time comparison, HMAC in the cookie rather than the
222
+ raw token, server-side expiry, and per-IP rate limiting are all handled.
223
+
224
+ Pass options only if the dashboard is not at `/polishd`:
225
+
226
+ ```tsx
227
+ import { createPolishdPage, polishdTokenAuth } from "@polishd/next/dashboard";
228
+ export default createPolishdPage({
229
+ authenticate: polishdTokenAuth({ basePath: "/admin/polishd" }),
230
+ });
231
+ ```
232
+
233
+ **2. The app's own auth.** If the app has an auth helper, prefer it. Ask the
234
+ user which one rather than guessing.
163
235
 
164
236
  ```tsx
165
237
  import { createPolishdPage } from "@polishd/next/dashboard";
@@ -170,25 +242,50 @@ export const runtime = "nodejs";
170
242
  export const dynamic = "force-dynamic";
171
243
 
172
244
  export default createPolishdPage({
173
- authenticate: isAuthenticated, // () => boolean | Promise<boolean>
245
+ authenticate: isAuthenticated, // (ctx) => boolean | Promise<boolean>
174
246
  unauthorized: <MyLogin />, // optional; a minimal screen otherwise
175
247
  });
176
248
  ```
177
249
 
178
- The same callback guards the AI server actions, re-running per request against
179
- the caller's cookies. Polishd ships no auth code of its own.
250
+ `authenticate` receives `{ cookies, headers }` for the request, so it need not
251
+ call `cookies()`/`headers()` itself. A zero-argument callback still works.
180
252
 
181
- A common pattern is to stay open in development and require auth in production:
253
+ **3. Deliberately public.** Only if the user asks for it:
182
254
 
183
- ```tsx
184
- authenticate: async () =>
185
- process.env.NODE_ENV !== "production" || (await isAuthenticated()),
255
+ ```bash
256
+ POLISHD_DASHBOARD_PUBLIC=true
186
257
  ```
187
258
 
259
+ In every case the same policy guards the AI server actions, re-running per
260
+ request against the caller's cookies. Note that an app which deletes
261
+ `app/polishd/page.tsx` while keeping the package leaves those actions routable
262
+ but always denying — correct, but confusing if you meet it cold.
263
+
188
264
  ## Phase 7 — Verify
189
265
 
190
266
  Run all of these. Report the results; do not declare success from the build
191
- alone.
267
+ alone — most of what can be wrong here builds cleanly and fails silently.
268
+
269
+ Start with the doctor, which checks the whole wiring in one command:
270
+
271
+ ```bash
272
+ npx @polishd/next doctor
273
+ ```
274
+
275
+ It verifies the proxy is named and exported for the detected Next major, that
276
+ `config.matcher` is an inline literal excluding `/api`, that `runtime` and
277
+ `dynamic` are declared rather than re-exported, that capture and ingest exist,
278
+ that the dashboard stylesheet is imported, and the access posture. Fix everything it
279
+ reports before continuing.
280
+
281
+ With the app running, add live checks — this is the only way to confirm the
282
+ cookie is really minted and events really store:
283
+
284
+ ```bash
285
+ npx @polishd/next doctor --url http://localhost:3000
286
+ ```
287
+
288
+ Then confirm by hand:
192
289
 
193
290
  ```bash
194
291
  # 1. Build must pass.
@@ -213,7 +310,7 @@ curl -s -X POST http://localhost:3000/api/polishd \
213
310
  # expect: {"ok":true,"stored":1}
214
311
 
215
312
  # 6. Styling is present — the stylesheet must contain dashboard utilities.
216
- # Fetch the dashboard's CSS chunk and grep for `tabular-nums`.
313
+ # The dashboard page must import "@polishd/next/dashboard.css".
217
314
  ```
218
315
 
219
316
  In development the store writes `.polishd/analytics.db` (SQLite). Its presence
@@ -228,6 +325,9 @@ survive there.
228
325
  | Variable | Required | Purpose |
229
326
  |---|---|---|
230
327
  | `POLISHD_DATABASE_URL` | for production capture | Pooled Postgres connection string |
328
+ | `POLISHD_DASHBOARD_TOKEN` | for production, unless auth is wired | Protects `/polishd`. `openssl rand -hex 32` |
329
+ | `POLISHD_DASHBOARD_PUBLIC` | no | `true` to serve the dashboard unguarded on purpose |
330
+ | `POLISHD_SESSION_COOKIE` | no | Rename the session cookie; read by both proxy and ingest |
231
331
  | `POLISHD_DB_PATH` | no | Custom SQLite path in dev |
232
332
  | `POLISHD_AI_*` | no | Preset AI provider/model/key instead of using the dashboard |
233
333
  | `POLISHD_GITHUB_*` | no | Repo + token for filing issues from findings |
@@ -240,14 +340,22 @@ a notice on the dashboard, and no impact on the host app.
240
340
 
241
341
  ## Troubleshooting
242
342
 
343
+ Run `npx @polishd/next doctor` first — it names most of these directly.
344
+
243
345
  | Symptom | Cause | Fix |
244
346
  |---|---|---|
245
347
  | *"can't recognize the exported `runtime` field"* | Route segment config re-exported | Declare `runtime`/`dynamic` inline (Phase 3b) |
246
348
  | Every `/api/*` route 404s | Proxy matcher matches `/api` | Restore the `api` exclusion in the matcher |
247
- | Dashboard renders unstyled | Tailwind isn't scanning `dist` | Phase 4 |
349
+ | Dashboard renders unstyled | `@polishd/next/dashboard.css` not imported in the page | Phase 4 |
248
350
  | Dashboard is empty, warning about the store | No writable backend | Set `POLISHD_DATABASE_URL`, or check Node ≥ 22.5 in dev |
351
+ | Dashboard shows *"no session cookie — your proxy isn't running"* | Proxy missing, misnamed for the Next major, matcher not covering the path, or a botched merge | Phase 3c and `doctor --url` |
352
+ | Beacons return 200 but nothing is stored | Same as above — ingest answers `{ok:true,stored:0,reason:"no_session"}` so the client cannot see a failure | `doctor --url http://localhost:3000` |
353
+ | Proxy and ingest disagree on the cookie name | `sessionCookie` threaded into one layer but not another | Set `POLISHD_SESSION_COOKIE`; both read it |
249
354
  | `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite` | Node < 22.5 | Upgrade Node, or configure Postgres |
250
- | No `polishd_session` cookie | Proxy file missing/misnamed, or matcher excludes the path | Check `proxy.ts` (Next 16) vs `middleware.ts` (Next 15) |
355
+ | `ExperimentalWarning: SQLite is an experimental feature` | Node's own `node:sqlite` module on 22.x | **Expected and harmless.** Not a Polishd instability. Do not "fix" it or switch stores because of it |
356
+ | No `polishd_session` cookie | Proxy file missing/misnamed, or matcher excludes the path | Check `proxy.ts` (Next 16+) vs `middleware.ts` (Next 15) |
357
+ | Dashboard content unreachable below the fold | Host `body` doesn't scroll | Fixed by default; if an ancestor sets `transform`/`filter`/`contain`, pass `shell: false` and use a route-group layout (docs/SETUP.md) |
358
+ | Server actions 500 with an opaque digest | No dashboard page registered a policy | Expected if `app/polishd/page.tsx` was deleted — actions fail closed |
251
359
  | Database drivers in the client bundle | `@polishd/next/server` imported client-side | Import from `@polishd/next/client` |
252
360
 
253
361
  ## Entry points
@@ -258,5 +366,12 @@ a notice on the dashboard, and no impact on the host app.
258
366
  | `@polishd/next/client` | `initPolishd`, `PolishdMonitor` | browser |
259
367
  | `@polishd/next/server` | store, ingest, queries, `withPolishdSession` | Node only |
260
368
  | `@polishd/next/route` | `POST`, `createPolishdRoute` | Node only |
261
- | `@polishd/next/proxy` | `proxy`, `config`, `withPolishdSession`, `polishdMatcher` | Edge-safe |
262
- | `@polishd/next/dashboard` | `createPolishdPage`, `PolishdDashboard` | Node only |
369
+ | `@polishd/next/proxy` | `proxy`, `config`, `withPolishdSession`, `polishdMatcher`, `composePolishd` | Edge-safe |
370
+ | `@polishd/next/dashboard` | `createPolishdPage`, `polishdTokenAuth`, `PolishdDashboard` | Node only |
371
+
372
+ ## CLI
373
+
374
+ | Command | Purpose |
375
+ |---|---|
376
+ | `npx @polishd/next init` | Scaffold. `--dry-run`, `--force`, `--js`, `--config` |
377
+ | `npx @polishd/next doctor` | Check the install. `--url <origin>` adds live checks |
package/CHANGELOG.md ADDED
@@ -0,0 +1,128 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Everything in this release came out of the first real third-party integration of
6
+ the package, and most of it is the same underlying problem: Polishd assumed
7
+ things about the host app that it never checked and could not see.
8
+
9
+ ### Upgrading from 0.1.x
10
+
11
+ **Styling keeps working with no changes.** Your existing `@source
12
+ ".../@polishd/next/dist"` still compiles the dashboard's utilities exactly as
13
+ it did, so upgrading will not leave you with an unstyled page. Verified by
14
+ rendering the dashboard both ways: 491 elements, no visible differences.
15
+
16
+ There is **one required change**, and it is about access, not styling.
17
+
18
+ **Decide how the dashboard is protected in production.** It now refuses to serve
19
+ unless you have chosen one:
20
+
21
+ ```bash
22
+ POLISHD_DASHBOARD_TOKEN=$(openssl rand -hex 32) # built-in gate, no code
23
+ ```
24
+
25
+ or pass your own `authenticate` to `createPolishdPage()`, or opt out explicitly
26
+ with `POLISHD_DASHBOARD_PUBLIC=true`. Development is unaffected.
27
+
28
+ Then run `npx @polishd/next doctor`, which checks that and much else.
29
+
30
+ #### Optional: move to the shipped stylesheet
31
+
32
+ Recommended, but at your own pace. **Order matters** — add the import first, and
33
+ only then remove the old wiring, so the dashboard is never unstyled in between:
34
+
35
+ 1. Add to `app/polishd/page.tsx`:
36
+ ```tsx
37
+ import "@polishd/next/dashboard.css";
38
+ ```
39
+ 2. Confirm the dashboard still looks right.
40
+ 3. *Then* delete the `@source ".../@polishd/next/dist"` line from your Tailwind
41
+ entry stylesheet (or the `content` glob in `tailwind.config.*`).
42
+
43
+ Doing this drops Tailwind as a requirement for your app and stops your build
44
+ compiling utilities only the dashboard uses. `doctor` reports which of the two
45
+ setups it finds, and never calls a working 0.1.x setup broken.
46
+
47
+ #### If you are on Next 15, check this
48
+
49
+ 0.1.0's CLI wrote `proxy.ts` regardless of the installed Next major, and **Next
50
+ 15 does not load that file**. If you are on Next 15 your proxy has never run:
51
+ no session cookie, every beacon returning 200, and nothing captured, with no
52
+ error anywhere to show for it. Rename it to `middleware.ts` and rename the
53
+ exported function to `middleware`. `doctor` reports this explicitly.
54
+
55
+ ### Breaking
56
+
57
+ - **The dashboard fails shut in production.** With no `POLISHD_DASHBOARD_TOKEN`,
58
+ no `authenticate` callback and no `POLISHD_DASHBOARD_PUBLIC=true`, production
59
+ renders a locked screen and every AI server action denies. Previously the
60
+ default was open, and the warning about it was suppressed in production —
61
+ exactly where it was worth acting on. The dashboard's settings can change the
62
+ configured model and API base URL, so an open one is arbitrary spend on the
63
+ owner's key, not merely an analytics leak.
64
+ - **The dashboard renders in its own shell** (`position: fixed`, own background
65
+ and scroll container) rather than inheriting the host page. Pass
66
+ `shell: false` to `createPolishdPage()` for the old behaviour.
67
+
68
+ ### Added
69
+
70
+ - **`npx @polishd/next doctor`** — checks the wiring that fails silently: the
71
+ proxy named and exported for the installed Next major, `config.matcher` as an
72
+ inline literal excluding `/api`, `runtime`/`dynamic` declared rather than
73
+ re-exported, the stylesheet import, access posture, Node version.
74
+ `--url <origin>` adds live checks that the cookie is minted and events store.
75
+ - **Built-in token auth.** Setting `POLISHD_DASHBOARD_TOKEN` is the whole
76
+ integration for an app with no auth of its own — no import, no page edit. The
77
+ token is exchanged for an httpOnly, path-scoped cookie by a server action, so
78
+ it never appears in a URL, in history, in logs or in a `Referer` header. The
79
+ cookie holds an HMAC rather than the token, comparison is constant-time,
80
+ expiry is enforced server-side, and the unlock form is rate limited.
81
+ `polishdTokenAuth()` is exported for hosts that want to configure it.
82
+ - **`dist/dashboard.css`** — the dashboard ships its own stylesheet, compiled at
83
+ publish time. Tailwind is no longer required in the host app.
84
+ - **`composePolishd(handler, { matcher })`** — for apps that already have a
85
+ proxy. Declaring the handler's original scope re-applies it as an internal
86
+ guard, so widening `config.matcher` to the union doesn't silently start
87
+ running your handler on every request in the app.
88
+ - **`authenticate` receives the request context** (`cookies`, `headers`), so
89
+ callbacks are ordinary functions of their inputs and testable without a live
90
+ request. Zero-argument callbacks keep working unchanged.
91
+ - **`POLISHD_SESSION_COOKIE`** — read by both the proxy and the ingest route, so
92
+ renaming the cookie cannot go half-applied.
93
+
94
+ ### Fixed
95
+
96
+ - **`--force` destroyed hand-written proxies.** Both the exists-guard and the
97
+ merge hint were skipped under the flag, so a user who passed it to regenerate
98
+ an unrelated file lost their real proxy wholesale. Generated files now carry a
99
+ sentinel; files the CLI did not author are never overwritten, with or without
100
+ `--force`, and its own files are backed up to `.bak` before regenerating.
101
+ - **The CLI ignored the installed Next major**, always writing `proxy.ts`. On
102
+ Next 15 — a supported version — that file never runs: no session cookie, every
103
+ beacon still 200, nothing stored, no error anywhere. It now resolves the
104
+ installed version and picks the filename and exported function to match.
105
+ - **The merge hint keyed off the wrong filename**, so the Next 16 collision —
106
+ the one that actually happens — got no guidance and only `--force` as a
107
+ remedy, which was the data-loss bug above.
108
+ - **A missing session cookie is no longer silent.** Ingest records the rejection
109
+ in `polishd_meta`, the dashboard shows a banner naming the expected proxy
110
+ filename per Next major, and the client warns once in the dev console.
111
+ - **`--dry-run` under-reported**, omitting the `.gitignore` change.
112
+ - **`polishd.config.ts` was documented as being loaded automatically.** Nothing
113
+ imports it; it cannot be auto-loaded, because the three consumers run in three
114
+ runtimes and the Edge one has no filesystem. The docs now say what actually
115
+ has to happen.
116
+
117
+ ### Also
118
+
119
+ - Node `>= 22.5.0` is checked by the CLI rather than left to a confusing
120
+ "cannot find module `node:sqlite`" at first request.
121
+ - `bin/` is included in the published files. npm auto-includes the bin entry but
122
+ not the modules it imports, so the CLI would otherwise have shipped broken.
123
+ - Docs carry a paste-ready prompt pointing a coding agent at `AGENTS.md`, in the
124
+ README, `docs/SETUP.md` and `polishd --help`.
125
+
126
+ ## 0.1.0
127
+
128
+ Initial release.