@polishd/next 0.2.2 → 0.2.3-dev.1
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 +2 -1
- package/CHANGELOG.md +238 -0
- package/README.md +63 -0
- package/bin/doctor.mjs +8 -1
- package/dist/ai/digest.d.ts.map +1 -1
- package/dist/ai/digest.js +3 -2
- package/dist/ai/digest.js.map +1 -1
- package/dist/ai/github.d.ts +22 -1
- package/dist/ai/github.d.ts.map +1 -1
- package/dist/ai/github.js +99 -3
- package/dist/ai/github.js.map +1 -1
- package/dist/ai/issue-pages.d.ts +52 -0
- package/dist/ai/issue-pages.d.ts.map +1 -0
- package/dist/ai/issue-pages.js +50 -0
- package/dist/ai/issue-pages.js.map +1 -0
- package/dist/ai/issues.d.ts +13 -1
- package/dist/ai/issues.d.ts.map +1 -1
- package/dist/ai/issues.js +60 -4
- package/dist/ai/issues.js.map +1 -1
- package/dist/ai/summary.d.ts.map +1 -1
- package/dist/ai/summary.js +7 -0
- package/dist/ai/summary.js.map +1 -1
- package/dist/ai/types.d.ts +43 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/client/dom.d.ts +23 -0
- package/dist/client/dom.d.ts.map +1 -1
- package/dist/client/dom.js +45 -2
- package/dist/client/dom.js.map +1 -1
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/init.js +54 -14
- package/dist/client/init.js.map +1 -1
- package/dist/client/monitor.d.ts.map +1 -1
- package/dist/client/monitor.js +5 -1
- package/dist/client/monitor.js.map +1 -1
- package/dist/client/script-provenance.d.ts +21 -0
- package/dist/client/script-provenance.d.ts.map +1 -0
- package/dist/client/script-provenance.js +127 -0
- package/dist/client/script-provenance.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/chrome.d.ts +4 -2
- package/dist/dashboard/chrome.d.ts.map +1 -1
- package/dist/dashboard/chrome.js +15 -5
- package/dist/dashboard/chrome.js.map +1 -1
- package/dist/dashboard/design-review-card.d.ts.map +1 -1
- package/dist/dashboard/design-review-card.js +3 -3
- package/dist/dashboard/design-review-card.js.map +1 -1
- package/dist/dashboard/design.d.ts +5 -1
- package/dist/dashboard/design.d.ts.map +1 -1
- package/dist/dashboard/design.js +3 -2
- package/dist/dashboard/design.js.map +1 -1
- package/dist/dashboard/drawer.d.ts +34 -0
- package/dist/dashboard/drawer.d.ts.map +1 -0
- package/dist/dashboard/drawer.js +44 -0
- package/dist/dashboard/drawer.js.map +1 -0
- package/dist/dashboard/index.d.ts +3 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +48 -37
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/installs.d.ts.map +1 -1
- package/dist/dashboard/installs.js +2 -1
- package/dist/dashboard/installs.js.map +1 -1
- package/dist/dashboard/issues.d.ts +9 -0
- package/dist/dashboard/issues.d.ts.map +1 -0
- package/dist/dashboard/issues.js +81 -0
- package/dist/dashboard/issues.js.map +1 -0
- package/dist/dashboard/journeys.d.ts.map +1 -1
- package/dist/dashboard/journeys.js +4 -2
- package/dist/dashboard/journeys.js.map +1 -1
- package/dist/dashboard/metric-trends.d.ts +6 -0
- package/dist/dashboard/metric-trends.d.ts.map +1 -0
- package/dist/dashboard/metric-trends.js +236 -0
- package/dist/dashboard/metric-trends.js.map +1 -0
- package/dist/dashboard/pages.d.ts.map +1 -1
- package/dist/dashboard/pages.js +8 -46
- package/dist/dashboard/pages.js.map +1 -1
- package/dist/dashboard/settings.d.ts.map +1 -1
- package/dist/dashboard/settings.js +6 -2
- package/dist/dashboard/settings.js.map +1 -1
- package/dist/dashboard/show-more.d.ts.map +1 -1
- package/dist/dashboard/show-more.js +1 -1
- package/dist/dashboard/show-more.js.map +1 -1
- package/dist/dashboard/snapshot-actions.d.ts +16 -0
- package/dist/dashboard/snapshot-actions.d.ts.map +1 -0
- package/dist/dashboard/snapshot-actions.js +79 -0
- package/dist/dashboard/snapshot-actions.js.map +1 -0
- package/dist/dashboard/snapshots.d.ts +13 -0
- package/dist/dashboard/snapshots.d.ts.map +1 -0
- package/dist/dashboard/snapshots.js +88 -0
- package/dist/dashboard/snapshots.js.map +1 -0
- package/dist/dashboard/summary.d.ts.map +1 -1
- package/dist/dashboard/summary.js +11 -6
- package/dist/dashboard/summary.js.map +1 -1
- package/dist/dashboard/telemetry.d.ts +3 -1
- package/dist/dashboard/telemetry.d.ts.map +1 -1
- package/dist/dashboard/telemetry.js +55 -12
- package/dist/dashboard/telemetry.js.map +1 -1
- package/dist/dashboard/ui.d.ts +33 -6
- package/dist/dashboard/ui.d.ts.map +1 -1
- package/dist/dashboard/ui.js +44 -6
- package/dist/dashboard/ui.js.map +1 -1
- package/dist/dashboard.css +193 -2
- package/dist/server/ingest.d.ts +2 -5
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +47 -10
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/queries.d.ts +64 -11
- package/dist/server/queries.d.ts.map +1 -1
- package/dist/server/queries.js +207 -37
- package/dist/server/queries.js.map +1 -1
- package/dist/server/reclassify.d.ts +3 -0
- package/dist/server/reclassify.d.ts.map +1 -0
- package/dist/server/reclassify.js +65 -0
- package/dist/server/reclassify.js.map +1 -0
- package/dist/server/snapshots.d.ts +81 -0
- package/dist/server/snapshots.d.ts.map +1 -0
- package/dist/server/snapshots.js +371 -0
- package/dist/server/snapshots.js.map +1 -0
- package/dist/server/store.d.ts +5 -0
- package/dist/server/store.d.ts.map +1 -1
- package/dist/server/store.js +17 -0
- package/dist/server/store.js.map +1 -1
- package/dist/server/telemetry.d.ts +13 -0
- package/dist/server/telemetry.d.ts.map +1 -1
- package/dist/server/telemetry.js +28 -7
- package/dist/server/telemetry.js.map +1 -1
- package/dist/shared/capture-status.d.ts +99 -0
- package/dist/shared/capture-status.d.ts.map +1 -0
- package/dist/shared/capture-status.js +133 -0
- package/dist/shared/capture-status.js.map +1 -0
- package/dist/shared/error-noise.d.ts +39 -0
- package/dist/shared/error-noise.d.ts.map +1 -0
- package/dist/shared/error-noise.js +101 -0
- package/dist/shared/error-noise.js.map +1 -0
- package/dist/shared/metric-buckets.d.ts +31 -0
- package/dist/shared/metric-buckets.d.ts.map +1 -0
- package/dist/shared/metric-buckets.js +49 -0
- package/dist/shared/metric-buckets.js.map +1 -0
- package/dist/shared/text-signals.d.ts +56 -0
- package/dist/shared/text-signals.d.ts.map +1 -0
- package/dist/shared/text-signals.js +80 -0
- package/dist/shared/text-signals.js.map +1 -0
- package/dist/shared/types.d.ts +1 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +4 -0
- package/dist/shared/types.js.map +1 -1
- package/docs/SETUP.md +25 -0
- package/package.json +9 -2
package/AGENTS.md
CHANGED
|
@@ -325,6 +325,7 @@ survive there.
|
|
|
325
325
|
| Variable | Required | Purpose |
|
|
326
326
|
|---|---|---|
|
|
327
327
|
| `POLISHD_DATABASE_URL` | for production capture | Pooled Postgres connection string |
|
|
328
|
+
| `BLOB_READ_WRITE_TOKEN` | for production site snapshots | Injected automatically when a Vercel Blob store is connected; snapshot images upload there instead of the (read-only) filesystem |
|
|
328
329
|
| `POLISHD_DASHBOARD_TOKEN` | for production, unless auth is wired | Protects `/polishd`. `openssl rand -hex 32` |
|
|
329
330
|
| `POLISHD_DASHBOARD_PUBLIC` | no | `true` to serve the dashboard unguarded on purpose |
|
|
330
331
|
| `POLISHD_SESSION_COOKIE` | no | Rename the session cookie; read by both proxy and ingest |
|
|
@@ -348,7 +349,7 @@ Run `npx @polishd/next doctor` first — it names most of these directly.
|
|
|
348
349
|
| Every `/api/*` route 404s | Proxy matcher matches `/api` | Restore the `api` exclusion in the matcher |
|
|
349
350
|
| Dashboard renders unstyled | `@polishd/next/dashboard.css` not imported in the page | Phase 4 |
|
|
350
351
|
| 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
|
+
| 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. Red means *nothing is being stored* — with events landing you get a neutral note instead | Phase 3c and `doctor --url <origin>`; plain `doctor` only reads the source tree, so it can pass while a deployed build still mints no cookie |
|
|
352
353
|
| 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
354
|
| Proxy and ingest disagree on the cookie name | `sessionCookie` threaded into one layer but not another | Set `POLISHD_SESSION_COOKIE`; both read it |
|
|
354
355
|
| `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite` | Node < 22.5 | Upgrade Node, or configure Postgres |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,243 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Every Overview tile opens its own graph
|
|
6
|
+
|
|
7
|
+
The six numbers at the top of the Analytics tab were all-time totals, which
|
|
8
|
+
answer "how much" and never "when" — 37 rage clicks is a different problem if
|
|
9
|
+
they all landed on Tuesday. Each tile is now a button: clicking one opens a
|
|
10
|
+
bottom drawer with that metric bucketed over time.
|
|
11
|
+
|
|
12
|
+
- **Hourly or daily**, switchable in the drawer. Both series are computed on
|
|
13
|
+
the server and travel together, so flipping granularity — or switching
|
|
14
|
+
metric from inside the drawer — costs no round trip. Daily is the default
|
|
15
|
+
once there's more than a day of history; before that a daily chart is one
|
|
16
|
+
bar, and hourly is the only shape worth drawing.
|
|
17
|
+
- Every tile grows a **sparkline**, which is both the hint that there's a
|
|
18
|
+
chart behind the number and the shape of it at a glance.
|
|
19
|
+
- The window ends at the **newest bucket that has data**, not at now, so an
|
|
20
|
+
install last browsed three days ago still gets a chart instead of an empty
|
|
21
|
+
one. It covers the last 48 hours or 30 days of activity; where that differs
|
|
22
|
+
from the tile's all-time total, the drawer says so rather than leaving the
|
|
23
|
+
reader to notice.
|
|
24
|
+
- Quiet buckets are drawn as **measured zeros**, not skipped — an empty night
|
|
25
|
+
is a fact about the site, and dropping those points would silently compress
|
|
26
|
+
the axis.
|
|
27
|
+
- **Sessions are never summed.** A visitor active across three hours is
|
|
28
|
+
counted in all three buckets, so the drawer shows peak/average/latest for
|
|
29
|
+
that metric and explains why there's no total.
|
|
30
|
+
- Reading a bucket works by pointer, by tap, and by keyboard (←/→, Home/End
|
|
31
|
+
on the focused chart); the readout sits above the plot so it never covers
|
|
32
|
+
the tallest bar, which is the one you were reaching for.
|
|
33
|
+
- Buckets start at the UTC hour or day, matching the existing per-page chart.
|
|
34
|
+
A visitor whose clock is set to next year can't anchor the window there and
|
|
35
|
+
blank the chart — the windowing rule lives in
|
|
36
|
+
`src/shared/metric-buckets.ts`, pure and tested (`npm test`).
|
|
37
|
+
|
|
38
|
+
The bottom drawer itself, the info tooltip and the axis-tick maths are now
|
|
39
|
+
shared (`src/dashboard/drawer.tsx`, `src/dashboard/ui.tsx`) instead of copied
|
|
40
|
+
between the Top-pages table and this, which is what stopped the two charts
|
|
41
|
+
drifting apart.
|
|
42
|
+
### An Issues tab: every bug polishd filed, in one list
|
|
43
|
+
|
|
44
|
+
Filed issues used to exist only inside the loss that produced them — one
|
|
45
|
+
summary, one card, gone at the next regenerate. The dashboard grows an
|
|
46
|
+
**Issues** tab that lists them all, newest first, and appears only once a
|
|
47
|
+
GitHub repository is connected (nothing to list, and no way to list it,
|
|
48
|
+
otherwise).
|
|
49
|
+
|
|
50
|
+
- Each row joins two sources. The analytics **evidence**, the claim as the
|
|
51
|
+
summary stated it, and the verdict the source verification reached are
|
|
52
|
+
polishd's own record; the title, state, labels, assignees, comment count and
|
|
53
|
+
body are read live from GitHub on every render, so a bug closed an hour ago
|
|
54
|
+
reads as closed here.
|
|
55
|
+
- The issue log the dedupe already kept is what makes the list *ours* rather
|
|
56
|
+
than the repo's — a repo full of other people's issues stays that way. New
|
|
57
|
+
filings additionally record the claim, the verdict and the filing time;
|
|
58
|
+
entries written by earlier versions have only a number and a URL and still
|
|
59
|
+
render, dated from GitHub's own `created_at`.
|
|
60
|
+
- An issue that can no longer be read — deleted, transferred, token access
|
|
61
|
+
revoked — keeps its row and says so, rather than quietly disappearing from
|
|
62
|
+
a count you were trusting.
|
|
63
|
+
- Loading the tab is **one GitHub API call**, not one per issue. The repo's
|
|
64
|
+
issue list returns whole issues, body and all, a hundred at a time, so a
|
|
65
|
+
single page answers for every issue polishd has ever filed. Issue numbers
|
|
66
|
+
only climb and the list comes back newest-first, so a page that has dropped
|
|
67
|
+
below the oldest number we want proves no later page can hold it, and paging
|
|
68
|
+
stops; anything buried past a three-page budget is read individually. The
|
|
69
|
+
rule lives in `src/ai/issue-pages.ts` — pure, no network — with tests
|
|
70
|
+
(`npm test`).
|
|
71
|
+
|
|
72
|
+
### Site snapshots now work in production
|
|
73
|
+
|
|
74
|
+
Capturing a snapshot on a serverless host used to fail on two fronts at once:
|
|
75
|
+
no browser to launch and no writable filesystem to keep the images on. Both
|
|
76
|
+
are now handled, and neither needs configuration beyond connecting a Vercel
|
|
77
|
+
Blob store to the project:
|
|
78
|
+
|
|
79
|
+
- When `BLOB_READ_WRITE_TOKEN` is present (Vercel injects it once a Blob store
|
|
80
|
+
is connected), shots are staged in the system temp directory and uploaded to
|
|
81
|
+
Vercel Blob instead of `.polishd/snapshots/`. Pruning deletes the blobs;
|
|
82
|
+
a failed upload cleans up after itself so a half-stored snapshot never
|
|
83
|
+
survives. Blob pathnames get a random suffix and the dashboard still serves
|
|
84
|
+
every image through the auth-gated server action.
|
|
85
|
+
- When no local Chrome, Edge, or Playwright Chromium can launch on Linux,
|
|
86
|
+
capture falls back to `@sparticuz/chromium` — a Chromium built to run inside
|
|
87
|
+
Lambda-shaped sandboxes. It and `@vercel/blob` ship as optional
|
|
88
|
+
dependencies, so a standard `npm install` brings them along and
|
|
89
|
+
`--omit=optional` keeps them out.
|
|
90
|
+
- An install run with `--omit=optional` loses the feature but not the plot:
|
|
91
|
+
when the environment shows a missing optional dependency will be needed
|
|
92
|
+
(a Blob token with no `@vercel/blob`, a Vercel host with no
|
|
93
|
+
`@sparticuz/chromium`), the gallery says so up front and disables the
|
|
94
|
+
capture button, instead of failing a minute into a doomed run. Failures
|
|
95
|
+
that can only surface at capture time name the missing piece precisely.
|
|
96
|
+
- Local development is unchanged: your own Chrome first, images on disk,
|
|
97
|
+
same index, same gallery.
|
|
98
|
+
|
|
99
|
+
### Reading is not friction: text clicks and trivial scrolls stop counting
|
|
100
|
+
|
|
101
|
+
Three signals could report engagement or frustration where there was only
|
|
102
|
+
reading, inflating exactly the numbers the AI summary leans on hardest.
|
|
103
|
+
|
|
104
|
+
- **Dead clicks skip text.** A click inside a paragraph, heading, list item,
|
|
105
|
+
or other prose block — or on standalone text long enough to read as content
|
|
106
|
+
(40+ chars) — is cursor placement or the start of a selection, not a user
|
|
107
|
+
expecting something to happen. It no longer records a `dead_click`. Clicks
|
|
108
|
+
on non-interactive *layout* elements still do, and so do short standalone
|
|
109
|
+
labels (`<div>Submit</div>`, `<span>Click here</span>`) — that shape is
|
|
110
|
+
exactly what a mis-wired fake control looks like.
|
|
111
|
+
- **Rage clicks skip select-a-word/paragraph.** Double- and triple-clicking
|
|
112
|
+
prose selects text; a rapid burst on a non-interactive text target no
|
|
113
|
+
longer records a `rage_click`. Hammering a real button — or a fake one with
|
|
114
|
+
a short label — still does.
|
|
115
|
+
- **History gets the same fix.** A one-time sweep (versioned via
|
|
116
|
+
`polishd_meta`, run on the first dashboard load after upgrading) re-runs
|
|
117
|
+
the new classification over already-stored `dead_click`/`rage_click` rows,
|
|
118
|
+
using the stored selector path and label. Matching rows are retyped to
|
|
119
|
+
`text_click` — kept in the table, counted by no aggregate — never deleted.
|
|
120
|
+
Rage rows whose selector path contains an interactive element are left
|
|
121
|
+
untouched. Ingest applies the same retype to arriving events, so a cached
|
|
122
|
+
client bundle — or a telemetry emitter still on an older package — can't
|
|
123
|
+
keep refilling the table with the old semantics after the sweep has run. Old `component_view` rows always carried a scroll depth, so at
|
|
124
|
+
read time depth from components short enough to fit a typical viewport
|
|
125
|
+
(≤700px) is ignored; page-level `scroll_depth` history is unaffected (a
|
|
126
|
+
recorded depth needed a real scroll event, and genuine deep scrolls can't
|
|
127
|
+
be told apart after the fact).
|
|
128
|
+
- **100% scroll requires somewhere to scroll.** A page (or dashboard region)
|
|
129
|
+
whose scrollable distance is under 10% of the viewport reaches "100%" by
|
|
130
|
+
existing, so it now records no `scroll_depth` at all; likewise
|
|
131
|
+
`<PolishdMonitor content>` only reports scroll-through for components
|
|
132
|
+
taller than one screen. Aggregates already treat absent as "no data" (shown
|
|
133
|
+
as —), never 0.
|
|
134
|
+
- Both capture layers (host site and the dashboard's own telemetry) share the
|
|
135
|
+
new classification via `src/client/dom.ts`, so they can't drift.
|
|
136
|
+
- The AI digest now omits scroll % where it wasn't measurable and the system
|
|
137
|
+
prompt tells the model these semantics — no more "users read to the bottom!"
|
|
138
|
+
wins on pages that fit in one viewport, and reported dead/rage counts can be
|
|
139
|
+
taken as genuine friction.
|
|
140
|
+
|
|
141
|
+
### The "your proxy isn't running" banner stops crying wolf
|
|
142
|
+
|
|
143
|
+
A correctly installed, verifiably healthy site could show the red banner
|
|
144
|
+
indefinitely. The 0.2.2 rule compared an *all-time* drop total against an
|
|
145
|
+
all-time event total behind a one-hour recency gate, so the two halves covered
|
|
146
|
+
different time ranges: one fresh cookie-less batch — a crawler POSTing at the
|
|
147
|
+
public endpoint — re-armed every drop the install had ever recorded, including
|
|
148
|
+
the ones from before the proxy was first deployed. At the 1-in-400 ratio, 7
|
|
149
|
+
drops only needed fewer than 2,800 lifetime events to trip, which is most
|
|
150
|
+
personal sites for months.
|
|
151
|
+
|
|
152
|
+
- Drops are now bucketed by the hour, so the alert can compare drops and
|
|
153
|
+
stored events over **the same 24-hour window**. Records written before this
|
|
154
|
+
release carry no buckets, which means their history counts toward no window
|
|
155
|
+
and cannot re-arm anything.
|
|
156
|
+
- The strongest signal is now the cheapest one: an event stored *after* the
|
|
157
|
+
most recent drop proves the proxy is minting cookies right now, because
|
|
158
|
+
every event is attributed to that cookie and a broken proxy drops every
|
|
159
|
+
batch. No threshold to tune.
|
|
160
|
+
- Severity is split. Red — "your proxy isn't running" — is reserved for
|
|
161
|
+
nothing stored at all, or nothing stored in the window while drops keep
|
|
162
|
+
arriving. Otherwise the dashboard shows a neutral note: *N batches arrived
|
|
163
|
+
without a session cookie (bots, or visitors with cookies blocked) — X% of
|
|
164
|
+
traffic*, which is useful and true.
|
|
165
|
+
- "so nothing is being stored" only renders when nothing has been stored. It
|
|
166
|
+
used to sit directly above stat tiles counting stored sessions and events.
|
|
167
|
+
- The banner now points at `doctor --url <origin>` and says why: plain
|
|
168
|
+
`doctor` reads the source tree, so it can pass every check while a deployed
|
|
169
|
+
build still doesn't mint a cookie. Sending someone to a command that
|
|
170
|
+
answers "12 checks passed" was most of what made this alert expensive.
|
|
171
|
+
- The 60-second write collapse in `recordNoSession()` survives cold starts.
|
|
172
|
+
Its counters were module-level, so serverless reset them on every
|
|
173
|
+
invocation and the sparse traffic the throttle exists to absorb persisted
|
|
174
|
+
roughly 1:1; the durable `lastAt` now gates the write too.
|
|
175
|
+
- The rule lives in `src/shared/capture-status.ts` — pure, no database, no
|
|
176
|
+
React — with tests (`npm test`).
|
|
177
|
+
|
|
178
|
+
### Site snapshots on the Design tab
|
|
179
|
+
|
|
180
|
+
The Design tab grows a **Site snapshots** card: one click screenshots every
|
|
181
|
+
scanned route of the site — phone and desktop, light and dark — and renders
|
|
182
|
+
the shots as a filterable gallery with a full-page lightbox. Capture runs
|
|
183
|
+
through the new `allpages` dependency (Playwright underneath), launching the
|
|
184
|
+
developer's own Chrome/Edge before falling back to Playwright's Chromium, and
|
|
185
|
+
never auto-downloads a browser. The origin is learned from the request that
|
|
186
|
+
rendered the dashboard (`POLISHD_SITE_ORIGIN` overrides); shots land under
|
|
187
|
+
`.polishd/snapshots/` (`POLISHD_SNAPSHOT_DIR` overrides, last 6 runs kept)
|
|
188
|
+
with only an index in the store, and every image travels to the browser
|
|
189
|
+
through an auth-guarded server action rather than a new public route. Each
|
|
190
|
+
snapshot is stamped with the design-metrics fingerprint at capture time — the
|
|
191
|
+
hook for before/after visual diffing as design suggestions get applied. This
|
|
192
|
+
is the package's first runtime dependency beyond the optional `pg`.
|
|
193
|
+
|
|
194
|
+
### Errors are attributed to the code that threw them
|
|
195
|
+
|
|
196
|
+
`window` hears every throw in the tab, so an extension's failures — "Failed to
|
|
197
|
+
connect to MetaMask" being the canonical one — arrived as `js_error` events
|
|
198
|
+
indistinguishable from real bugs, often as the loudest error on the dashboard.
|
|
199
|
+
|
|
200
|
+
Rather than match error text, the client now works out *whose script threw*.
|
|
201
|
+
Resource Timing records every script the document actually fetched; extension
|
|
202
|
+
code is absent from it, because the browser injects content scripts and the
|
|
203
|
+
page never requested them. Comparing an error's stack against that live set
|
|
204
|
+
identifies injected code without knowing what it's called, so a new extension
|
|
205
|
+
needs no new pattern. Four verdicts come out: `site` (every frame is code you
|
|
206
|
+
shipped), `extension` (a frame names an extension URL scheme, including blobs
|
|
207
|
+
an extension minted at runtime), `foreign` (a frame names a script this
|
|
208
|
+
document never loaded), and `unknown` (nothing attributable — an opaque
|
|
209
|
+
cross-origin `Script error.`, an empty stack).
|
|
210
|
+
|
|
211
|
+
Only `extension` is dropped. `foreign` and `unknown` are kept, tagged with
|
|
212
|
+
`meta.origin`, and shown dimmed in Recent errors as "not your code" /
|
|
213
|
+
"unattributable" — a filter that silently eats what it can't identify is how a
|
|
214
|
+
real bug goes missing. A short list of message patterns remains as a last
|
|
215
|
+
resort for extension plumbing that throws with no attributable frame at all.
|
|
216
|
+
|
|
217
|
+
The drop also runs at ingest (a cached bundle can predate the filter, and the
|
|
218
|
+
endpoint is a public POST) and on the way out of `getRecentErrors`, so rows
|
|
219
|
+
written before this stop showing up too. New `ignoreErrors` config option —
|
|
220
|
+
strings matched case-insensitively, or regexes — drops anything else you've
|
|
221
|
+
decided not to chase.
|
|
222
|
+
|
|
223
|
+
### Telemetry answers the product questions clicks can't
|
|
224
|
+
|
|
225
|
+
The dashboard telemetry emitter now sends one `install_state` event per
|
|
226
|
+
session — whether a model is connected and which provider/model, resolved
|
|
227
|
+
server-side, never the key — and the consent prompt discloses it. On the
|
|
228
|
+
collecting side, the Installs tab grew a "With a model" stat and a per-domain
|
|
229
|
+
Model column built from each install's latest report, so connecting,
|
|
230
|
+
switching, or disconnecting a model reads correctly.
|
|
231
|
+
|
|
232
|
+
The dashboard's own controls also carry `data-component` names now
|
|
233
|
+
(`ai-summary-refresh`, `ai-summary-generate`, `ai-connect-model`,
|
|
234
|
+
`journey-open`, `loss-file-bug`, `loss-ignore`, `design-review-refresh`,
|
|
235
|
+
`settings-scan-codebase`, `show-more`, coarse `settings`/`ai-summary`
|
|
236
|
+
containers, …), so with `POLISHD_ANALYTICS_SOURCE=telemetry` the collector's
|
|
237
|
+
Most-used-features table groups dashboard clicks by feature instead of raw
|
|
238
|
+
selectors — the same component-level story polishd tells its hosts, told
|
|
239
|
+
about itself. The attributes are inert for host apps.
|
|
240
|
+
|
|
3
241
|
## 0.2.2
|
|
4
242
|
|
|
5
243
|
### Cross-install telemetry ingest
|
package/README.md
CHANGED
|
@@ -390,6 +390,27 @@ again.
|
|
|
390
390
|
Ignoring a loss marks the summary stale, exactly like new analytics does, so
|
|
391
391
|
the next refresh actually re-asks the model with the dismissal in hand.
|
|
392
392
|
|
|
393
|
+
### The Issues tab
|
|
394
|
+
|
|
395
|
+
Filing a bug is easy to lose track of — it happens inside one loss, on one
|
|
396
|
+
summary, and then the summary regenerates. The **Issues** tab is the standing
|
|
397
|
+
list: every issue polishd has filed in the connected repo, newest first,
|
|
398
|
+
whether you clicked **File bug** yourself or auto-filing did it for you. The
|
|
399
|
+
tab only appears once a GitHub repository is connected.
|
|
400
|
+
|
|
401
|
+
Each row joins what polishd knows to what GitHub knows. The **evidence** — the
|
|
402
|
+
selector, component or path the analytics cited — and whether the source
|
|
403
|
+
verification confirmed the report are polishd's own record, kept at filing
|
|
404
|
+
time. The title, state, labels, assignees and comment count are read live from
|
|
405
|
+
GitHub on every render, so a bug someone closed an hour ago reads as closed
|
|
406
|
+
here, and the issue body is one click away without leaving the dashboard. An
|
|
407
|
+
issue that can no longer be read — deleted, transferred, or beyond what the
|
|
408
|
+
token now covers — keeps its row and says exactly that.
|
|
409
|
+
|
|
410
|
+
Opening the tab costs a single GitHub API call in the ordinary case, not one
|
|
411
|
+
per issue: the repo's issue list hands back a hundred whole issues at a time,
|
|
412
|
+
which is more than polishd is ever likely to have filed.
|
|
413
|
+
|
|
393
414
|
## Design review
|
|
394
415
|
|
|
395
416
|
The dashboard's **Design** tab (in the sidebar) is your site's brand guideline
|
|
@@ -424,6 +445,33 @@ Nothing extra to install or wire: the scan ships with the existing client,
|
|
|
424
445
|
flows through the existing ingest route, and the dashboard itself is never
|
|
425
446
|
scanned.
|
|
426
447
|
|
|
448
|
+
### Site snapshots
|
|
449
|
+
|
|
450
|
+
The Design tab can also **screenshot the site itself** — every scanned route,
|
|
451
|
+
phone and desktop, light and dark — and render the shots as a gallery, so the
|
|
452
|
+
measured tokens sit next to what the pages actually look like. Capture uses
|
|
453
|
+
[allpages](https://www.npmjs.com/package/allpages) (Playwright underneath): it
|
|
454
|
+
launches your installed Chrome or Edge, falling back to Playwright's Chromium
|
|
455
|
+
(`npx playwright install chromium` once, if you have neither). The origin to
|
|
456
|
+
shoot is taken from the request that rendered the dashboard, or set
|
|
457
|
+
`POLISHD_SITE_ORIGIN` to override it. Images live on disk under
|
|
458
|
+
`.polishd/snapshots/` (override with `POLISHD_SNAPSHOT_DIR`; the last 6
|
|
459
|
+
snapshots are kept), and each snapshot is stamped with the design-metrics
|
|
460
|
+
fingerprint at capture time — so a snapshot taken before a round of design
|
|
461
|
+
fixes can be held against one taken after.
|
|
462
|
+
|
|
463
|
+
**In production on Vercel**, capture works too — connect a
|
|
464
|
+
[Blob store](https://vercel.com/docs/vercel-blob) to the project and that's
|
|
465
|
+
the whole setup: the `BLOB_READ_WRITE_TOKEN` it injects switches image storage
|
|
466
|
+
from disk to Blob, and the browser comes from `@sparticuz/chromium` (a
|
|
467
|
+
Chromium built for serverless sandboxes, installed automatically as an
|
|
468
|
+
optional dependency). The snapshot index rides in the same database the rest
|
|
469
|
+
of Polishd already requires in production, so no other wiring changes. If a
|
|
470
|
+
capture of many routes brushes against your function's time limit, raise it
|
|
471
|
+
with `export const maxDuration = 300` in the dashboard's `page.tsx`. On hosts
|
|
472
|
+
with neither a writable filesystem nor a Blob token, the button explains what's
|
|
473
|
+
missing rather than failing silently.
|
|
474
|
+
|
|
427
475
|
## Component-level tracking
|
|
428
476
|
|
|
429
477
|
Wrap any element to track it explicitly. Interactive controls get hover + click
|
|
@@ -459,6 +507,9 @@ once; nothing is sent unless you say yes. What's shared when you do:
|
|
|
459
507
|
runs — and an anonymous per-browser-session id. No cookies, no
|
|
460
508
|
fingerprinting, no URLs, page content, or user data from your site — your
|
|
461
509
|
analytics never leave your database.
|
|
510
|
+
- Once per session, whether a **model is connected** and which provider/model
|
|
511
|
+
it is — never the key, never any setting beyond those two names. This is how
|
|
512
|
+
the project learns which AI setups to test against.
|
|
462
513
|
|
|
463
514
|
Say no and it never asks again. Change your mind, or disable it fleet-wide,
|
|
464
515
|
with `POLISHD_TELEMETRY=off`; `navigator.doNotTrack` is honored regardless of
|
|
@@ -488,9 +539,21 @@ import { definePolishdConfig } from "@polishd/next";
|
|
|
488
539
|
export default definePolishdConfig({
|
|
489
540
|
sampleRate: 1,
|
|
490
541
|
rageClick: { count: 3, windowMs: 500 },
|
|
542
|
+
ignoreErrors: [],
|
|
491
543
|
});
|
|
492
544
|
```
|
|
493
545
|
|
|
546
|
+
Browser-extension errors are never recorded. `window` hears every throw in the
|
|
547
|
+
tab, so an extension's failures — "Failed to connect to MetaMask" and its kin —
|
|
548
|
+
would otherwise show up as your site's noisiest bugs. Rather than match error
|
|
549
|
+
text, polishd asks whose script threw: the browser records every script your
|
|
550
|
+
document actually fetched, extension code is never among them, so an error
|
|
551
|
+
whose stack points outside that set is identifiable as injected without knowing
|
|
552
|
+
what it's called. Confident extension errors are dropped at capture and again
|
|
553
|
+
at ingest; errors that merely *can't* be attributed to your code are kept and
|
|
554
|
+
labelled, not hidden. `ignoreErrors` (strings, matched case-insensitively, or
|
|
555
|
+
regexes) drops anything else you'd rather not see.
|
|
556
|
+
|
|
494
557
|
The dashboard never measures itself: nothing on `/polishd` (or below it) is
|
|
495
558
|
captured, so reading your analytics can't show up as your site's most-used
|
|
496
559
|
feature. **Moved the dashboard elsewhere?** Set `dashboardRoute` in your
|
package/bin/doctor.mjs
CHANGED
|
@@ -349,8 +349,15 @@ function report(results, origin) {
|
|
|
349
349
|
c.dim(`, ${results.filter((r) => r.status === PASS).length} checks passed`),
|
|
350
350
|
);
|
|
351
351
|
if (!origin) {
|
|
352
|
+
// "no problems" means the source tree is right, which is not the same as
|
|
353
|
+
// the deployed site minting cookies — a build that ships without the proxy
|
|
354
|
+
// passes every check here. Say so, so a green run isn't read as proof.
|
|
352
355
|
console.log(
|
|
353
|
-
c.dim(
|
|
356
|
+
c.dim(
|
|
357
|
+
"\nThese are static checks on your source tree. Run with" +
|
|
358
|
+
" --url http://localhost:3000 (or your deployed origin) to verify the" +
|
|
359
|
+
" cookie and ingest on the running site.",
|
|
360
|
+
),
|
|
354
361
|
);
|
|
355
362
|
}
|
|
356
363
|
return fails > 0 ? 1 : 0;
|
package/dist/ai/digest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/ai/digest.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAS9D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/ai/digest.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAS9D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA+FhF;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
package/dist/ai/digest.js
CHANGED
|
@@ -55,10 +55,11 @@ export function buildDigest(data, context) {
|
|
|
55
55
|
.sort((a, b) => b.score - a.score)
|
|
56
56
|
.slice(0, 6);
|
|
57
57
|
if (worst.length) {
|
|
58
|
-
lines.push("PAGES WITH FRICTION (worst first):");
|
|
58
|
+
lines.push("PAGES WITH FRICTION (worst first; scroll depth is only measured on pages tall enough to scroll — absent means no signal, not disengagement):");
|
|
59
59
|
for (const p of worst) {
|
|
60
|
+
const scroll = p.avgScrollDepth === null ? "" : `, scroll ${r(p.avgScrollDepth)}%`;
|
|
60
61
|
lines.push(` - ${p.path}: ${p.sessions} sessions, ${p.rageClicks} rage, ` +
|
|
61
|
-
`${p.deadClicks} dead, ${p.jsErrors} err
|
|
62
|
+
`${p.deadClicks} dead, ${p.jsErrors} err${scroll}`);
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
const brokenEls = elements.filter((e) => e.rageClicks > 0 || e.deadClicks > 0).slice(0, 8);
|
package/dist/ai/digest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/ai/digest.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/ai/digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,6DAA6D;AAC7D,MAAM,CAAC,GAAG,CAAC,CAAgB,EAAU,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF,4EAA4E;AAC5E,MAAM,IAAI,GAAG,CAAC,CAA4B,EAAE,GAAG,GAAG,EAAE,EAAU,EAAE,CAC9D,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAA0B,EAAE,OAAgB;IACtE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAChF,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,IAAI,CACR,WAAW,QAAQ,CAAC,QAAQ,cAAc,QAAQ,CAAC,SAAS,eAAe;QACzE,GAAG,QAAQ,CAAC,UAAU,iBAAiB,QAAQ,CAAC,UAAU,gBAAgB;QAC1E,GAAG,QAAQ,CAAC,QAAQ,eAAe,QAAQ,CAAC,WAAW,iBAAiB,CAC3E,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACR,WAAW;YACT,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACxD,GAAG,CACN,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,WAAW,CAAC;YAC/E,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,YAAY,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,GAAG,CAAC;QACJ,KAAK,EACH,CAAC,CAAC,UAAU,GAAG,CAAC;YAChB,CAAC,CAAC,UAAU,GAAG,CAAC;YAChB,CAAC,CAAC,QAAQ,GAAG,GAAG;YAChB,CAAC,CAAC,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,CAAC,cAAc,GAAG,EAAE;gBACjD,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE;gBAC9B,CAAC,CAAC,CAAC,CAAC;KACT,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;SAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACf,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACR,8IAA8I,CAC/I,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,CAAC,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC;YACnF,KAAK,CAAC,IAAI,CACR,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,cAAc,CAAC,CAAC,UAAU,SAAS;gBAC7D,GAAG,CAAC,CAAC,UAAU,UAAU,CAAC,CAAC,QAAQ,OAAO,MAAM,EAAE,CACrD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3F,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC5C,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,UAAU,UAAU,CAAC,CAAC,UAAU,UAAU,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACtC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,cAAc,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YAC3F,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,cAAc,YAAY,CAAC,CAAC;YAC3E,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,OAAO,CAAC,CAAC;YACzD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,0EAA0E;QAC1E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,IAAI,GAAkB,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;gBAAE,SAAS;YAClC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;gBAAE,MAAM;QAC9B,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC"}
|
package/dist/ai/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PolishdGithubStatus, VerifyGithubResult } from "./types";
|
|
1
|
+
import type { PolishdGithubIssue, PolishdGithubStatus, VerifyGithubResult } from "./types";
|
|
2
2
|
/** True when both a repo and a token are configured. */
|
|
3
3
|
export declare function isGithubConnected(): Promise<boolean>;
|
|
4
4
|
/**
|
|
@@ -39,6 +39,27 @@ export declare function createGithubPullRequest(input: {
|
|
|
39
39
|
url: string;
|
|
40
40
|
number: number;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Read one issue by number. Returns null when it can't be read — deleted,
|
|
44
|
+
* transferred, or the token lost access — so a single missing issue never
|
|
45
|
+
* takes down the list that contains it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readGithubIssue(number: number): Promise<PolishdGithubIssue | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Read a known set of issues by number, in as few API calls as possible.
|
|
50
|
+
*
|
|
51
|
+
* The naive way — one call per number — is what the repo's own issue list
|
|
52
|
+
* saves us from: it returns whole issue objects, body and all, a hundred at a
|
|
53
|
+
* time, so a single call usually answers for every issue polishd has ever
|
|
54
|
+
* filed. When to stop asking for pages is its own rule, in issue-pages.ts.
|
|
55
|
+
*
|
|
56
|
+
* Anything the pages didn't reach is read individually afterwards, which is
|
|
57
|
+
* also the whole strategy when there's only one number to find.
|
|
58
|
+
*
|
|
59
|
+
* Numbers that can't be read at all (deleted, transferred, token access
|
|
60
|
+
* narrowed) are simply absent from the returned map.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readGithubIssues(numbers: number[]): Promise<Map<number, PolishdGithubIssue>>;
|
|
42
63
|
/** File an issue in the connected repo. Throws on failure. */
|
|
43
64
|
export declare function createGithubIssue(input: {
|
|
44
65
|
title: string;
|
package/dist/ai/github.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAajB,wDAAwD;AACxD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AA0CD;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA2B1E;AAED,gFAAgF;AAChF,wBAAsB,eAAe,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAG3E;AAID;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAclF;AAID;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAa3C;AA2CD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUxF;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CA4C1C;AAED,8DAA8D;AAC9D,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAU3C"}
|
package/dist/ai/github.js
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* onboarding. Capabilities, all scoped to that one repo: read and search source
|
|
6
6
|
* (Contents + code-search APIs — lets the AI layer ground its findings in real
|
|
7
7
|
* code even where the source tree isn't on disk, e.g. Vercel), create branches,
|
|
8
|
-
* open pull requests, and file issues
|
|
9
|
-
*
|
|
8
|
+
* open pull requests, and file issues — then read those issues back, which is
|
|
9
|
+
* what the dashboard's Issues tab lists. The "loss → verified bug"
|
|
10
|
+
* orchestration that uses this client lives in issues.ts.
|
|
10
11
|
*
|
|
11
12
|
* The token lives in the same server-side settings blob as the model API key
|
|
12
13
|
* and never reaches the browser; the client only sees a "connected" flag. A
|
|
13
14
|
* fine-grained PAT with Contents (read), Issues (write), and Pull requests
|
|
14
15
|
* (write) on the one repo is all it needs.
|
|
15
16
|
*/
|
|
17
|
+
import { ISSUE_PAGE_SIZE, keepPaging, worthListing, } from "./issue-pages";
|
|
16
18
|
import { resolveSettings } from "./settings";
|
|
17
19
|
const API = "https://api.github.com";
|
|
18
20
|
/** The connected repo + token, or null when either half is missing. */
|
|
@@ -163,7 +165,101 @@ export async function createGithubPullRequest(input) {
|
|
|
163
165
|
const pr = await gh(conn.token, "POST", `/repos/${conn.repo}/pulls`, { title: input.title, body: input.body ?? "", head: input.head, base });
|
|
164
166
|
return { url: pr.html_url, number: pr.number };
|
|
165
167
|
}
|
|
166
|
-
|
|
168
|
+
function toIssue(raw) {
|
|
169
|
+
return {
|
|
170
|
+
number: raw.number,
|
|
171
|
+
title: raw.title,
|
|
172
|
+
body: raw.body ?? "",
|
|
173
|
+
url: raw.html_url,
|
|
174
|
+
state: raw.state === "closed" ? "closed" : "open",
|
|
175
|
+
stateReason: raw.state_reason ?? undefined,
|
|
176
|
+
labels: (raw.labels ?? [])
|
|
177
|
+
.map((l) => (typeof l === "string" ? l : l.name))
|
|
178
|
+
.filter((n) => typeof n === "string" && n.length > 0),
|
|
179
|
+
author: raw.user?.login ?? undefined,
|
|
180
|
+
assignees: (raw.assignees ?? [])
|
|
181
|
+
.map((a) => a.login)
|
|
182
|
+
.filter((n) => typeof n === "string" && n.length > 0),
|
|
183
|
+
comments: raw.comments ?? 0,
|
|
184
|
+
createdAt: Date.parse(raw.created_at),
|
|
185
|
+
updatedAt: Date.parse(raw.updated_at),
|
|
186
|
+
closedAt: raw.closed_at ? Date.parse(raw.closed_at) : undefined,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read one issue by number. Returns null when it can't be read — deleted,
|
|
191
|
+
* transferred, or the token lost access — so a single missing issue never
|
|
192
|
+
* takes down the list that contains it.
|
|
193
|
+
*/
|
|
194
|
+
export async function readGithubIssue(number) {
|
|
195
|
+
const conn = await connection();
|
|
196
|
+
if (!conn)
|
|
197
|
+
return null;
|
|
198
|
+
try {
|
|
199
|
+
return toIssue(await gh(conn.token, "GET", `/repos/${conn.repo}/issues/${number}`));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** How many individual issue reads run at once, when it comes to that. */
|
|
206
|
+
const ISSUE_FETCH_CONCURRENCY = 6;
|
|
207
|
+
/**
|
|
208
|
+
* Read a known set of issues by number, in as few API calls as possible.
|
|
209
|
+
*
|
|
210
|
+
* The naive way — one call per number — is what the repo's own issue list
|
|
211
|
+
* saves us from: it returns whole issue objects, body and all, a hundred at a
|
|
212
|
+
* time, so a single call usually answers for every issue polishd has ever
|
|
213
|
+
* filed. When to stop asking for pages is its own rule, in issue-pages.ts.
|
|
214
|
+
*
|
|
215
|
+
* Anything the pages didn't reach is read individually afterwards, which is
|
|
216
|
+
* also the whole strategy when there's only one number to find.
|
|
217
|
+
*
|
|
218
|
+
* Numbers that can't be read at all (deleted, transferred, token access
|
|
219
|
+
* narrowed) are simply absent from the returned map.
|
|
220
|
+
*/
|
|
221
|
+
export async function readGithubIssues(numbers) {
|
|
222
|
+
const found = new Map();
|
|
223
|
+
const conn = await connection();
|
|
224
|
+
if (!conn || numbers.length === 0)
|
|
225
|
+
return found;
|
|
226
|
+
const wanted = new Set(numbers);
|
|
227
|
+
const oldestWanted = Math.min(...numbers);
|
|
228
|
+
let pagesRead = 0;
|
|
229
|
+
let morePages = worthListing(wanted.size);
|
|
230
|
+
while (morePages) {
|
|
231
|
+
let batch;
|
|
232
|
+
try {
|
|
233
|
+
batch = await gh(conn.token, "GET", `/repos/${conn.repo}/issues?state=all&per_page=${ISSUE_PAGE_SIZE}&page=${pagesRead + 1}`);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
break; // fall through to the per-issue reads
|
|
237
|
+
}
|
|
238
|
+
pagesRead++;
|
|
239
|
+
let lowestSeen = Infinity;
|
|
240
|
+
for (const raw of batch) {
|
|
241
|
+
if (wanted.has(raw.number))
|
|
242
|
+
found.set(raw.number, toIssue(raw));
|
|
243
|
+
lowestSeen = Math.min(lowestSeen, raw.number);
|
|
244
|
+
}
|
|
245
|
+
morePages = keepPaging({
|
|
246
|
+
pagesRead,
|
|
247
|
+
batchSize: batch.length,
|
|
248
|
+
lowestSeen,
|
|
249
|
+
oldestWanted,
|
|
250
|
+
outstanding: wanted.size - found.size,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const missing = numbers.filter((n) => !found.has(n));
|
|
254
|
+
for (let i = 0; i < missing.length; i += ISSUE_FETCH_CONCURRENCY) {
|
|
255
|
+
const slice = missing.slice(i, i + ISSUE_FETCH_CONCURRENCY);
|
|
256
|
+
for (const issue of await Promise.all(slice.map((n) => readGithubIssue(n)))) {
|
|
257
|
+
if (issue)
|
|
258
|
+
found.set(issue.number, issue);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return found;
|
|
262
|
+
}
|
|
167
263
|
/** File an issue in the connected repo. Throws on failure. */
|
|
168
264
|
export async function createGithubIssue(input) {
|
|
169
265
|
const conn = await connection();
|
package/dist/ai/github.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"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"}
|