@polishd/next 0.1.0 → 0.2.1-dev.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.
- package/AGENTS.md +161 -46
- package/CHANGELOG.md +163 -0
- package/README.md +275 -49
- package/bin/doctor.mjs +374 -0
- package/bin/init.mjs +215 -188
- package/bin/polishd.mjs +85 -0
- package/bin/shared.mjs +114 -0
- package/dist/ai/actions.d.ts +13 -2
- package/dist/ai/actions.d.ts.map +1 -1
- package/dist/ai/actions.js +19 -1
- package/dist/ai/actions.js.map +1 -1
- package/dist/ai/design.d.ts +28 -0
- package/dist/ai/design.d.ts.map +1 -0
- package/dist/ai/design.js +245 -0
- package/dist/ai/design.js.map +1 -0
- package/dist/ai/guard.d.ts +44 -3
- package/dist/ai/guard.d.ts.map +1 -1
- package/dist/ai/guard.js +13 -2
- package/dist/ai/guard.js.map +1 -1
- package/dist/ai/providers.d.ts +2 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +70 -0
- package/dist/ai/providers.js.map +1 -1
- package/dist/ai/settings.d.ts +15 -1
- package/dist/ai/settings.d.ts.map +1 -1
- package/dist/ai/settings.js +38 -1
- package/dist/ai/settings.js.map +1 -1
- package/dist/ai/types.d.ts +57 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/client/design-scan.d.ts +37 -0
- package/dist/client/design-scan.d.ts.map +1 -0
- package/dist/client/design-scan.js +319 -0
- package/dist/client/design-scan.js.map +1 -0
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/init.js +67 -2
- package/dist/client/init.js.map +1 -1
- package/dist/config.d.ts +57 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +62 -3
- package/dist/config.js.map +1 -1
- package/dist/dashboard/design-review-card.d.ts +15 -0
- package/dist/dashboard/design-review-card.d.ts.map +1 -0
- package/dist/dashboard/design-review-card.js +82 -0
- package/dist/dashboard/design-review-card.js.map +1 -0
- package/dist/dashboard/design.d.ts +10 -0
- package/dist/dashboard/design.d.ts.map +1 -0
- package/dist/dashboard/design.js +83 -0
- package/dist/dashboard/design.js.map +1 -0
- package/dist/dashboard/index.d.ts +35 -5
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +176 -18
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/journeys.d.ts.map +1 -1
- package/dist/dashboard/journeys.js +1 -0
- package/dist/dashboard/journeys.js.map +1 -1
- package/dist/dashboard/summary.d.ts.map +1 -1
- package/dist/dashboard/summary.js +59 -5
- package/dist/dashboard/summary.js.map +1 -1
- package/dist/dashboard/token-auth.d.ts +56 -0
- package/dist/dashboard/token-auth.d.ts.map +1 -0
- package/dist/dashboard/token-auth.js +158 -0
- package/dist/dashboard/token-auth.js.map +1 -0
- package/dist/dashboard/unlock.d.ts +2 -0
- package/dist/dashboard/unlock.d.ts.map +1 -0
- package/dist/dashboard/unlock.js +54 -0
- package/dist/dashboard/unlock.js.map +1 -0
- package/dist/dashboard.css +2740 -0
- package/dist/proxy.d.ts +53 -5
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +73 -3
- package/dist/proxy.js.map +1 -1
- package/dist/route.d.ts.map +1 -1
- package/dist/route.js +11 -3
- package/dist/route.js.map +1 -1
- package/dist/server/design.d.ts +93 -0
- package/dist/server/design.d.ts.map +1 -0
- package/dist/server/design.js +430 -0
- package/dist/server/design.js.map +1 -0
- package/dist/server/ingest.d.ts +33 -2
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +70 -8
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/queries.d.ts +16 -0
- package/dist/server/queries.d.ts.map +1 -1
- package/dist/server/queries.js +80 -16
- package/dist/server/queries.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +4 -2
- package/dist/session.js.map +1 -1
- package/dist/shared/types.d.ts +31 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +1 -0
- package/dist/shared/types.js.map +1 -1
- package/docs/SETUP.md +308 -51
- 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
|
|
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` (
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
export const config = {
|
|
145
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon|assets).*)"],
|
|
146
|
+
};
|
|
122
147
|
```
|
|
123
148
|
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
131
|
-
Tailwind to
|
|
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. **
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
162
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
253
|
+
**3. Deliberately public.** Only if the user asks for it:
|
|
182
254
|
|
|
183
|
-
```
|
|
184
|
-
|
|
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
|
-
#
|
|
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 |
|
|
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
|
-
|
|
|
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,163 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Design review — a new dashboard tab
|
|
6
|
+
|
|
7
|
+
The dashboard now has a sidebar with two tabs: **Analytics** (everything it
|
|
8
|
+
showed before) and **Design**, a new aesthetic layer. The Design tab renders
|
|
9
|
+
your site's brand guideline *reverse-engineered from the rendered pages*:
|
|
10
|
+
|
|
11
|
+
- **Typography** — every font family / size / weight combination actually in
|
|
12
|
+
use, shown as specimens with real text sampled from the site.
|
|
13
|
+
- **Color palette** — the colors your pages paint, laid out like a brand
|
|
14
|
+
palette and weighted by how much of the UI they cover, split by role
|
|
15
|
+
(text / background / border).
|
|
16
|
+
- **Contrast** — every text-on-background pairing measured against WCAG AA,
|
|
17
|
+
failures shown with live samples.
|
|
18
|
+
- **Corner radii and spacing** — the rounding and padding scales in use.
|
|
19
|
+
|
|
20
|
+
Each section flags what breaks the system deterministically — no model
|
|
21
|
+
involved: font-size sprawl (16 sizes is not a type scale), extra families,
|
|
22
|
+
near-duplicate colors (`#fefefe` next to `#ffffff`), contrast failures,
|
|
23
|
+
one-off values that appear on a single page, and padding that drifts off a
|
|
24
|
+
4px grid.
|
|
25
|
+
|
|
26
|
+
On top of the measured metrics, an **Aesthetic review** card asks the same
|
|
27
|
+
model you configured for the analytics summary how coherent the design reads
|
|
28
|
+
— strengths, and evidence-cited issues with concrete fixes. Like the summary,
|
|
29
|
+
it is fingerprint-cached: reopening the tab is free, and regenerating with
|
|
30
|
+
unchanged metrics never spends tokens. Refresh buttons for both the metrics
|
|
31
|
+
and the analysis sit on the tab.
|
|
32
|
+
|
|
33
|
+
Nothing new to wire up: pages measure their own rendered design once per
|
|
34
|
+
visitor session (a compact few-KB tally, sampled after the page settles) and
|
|
35
|
+
the data flows through the existing ingest route and store. The dashboard
|
|
36
|
+
itself is never scanned. Existing installs only need the new package version.
|
|
37
|
+
|
|
38
|
+
## 0.2.0
|
|
39
|
+
|
|
40
|
+
Everything in this release came out of the first real third-party integration of
|
|
41
|
+
the package, and most of it is the same underlying problem: Polishd assumed
|
|
42
|
+
things about the host app that it never checked and could not see.
|
|
43
|
+
|
|
44
|
+
### Upgrading from 0.1.x
|
|
45
|
+
|
|
46
|
+
**Styling keeps working with no changes.** Your existing `@source
|
|
47
|
+
".../@polishd/next/dist"` still compiles the dashboard's utilities exactly as
|
|
48
|
+
it did, so upgrading will not leave you with an unstyled page. Verified by
|
|
49
|
+
rendering the dashboard both ways: 491 elements, no visible differences.
|
|
50
|
+
|
|
51
|
+
There is **one required change**, and it is about access, not styling.
|
|
52
|
+
|
|
53
|
+
**Decide how the dashboard is protected in production.** It now refuses to serve
|
|
54
|
+
unless you have chosen one:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
POLISHD_DASHBOARD_TOKEN=$(openssl rand -hex 32) # built-in gate, no code
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
or pass your own `authenticate` to `createPolishdPage()`, or opt out explicitly
|
|
61
|
+
with `POLISHD_DASHBOARD_PUBLIC=true`. Development is unaffected.
|
|
62
|
+
|
|
63
|
+
Then run `npx @polishd/next doctor`, which checks that and much else.
|
|
64
|
+
|
|
65
|
+
#### Optional: move to the shipped stylesheet
|
|
66
|
+
|
|
67
|
+
Recommended, but at your own pace. **Order matters** — add the import first, and
|
|
68
|
+
only then remove the old wiring, so the dashboard is never unstyled in between:
|
|
69
|
+
|
|
70
|
+
1. Add to `app/polishd/page.tsx`:
|
|
71
|
+
```tsx
|
|
72
|
+
import "@polishd/next/dashboard.css";
|
|
73
|
+
```
|
|
74
|
+
2. Confirm the dashboard still looks right.
|
|
75
|
+
3. *Then* delete the `@source ".../@polishd/next/dist"` line from your Tailwind
|
|
76
|
+
entry stylesheet (or the `content` glob in `tailwind.config.*`).
|
|
77
|
+
|
|
78
|
+
Doing this drops Tailwind as a requirement for your app and stops your build
|
|
79
|
+
compiling utilities only the dashboard uses. `doctor` reports which of the two
|
|
80
|
+
setups it finds, and never calls a working 0.1.x setup broken.
|
|
81
|
+
|
|
82
|
+
#### If you are on Next 15, check this
|
|
83
|
+
|
|
84
|
+
0.1.0's CLI wrote `proxy.ts` regardless of the installed Next major, and **Next
|
|
85
|
+
15 does not load that file**. If you are on Next 15 your proxy has never run:
|
|
86
|
+
no session cookie, every beacon returning 200, and nothing captured, with no
|
|
87
|
+
error anywhere to show for it. Rename it to `middleware.ts` and rename the
|
|
88
|
+
exported function to `middleware`. `doctor` reports this explicitly.
|
|
89
|
+
|
|
90
|
+
### Breaking
|
|
91
|
+
|
|
92
|
+
- **The dashboard fails shut in production.** With no `POLISHD_DASHBOARD_TOKEN`,
|
|
93
|
+
no `authenticate` callback and no `POLISHD_DASHBOARD_PUBLIC=true`, production
|
|
94
|
+
renders a locked screen and every AI server action denies. Previously the
|
|
95
|
+
default was open, and the warning about it was suppressed in production —
|
|
96
|
+
exactly where it was worth acting on. The dashboard's settings can change the
|
|
97
|
+
configured model and API base URL, so an open one is arbitrary spend on the
|
|
98
|
+
owner's key, not merely an analytics leak.
|
|
99
|
+
- **The dashboard renders in its own shell** (`position: fixed`, own background
|
|
100
|
+
and scroll container) rather than inheriting the host page. Pass
|
|
101
|
+
`shell: false` to `createPolishdPage()` for the old behaviour.
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- **`npx @polishd/next doctor`** — checks the wiring that fails silently: the
|
|
106
|
+
proxy named and exported for the installed Next major, `config.matcher` as an
|
|
107
|
+
inline literal excluding `/api`, `runtime`/`dynamic` declared rather than
|
|
108
|
+
re-exported, the stylesheet import, access posture, Node version.
|
|
109
|
+
`--url <origin>` adds live checks that the cookie is minted and events store.
|
|
110
|
+
- **Built-in token auth.** Setting `POLISHD_DASHBOARD_TOKEN` is the whole
|
|
111
|
+
integration for an app with no auth of its own — no import, no page edit. The
|
|
112
|
+
token is exchanged for an httpOnly, path-scoped cookie by a server action, so
|
|
113
|
+
it never appears in a URL, in history, in logs or in a `Referer` header. The
|
|
114
|
+
cookie holds an HMAC rather than the token, comparison is constant-time,
|
|
115
|
+
expiry is enforced server-side, and the unlock form is rate limited.
|
|
116
|
+
`polishdTokenAuth()` is exported for hosts that want to configure it.
|
|
117
|
+
- **`dist/dashboard.css`** — the dashboard ships its own stylesheet, compiled at
|
|
118
|
+
publish time. Tailwind is no longer required in the host app.
|
|
119
|
+
- **`composePolishd(handler, { matcher })`** — for apps that already have a
|
|
120
|
+
proxy. Declaring the handler's original scope re-applies it as an internal
|
|
121
|
+
guard, so widening `config.matcher` to the union doesn't silently start
|
|
122
|
+
running your handler on every request in the app.
|
|
123
|
+
- **`authenticate` receives the request context** (`cookies`, `headers`), so
|
|
124
|
+
callbacks are ordinary functions of their inputs and testable without a live
|
|
125
|
+
request. Zero-argument callbacks keep working unchanged.
|
|
126
|
+
- **`POLISHD_SESSION_COOKIE`** — read by both the proxy and the ingest route, so
|
|
127
|
+
renaming the cookie cannot go half-applied.
|
|
128
|
+
|
|
129
|
+
### Fixed
|
|
130
|
+
|
|
131
|
+
- **`--force` destroyed hand-written proxies.** Both the exists-guard and the
|
|
132
|
+
merge hint were skipped under the flag, so a user who passed it to regenerate
|
|
133
|
+
an unrelated file lost their real proxy wholesale. Generated files now carry a
|
|
134
|
+
sentinel; files the CLI did not author are never overwritten, with or without
|
|
135
|
+
`--force`, and its own files are backed up to `.bak` before regenerating.
|
|
136
|
+
- **The CLI ignored the installed Next major**, always writing `proxy.ts`. On
|
|
137
|
+
Next 15 — a supported version — that file never runs: no session cookie, every
|
|
138
|
+
beacon still 200, nothing stored, no error anywhere. It now resolves the
|
|
139
|
+
installed version and picks the filename and exported function to match.
|
|
140
|
+
- **The merge hint keyed off the wrong filename**, so the Next 16 collision —
|
|
141
|
+
the one that actually happens — got no guidance and only `--force` as a
|
|
142
|
+
remedy, which was the data-loss bug above.
|
|
143
|
+
- **A missing session cookie is no longer silent.** Ingest records the rejection
|
|
144
|
+
in `polishd_meta`, the dashboard shows a banner naming the expected proxy
|
|
145
|
+
filename per Next major, and the client warns once in the dev console.
|
|
146
|
+
- **`--dry-run` under-reported**, omitting the `.gitignore` change.
|
|
147
|
+
- **`polishd.config.ts` was documented as being loaded automatically.** Nothing
|
|
148
|
+
imports it; it cannot be auto-loaded, because the three consumers run in three
|
|
149
|
+
runtimes and the Edge one has no filesystem. The docs now say what actually
|
|
150
|
+
has to happen.
|
|
151
|
+
|
|
152
|
+
### Also
|
|
153
|
+
|
|
154
|
+
- Node `>= 22.5.0` is checked by the CLI rather than left to a confusing
|
|
155
|
+
"cannot find module `node:sqlite`" at first request.
|
|
156
|
+
- `bin/` is included in the published files. npm auto-includes the bin entry but
|
|
157
|
+
not the modules it imports, so the CLI would otherwise have shipped broken.
|
|
158
|
+
- Docs carry a paste-ready prompt pointing a coding agent at `AGENTS.md`, in the
|
|
159
|
+
README, `docs/SETUP.md` and `polishd --help`.
|
|
160
|
+
|
|
161
|
+
## 0.1.0
|
|
162
|
+
|
|
163
|
+
Initial release.
|