@polishd/next 0.2.0 → 0.2.1-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +49 -2
  3. package/dist/ai/actions.d.ts +7 -1
  4. package/dist/ai/actions.d.ts.map +1 -1
  5. package/dist/ai/actions.js +10 -0
  6. package/dist/ai/actions.js.map +1 -1
  7. package/dist/ai/design.d.ts +28 -0
  8. package/dist/ai/design.d.ts.map +1 -0
  9. package/dist/ai/design.js +245 -0
  10. package/dist/ai/design.js.map +1 -0
  11. package/dist/ai/types.d.ts +47 -0
  12. package/dist/ai/types.d.ts.map +1 -1
  13. package/dist/client/design-scan.d.ts +37 -0
  14. package/dist/client/design-scan.d.ts.map +1 -0
  15. package/dist/client/design-scan.js +325 -0
  16. package/dist/client/design-scan.js.map +1 -0
  17. package/dist/client/init.d.ts.map +1 -1
  18. package/dist/client/init.js +16 -0
  19. package/dist/client/init.js.map +1 -1
  20. package/dist/config.d.ts +12 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +34 -0
  23. package/dist/config.js.map +1 -1
  24. package/dist/dashboard/design-review-card.d.ts +15 -0
  25. package/dist/dashboard/design-review-card.d.ts.map +1 -0
  26. package/dist/dashboard/design-review-card.js +82 -0
  27. package/dist/dashboard/design-review-card.js.map +1 -0
  28. package/dist/dashboard/design.d.ts +10 -0
  29. package/dist/dashboard/design.d.ts.map +1 -0
  30. package/dist/dashboard/design.js +118 -0
  31. package/dist/dashboard/design.js.map +1 -0
  32. package/dist/dashboard/index.d.ts +11 -0
  33. package/dist/dashboard/index.d.ts.map +1 -1
  34. package/dist/dashboard/index.js +58 -8
  35. package/dist/dashboard/index.js.map +1 -1
  36. package/dist/dashboard/journeys.d.ts.map +1 -1
  37. package/dist/dashboard/journeys.js +1 -0
  38. package/dist/dashboard/journeys.js.map +1 -1
  39. package/dist/dashboard/show-more.d.ts +17 -0
  40. package/dist/dashboard/show-more.d.ts.map +1 -0
  41. package/dist/dashboard/show-more.js +18 -0
  42. package/dist/dashboard/show-more.js.map +1 -0
  43. package/dist/dashboard.css +159 -0
  44. package/dist/server/design.d.ts +93 -0
  45. package/dist/server/design.d.ts.map +1 -0
  46. package/dist/server/design.js +405 -0
  47. package/dist/server/design.js.map +1 -0
  48. package/dist/server/ingest.d.ts.map +1 -1
  49. package/dist/server/ingest.js +15 -3
  50. package/dist/server/ingest.js.map +1 -1
  51. package/dist/server/queries.d.ts +22 -0
  52. package/dist/server/queries.d.ts.map +1 -1
  53. package/dist/server/queries.js +22 -25
  54. package/dist/server/queries.js.map +1 -1
  55. package/dist/shared/types.d.ts +31 -1
  56. package/dist/shared/types.d.ts.map +1 -1
  57. package/dist/shared/types.js +1 -0
  58. package/dist/shared/types.js.map +1 -1
  59. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ### A dashboard mounted off `/polishd` no longer reads itself back
6
+
7
+ The read side of the dashboard (its own queries) previously excluded only the
8
+ *default* route, so an app that mounted the dashboard elsewhere — say
9
+ `/polish` — saw its own dashboard traffic ranked among the site's pages, and
10
+ the Design tab could measure the dashboard's design as the site's. The route
11
+ now resolves dynamically everywhere:
12
+
13
+ - `createPolishdPage({ config })` — pass the same config you thread into
14
+ `initPolishd()` and `createPolishdRoute()`; the page registers
15
+ `dashboardRoute` for the queries running in the same runtime.
16
+ - `POLISHD_DASHBOARD_ROUTE` — the no-code channel; ingest and the queries
17
+ both read it.
18
+ - The design scanner also recognizes the dashboard by its `.polishd-root`
19
+ scope element, so it never measures the dashboard regardless of
20
+ configuration.
21
+
22
+ Because the exclusion is applied at read time, dashboard rows captured before
23
+ this fix disappear from the stats without touching the database.
24
+
25
+ ### Quieter, truer signals
26
+
27
+ - The "your proxy isn't running" banner no longer fires over a handful of
28
+ cookie-less batches on a site that is otherwise storing events fine (bots,
29
+ a cookie-blocking visitor, someone's curl test). It now requires the drops
30
+ to be recent **and** material relative to stored volume — or a store with
31
+ nothing in it at all, which is the real broken-proxy signature.
32
+ - The Design tab's per-section findings render as one combined note instead
33
+ of a stack of alert boxes.
34
+ - Long lists (type styles, colors, contrast failures, radii, spacing, pages)
35
+ collapse behind "Show more" past 5 rows.
36
+ - Dashboard tabs navigate client-side — switching tabs swaps the content
37
+ without reloading the whole page.
38
+
39
+ ### Design review — a new dashboard tab
40
+
41
+ The dashboard now has a sidebar with two tabs: **Analytics** (everything it
42
+ showed before) and **Design**, a new aesthetic layer. The Design tab renders
43
+ your site's brand guideline *reverse-engineered from the rendered pages*:
44
+
45
+ - **Typography** — every font family / size / weight combination actually in
46
+ use, shown as specimens with real text sampled from the site.
47
+ - **Color palette** — the colors your pages paint, laid out like a brand
48
+ palette and weighted by how much of the UI they cover, split by role
49
+ (text / background / border).
50
+ - **Contrast** — every text-on-background pairing measured against WCAG AA,
51
+ failures shown with live samples.
52
+ - **Corner radii and spacing** — the rounding and padding scales in use.
53
+
54
+ Each section flags what breaks the system deterministically — no model
55
+ involved: font-size sprawl (16 sizes is not a type scale), extra families,
56
+ near-duplicate colors (`#fefefe` next to `#ffffff`), contrast failures,
57
+ one-off values that appear on a single page, and padding that drifts off a
58
+ 4px grid.
59
+
60
+ On top of the measured metrics, an **Aesthetic review** card asks the same
61
+ model you configured for the analytics summary how coherent the design reads
62
+ — strengths, and evidence-cited issues with concrete fixes. Like the summary,
63
+ it is fingerprint-cached: reopening the tab is free, and regenerating with
64
+ unchanged metrics never spends tokens. Refresh buttons for both the metrics
65
+ and the analysis sit on the tab.
66
+
67
+ Nothing new to wire up: pages measure their own rendered design once per
68
+ visitor session (a compact few-KB tally, sampled after the page settles) and
69
+ the data flows through the existing ingest route and store. The dashboard
70
+ itself is never scanned. Existing installs only need the new package version.
71
+
3
72
  ## 0.2.0
4
73
 
5
74
  Everything in this release came out of the first real third-party integration of
package/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  Drop-in **product analytics** for Next.js (App Router). Capture real user
6
6
  behavioral signals — rage clicks, dead clicks, scroll depth, JS errors, web
7
7
  vitals, component engagement — and explore them on a built-in, Vercel-style
8
- dashboard at `/polishd`.
8
+ dashboard at `/polishd`. A second **Design** tab reverse-engineers your site's
9
+ design system from the rendered pages — typography, palette, radii, spacing —
10
+ and flags what breaks it.
9
11
 
10
12
  No third-party service. Events go to your own database: **SQLite in dev**
11
13
  (zero-config, built into Node), **Postgres in production**. Anonymous by design —
@@ -360,6 +362,40 @@ authoritative context, so:
360
362
  > provide. Your source is read only during an explicit scan (or a targeted
361
363
  > gap-fill), and always under strict size budgets.
362
364
 
365
+ ## Design review
366
+
367
+ The dashboard's **Design** tab (in the sidebar) is your site's brand guideline
368
+ reverse-engineered from what actually shipped. Every page measures its own
369
+ rendered design once per visitor session — the computed typography, colors,
370
+ corner radii and padding, reduced to a compact tally of a few KB — and the tab
371
+ assembles the site-wide picture:
372
+
373
+ - **Typography** — every font family / size / weight combination in use,
374
+ rendered as specimens with real text from your pages.
375
+ - **Color palette** — swatches weighted by how much of the UI each color
376
+ covers, split by role (text / background / border).
377
+ - **Contrast** — each text-on-background pairing checked against WCAG AA.
378
+ - **Corner radii & spacing** — the rounding and padding scales in use.
379
+
380
+ The first layer of review is **deterministic** — rule-based checks, no model:
381
+ too many font sizes to be a type scale, extra font families, near-duplicate
382
+ colors (`#fefefe` living next to `#ffffff`), contrast failures with the exact
383
+ ratio, one-off values that appear on a single page, padding off the 4px grid.
384
+
385
+ The second layer is the **Aesthetic review** card: the same model you
386
+ configured for the analytics summary reads the measured tokens and findings
387
+ and answers the real question — does this read as one deliberate design, or an
388
+ accumulation of one-off decisions? Issues it raises must cite a token that
389
+ actually appears in the metrics (a hex value, a px size, a page path), and each
390
+ comes with a concrete fix. It follows the summary's token-thrift rules: the
391
+ model sees a few hundred tokens of aggregated metrics, the result is cached and
392
+ fingerprinted, and regenerating over unchanged metrics is free. Refresh buttons
393
+ for both the metrics and the analysis are on the tab.
394
+
395
+ Nothing extra to install or wire: the scan ships with the existing client,
396
+ flows through the existing ingest route, and the dashboard itself is never
397
+ scanned.
398
+
363
399
  ## Component-level tracking
364
400
 
365
401
  Wrap any element to track it explicitly. Interactive controls get hover + click
@@ -393,7 +429,17 @@ export default definePolishdConfig({
393
429
 
394
430
  The dashboard never measures itself: nothing on `/polishd` (or below it) is
395
431
  captured, so reading your analytics can't show up as your site's most-used
396
- feature. Moved the dashboard elsewhere? Set `dashboardRoute`.
432
+ feature. **Moved the dashboard elsewhere?** Set `dashboardRoute` in your
433
+ config, thread the config into the client and route as shown below, and also
434
+ pass it to the page — the dashboard's own queries have no other way to learn
435
+ where they're mounted:
436
+
437
+ ```tsx
438
+ export default createPolishdPage({ config: polishdConfig });
439
+ ```
440
+
441
+ (`POLISHD_DASHBOARD_ROUTE` is the no-code alternative: ingest and the
442
+ dashboard's queries both read it.)
397
443
 
398
444
  > **Nothing imports this file for you.** It cannot be auto-loaded: the three
399
445
  > places that read config run in three different runtimes — the browser, the
@@ -428,6 +474,7 @@ POLISHD_SESSION_COOKIE=my_session
428
474
  | `POLISHD_DASHBOARD_TOKEN` | production | Protects `/polishd` with the built-in gate. Generate with `openssl rand -hex 32` |
429
475
  | `POLISHD_DASHBOARD_PUBLIC` | production (optional) | `true` — serve the dashboard unguarded on purpose |
430
476
  | `POLISHD_SESSION_COOKIE` | optional | Rename the anonymous session cookie. Read by both the proxy and ingest, so they cannot disagree |
477
+ | `POLISHD_DASHBOARD_ROUTE` | optional | Where the dashboard is mounted, if not `/polishd`. Read by ingest and the dashboard's queries so its own traffic is excluded; `createPolishdPage({ config })` is the code-side equivalent |
431
478
  | `POLISHD_AI_PROVIDER` | AI (optional) | `anthropic` \| `openai` \| `openai-compatible` \| `google` |
432
479
  | `POLISHD_AI_MODEL` | AI (optional) | Model id (defaults per provider, e.g. `claude-opus-4-8`) |
433
480
  | `POLISHD_AI_API_KEY` | AI (optional) | Model API key — preset instead of using the dashboard |
@@ -1,7 +1,13 @@
1
1
  import { type ListModelsInput, type SaveAISettingsInput } from "./settings";
2
- import type { PolishdAISettingsPublic, PolishdLossItem, CreateIssueResult, GenerateProfileResult, GenerateSummaryResult, ListModelsResult, VerifyGithubResult } from "./types";
2
+ import type { PolishdAISettingsPublic, PolishdLossItem, CreateIssueResult, GenerateDesignReviewResult, GenerateProfileResult, GenerateSummaryResult, ListModelsResult, VerifyGithubResult } from "./types";
3
3
  /** Generate or refresh the narrative. `force` re-asks the model even if data is unchanged. */
4
4
  export declare function generateSummaryAction(force?: boolean): Promise<GenerateSummaryResult>;
5
+ /**
6
+ * Generate or refresh the aesthetic design review (the Design tab). Uses the
7
+ * same configured provider/model as the summary. `force` re-asks the model
8
+ * even when the design metrics haven't changed.
9
+ */
10
+ export declare function generateDesignReviewAction(force?: boolean): Promise<GenerateDesignReviewResult>;
5
11
  /** Persist provider/model/key/instructions/context; returns the no-secret view. */
6
12
  export declare function saveAISettingsAction(input: SaveAISettingsInput): Promise<PolishdAISettingsPublic>;
7
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/ai/actions.ts"],"names":[],"mappings":"AAoBA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EACV,uBAAuB,EACvB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,8FAA8F;AAC9F,wBAAsB,qBAAqB,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAGzF;AAED,mFAAmF;AACnF,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAGlC;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAI5E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGxF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAGtE;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC,GAC7D,OAAO,CAAC,iBAAiB,CAAC,CAG5B"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/ai/actions.ts"],"names":[],"mappings":"AAqBA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EACV,uBAAuB,EACvB,eAAe,EACf,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,8FAA8F;AAC9F,wBAAsB,qBAAqB,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAGzF;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,UAAQ,GACZ,OAAO,CAAC,0BAA0B,CAAC,CAGrC;AAED,mFAAmF;AACnF,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAGlC;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAI5E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGxF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAGtE;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC,GAC7D,OAAO,CAAC,iBAAiB,CAAC,CAG5B"}
@@ -13,6 +13,7 @@
13
13
  * re-runs that same callback against the caller's cookies on every call.
14
14
  */
15
15
  import { requirePolishdAuth } from "./guard";
16
+ import { generateDesignReview } from "./design";
16
17
  import { verifyGithubConnection } from "./github";
17
18
  import { createIssueFromLoss } from "./issues";
18
19
  import { generateProjectProfile } from "./profile";
@@ -24,6 +25,15 @@ export async function generateSummaryAction(force = false) {
24
25
  await requirePolishdAuth();
25
26
  return generateSummary({ force });
26
27
  }
28
+ /**
29
+ * Generate or refresh the aesthetic design review (the Design tab). Uses the
30
+ * same configured provider/model as the summary. `force` re-asks the model
31
+ * even when the design metrics haven't changed.
32
+ */
33
+ export async function generateDesignReviewAction(force = false) {
34
+ await requirePolishdAuth();
35
+ return generateDesignReview({ force });
36
+ }
27
37
  /** Persist provider/model/key/instructions/context; returns the no-secret view. */
28
38
  export async function saveAISettingsAction(input) {
29
39
  await requirePolishdAuth();
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/ai/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,cAAc,GAGf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAW5C,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAK,GAAG,KAAK;IACvD,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA0B;IAE1B,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,kBAAkB,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,wBAAwB,EAAE,CAAC;IAC9C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAsB;IAC3D,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,sBAAsB,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAA8D;IAE9D,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/ai/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EACL,qBAAqB,EACrB,cAAc,GAGf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAY5C,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAK,GAAG,KAAK;IACvD,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,KAAK,GAAG,KAAK;IAEb,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA0B;IAE1B,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,kBAAkB,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,wBAAwB,EAAE,CAAC;IAC9C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAsB;IAC3D,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,sBAAsB,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAA8D;IAE9D,MAAM,kBAAkB,EAAE,CAAC;IAC3B,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type PolishdDesignData } from "../server/design";
2
+ import type { GenerateDesignReviewResult, PolishdDesignReview } from "./types";
3
+ /** The last generated review, or null. Read-only — never calls a model. */
4
+ export declare function loadDesignReview(): Promise<PolishdDesignReview | null>;
5
+ /**
6
+ * Render the aggregated design data as a terse, model-readable brief.
7
+ * Sections are omitted when empty so the prompt only carries real signal.
8
+ */
9
+ export declare function buildDesignDigest(data: PolishdDesignData, context?: string): string;
10
+ /**
11
+ * Generate (or reuse) the design review.
12
+ *
13
+ * @param force When false (default), a review whose fingerprint matches the
14
+ * current metrics is returned as-is — no model call. When true,
15
+ * always re-asks the model.
16
+ */
17
+ export declare function generateDesignReview(opts?: {
18
+ force?: boolean;
19
+ }): Promise<GenerateDesignReviewResult>;
20
+ /**
21
+ * For the Design tab's first render: the cached review plus whether the live
22
+ * metrics have drifted from it. Only reads the store; never calls a model.
23
+ */
24
+ export declare function loadDesignReviewState(preloaded?: PolishdDesignData): Promise<{
25
+ review: PolishdDesignReview | null;
26
+ stale: boolean;
27
+ }>;
28
+ //# sourceMappingURL=design.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design.d.ts","sourceRoot":"","sources":["../../src/ai/design.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIzE,OAAO,KAAK,EACV,0BAA0B,EAE1B,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAIjB,2EAA2E;AAC3E,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAQ5E;AAsBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAmEnF;AAgED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CAgErC;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAClF,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC,CASD"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Polishd — AI design review orchestration (server only).
3
+ *
4
+ * The Design tab's second layer. The first layer is deterministic
5
+ * (server/design.ts): measured typography, palette, radii, spacing, and
6
+ * rule-based flags. This module renders those metrics as a compact digest and
7
+ * asks the owner's configured model — the same provider, model, and key the
8
+ * analytics summary uses — for an aesthetic read: how coherent the design is,
9
+ * what works, and what specifically breaks the system.
10
+ *
11
+ * Token thrift mirrors summary.ts: the model sees aggregated tokens (a few
12
+ * hundred tokens for the whole site), never raw scans; the digest is
13
+ * fingerprinted and the review cached, so reopening the tab is free and a
14
+ * regenerate with unchanged metrics returns the cached text without a call.
15
+ */
16
+ import { getMeta, setMeta } from "../server/store";
17
+ import { getDesignData } from "../server/design";
18
+ import { fingerprintDigest } from "./digest";
19
+ import { callModel } from "./providers";
20
+ import { resolveSettings } from "./settings";
21
+ const DESIGN_REVIEW_KEY = "ai_design_review";
22
+ /** The last generated review, or null. Read-only — never calls a model. */
23
+ export async function loadDesignReview() {
24
+ const raw = await getMeta(DESIGN_REVIEW_KEY);
25
+ if (!raw)
26
+ return null;
27
+ try {
28
+ return JSON.parse(raw);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ const SYSTEM_PROMPT = "You are a senior brand and product designer reviewing a shipped website. " +
35
+ "You receive design tokens reverse-engineered from the rendered pages — the " +
36
+ "typography, color palette, corner radii and spacing actually in use, plus " +
37
+ "deterministic findings from rule-based checks. You never see screenshots; " +
38
+ "judge coherence from the system the numbers describe.\n\n" +
39
+ "Respond with ONLY a JSON object (no markdown fences, no preamble):\n" +
40
+ '{ "assessment": string, "strengths": string[], "issues": [{ "issue": string, "evidence": string, "suggestion": string }] }\n\n' +
41
+ "assessment: one tight paragraph (3-5 sentences) on how coherent and " +
42
+ "tasteful the design system reads — does it feel like one deliberate brand, " +
43
+ "or an accumulation of one-off decisions? Name the strongest and weakest " +
44
+ "aspects. If the data covers too few pages to be confident, say so.\n" +
45
+ "strengths: 2-4 things the design gets right (a disciplined palette, a " +
46
+ "clear type hierarchy, consistent radii…).\n" +
47
+ "issues: 0-5 specific aesthetic problems, worst first. Each issue must be " +
48
+ "concrete, evidence must be an exact token copied verbatim from the metrics " +
49
+ "(a hex color, a px value, a page path), and suggestion must be an " +
50
+ "actionable fix (e.g. 'collapse 15px and 17px into the 16px body size'). " +
51
+ "Never invent an issue: if the system is genuinely coherent, return fewer.";
52
+ /**
53
+ * Render the aggregated design data as a terse, model-readable brief.
54
+ * Sections are omitted when empty so the prompt only carries real signal.
55
+ */
56
+ export function buildDesignDigest(data, context) {
57
+ const lines = [];
58
+ if (context && context.trim())
59
+ lines.push(`SITE: ${context.trim()}`);
60
+ lines.push(`SCANNED: ${data.pages.length} pages (${data.pages
61
+ .slice(0, 10)
62
+ .map((p) => p.path)
63
+ .join(", ")}${data.pages.length > 10 ? ", …" : ""}).`);
64
+ if (data.families.length) {
65
+ lines.push("FONT FAMILIES: " + data.families.map((f) => `${f.family} (${f.count} uses)`).join(", "));
66
+ }
67
+ if (data.fonts.length) {
68
+ lines.push("TYPE STYLES (family size/weight — uses, tags):");
69
+ for (const f of data.fonts.slice(0, 20)) {
70
+ lines.push(` - ${f.family} ${f.sizePx}px/${f.weight}: ${f.count} uses, ${f.pages.length} pages` +
71
+ `${f.tags.length ? ` (${f.tags.join(",")})` : ""}${f.outlier ? " [one-off]" : ""}`);
72
+ }
73
+ }
74
+ if (data.colors.length) {
75
+ lines.push("PALETTE (share of all color usage):");
76
+ for (const c of data.colors.slice(0, 20)) {
77
+ const roles = Object.entries(c.roles)
78
+ .filter(([, n]) => n > 0)
79
+ .map(([r]) => r)
80
+ .join("/");
81
+ lines.push(` - ${c.hex}: ${c.pct}% (${roles})${c.outlier ? " [one-off]" : ""}`);
82
+ }
83
+ }
84
+ const failing = data.contrast.filter((p) => !p.passes);
85
+ if (failing.length) {
86
+ lines.push("CONTRAST FAILURES (WCAG AA):");
87
+ for (const p of failing.slice(0, 6)) {
88
+ lines.push(` - ${p.fg} on ${p.bg}: ${p.ratio}:1 at ${p.minSizePx}px (needs ${p.required}:1)`);
89
+ }
90
+ }
91
+ if (data.radii.length) {
92
+ lines.push("CORNER RADII: " + data.radii.map((r) => `${r.value} ×${r.count}${r.outlier ? " [one-off]" : ""}`).join(", "));
93
+ }
94
+ if (data.spacing.length) {
95
+ lines.push("PADDING VALUES (px ×uses): " +
96
+ data.spacing
97
+ .slice(0, 18)
98
+ .map((s) => `${s.px} ×${s.count}${s.offGrid ? "*" : ""}`)
99
+ .join(", ") +
100
+ " (* = off the 4px grid)");
101
+ }
102
+ if (data.flags.length) {
103
+ lines.push("RULE-BASED FINDINGS:");
104
+ for (const f of data.flags)
105
+ lines.push(` - [${f.section}] ${f.message}`);
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+ /** Parse the model's JSON, tolerating fences/prose; fall back to plain text. */
110
+ function parseStructured(text) {
111
+ let t = text.trim();
112
+ const fence = t.match(/```(?:json)?\s*([\s\S]*?)```/);
113
+ if (fence)
114
+ t = fence[1].trim();
115
+ const start = t.indexOf("{");
116
+ const end = t.lastIndexOf("}");
117
+ if (start >= 0 && end > start) {
118
+ try {
119
+ const obj = JSON.parse(t.slice(start, end + 1));
120
+ const assessment = typeof obj.assessment === "string" ? obj.assessment.trim() : "";
121
+ if (assessment) {
122
+ const strengths = Array.isArray(obj.strengths)
123
+ ? obj.strengths.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 4)
124
+ : [];
125
+ const issues = Array.isArray(obj.issues) ? obj.issues.slice(0, 8) : [];
126
+ return { assessment, strengths, issues };
127
+ }
128
+ }
129
+ catch {
130
+ /* fall through */
131
+ }
132
+ }
133
+ return { assessment: text.trim(), strengths: [], issues: [] };
134
+ }
135
+ /**
136
+ * Hold issues to their evidence bar: the citation must literally appear in
137
+ * the digest we sent, so the model can't critique a color that isn't there.
138
+ */
139
+ function verifyIssues(raw, digest) {
140
+ const hay = digest.toLowerCase();
141
+ const out = [];
142
+ for (const r of raw) {
143
+ if (typeof r.issue !== "string" || !r.issue.trim())
144
+ continue;
145
+ const evidence = typeof r.evidence === "string" ? r.evidence.trim() : "";
146
+ if (!evidence)
147
+ continue;
148
+ const tokens = evidence
149
+ .toLowerCase()
150
+ .split(/[^\w#/.-]+/)
151
+ .filter((t) => t.length >= 3);
152
+ if (!tokens.length || !tokens.some((t) => hay.includes(t)))
153
+ continue;
154
+ out.push({
155
+ issue: r.issue.trim(),
156
+ evidence,
157
+ suggestion: typeof r.suggestion === "string" && r.suggestion.trim() ? r.suggestion.trim() : undefined,
158
+ });
159
+ if (out.length >= 5)
160
+ break;
161
+ }
162
+ return out;
163
+ }
164
+ /**
165
+ * Generate (or reuse) the design review.
166
+ *
167
+ * @param force When false (default), a review whose fingerprint matches the
168
+ * current metrics is returned as-is — no model call. When true,
169
+ * always re-asks the model.
170
+ */
171
+ export async function generateDesignReview(opts = {}) {
172
+ const { settings } = await resolveSettings();
173
+ if (!settings.apiKey) {
174
+ return { ok: false, error: "no-key", message: "No API key configured." };
175
+ }
176
+ const data = await getDesignData();
177
+ if (!data.ready || data.pages.length === 0) {
178
+ return {
179
+ ok: false,
180
+ error: "no-data",
181
+ message: "No pages scanned yet — browse the site, then refresh.",
182
+ };
183
+ }
184
+ const digest = buildDesignDigest(data, settings.context);
185
+ const fingerprint = fingerprintDigest(digest);
186
+ if (!opts.force) {
187
+ const cached = await loadDesignReview();
188
+ if (cached && cached.fingerprint === fingerprint) {
189
+ return { ok: true, review: cached, regenerated: false };
190
+ }
191
+ }
192
+ const system = settings.instructions
193
+ ? `${SYSTEM_PROMPT}\n\nAdditional instructions from the site owner: ${settings.instructions}`
194
+ : SYSTEM_PROMPT;
195
+ let reply;
196
+ try {
197
+ reply = await callModel(settings, system, digest);
198
+ }
199
+ catch (err) {
200
+ return {
201
+ ok: false,
202
+ error: "provider-error",
203
+ message: err instanceof Error ? err.message : "The model request failed.",
204
+ };
205
+ }
206
+ if (!reply.text) {
207
+ return { ok: false, error: "provider-error", message: "The model returned an empty response." };
208
+ }
209
+ if (reply.truncated) {
210
+ return {
211
+ ok: false,
212
+ error: "provider-error",
213
+ message: "The model's response was cut off at the output limit, twice. Try a model that reasons less, or regenerate.",
214
+ };
215
+ }
216
+ const parsed = parseStructured(reply.text);
217
+ const review = {
218
+ text: parsed.assessment,
219
+ strengths: parsed.strengths,
220
+ issues: verifyIssues(parsed.issues, digest),
221
+ provider: settings.provider,
222
+ model: settings.model,
223
+ generatedAt: Date.now(),
224
+ fingerprint,
225
+ usage: reply.usage,
226
+ };
227
+ await setMeta(DESIGN_REVIEW_KEY, JSON.stringify(review));
228
+ return { ok: true, review, regenerated: true };
229
+ }
230
+ /**
231
+ * For the Design tab's first render: the cached review plus whether the live
232
+ * metrics have drifted from it. Only reads the store; never calls a model.
233
+ */
234
+ export async function loadDesignReviewState(preloaded) {
235
+ const [review, data] = await Promise.all([
236
+ loadDesignReview(),
237
+ preloaded ? Promise.resolve(preloaded) : getDesignData(),
238
+ ]);
239
+ if (!data.ready || data.pages.length === 0)
240
+ return { review, stale: false };
241
+ const { settings } = await resolveSettings();
242
+ const fingerprint = fingerprintDigest(buildDesignDigest(data, settings.context));
243
+ return { review, stale: review !== null && review.fingerprint !== fingerprint };
244
+ }
245
+ //# sourceMappingURL=design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design.js","sourceRoot":"","sources":["../../src/ai/design.ts"],"names":[],"mappings":"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"}
@@ -151,6 +151,53 @@ export type GenerateSummaryResult = {
151
151
  message: string;
152
152
  };
153
153
  export type GenerateSummaryError = "no-key" | "no-data" | "provider-error";
154
+ /**
155
+ * One aesthetic problem the model called out. `evidence` must be a concrete
156
+ * token from the design digest (a hex color, a px value, a page path) — the
157
+ * same citation bar the summary's losses are held to, so a review can't
158
+ * invent a color the site never renders.
159
+ */
160
+ export interface PolishdDesignIssue {
161
+ /** The problem, in plain English. */
162
+ issue: string;
163
+ /** The token from the metrics that shows it (hex, px value, page path). */
164
+ evidence: string;
165
+ /** A concrete fix, when the model offered one. */
166
+ suggestion?: string;
167
+ }
168
+ /**
169
+ * The model's aesthetic read of the deterministic design metrics — generated
170
+ * by the same provider/model the owner configured for the analytics summary,
171
+ * cached in the store, and refreshed only on demand or when the metrics change.
172
+ */
173
+ export interface PolishdDesignReview {
174
+ /** The overall assessment — how coherent and tasteful the design reads. */
175
+ text: string;
176
+ /** What the design gets right. */
177
+ strengths?: string[];
178
+ /** Specific, evidence-cited aesthetic problems. */
179
+ issues?: PolishdDesignIssue[];
180
+ provider: PolishdAIProvider;
181
+ model: string;
182
+ generatedAt: number;
183
+ /** Hash of the design digest it was generated from — drives staleness. */
184
+ fingerprint: string;
185
+ usage?: {
186
+ inputTokens?: number;
187
+ outputTokens?: number;
188
+ };
189
+ }
190
+ /** Result of a design review attempt, returned to the dashboard. */
191
+ export type GenerateDesignReviewResult = {
192
+ ok: true;
193
+ review: PolishdDesignReview;
194
+ regenerated: boolean;
195
+ } | {
196
+ ok: false;
197
+ error: GenerateDesignReviewError;
198
+ message: string;
199
+ };
200
+ export type GenerateDesignReviewError = "no-key" | "no-data" | "provider-error";
154
201
  /**
155
202
  * What the configured token can actually do against the configured repo,
156
203
  * checked live during onboarding. `contents` (read) powers source reading;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ai/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ai/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Polishd — client design scanner.
3
+ *
4
+ * Samples the *rendered* page — computed styles, not source CSS — and reduces
5
+ * it to compact tallies: which font family/size/weight combinations actually
6
+ * appear, which colors paint text/backgrounds/borders, which text-on-background
7
+ * pairs exist (for contrast checking), and which radii and paddings the page
8
+ * really uses. This is the deterministic half of the dashboard's Design tab:
9
+ * reverse-engineering the design system out of the built site.
10
+ *
11
+ * Deliberately tally-based: a page emits one `design_scan` event of a few KB,
12
+ * never per-element rows. Scanning runs once per session per path, after the
13
+ * page has settled, and only when the tab is visible (a background prerender
14
+ * has no layout worth measuring).
15
+ */
16
+ import type { PolishdDesignScanPayload } from "../shared/types";
17
+ /**
18
+ * Parse a computed color into `#rrggbb` / `#rrggbbaa`, or null when fully
19
+ * transparent or in a syntax we don't recognize (rare wide-gamut outputs are
20
+ * skipped rather than guessed at).
21
+ */
22
+ export declare function normalizeColor(value: string): string | null;
23
+ /**
24
+ * Walk the page and reduce it to design tallies. Pure DOM reads — call it when
25
+ * the page has settled. Returns null when there is nothing measurable.
26
+ */
27
+ export declare function collectDesignScan(root?: Element): PolishdDesignScanPayload | null;
28
+ /** Serialize, shrinking the longest lists if a pathological page overflows. */
29
+ export declare function serializeDesignScan(payload: PolishdDesignScanPayload): string;
30
+ /**
31
+ * Scan `path` once per session, after the page settles, and hand the payload
32
+ * to `emit`. Skips silently when the tab is hidden at fire time (layout of a
33
+ * backgrounded prerender isn't the design anyone sees) — a later visit to the
34
+ * same path in this session will still be un-scanned and retry.
35
+ */
36
+ export declare function scheduleDesignScan(path: string, emit: (payloadJson: string, elements: number) => void, delayMs?: number): void;
37
+ //# sourceMappingURL=design-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design-scan.d.ts","sourceRoot":"","sources":["../../src/client/design-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAMV,wBAAwB,EACzB,MAAM,iBAAiB,CAAC;AA0BzB;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB3D;AAyCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,OAAuB,GAAG,wBAAwB,GAAG,IAAI,CAqKhG;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAe7E;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,EACrD,OAAO,SAAO,GACb,IAAI,CA8CN"}