@polishd/next 0.1.0 → 0.2.1-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +161 -46
- package/CHANGELOG.md +163 -0
- package/README.md +275 -49
- package/bin/doctor.mjs +374 -0
- package/bin/init.mjs +215 -188
- package/bin/polishd.mjs +85 -0
- package/bin/shared.mjs +114 -0
- package/dist/ai/actions.d.ts +13 -2
- package/dist/ai/actions.d.ts.map +1 -1
- package/dist/ai/actions.js +19 -1
- package/dist/ai/actions.js.map +1 -1
- package/dist/ai/design.d.ts +28 -0
- package/dist/ai/design.d.ts.map +1 -0
- package/dist/ai/design.js +245 -0
- package/dist/ai/design.js.map +1 -0
- package/dist/ai/guard.d.ts +44 -3
- package/dist/ai/guard.d.ts.map +1 -1
- package/dist/ai/guard.js +13 -2
- package/dist/ai/guard.js.map +1 -1
- package/dist/ai/providers.d.ts +2 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +70 -0
- package/dist/ai/providers.js.map +1 -1
- package/dist/ai/settings.d.ts +15 -1
- package/dist/ai/settings.d.ts.map +1 -1
- package/dist/ai/settings.js +38 -1
- package/dist/ai/settings.js.map +1 -1
- package/dist/ai/types.d.ts +57 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/client/design-scan.d.ts +37 -0
- package/dist/client/design-scan.d.ts.map +1 -0
- package/dist/client/design-scan.js +319 -0
- package/dist/client/design-scan.js.map +1 -0
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/init.js +67 -2
- package/dist/client/init.js.map +1 -1
- package/dist/config.d.ts +57 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +62 -3
- package/dist/config.js.map +1 -1
- package/dist/dashboard/design-review-card.d.ts +15 -0
- package/dist/dashboard/design-review-card.d.ts.map +1 -0
- package/dist/dashboard/design-review-card.js +82 -0
- package/dist/dashboard/design-review-card.js.map +1 -0
- package/dist/dashboard/design.d.ts +10 -0
- package/dist/dashboard/design.d.ts.map +1 -0
- package/dist/dashboard/design.js +83 -0
- package/dist/dashboard/design.js.map +1 -0
- package/dist/dashboard/index.d.ts +35 -5
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +176 -18
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/journeys.d.ts.map +1 -1
- package/dist/dashboard/journeys.js +1 -0
- package/dist/dashboard/journeys.js.map +1 -1
- package/dist/dashboard/summary.d.ts.map +1 -1
- package/dist/dashboard/summary.js +59 -5
- package/dist/dashboard/summary.js.map +1 -1
- package/dist/dashboard/token-auth.d.ts +56 -0
- package/dist/dashboard/token-auth.d.ts.map +1 -0
- package/dist/dashboard/token-auth.js +158 -0
- package/dist/dashboard/token-auth.js.map +1 -0
- package/dist/dashboard/unlock.d.ts +2 -0
- package/dist/dashboard/unlock.d.ts.map +1 -0
- package/dist/dashboard/unlock.js +54 -0
- package/dist/dashboard/unlock.js.map +1 -0
- package/dist/dashboard.css +2740 -0
- package/dist/proxy.d.ts +53 -5
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +73 -3
- package/dist/proxy.js.map +1 -1
- package/dist/route.d.ts.map +1 -1
- package/dist/route.js +11 -3
- package/dist/route.js.map +1 -1
- package/dist/server/design.d.ts +93 -0
- package/dist/server/design.d.ts.map +1 -0
- package/dist/server/design.js +430 -0
- package/dist/server/design.js.map +1 -0
- package/dist/server/ingest.d.ts +33 -2
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +70 -8
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/queries.d.ts +16 -0
- package/dist/server/queries.d.ts.map +1 -1
- package/dist/server/queries.js +80 -16
- package/dist/server/queries.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +4 -2
- package/dist/session.js.map +1 -1
- package/dist/shared/types.d.ts +31 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +1 -0
- package/dist/shared/types.js.map +1 -1
- package/docs/SETUP.md +308 -51
- package/package.json +14 -7
|
@@ -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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"}
|
package/dist/server/ingest.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
13
|
-
|
|
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":"
|
|
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"}
|
package/dist/server/ingest.js
CHANGED
|
@@ -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,
|
|
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
|
-
/**
|
|
57
|
-
|
|
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
|
-
|
|
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":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"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"}
|