@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.
Files changed (95) hide show
  1. package/AGENTS.md +161 -46
  2. package/CHANGELOG.md +163 -0
  3. package/README.md +275 -49
  4. package/bin/doctor.mjs +374 -0
  5. package/bin/init.mjs +215 -188
  6. package/bin/polishd.mjs +85 -0
  7. package/bin/shared.mjs +114 -0
  8. package/dist/ai/actions.d.ts +13 -2
  9. package/dist/ai/actions.d.ts.map +1 -1
  10. package/dist/ai/actions.js +19 -1
  11. package/dist/ai/actions.js.map +1 -1
  12. package/dist/ai/design.d.ts +28 -0
  13. package/dist/ai/design.d.ts.map +1 -0
  14. package/dist/ai/design.js +245 -0
  15. package/dist/ai/design.js.map +1 -0
  16. package/dist/ai/guard.d.ts +44 -3
  17. package/dist/ai/guard.d.ts.map +1 -1
  18. package/dist/ai/guard.js +13 -2
  19. package/dist/ai/guard.js.map +1 -1
  20. package/dist/ai/providers.d.ts +2 -0
  21. package/dist/ai/providers.d.ts.map +1 -1
  22. package/dist/ai/providers.js +70 -0
  23. package/dist/ai/providers.js.map +1 -1
  24. package/dist/ai/settings.d.ts +15 -1
  25. package/dist/ai/settings.d.ts.map +1 -1
  26. package/dist/ai/settings.js +38 -1
  27. package/dist/ai/settings.js.map +1 -1
  28. package/dist/ai/types.d.ts +57 -0
  29. package/dist/ai/types.d.ts.map +1 -1
  30. package/dist/client/design-scan.d.ts +37 -0
  31. package/dist/client/design-scan.d.ts.map +1 -0
  32. package/dist/client/design-scan.js +319 -0
  33. package/dist/client/design-scan.js.map +1 -0
  34. package/dist/client/init.d.ts.map +1 -1
  35. package/dist/client/init.js +67 -2
  36. package/dist/client/init.js.map +1 -1
  37. package/dist/config.d.ts +57 -3
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +62 -3
  40. package/dist/config.js.map +1 -1
  41. package/dist/dashboard/design-review-card.d.ts +15 -0
  42. package/dist/dashboard/design-review-card.d.ts.map +1 -0
  43. package/dist/dashboard/design-review-card.js +82 -0
  44. package/dist/dashboard/design-review-card.js.map +1 -0
  45. package/dist/dashboard/design.d.ts +10 -0
  46. package/dist/dashboard/design.d.ts.map +1 -0
  47. package/dist/dashboard/design.js +83 -0
  48. package/dist/dashboard/design.js.map +1 -0
  49. package/dist/dashboard/index.d.ts +35 -5
  50. package/dist/dashboard/index.d.ts.map +1 -1
  51. package/dist/dashboard/index.js +176 -18
  52. package/dist/dashboard/index.js.map +1 -1
  53. package/dist/dashboard/journeys.d.ts.map +1 -1
  54. package/dist/dashboard/journeys.js +1 -0
  55. package/dist/dashboard/journeys.js.map +1 -1
  56. package/dist/dashboard/summary.d.ts.map +1 -1
  57. package/dist/dashboard/summary.js +59 -5
  58. package/dist/dashboard/summary.js.map +1 -1
  59. package/dist/dashboard/token-auth.d.ts +56 -0
  60. package/dist/dashboard/token-auth.d.ts.map +1 -0
  61. package/dist/dashboard/token-auth.js +158 -0
  62. package/dist/dashboard/token-auth.js.map +1 -0
  63. package/dist/dashboard/unlock.d.ts +2 -0
  64. package/dist/dashboard/unlock.d.ts.map +1 -0
  65. package/dist/dashboard/unlock.js +54 -0
  66. package/dist/dashboard/unlock.js.map +1 -0
  67. package/dist/dashboard.css +2740 -0
  68. package/dist/proxy.d.ts +53 -5
  69. package/dist/proxy.d.ts.map +1 -1
  70. package/dist/proxy.js +73 -3
  71. package/dist/proxy.js.map +1 -1
  72. package/dist/route.d.ts.map +1 -1
  73. package/dist/route.js +11 -3
  74. package/dist/route.js.map +1 -1
  75. package/dist/server/design.d.ts +93 -0
  76. package/dist/server/design.d.ts.map +1 -0
  77. package/dist/server/design.js +430 -0
  78. package/dist/server/design.js.map +1 -0
  79. package/dist/server/ingest.d.ts +33 -2
  80. package/dist/server/ingest.d.ts.map +1 -1
  81. package/dist/server/ingest.js +70 -8
  82. package/dist/server/ingest.js.map +1 -1
  83. package/dist/server/queries.d.ts +16 -0
  84. package/dist/server/queries.d.ts.map +1 -1
  85. package/dist/server/queries.js +80 -16
  86. package/dist/server/queries.js.map +1 -1
  87. package/dist/session.d.ts.map +1 -1
  88. package/dist/session.js +4 -2
  89. package/dist/session.js.map +1 -1
  90. package/dist/shared/types.d.ts +31 -1
  91. package/dist/shared/types.d.ts.map +1 -1
  92. package/dist/shared/types.js +1 -0
  93. package/dist/shared/types.js.map +1 -1
  94. package/docs/SETUP.md +308 -51
  95. package/package.json +14 -7
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Polishd — design metrics aggregation (server only).
3
+ *
4
+ * Merges the latest `design_scan` per page into one site-wide picture: the
5
+ * typography actually in use, the color palette as rendered, the radius and
6
+ * spacing scales — a brand guideline reverse-engineered from the built site.
7
+ *
8
+ * The second half is deterministic review: rule-based checks against widely
9
+ * accepted UI practice (a bounded type scale, one or two families, a palette
10
+ * without near-duplicates, WCAG contrast, spacing on a grid). These produce
11
+ * `flags` — no model involved — and both the metrics and the flags feed the
12
+ * AI design review in ai/design.ts.
13
+ */
14
+ import { defaultPolishdConfig } from "../config";
15
+ import { parseMeta, query, storeReady } from "./store";
16
+ // Same dashboard-route exclusion the analytics queries use; duplicated as a
17
+ // tiny helper because queries.ts keeps its `EVENTS` fragment module-private.
18
+ const EVENTS = (() => {
19
+ const route = defaultPolishdConfig.dashboardRoute.replace(/\/+$/, "");
20
+ if (!route)
21
+ return "events";
22
+ const literal = `'${route.replace(/'/g, "''")}'`;
23
+ const prefix = `'${`${route}/`.replace(/'/g, "''")}'`;
24
+ return `(SELECT * FROM events
25
+ WHERE path <> ${literal}
26
+ AND substr(path, 1, ${route.length + 1}) <> ${prefix}) AS events`;
27
+ })();
28
+ export const EMPTY_DESIGN_DATA = {
29
+ ready: false,
30
+ pages: [],
31
+ fonts: [],
32
+ families: [],
33
+ colors: [],
34
+ contrast: [],
35
+ radii: [],
36
+ spacing: [],
37
+ flags: [],
38
+ };
39
+ // ── Color math ───────────────────────────────────────────────────────────────
40
+ function rgbOf(hex) {
41
+ const m = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);
42
+ if (!m)
43
+ return null;
44
+ return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
45
+ }
46
+ /** WCAG 2.x relative luminance. */
47
+ function luminance(hex) {
48
+ const rgb = rgbOf(hex);
49
+ if (!rgb)
50
+ return null;
51
+ const [r, g, b] = rgb.map((v) => {
52
+ const s = v / 255;
53
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
54
+ });
55
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
56
+ }
57
+ /** WCAG 2.x contrast ratio between two hex colors, or null if unparseable. */
58
+ export function contrastRatio(a, b) {
59
+ const la = luminance(a);
60
+ const lb = luminance(b);
61
+ if (la === null || lb === null)
62
+ return null;
63
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
64
+ return Math.round(((hi + 0.05) / (lo + 0.05)) * 100) / 100;
65
+ }
66
+ /** Euclidean RGB distance — crude but plenty to catch #fefefe vs #ffffff. */
67
+ function colorDistance(a, b) {
68
+ const ra = rgbOf(a);
69
+ const rb = rgbOf(b);
70
+ if (!ra || !rb)
71
+ return null;
72
+ return Math.sqrt((ra[0] - rb[0]) ** 2 + (ra[1] - rb[1]) ** 2 + (ra[2] - rb[2]) ** 2);
73
+ }
74
+ // ── Aggregation ──────────────────────────────────────────────────────────────
75
+ function toNum(v) {
76
+ if (typeof v === "number")
77
+ return Number.isFinite(v) ? v : 0;
78
+ if (typeof v === "string") {
79
+ const n = Number(v);
80
+ return Number.isFinite(n) ? n : 0;
81
+ }
82
+ return 0;
83
+ }
84
+ /** Latest parseable scan per page, newest first. */
85
+ async function loadPageScans() {
86
+ const rows = await query(`SELECT path, ts, meta FROM ${EVENTS}
87
+ WHERE type = 'design_scan'
88
+ ORDER BY id DESC
89
+ LIMIT 300`);
90
+ const byPath = new Map();
91
+ for (const r of rows) {
92
+ const path = r.path;
93
+ if (byPath.has(path))
94
+ continue; // newest wins (rows are id DESC)
95
+ const meta = parseMeta(r.meta);
96
+ const raw = meta && typeof meta.payload === "string" ? meta.payload : null;
97
+ if (!raw)
98
+ continue;
99
+ try {
100
+ const payload = JSON.parse(raw);
101
+ if (payload?.v !== 1 || !Array.isArray(payload.fonts))
102
+ continue;
103
+ byPath.set(path, { path, ts: toNum(r.ts), payload });
104
+ }
105
+ catch {
106
+ // A clipped or malformed payload is dropped, never fatal.
107
+ }
108
+ }
109
+ return [...byPath.values()].sort((a, b) => b.ts - a.ts);
110
+ }
111
+ /**
112
+ * Build the full design dataset: merge per-page tallies site-wide, then run
113
+ * the deterministic checks. Read-only; degrades to `EMPTY_DESIGN_DATA`.
114
+ */
115
+ export async function getDesignData() {
116
+ if (!(await storeReady()))
117
+ return EMPTY_DESIGN_DATA;
118
+ const scans = await loadPageScans();
119
+ if (scans.length === 0)
120
+ return { ...EMPTY_DESIGN_DATA, ready: true };
121
+ // fonts: family|size|weight → merged usage
122
+ const fonts = new Map();
123
+ // colors: hex → role tallies
124
+ const colors = new Map();
125
+ const pairs = new Map();
126
+ const radii = new Map();
127
+ const spacing = new Map();
128
+ const addColor = (hex, role, count, page) => {
129
+ // Palette ignores alpha variants' suffix so #ffffff and #ffffffcc merge
130
+ // visually apart — keep them distinct only when fully opaque differs.
131
+ let c = colors.get(hex);
132
+ if (!c)
133
+ colors.set(hex, (c = { count: 0, roles: { text: 0, background: 0, border: 0 }, pages: new Set() }));
134
+ c.count += count;
135
+ c.roles[role] += count;
136
+ c.pages.add(page);
137
+ };
138
+ for (const scan of scans) {
139
+ const p = scan.payload;
140
+ const page = scan.path;
141
+ for (const [family, sizePx, weight, count, sample, tags] of p.fonts) {
142
+ if (typeof family !== "string" || !Number.isFinite(sizePx) || !Number.isFinite(count))
143
+ continue;
144
+ const key = `${family}|${sizePx}|${weight}`;
145
+ let f = fonts.get(key);
146
+ if (!f) {
147
+ fonts.set(key, (f = { family, sizePx, weight, count: 0, sample: "", tags: new Set(), pages: new Set() }));
148
+ }
149
+ f.count += count;
150
+ f.pages.add(page);
151
+ if (!f.sample && typeof sample === "string")
152
+ f.sample = sample;
153
+ if (typeof tags === "string") {
154
+ for (const t of tags.split(","))
155
+ if (t && f.tags.size < 6)
156
+ f.tags.add(t);
157
+ }
158
+ }
159
+ const roleLists = [
160
+ [p.text ?? [], "text"],
161
+ [p.bg ?? [], "background"],
162
+ [p.border ?? [], "border"],
163
+ ];
164
+ for (const [list, role] of roleLists) {
165
+ for (const [hex, count] of list) {
166
+ if (typeof hex !== "string" || !hex.startsWith("#"))
167
+ continue;
168
+ addColor(hex.toLowerCase(), role, toNum(count), page);
169
+ }
170
+ }
171
+ for (const [fg, bg, count, minSizePx] of p.pairs ?? []) {
172
+ if (typeof fg !== "string" || typeof bg !== "string")
173
+ continue;
174
+ const key = `${fg}|${bg}`;
175
+ let pr = pairs.get(key);
176
+ if (!pr)
177
+ pairs.set(key, (pr = { fg: fg.toLowerCase(), bg: bg.toLowerCase(), count: 0, minSizePx: Infinity, pages: new Set() }));
178
+ pr.count += toNum(count);
179
+ if (Number.isFinite(minSizePx) && minSizePx < pr.minSizePx)
180
+ pr.minSizePx = minSizePx;
181
+ pr.pages.add(page);
182
+ }
183
+ for (const [value, count] of p.radii ?? []) {
184
+ if (typeof value !== "string")
185
+ continue;
186
+ let rd = radii.get(value);
187
+ if (!rd)
188
+ radii.set(value, (rd = { count: 0, pages: new Set() }));
189
+ rd.count += toNum(count);
190
+ rd.pages.add(page);
191
+ }
192
+ for (const [px, count] of p.pad ?? []) {
193
+ if (!Number.isFinite(px))
194
+ continue;
195
+ let sp = spacing.get(px);
196
+ if (!sp)
197
+ spacing.set(px, (sp = { count: 0, pages: new Set() }));
198
+ sp.count += toNum(count);
199
+ sp.pages.add(page);
200
+ }
201
+ }
202
+ // ── Shape + outlier marking ────────────────────────────────────────────────
203
+ const totalFontTally = [...fonts.values()].reduce((s, f) => s + f.count, 0);
204
+ // With only a handful of measured elements, everything is statistically a
205
+ // one-off — the badge means nothing until there's enough signal to compare
206
+ // against, so outlier marking waits for a minimum sample per section.
207
+ const fontSignal = totalFontTally >= 60;
208
+ const fontList = [...fonts.values()]
209
+ .sort((a, b) => b.count - a.count)
210
+ .map((f) => ({
211
+ family: f.family,
212
+ sizePx: f.sizePx,
213
+ weight: f.weight,
214
+ count: f.count,
215
+ sample: f.sample || null,
216
+ tags: [...f.tags],
217
+ pages: [...f.pages],
218
+ // A combination carrying under ~1.5% of all text, on one page only, is a
219
+ // one-off — the kind of stray 17px that makes a type scale incoherent.
220
+ outlier: fontSignal && f.count <= Math.max(2, totalFontTally * 0.015) && f.pages.size === 1,
221
+ }));
222
+ const familyTally = new Map();
223
+ for (const f of fontList)
224
+ familyTally.set(f.family, (familyTally.get(f.family) ?? 0) + f.count);
225
+ const families = [...familyTally.entries()]
226
+ .map(([family, count]) => ({ family, count }))
227
+ .sort((a, b) => b.count - a.count);
228
+ const totalColorTally = [...colors.values()].reduce((s, c) => s + c.count, 0);
229
+ const colorSignal = totalColorTally >= 120;
230
+ const colorList = [...colors.entries()]
231
+ .sort((a, b) => b[1].count - a[1].count)
232
+ .map(([hex, c]) => ({
233
+ hex,
234
+ count: c.count,
235
+ roles: c.roles,
236
+ pages: [...c.pages],
237
+ pct: totalColorTally > 0 ? Math.round((c.count / totalColorTally) * 1000) / 10 : 0,
238
+ outlier: colorSignal && c.count <= Math.max(2, totalColorTally * 0.005) && c.pages.size === 1,
239
+ }))
240
+ .slice(0, 64);
241
+ const contrast = [...pairs.values()]
242
+ .map((pr) => {
243
+ const ratio = contrastRatio(pr.fg, pr.bg);
244
+ if (ratio === null)
245
+ return null;
246
+ const minSizePx = Number.isFinite(pr.minSizePx) ? pr.minSizePx : 16;
247
+ // WCAG AA: 3.0 for large text (≥24px, or ≥18.66px bold — we lack the
248
+ // bold flag post-merge, so use the conservative 24px cut), else 4.5.
249
+ const required = minSizePx >= 24 ? 3 : 4.5;
250
+ return {
251
+ fg: pr.fg,
252
+ bg: pr.bg,
253
+ count: pr.count,
254
+ minSizePx,
255
+ ratio,
256
+ required,
257
+ passes: ratio >= required,
258
+ pages: [...pr.pages],
259
+ };
260
+ })
261
+ .filter((p) => p !== null)
262
+ .sort((a, b) => Number(a.passes) - Number(b.passes) || a.ratio - b.ratio);
263
+ const totalRadiusTally = [...radii.values()].reduce((s, r) => s + r.count, 0);
264
+ const radiusSignal = totalRadiusTally >= 20;
265
+ const radiusList = [...radii.entries()]
266
+ .sort((a, b) => b[1].count - a[1].count)
267
+ .map(([value, r]) => ({
268
+ value,
269
+ count: r.count,
270
+ pages: [...r.pages],
271
+ outlier: radiusSignal &&
272
+ value !== "full" &&
273
+ r.count <= Math.max(2, totalRadiusTally * 0.02) &&
274
+ r.pages.size === 1,
275
+ }));
276
+ // Dominant grid: 4px unless the site's own values say 8px.
277
+ const spacingEntries = [...spacing.entries()].sort((a, b) => b[1].count - a[1].count);
278
+ const spacingList = spacingEntries.map(([px, s]) => ({
279
+ px,
280
+ count: s.count,
281
+ pages: [...s.pages],
282
+ offGrid: px % 4 !== 0,
283
+ }));
284
+ const data = {
285
+ ready: true,
286
+ pages: scans.map((s) => ({ path: s.path, scannedAt: s.ts, elements: s.payload.el })),
287
+ fonts: fontList,
288
+ families,
289
+ colors: colorList,
290
+ contrast,
291
+ radii: radiusList,
292
+ spacing: spacingList,
293
+ flags: [],
294
+ };
295
+ data.flags = buildDesignFlags(data);
296
+ return data;
297
+ }
298
+ // ── Deterministic review ─────────────────────────────────────────────────────
299
+ /** Cohesion thresholds, drawn from common design-system practice. */
300
+ const LIMITS = {
301
+ fontSizes: 8, // a considered type scale is 5–8 steps
302
+ fontFamilies: 2, // one for UI, maybe one display/mono
303
+ fontWeights: 4,
304
+ colors: 24, // full palette incl. neutrals
305
+ radii: 4, // distinct corner radii (excluding 0/full)
306
+ spacingValues: 14,
307
+ };
308
+ /**
309
+ * Rule-based checks over the aggregated data. Pure function of its input so
310
+ * it's easy to test and the AI layer can quote the same findings verbatim.
311
+ */
312
+ export function buildDesignFlags(data) {
313
+ const flags = [];
314
+ if (data.pages.length === 0)
315
+ return flags;
316
+ // Typography ---------------------------------------------------------------
317
+ const sizes = [...new Set(data.fonts.map((f) => f.sizePx))].sort((a, b) => a - b);
318
+ if (sizes.length > LIMITS.fontSizes) {
319
+ flags.push({
320
+ section: "typography",
321
+ severity: "warn",
322
+ message: `${sizes.length} distinct font sizes in use (${sizes.slice(0, 12).map((s) => `${s}px`).join(", ")}${sizes.length > 12 ? ", …" : ""}). ` +
323
+ `Cohesive interfaces usually hold to a 5–8 step type scale; sizes 1–2px apart read as accidental.`,
324
+ });
325
+ }
326
+ if (data.families.length > LIMITS.fontFamilies) {
327
+ // A mono family for code is normal; flag only when even ignoring one
328
+ // there are still too many.
329
+ const nonMono = data.families.filter((f) => !/mono|courier|consolas|menlo/i.test(f.family));
330
+ if (nonMono.length > LIMITS.fontFamilies) {
331
+ flags.push({
332
+ section: "typography",
333
+ severity: "warn",
334
+ message: `${data.families.length} font families detected (${data.families.map((f) => f.family).join(", ")}). ` +
335
+ `Most coherent brands use one or two, plus a monospace for code.`,
336
+ });
337
+ }
338
+ }
339
+ const weights = [...new Set(data.fonts.map((f) => f.weight))].sort((a, b) => a - b);
340
+ if (weights.length > LIMITS.fontWeights) {
341
+ flags.push({
342
+ section: "typography",
343
+ severity: "info",
344
+ message: `${weights.length} font weights in use (${weights.join(", ")}) — hierarchies usually need 3–4.`,
345
+ });
346
+ }
347
+ const tiny = data.fonts.filter((f) => f.sizePx < 11 && f.count >= 3);
348
+ if (tiny.length > 0) {
349
+ flags.push({
350
+ section: "typography",
351
+ severity: "warn",
352
+ message: `Text below 11px found (${[...new Set(tiny.map((t) => `${t.sizePx}px`))].join(", ")}) — hard to read on most screens.`,
353
+ });
354
+ }
355
+ const oneOffs = data.fonts.filter((f) => f.outlier);
356
+ if (oneOffs.length >= 3) {
357
+ flags.push({
358
+ section: "typography",
359
+ severity: "info",
360
+ message: `${oneOffs.length} one-off type styles appear on a single page each — candidates to fold into the scale.`,
361
+ });
362
+ }
363
+ // Color ----------------------------------------------------------------------
364
+ const opaque = data.colors.filter((c) => c.hex.length === 7);
365
+ if (opaque.length > LIMITS.colors) {
366
+ flags.push({
367
+ section: "color",
368
+ severity: "warn",
369
+ message: `${opaque.length} distinct colors rendered site-wide — palettes past ~${LIMITS.colors} usually mean drifted one-off values rather than a system.`,
370
+ });
371
+ }
372
+ // Near-duplicates among the meaningfully used colors.
373
+ const used = opaque.filter((c) => c.count >= 3).slice(0, 32);
374
+ const dupes = [];
375
+ for (let i = 0; i < used.length; i++) {
376
+ for (let j = i + 1; j < used.length; j++) {
377
+ const d = colorDistance(used[i].hex, used[j].hex);
378
+ if (d !== null && d > 0 && d < 10)
379
+ dupes.push(`${used[i].hex} ≈ ${used[j].hex}`);
380
+ }
381
+ }
382
+ if (dupes.length > 0) {
383
+ flags.push({
384
+ section: "color",
385
+ severity: "warn",
386
+ message: `Near-duplicate colors: ${dupes.slice(0, 4).join("; ")}${dupes.length > 4 ? ` (+${dupes.length - 4} more)` : ""} — likely meant to be the same token.`,
387
+ });
388
+ }
389
+ // Contrast -------------------------------------------------------------------
390
+ const failing = data.contrast.filter((p) => !p.passes && p.count >= 2);
391
+ for (const p of failing.slice(0, 5)) {
392
+ flags.push({
393
+ section: "contrast",
394
+ severity: "warn",
395
+ message: `${p.fg} text on ${p.bg} is ${p.ratio}:1 at ${p.minSizePx}px — below the WCAG AA ${p.required}:1 minimum` +
396
+ ` (seen on ${p.pages.slice(0, 2).join(", ")}${p.pages.length > 2 ? ", …" : ""}).`,
397
+ });
398
+ }
399
+ // Radius ----------------------------------------------------------------------
400
+ const distinctRadii = data.radii.filter((r) => r.value !== "full" && !r.value.includes("mixed"));
401
+ if (distinctRadii.length > LIMITS.radii) {
402
+ flags.push({
403
+ section: "radius",
404
+ severity: "warn",
405
+ message: `${distinctRadii.length} distinct corner radii (${distinctRadii.slice(0, 8).map((r) => r.value).join(", ")}${distinctRadii.length > 8 ? ", …" : ""}) — ` +
406
+ `most systems settle on 2–4 (e.g. small, medium, full).`,
407
+ });
408
+ }
409
+ // Spacing ----------------------------------------------------------------------
410
+ const totalPad = data.spacing.reduce((s, v) => s + v.count, 0);
411
+ const offGrid = data.spacing.filter((v) => v.offGrid);
412
+ const offGridShare = totalPad > 0 ? offGrid.reduce((s, v) => s + v.count, 0) / totalPad : 0;
413
+ if (offGridShare > 0.25 && offGrid.length >= 3) {
414
+ flags.push({
415
+ section: "spacing",
416
+ severity: "info",
417
+ message: `${Math.round(offGridShare * 100)}% of padding values sit off a 4px grid (${offGrid.slice(0, 6).map((v) => `${v.px}px`).join(", ")}${offGrid.length > 6 ? ", …" : ""}) — ` +
418
+ `consistent rhythm usually comes from a 4px or 8px base unit.`,
419
+ });
420
+ }
421
+ if (data.spacing.length > LIMITS.spacingValues) {
422
+ flags.push({
423
+ section: "spacing",
424
+ severity: "info",
425
+ message: `${data.spacing.length} distinct padding values in use — a spacing scale of 6–10 steps covers most interfaces.`,
426
+ });
427
+ }
428
+ return flags;
429
+ }
430
+ //# sourceMappingURL=design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design.js","sourceRoot":"","sources":["../../src/server/design.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,28 @@
1
+ /**
2
+ * Polishd — ingest (server only).
3
+ *
4
+ * Validates and sanitizes an inbound batch, then hands clean events to the
5
+ * store. The session id is taken from the request's cookie, never from the
6
+ * body, so events are always attributed to the bearer of the cookie.
7
+ */
8
+ import { type PolishdConfig } from "../config";
9
+ /**
10
+ * Beacons dropped for want of a session cookie, recorded in `polishd_meta`.
11
+ *
12
+ * A missing cookie means the proxy isn't running — a misnamed proxy file, a
13
+ * matcher that excludes the path, or a botched merge with an existing handler.
14
+ * Left uncounted this is the worst failure mode in the package: ingest returns
15
+ * `ok: true` with HTTP 200, the client cannot read the reason (`sendBeacon`
16
+ * has no response), and the only symptom is an empty dashboard with nothing
17
+ * anywhere pointing at the cause. Counting it is what lets the dashboard say
18
+ * so out loud.
19
+ */
20
+ export declare const NO_SESSION_META_KEY = "no_session_rejects";
21
+ /** Shape stored under {@link NO_SESSION_META_KEY}. */
22
+ export interface NoSessionRecord {
23
+ count: number;
24
+ lastAt: number;
25
+ }
1
26
  export interface IngestResult {
2
27
  ok: boolean;
3
28
  stored: number;
@@ -9,6 +34,12 @@ export interface IngestResult {
9
34
  * legitimately has none, in which case we skip rather than error.
10
35
  */
11
36
  export declare function sessionIdFromCookie(cookieValue: string | undefined): string | null;
12
- /** Ingest a raw request body. `cookieValue` is the session cookie's value. */
13
- export declare function ingest(body: unknown, cookieValue: string | undefined): Promise<IngestResult>;
37
+ /**
38
+ * Ingest a raw request body. `cookieValue` is the session cookie's value.
39
+ *
40
+ * `config` carries the host app's overrides (the route handler forwards what it
41
+ * was built with) so `enabled` and `dashboardRoute` mean the same thing here as
42
+ * they do in the browser. It falls back to the defaults when omitted.
43
+ */
44
+ export declare function ingest(body: unknown, cookieValue: string | undefined, config?: Partial<PolishdConfig>): Promise<IngestResult>;
14
45
  //# sourceMappingURL=ingest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAElF;AAiCD,8EAA8E;AAC9E,wBAAsB,MAAM,CAC1B,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,YAAY,CAAC,CAevB"}
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAyC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAetF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAExD,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AA+BD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAElF;AAkCD;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,MAAM,GAAE,OAAO,CAAC,aAAa,CAAM,GAClC,OAAO,CAAC,YAAY,CAAC,CAwBvB"}
@@ -5,12 +5,61 @@
5
5
  * store. The session id is taken from the request's cookie, never from the
6
6
  * body, so events are always attributed to the bearer of the cookie.
7
7
  */
8
- import { defaultPolishdConfig } from "../config";
8
+ import { defaultPolishdConfig, isDashboardPath } from "../config";
9
9
  import { CLIENT_EVENT_TYPES } from "../shared/types";
10
- import { insertEvents } from "./store";
10
+ import { getMeta, insertEvents, setMeta } from "./store";
11
11
  const MAX_EVENTS_PER_BATCH = 200;
12
12
  const MAX_TEXT_LEN = 120;
13
13
  const MAX_SELECTOR_LEN = 300;
14
+ /**
15
+ * `design_scan` events carry a whole page's design tallies as one JSON string
16
+ * in `meta.payload`, so that one key gets a wider (but still bounded) cap than
17
+ * the 500 chars every other meta value is clamped to. The client serializes to
18
+ * ≤28KB (see client/design-scan.ts); anything beyond this is not ours.
19
+ */
20
+ const MAX_DESIGN_PAYLOAD_LEN = 32_000;
21
+ /**
22
+ * Beacons dropped for want of a session cookie, recorded in `polishd_meta`.
23
+ *
24
+ * A missing cookie means the proxy isn't running — a misnamed proxy file, a
25
+ * matcher that excludes the path, or a botched merge with an existing handler.
26
+ * Left uncounted this is the worst failure mode in the package: ingest returns
27
+ * `ok: true` with HTTP 200, the client cannot read the reason (`sendBeacon`
28
+ * has no response), and the only symptom is an empty dashboard with nothing
29
+ * anywhere pointing at the cause. Counting it is what lets the dashboard say
30
+ * so out loud.
31
+ */
32
+ export const NO_SESSION_META_KEY = "no_session_rejects";
33
+ // A broken proxy means *every* beacon is rejected, so writing per rejection
34
+ // would answer a diagnostic with a write storm. Collapse them instead: count
35
+ // in memory, persist at most once a minute.
36
+ const RECORD_INTERVAL_MS = 60_000;
37
+ let lastPersistedAt = 0;
38
+ let unpersisted = 0;
39
+ async function recordNoSession() {
40
+ unpersisted++;
41
+ const now = Date.now();
42
+ if (now - lastPersistedAt < RECORD_INTERVAL_MS)
43
+ return;
44
+ lastPersistedAt = now;
45
+ const batch = unpersisted;
46
+ unpersisted = 0;
47
+ try {
48
+ let count = 0;
49
+ const raw = await getMeta(NO_SESSION_META_KEY);
50
+ if (raw) {
51
+ const prev = JSON.parse(raw);
52
+ if (typeof prev.count === "number" && Number.isFinite(prev.count))
53
+ count = prev.count;
54
+ }
55
+ const next = { count: count + batch, lastAt: now };
56
+ await setMeta(NO_SESSION_META_KEY, JSON.stringify(next));
57
+ }
58
+ catch {
59
+ // Diagnostics must never take ingest down with them. A store that can't
60
+ // record the rejection still returns a clean result to the beacon.
61
+ }
62
+ }
14
63
  /**
15
64
  * Pull the anonymous session id from the cookie. Returns null when absent —
16
65
  * the middleware normally guarantees one, but a client with cookies disabled
@@ -44,8 +93,9 @@ function sanitize(raw) {
44
93
  for (const [k, v] of Object.entries(e.meta)) {
45
94
  if (k.length > 40)
46
95
  continue;
96
+ const cap = out.type === "design_scan" && k === "payload" ? MAX_DESIGN_PAYLOAD_LEN : 500;
47
97
  if (typeof v === "string")
48
- meta[k] = v.slice(0, 500);
98
+ meta[k] = v.slice(0, cap);
49
99
  else if (typeof v === "number" || typeof v === "boolean" || v === null)
50
100
  meta[k] = v;
51
101
  }
@@ -53,20 +103,32 @@ function sanitize(raw) {
53
103
  }
54
104
  return out;
55
105
  }
56
- /** Ingest a raw request body. `cookieValue` is the session cookie's value. */
57
- export async function ingest(body, cookieValue) {
106
+ /**
107
+ * Ingest a raw request body. `cookieValue` is the session cookie's value.
108
+ *
109
+ * `config` carries the host app's overrides (the route handler forwards what it
110
+ * was built with) so `enabled` and `dashboardRoute` mean the same thing here as
111
+ * they do in the browser. It falls back to the defaults when omitted.
112
+ */
113
+ export async function ingest(body, cookieValue, config = {}) {
114
+ const cfg = { ...defaultPolishdConfig, ...config };
58
115
  const sessionId = sessionIdFromCookie(cookieValue);
59
- if (!sessionId)
116
+ if (!sessionId) {
117
+ await recordNoSession();
60
118
  return { ok: true, stored: 0, reason: "no_session" };
61
- if (!defaultPolishdConfig.enabled)
119
+ }
120
+ if (!cfg.enabled)
62
121
  return { ok: true, stored: 0, reason: "disabled" };
63
122
  const rawEvents = body?.events;
64
123
  if (!Array.isArray(rawEvents))
65
124
  return { ok: false, stored: 0, reason: "no_events" };
125
+ // The client already drops the dashboard's own traffic; this is the copy of
126
+ // that rule that actually holds, since the endpoint is a public POST and a
127
+ // cached client bundle can lag a config change by a full session.
66
128
  const events = rawEvents
67
129
  .slice(0, MAX_EVENTS_PER_BATCH)
68
130
  .map(sanitize)
69
- .filter((e) => e !== null);
131
+ .filter((e) => e !== null && !isDashboardPath(e.path, cfg.dashboardRoute));
70
132
  const stored = await insertEvents(sessionId, events);
71
133
  return { ok: true, stored };
72
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"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"}