@polishd/next 0.1.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 +262 -0
- package/DATABASE.md +147 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/bin/init.mjs +224 -0
- package/dist/ai/actions.d.ts +23 -0
- package/dist/ai/actions.d.ts.map +1 -0
- package/dist/ai/actions.js +58 -0
- package/dist/ai/actions.js.map +1 -0
- package/dist/ai/digest.d.ts +9 -0
- package/dist/ai/digest.d.ts.map +1 -0
- package/dist/ai/digest.js +108 -0
- package/dist/ai/digest.js.map +1 -0
- package/dist/ai/github.d.ts +51 -0
- package/dist/ai/github.d.ts.map +1 -0
- package/dist/ai/github.js +175 -0
- package/dist/ai/github.js.map +1 -0
- package/dist/ai/guard.d.ts +43 -0
- package/dist/ai/guard.d.ts.map +1 -0
- package/dist/ai/guard.js +57 -0
- package/dist/ai/guard.js.map +1 -0
- package/dist/ai/issues.d.ts +21 -0
- package/dist/ai/issues.d.ts.map +1 -0
- package/dist/ai/issues.js +291 -0
- package/dist/ai/issues.js.map +1 -0
- package/dist/ai/profile.d.ts +26 -0
- package/dist/ai/profile.d.ts.map +1 -0
- package/dist/ai/profile.js +160 -0
- package/dist/ai/profile.js.map +1 -0
- package/dist/ai/providers.d.ts +40 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +142 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/scan.d.ts +38 -0
- package/dist/ai/scan.d.ts.map +1 -0
- package/dist/ai/scan.js +274 -0
- package/dist/ai/scan.js.map +1 -0
- package/dist/ai/settings.d.ts +31 -0
- package/dist/ai/settings.d.ts.map +1 -0
- package/dist/ai/settings.js +182 -0
- package/dist/ai/settings.js.map +1 -0
- package/dist/ai/summary.d.ts +26 -0
- package/dist/ai/summary.d.ts.map +1 -0
- package/dist/ai/summary.js +260 -0
- package/dist/ai/summary.js.map +1 -0
- package/dist/ai/types.d.ts +188 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +13 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/client/init.d.ts +14 -0
- package/dist/client/init.d.ts.map +1 -0
- package/dist/client/init.js +274 -0
- package/dist/client/init.js.map +1 -0
- package/dist/client/monitor.d.ts +23 -0
- package/dist/client/monitor.d.ts.map +1 -0
- package/dist/client/monitor.js +176 -0
- package/dist/client/monitor.js.map +1 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +10 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/elements.d.ts +6 -0
- package/dist/dashboard/elements.d.ts.map +1 -0
- package/dist/dashboard/elements.js +35 -0
- package/dist/dashboard/elements.js.map +1 -0
- package/dist/dashboard/features.d.ts +6 -0
- package/dist/dashboard/features.d.ts.map +1 -0
- package/dist/dashboard/features.js +28 -0
- package/dist/dashboard/features.js.map +1 -0
- package/dist/dashboard/index.d.ts +54 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +140 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/journeys.d.ts +5 -0
- package/dist/dashboard/journeys.d.ts.map +1 -0
- package/dist/dashboard/journeys.js +158 -0
- package/dist/dashboard/journeys.js.map +1 -0
- package/dist/dashboard/pages.d.ts +5 -0
- package/dist/dashboard/pages.d.ts.map +1 -0
- package/dist/dashboard/pages.js +104 -0
- package/dist/dashboard/pages.js.map +1 -0
- package/dist/dashboard/summary.d.ts +13 -0
- package/dist/dashboard/summary.d.ts.map +1 -0
- package/dist/dashboard/summary.js +333 -0
- package/dist/dashboard/summary.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +29 -0
- package/dist/proxy.js.map +1 -0
- package/dist/route.d.ts +24 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +37 -0
- package/dist/route.js.map +1 -0
- package/dist/server/ingest.d.ts +14 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +73 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/queries.d.ts +245 -0
- package/dist/server/queries.d.ts.map +1 -0
- package/dist/server/queries.js +547 -0
- package/dist/server/queries.js.map +1 -0
- package/dist/server/store.d.ts +29 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/store.js +359 -0
- package/dist/server/store.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +29 -0
- package/dist/session.js.map +1 -0
- package/dist/shared/types.d.ts +56 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +27 -0
- package/dist/shared/types.js.map +1 -0
- package/docs/SETUP.md +315 -0
- package/package.json +92 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — dashboard aggregations (server only).
|
|
3
|
+
*
|
|
4
|
+
* Read-side queries over the events table. These power the `/polishd` dashboard
|
|
5
|
+
* and, later, feed the Stage-2 synthesis prompt. Everything degrades to empty
|
|
6
|
+
* results when the store is in no-op mode, so the dashboard always renders.
|
|
7
|
+
*
|
|
8
|
+
* SQL here is written in a portable subset that runs on both backends: `?`
|
|
9
|
+
* placeholders (the store rewrites them per dialect) and boolean aggregates
|
|
10
|
+
* spelled `SUM(CASE WHEN … THEN 1 ELSE 0 END)` rather than SQLite's `SUM(x = y)`.
|
|
11
|
+
*/
|
|
12
|
+
import { parseMeta, query, storeReady } from "./store";
|
|
13
|
+
/** Coerce a DB cell to a finite number. Tolerates pg's stringified bigints. */
|
|
14
|
+
function num(row, key) {
|
|
15
|
+
const v = row?.[key];
|
|
16
|
+
if (typeof v === "number")
|
|
17
|
+
return Number.isFinite(v) ? v : 0;
|
|
18
|
+
if (typeof v === "string") {
|
|
19
|
+
const n = Number(v);
|
|
20
|
+
return Number.isFinite(n) ? n : 0;
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
export async function getOverview() {
|
|
25
|
+
if (!(await storeReady())) {
|
|
26
|
+
return {
|
|
27
|
+
ready: false,
|
|
28
|
+
totalEvents: 0,
|
|
29
|
+
sessions: 0,
|
|
30
|
+
pageViews: 0,
|
|
31
|
+
rageClicks: 0,
|
|
32
|
+
deadClicks: 0,
|
|
33
|
+
jsErrors: 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const [row] = await query(`SELECT
|
|
37
|
+
COUNT(*) AS "totalEvents",
|
|
38
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
39
|
+
SUM(CASE WHEN type = 'page_view' THEN 1 ELSE 0 END) AS "pageViews",
|
|
40
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS "rageClicks",
|
|
41
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS "deadClicks",
|
|
42
|
+
SUM(CASE WHEN type = 'js_error' THEN 1 ELSE 0 END) AS "jsErrors"
|
|
43
|
+
FROM events`);
|
|
44
|
+
return {
|
|
45
|
+
ready: true,
|
|
46
|
+
totalEvents: num(row, "totalEvents"),
|
|
47
|
+
sessions: num(row, "sessions"),
|
|
48
|
+
pageViews: num(row, "pageViews"),
|
|
49
|
+
rageClicks: num(row, "rageClicks"),
|
|
50
|
+
deadClicks: num(row, "deadClicks"),
|
|
51
|
+
jsErrors: num(row, "jsErrors"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Score ranking. Weights match the build plan's guidance — rage clicks are
|
|
56
|
+
* the strongest frustration signal, then dead clicks and errors; shallow
|
|
57
|
+
* scroll (content not reached) contributes mildly.
|
|
58
|
+
*/
|
|
59
|
+
export async function getPageStats(limit = 5) {
|
|
60
|
+
const rows = await query(`SELECT
|
|
61
|
+
path,
|
|
62
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
63
|
+
SUM(CASE WHEN type = 'page_view' THEN 1 ELSE 0 END) AS "pageViews",
|
|
64
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS "rageClicks",
|
|
65
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS "deadClicks",
|
|
66
|
+
SUM(CASE WHEN type = 'js_error' THEN 1 ELSE 0 END) AS "jsErrors",
|
|
67
|
+
AVG(CASE WHEN type = 'scroll_depth' THEN value END) AS "avgScrollDepth"
|
|
68
|
+
FROM events
|
|
69
|
+
GROUP BY path`);
|
|
70
|
+
return rows
|
|
71
|
+
.map((r) => {
|
|
72
|
+
const rageClicks = num(r, "rageClicks");
|
|
73
|
+
const deadClicks = num(r, "deadClicks");
|
|
74
|
+
const jsErrors = num(r, "jsErrors");
|
|
75
|
+
const avg = r.avgScrollDepth;
|
|
76
|
+
const avgNum = typeof avg === "number" ? avg : typeof avg === "string" ? Number(avg) : NaN;
|
|
77
|
+
const avgScrollDepth = Number.isFinite(avgNum) ? Math.round(avgNum) : null;
|
|
78
|
+
const shallowPenalty = avgScrollDepth !== null && avgScrollDepth < 50 ? (50 - avgScrollDepth) / 25 : 0;
|
|
79
|
+
const score = rageClicks * 3 + deadClicks * 2 + jsErrors * 2.5 + shallowPenalty;
|
|
80
|
+
return {
|
|
81
|
+
path: r.path,
|
|
82
|
+
sessions: num(r, "sessions"),
|
|
83
|
+
pageViews: num(r, "pageViews"),
|
|
84
|
+
rageClicks,
|
|
85
|
+
deadClicks,
|
|
86
|
+
jsErrors,
|
|
87
|
+
avgScrollDepth,
|
|
88
|
+
score: Math.round(score * 10) / 10,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.sort((a, b) => b.score - a.score)
|
|
92
|
+
.slice(0, limit);
|
|
93
|
+
}
|
|
94
|
+
const DAY_MS = 86_400_000;
|
|
95
|
+
/** Cap the trend window so a wide date range stays a readable chart. */
|
|
96
|
+
const MAX_TREND_DAYS = 30;
|
|
97
|
+
function dayStart(ts) {
|
|
98
|
+
return ts - (ts % DAY_MS);
|
|
99
|
+
}
|
|
100
|
+
function fmtDay(ts) {
|
|
101
|
+
return new Date(ts).toLocaleDateString(undefined, {
|
|
102
|
+
month: "short",
|
|
103
|
+
day: "numeric",
|
|
104
|
+
timeZone: "UTC",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The most-visited pages, ranked by distinct sessions, each with a daily
|
|
109
|
+
* sessions-over-time series for the detail chart. The aggregate is one grouped
|
|
110
|
+
* query; the trend comes from a second pull of page_view events for just the
|
|
111
|
+
* top paths, bucketed by UTC day in JS so it stays backend-portable.
|
|
112
|
+
*/
|
|
113
|
+
export async function getTopPages(limit = 8) {
|
|
114
|
+
if (!(await storeReady()))
|
|
115
|
+
return [];
|
|
116
|
+
const rows = await query(`SELECT
|
|
117
|
+
path,
|
|
118
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
119
|
+
SUM(CASE WHEN type = 'page_view' THEN 1 ELSE 0 END) AS "pageViews",
|
|
120
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS "rageClicks",
|
|
121
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS "deadClicks",
|
|
122
|
+
SUM(CASE WHEN type = 'js_error' THEN 1 ELSE 0 END) AS "jsErrors",
|
|
123
|
+
AVG(CASE WHEN type = 'scroll_depth' THEN value END) AS "avgScrollDepth"
|
|
124
|
+
FROM events
|
|
125
|
+
GROUP BY path`);
|
|
126
|
+
const pages = rows
|
|
127
|
+
.map((r) => {
|
|
128
|
+
const avg = r.avgScrollDepth;
|
|
129
|
+
const avgNum = typeof avg === "number" ? avg : typeof avg === "string" ? Number(avg) : NaN;
|
|
130
|
+
return {
|
|
131
|
+
path: r.path,
|
|
132
|
+
sessions: num(r, "sessions"),
|
|
133
|
+
pageViews: num(r, "pageViews"),
|
|
134
|
+
rageClicks: num(r, "rageClicks"),
|
|
135
|
+
deadClicks: num(r, "deadClicks"),
|
|
136
|
+
jsErrors: num(r, "jsErrors"),
|
|
137
|
+
avgScrollDepth: Number.isFinite(avgNum) ? Math.round(avgNum) : null,
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
// "Top" = most visited; page views break ties.
|
|
141
|
+
.sort((a, b) => b.sessions - a.sessions || b.pageViews - a.pageViews)
|
|
142
|
+
.slice(0, limit);
|
|
143
|
+
if (pages.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
// Pull page_view timestamps for just the top paths, then bucket by day.
|
|
146
|
+
const paths = pages.map((p) => p.path);
|
|
147
|
+
const placeholders = paths.map(() => "?").join(", ");
|
|
148
|
+
const views = await query(`SELECT path, ts, session_id
|
|
149
|
+
FROM events
|
|
150
|
+
WHERE type = 'page_view' AND path IN (${placeholders})`, paths);
|
|
151
|
+
// path → (UTC day → set of sessions seen that day)
|
|
152
|
+
const byPath = new Map();
|
|
153
|
+
for (const v of views) {
|
|
154
|
+
const p = v.path;
|
|
155
|
+
const day = dayStart(num(v, "ts"));
|
|
156
|
+
let days = byPath.get(p);
|
|
157
|
+
if (!days)
|
|
158
|
+
byPath.set(p, (days = new Map()));
|
|
159
|
+
let set = days.get(day);
|
|
160
|
+
if (!set)
|
|
161
|
+
days.set(day, (set = new Set()));
|
|
162
|
+
set.add(v.session_id);
|
|
163
|
+
}
|
|
164
|
+
// Build a gap-filled daily series (zeros included) over the page's range,
|
|
165
|
+
// capped to the most recent MAX_TREND_DAYS so wide ranges stay readable.
|
|
166
|
+
const buildTrend = (days) => {
|
|
167
|
+
if (!days || days.size === 0)
|
|
168
|
+
return [];
|
|
169
|
+
const keys = [...days.keys()].sort((a, b) => a - b);
|
|
170
|
+
const end = keys[keys.length - 1];
|
|
171
|
+
let start = keys[0];
|
|
172
|
+
if ((end - start) / DAY_MS > MAX_TREND_DAYS - 1)
|
|
173
|
+
start = end - (MAX_TREND_DAYS - 1) * DAY_MS;
|
|
174
|
+
const out = [];
|
|
175
|
+
for (let d = start; d <= end; d += DAY_MS) {
|
|
176
|
+
out.push({ ts: d, label: fmtDay(d), sessions: days.get(d)?.size ?? 0 });
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
};
|
|
180
|
+
return pages.map((p) => ({ ...p, trend: buildTrend(byPath.get(p.path)) }));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Per-element interaction breakdown, grouped by DOM selector path. This is the
|
|
184
|
+
* data that makes Stage 2 synthesis possible: it tells you *which UI element*
|
|
185
|
+
* the issues are, not just which page.
|
|
186
|
+
*
|
|
187
|
+
* Explicitly monitored components (those wrapped in <PolishdMonitor>, the only
|
|
188
|
+
* source of `data-component`) are excluded here — they get their own dedicated
|
|
189
|
+
* "Monitored components" section, so this table covers the rest of the UI.
|
|
190
|
+
*/
|
|
191
|
+
export async function getElementStats(limit = 12) {
|
|
192
|
+
const rows = await query(`SELECT
|
|
193
|
+
COALESCE(selector, '(unknown)') AS label,
|
|
194
|
+
MAX(selector) AS selector,
|
|
195
|
+
MAX(text) AS "sampleText",
|
|
196
|
+
COUNT(DISTINCT path) AS pages,
|
|
197
|
+
SUM(CASE WHEN type = 'click' THEN 1 ELSE 0 END) AS clicks,
|
|
198
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS "rageClicks",
|
|
199
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS "deadClicks",
|
|
200
|
+
MAX(ts) AS "lastTs"
|
|
201
|
+
FROM events
|
|
202
|
+
WHERE type IN ('click', 'rage_click', 'dead_click') AND component IS NULL
|
|
203
|
+
GROUP BY COALESCE(selector, '(unknown)')`);
|
|
204
|
+
return rows
|
|
205
|
+
.map((r) => {
|
|
206
|
+
const rageClicks = num(r, "rageClicks");
|
|
207
|
+
const deadClicks = num(r, "deadClicks");
|
|
208
|
+
const clicks = num(r, "clicks");
|
|
209
|
+
return {
|
|
210
|
+
label: r.label,
|
|
211
|
+
selector: r.selector ?? null,
|
|
212
|
+
sampleText: r.sampleText ?? null,
|
|
213
|
+
pages: num(r, "pages"),
|
|
214
|
+
clicks,
|
|
215
|
+
rageClicks,
|
|
216
|
+
deadClicks,
|
|
217
|
+
score: Math.round((rageClicks * 3 + deadClicks * 2) * 10) / 10,
|
|
218
|
+
lastInteraction: num(r, "lastTs"),
|
|
219
|
+
};
|
|
220
|
+
})
|
|
221
|
+
// Most recently interacted-with element first; score breaks ties.
|
|
222
|
+
.sort((a, b) => b.lastInteraction - a.lastInteraction || b.score - a.score)
|
|
223
|
+
.slice(0, limit);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Device-size distribution. One `viewport` event is sampled per session at
|
|
227
|
+
* start; its category lives in the `text` column (mobile/tablet/desktop) and
|
|
228
|
+
* its width in `value`, so this groups without touching the JSON `meta` column.
|
|
229
|
+
* Sorted most-used device first — answers "what size screen do visitors use?".
|
|
230
|
+
*/
|
|
231
|
+
export async function getDeviceBreakdown() {
|
|
232
|
+
const rows = await query(`SELECT
|
|
233
|
+
text AS category,
|
|
234
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
235
|
+
AVG(value) AS "avgWidth"
|
|
236
|
+
FROM events
|
|
237
|
+
WHERE type = 'viewport' AND text IS NOT NULL
|
|
238
|
+
GROUP BY text`);
|
|
239
|
+
const buckets = rows.map((r) => {
|
|
240
|
+
const avg = r.avgWidth;
|
|
241
|
+
const avgNum = typeof avg === "number" ? avg : typeof avg === "string" ? Number(avg) : NaN;
|
|
242
|
+
return {
|
|
243
|
+
category: r.category,
|
|
244
|
+
sessions: num(r, "sessions"),
|
|
245
|
+
avgWidth: Number.isFinite(avgNum) ? Math.round(avgNum) : null,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
const total = buckets.reduce((sum, b) => sum + b.sessions, 0);
|
|
249
|
+
return buckets
|
|
250
|
+
.map((b) => ({
|
|
251
|
+
...b,
|
|
252
|
+
pct: total > 0 ? Math.round((b.sessions / total) * 1000) / 10 : 0,
|
|
253
|
+
}))
|
|
254
|
+
.sort((a, b) => b.sessions - a.sessions);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Most-used features. Ranks interactive elements by raw click volume (the
|
|
258
|
+
* `click` type — successful clicks on links/buttons/etc., excluding rage and
|
|
259
|
+
* dead clicks). Where `getElementStats` surfaces what's *broken*, this
|
|
260
|
+
* surfaces what's *popular* — which buttons and features people actually use.
|
|
261
|
+
*/
|
|
262
|
+
export async function getTopInteractions(limit = 12) {
|
|
263
|
+
const rows = await query(`SELECT
|
|
264
|
+
COALESCE(component, selector, '(unknown)') AS label,
|
|
265
|
+
MAX(CASE WHEN component IS NOT NULL THEN 1 ELSE 0 END) AS "isComponent",
|
|
266
|
+
MAX(selector) AS selector,
|
|
267
|
+
MAX(text) AS "sampleText",
|
|
268
|
+
COUNT(*) AS clicks,
|
|
269
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
270
|
+
COUNT(DISTINCT path) AS pages
|
|
271
|
+
FROM events
|
|
272
|
+
WHERE type = 'click'
|
|
273
|
+
GROUP BY COALESCE(component, selector, '(unknown)')`);
|
|
274
|
+
return rows
|
|
275
|
+
.map((r) => ({
|
|
276
|
+
label: r.label,
|
|
277
|
+
isComponent: num(r, "isComponent") === 1,
|
|
278
|
+
selector: r.selector ?? null,
|
|
279
|
+
sampleText: r.sampleText ?? null,
|
|
280
|
+
clicks: num(r, "clicks"),
|
|
281
|
+
sessions: num(r, "sessions"),
|
|
282
|
+
pages: num(r, "pages"),
|
|
283
|
+
}))
|
|
284
|
+
.sort((a, b) => b.clicks - a.clicks || b.sessions - a.sessions)
|
|
285
|
+
.slice(0, limit);
|
|
286
|
+
}
|
|
287
|
+
/** Event types that are user *actions* (the flow-chart nodes). */
|
|
288
|
+
const JOURNEY_ACTION_TYPES = new Set([
|
|
289
|
+
"page_view",
|
|
290
|
+
"click",
|
|
291
|
+
"rage_click",
|
|
292
|
+
"dead_click",
|
|
293
|
+
"js_error",
|
|
294
|
+
"session_end",
|
|
295
|
+
]);
|
|
296
|
+
/** Keep journeys readable: cap how many steps a single flow chart renders. */
|
|
297
|
+
const MAX_JOURNEY_STEPS = 50;
|
|
298
|
+
/**
|
|
299
|
+
* Sampled full-session recordings, reconstructed as ordered action flows.
|
|
300
|
+
*
|
|
301
|
+
* Sampling strategy: every session is scored by a composite score (rage×3 + dead×2 +
|
|
302
|
+
* errors×2.5). We surface the highest-scoring sessions first — those are the
|
|
303
|
+
* ones worth replaying — preferring *complete* recordings (a `session_end` was
|
|
304
|
+
* captured) and breaking ties by recency. When there's no signal at all this
|
|
305
|
+
* degrades to "most recent complete sessions", so the section is still useful.
|
|
306
|
+
*/
|
|
307
|
+
export async function getSessionJourneys(limit = 6) {
|
|
308
|
+
if (!(await storeReady()))
|
|
309
|
+
return [];
|
|
310
|
+
// 1. Score and rank candidate sessions for sampling.
|
|
311
|
+
const summary = await query(`SELECT
|
|
312
|
+
session_id AS id,
|
|
313
|
+
MIN(ts) AS started,
|
|
314
|
+
MAX(ts) AS ended,
|
|
315
|
+
COUNT(DISTINCT path) AS pages,
|
|
316
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS rage,
|
|
317
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS dead,
|
|
318
|
+
SUM(CASE WHEN type = 'js_error' THEN 1 ELSE 0 END) AS errors,
|
|
319
|
+
MAX(CASE WHEN type = 'session_end' THEN 1 ELSE 0 END) AS complete,
|
|
320
|
+
SUM(CASE WHEN type IN ('page_view','click','rage_click','dead_click')
|
|
321
|
+
THEN 1 ELSE 0 END) AS actions
|
|
322
|
+
FROM events
|
|
323
|
+
GROUP BY session_id`);
|
|
324
|
+
const ranked = summary
|
|
325
|
+
.map((r) => {
|
|
326
|
+
const rage = num(r, "rage");
|
|
327
|
+
const dead = num(r, "dead");
|
|
328
|
+
const errors = num(r, "errors");
|
|
329
|
+
return {
|
|
330
|
+
id: r.id,
|
|
331
|
+
started: num(r, "started"),
|
|
332
|
+
ended: num(r, "ended"),
|
|
333
|
+
pages: num(r, "pages"),
|
|
334
|
+
rage,
|
|
335
|
+
dead,
|
|
336
|
+
errors,
|
|
337
|
+
complete: num(r, "complete") === 1,
|
|
338
|
+
actions: num(r, "actions"),
|
|
339
|
+
score: Math.round((rage * 3 + dead * 2 + errors * 2.5) * 10) / 10,
|
|
340
|
+
};
|
|
341
|
+
})
|
|
342
|
+
// A "journey" needs at least a couple of actions to be worth showing.
|
|
343
|
+
.filter((s) => s.actions >= 2)
|
|
344
|
+
.sort((a, b) => b.score - a.score ||
|
|
345
|
+
Number(b.complete) - Number(a.complete) ||
|
|
346
|
+
b.ended - a.ended)
|
|
347
|
+
.slice(0, limit);
|
|
348
|
+
if (ranked.length === 0)
|
|
349
|
+
return [];
|
|
350
|
+
// 2. Pull every event for just the sampled sessions, in chronological order.
|
|
351
|
+
const ids = ranked.map((s) => s.id);
|
|
352
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
353
|
+
const rows = await query(`SELECT session_id, type, ts, path, selector, component, text, value, meta
|
|
354
|
+
FROM events
|
|
355
|
+
WHERE session_id IN (${placeholders})
|
|
356
|
+
ORDER BY session_id, id`, ids);
|
|
357
|
+
// 3. Group rows back into per-session event streams.
|
|
358
|
+
const streams = new Map();
|
|
359
|
+
for (const r of rows) {
|
|
360
|
+
const sid = r.session_id;
|
|
361
|
+
const list = streams.get(sid);
|
|
362
|
+
if (list)
|
|
363
|
+
list.push(r);
|
|
364
|
+
else
|
|
365
|
+
streams.set(sid, [r]);
|
|
366
|
+
}
|
|
367
|
+
// 4. Build a journey per ranked session, preserving the ranking order.
|
|
368
|
+
return ranked.map((s) => {
|
|
369
|
+
const events = streams.get(s.id) ?? [];
|
|
370
|
+
const device = { category: null, width: null, height: null, dpr: null };
|
|
371
|
+
const steps = [];
|
|
372
|
+
for (const e of events) {
|
|
373
|
+
const type = e.type;
|
|
374
|
+
// The viewport event isn't an action — it carries device details.
|
|
375
|
+
if (type === "viewport") {
|
|
376
|
+
device.category = e.text ?? null;
|
|
377
|
+
device.width = e.value == null ? null : Number(e.value);
|
|
378
|
+
const meta = parseMeta(e.meta);
|
|
379
|
+
if (meta) {
|
|
380
|
+
if (typeof meta.h === "number")
|
|
381
|
+
device.height = meta.h;
|
|
382
|
+
if (typeof meta.dpr === "number")
|
|
383
|
+
device.dpr = meta.dpr;
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (!JOURNEY_ACTION_TYPES.has(type))
|
|
388
|
+
continue; // scroll_depth, web_vital
|
|
389
|
+
if (steps.length >= MAX_JOURNEY_STEPS)
|
|
390
|
+
break;
|
|
391
|
+
let label = null;
|
|
392
|
+
let detail = null;
|
|
393
|
+
if (type === "js_error") {
|
|
394
|
+
const meta = parseMeta(e.meta);
|
|
395
|
+
detail = meta && typeof meta.message === "string" ? meta.message : "Unknown error";
|
|
396
|
+
}
|
|
397
|
+
else if (type !== "page_view" && type !== "session_end") {
|
|
398
|
+
label =
|
|
399
|
+
e.component || e.text || e.selector || null;
|
|
400
|
+
}
|
|
401
|
+
steps.push({ type, ts: num(e, "ts"), path: e.path, label, detail });
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
id: s.id.slice(0, 8),
|
|
405
|
+
startedAt: s.started,
|
|
406
|
+
endedAt: s.ended,
|
|
407
|
+
durationMs: Math.max(0, s.ended - s.started),
|
|
408
|
+
complete: s.complete,
|
|
409
|
+
pages: s.pages,
|
|
410
|
+
device,
|
|
411
|
+
rageClicks: s.rage,
|
|
412
|
+
deadClicks: s.dead,
|
|
413
|
+
jsErrors: s.errors,
|
|
414
|
+
score: s.score,
|
|
415
|
+
steps,
|
|
416
|
+
truncated: steps.length >= MAX_JOURNEY_STEPS,
|
|
417
|
+
};
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Fetch all components explicitly wrapped in <PolishdMonitor>. The definitive
|
|
422
|
+
* marker is the presence of at least one "hover", "component_view", or "mount"
|
|
423
|
+
* event for that component name (all three are only emitted by PolishdMonitor).
|
|
424
|
+
* We then pull all event types for those components so the table shows the
|
|
425
|
+
* complete picture.
|
|
426
|
+
*
|
|
427
|
+
* The per-view `meta` (height, scrollDepth) is aggregated in JS rather than SQL:
|
|
428
|
+
* the two backends spell JSON extraction differently (SQLite `JSON_EXTRACT`,
|
|
429
|
+
* Postgres `->>`), and there's no portable function, so we parse meta here —
|
|
430
|
+
* matching how `getSessionJourneys` and `getTopPages` keep their SQL portable.
|
|
431
|
+
*/
|
|
432
|
+
export async function getMonitoredComponents() {
|
|
433
|
+
if (!(await storeReady()))
|
|
434
|
+
return [];
|
|
435
|
+
const rows = await query(`SELECT
|
|
436
|
+
component AS name,
|
|
437
|
+
SUM(CASE WHEN type = 'click' THEN 1 ELSE 0 END) AS clicks,
|
|
438
|
+
SUM(CASE WHEN type = 'rage_click' THEN 1 ELSE 0 END) AS "rageClicks",
|
|
439
|
+
SUM(CASE WHEN type = 'dead_click' THEN 1 ELSE 0 END) AS "deadClicks",
|
|
440
|
+
SUM(CASE WHEN type = 'hover' THEN 1 ELSE 0 END) AS hovers,
|
|
441
|
+
AVG(CASE WHEN type = 'hover' THEN value END) AS "avgHoverMs",
|
|
442
|
+
SUM(CASE WHEN type = 'mount' THEN 1 ELSE 0 END) AS mounts,
|
|
443
|
+
SUM(CASE WHEN type = 'component_view' THEN 1 ELSE 0 END) AS "componentViews",
|
|
444
|
+
AVG(CASE WHEN type = 'component_view' THEN value END) AS "avgViewMs",
|
|
445
|
+
COUNT(DISTINCT session_id) AS sessions,
|
|
446
|
+
COUNT(DISTINCT path) AS pages
|
|
447
|
+
FROM events
|
|
448
|
+
WHERE component IN (
|
|
449
|
+
SELECT DISTINCT component FROM events
|
|
450
|
+
WHERE type IN ('hover', 'component_view', 'mount') AND component IS NOT NULL
|
|
451
|
+
)
|
|
452
|
+
GROUP BY component
|
|
453
|
+
ORDER BY "componentViews" DESC, mounts DESC, hovers DESC`);
|
|
454
|
+
if (rows.length === 0)
|
|
455
|
+
return [];
|
|
456
|
+
// Pull component_view meta for just these components and reduce the
|
|
457
|
+
// height/scrollDepth in JS (portable across SQLite + Postgres). Height takes
|
|
458
|
+
// the max observed (the tallest element the name was attached to — the
|
|
459
|
+
// article body); scroll depth is averaged across visits.
|
|
460
|
+
const names = rows.map((r) => r.name);
|
|
461
|
+
const placeholders = names.map(() => "?").join(", ");
|
|
462
|
+
const viewRows = await query(`SELECT component, meta FROM events
|
|
463
|
+
WHERE type = 'component_view' AND component IN (${placeholders})`, names);
|
|
464
|
+
// component → max height + running scroll-depth average.
|
|
465
|
+
const dims = new Map();
|
|
466
|
+
for (const v of viewRows) {
|
|
467
|
+
const name = v.component;
|
|
468
|
+
const meta = parseMeta(v.meta);
|
|
469
|
+
if (!meta)
|
|
470
|
+
continue;
|
|
471
|
+
let acc = dims.get(name);
|
|
472
|
+
if (!acc)
|
|
473
|
+
dims.set(name, (acc = { hMax: 0, hSeen: false, dSum: 0, dN: 0 }));
|
|
474
|
+
if (typeof meta.height === "number") {
|
|
475
|
+
acc.hSeen = true;
|
|
476
|
+
if (meta.height > acc.hMax)
|
|
477
|
+
acc.hMax = meta.height;
|
|
478
|
+
}
|
|
479
|
+
if (typeof meta.scrollDepth === "number") {
|
|
480
|
+
acc.dSum += meta.scrollDepth;
|
|
481
|
+
acc.dN++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const toNum = (v) => {
|
|
485
|
+
if (typeof v === "number")
|
|
486
|
+
return Number.isFinite(v) ? v : null;
|
|
487
|
+
if (typeof v === "string") {
|
|
488
|
+
const n = Number(v);
|
|
489
|
+
return Number.isFinite(n) ? n : null;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
};
|
|
493
|
+
return rows.map((r) => {
|
|
494
|
+
const name = r.name;
|
|
495
|
+
const acc = dims.get(name);
|
|
496
|
+
return {
|
|
497
|
+
name,
|
|
498
|
+
clicks: num(r, "clicks"),
|
|
499
|
+
rageClicks: num(r, "rageClicks"),
|
|
500
|
+
deadClicks: num(r, "deadClicks"),
|
|
501
|
+
hovers: num(r, "hovers"),
|
|
502
|
+
avgHoverMs: toNum(r.avgHoverMs) !== null ? Math.round(toNum(r.avgHoverMs)) : null,
|
|
503
|
+
mounts: num(r, "mounts"),
|
|
504
|
+
componentViews: num(r, "componentViews"),
|
|
505
|
+
avgViewMs: toNum(r.avgViewMs) !== null ? Math.round(toNum(r.avgViewMs)) : null,
|
|
506
|
+
heightPx: acc && acc.hSeen ? Math.round(acc.hMax) : null,
|
|
507
|
+
avgScrollDepth: acc && acc.dN > 0 ? Math.round(acc.dSum / acc.dN) : null,
|
|
508
|
+
sessions: num(r, "sessions"),
|
|
509
|
+
pages: num(r, "pages"),
|
|
510
|
+
};
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Fetch every dashboard query in parallel. Server-only. Lives here (not in the
|
|
515
|
+
* dashboard) so the AI summary layer can reuse it without importing React.
|
|
516
|
+
*/
|
|
517
|
+
export async function loadPolishdDashboardData() {
|
|
518
|
+
const [overview, pages, elements, devices, topUsed, journeys, errors, monitored] = await Promise.all([
|
|
519
|
+
getOverview(),
|
|
520
|
+
getTopPages(8),
|
|
521
|
+
getElementStats(12),
|
|
522
|
+
getDeviceBreakdown(),
|
|
523
|
+
getTopInteractions(12),
|
|
524
|
+
getSessionJourneys(6),
|
|
525
|
+
getRecentErrors(8),
|
|
526
|
+
getMonitoredComponents(),
|
|
527
|
+
]);
|
|
528
|
+
return { overview, pages, elements, devices, topUsed, journeys, errors, monitored };
|
|
529
|
+
}
|
|
530
|
+
export async function getRecentErrors(limit = 10) {
|
|
531
|
+
const rows = await query(`SELECT path, component, meta, ts
|
|
532
|
+
FROM events WHERE type = 'js_error'
|
|
533
|
+
ORDER BY id DESC LIMIT ?`, [limit]);
|
|
534
|
+
return rows.map((r) => {
|
|
535
|
+
let message = "Unknown error";
|
|
536
|
+
const meta = parseMeta(r.meta);
|
|
537
|
+
if (meta && typeof meta.message === "string")
|
|
538
|
+
message = meta.message;
|
|
539
|
+
return {
|
|
540
|
+
path: r.path,
|
|
541
|
+
component: r.component ?? undefined,
|
|
542
|
+
message,
|
|
543
|
+
ts: num(r, "ts"),
|
|
544
|
+
};
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/server/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PolishdEvent, PolishdEventRow } from "../shared/types";
|
|
2
|
+
/**
|
|
3
|
+
* A backend-agnostic row map. Queries are written once, in a portable SQL
|
|
4
|
+
* subset, with `?` placeholders; each backend adapts them to its own dialect.
|
|
5
|
+
*/
|
|
6
|
+
type Row = Record<string, unknown>;
|
|
7
|
+
/** True when events are actually being persisted (used by the dashboard). */
|
|
8
|
+
export declare function storeReady(): Promise<boolean>;
|
|
9
|
+
/** Persist a batch of events for one session. Silently drops if no store. */
|
|
10
|
+
export declare function insertEvents(sessionId: string, events: PolishdEvent[]): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Run a portable read query, or return `[]` in no-op mode. Use `?` placeholders;
|
|
13
|
+
* each backend rewrites them to its own dialect. The aggregation layer (and the
|
|
14
|
+
* dashboard) goes through here so it never has to know which backend is live.
|
|
15
|
+
*/
|
|
16
|
+
export declare function query(sql: string, params?: unknown[]): Promise<Row[]>;
|
|
17
|
+
/** Read one metadata value by key, or null when absent / no store. */
|
|
18
|
+
export declare function getMeta(key: string): Promise<string | null>;
|
|
19
|
+
/** Upsert one metadata value. Returns false when the store is unavailable. */
|
|
20
|
+
export declare function setMeta(key: string, value: string): Promise<boolean>;
|
|
21
|
+
/** Raw rows, newest first. For debugging and the queries layer. */
|
|
22
|
+
export declare function allRows(limit?: number): Promise<PolishdEventRow[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize the `meta` column across backends: SQLite stores it as a JSON
|
|
25
|
+
* string, Postgres' JSONB comes back already parsed. Accept either.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseMeta(raw: unknown): PolishdEventRow["meta"];
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/server/store.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;GAGG;AACH,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA8RnC,6EAA6E;AAC7E,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAEnD;AAED,6EAA6E;AAC7E,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAY/E;AAQD,sEAAsE;AACtE,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIjE;AAED,8EAA8E;AAC9E,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB1E;AAED,mEAAmE;AACnE,wBAAsB,OAAO,CAAC,KAAK,SAAO,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAGtE;AAkBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAW/D"}
|