@slowcook-ai/review-overlay 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amin Azar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @slowcook-ai/review-overlay
2
+
3
+ > Floating review overlay for slowcook mock previews. PMs leave element-anchored comments by clicking the element; comments POST to the mockup PR via a GitHub PAT. Plate parses them back out for amendments. Ships in slowcook 0.16-α.6.
4
+
5
+ ## What it does
6
+
7
+ When mounted into the consumer's mock app:
8
+
9
+ - A floating mode toggle (top-right) shows three modes: **Nav** / **💬 Comment** / **✅ Approve**.
10
+ - In **Comment** mode, clicks on any element open a sidebar where the PM types prose. On submit, the overlay POSTs a structured comment to the configured PR.
11
+ - In **Approve** mode, the PM clicks an element (or just toggles back to Nav after one click) and the overlay posts an approval comment with a hidden marker that plate detects.
12
+ - Each comment carries the element's stable selector (id > data-testid > role+name > tag.classes:nth-child > XPath fallback), bounding box, viewport size + color scheme, current URL, and user agent — both as human-readable markdown AND as a JSON payload inside an HTML comment that plate parses.
13
+
14
+ The package has TWO entries:
15
+
16
+ ```ts
17
+ // Framework-free core (parser, selector, GitHub submit, PAT storage).
18
+ // This is what plate imports server-side to decode review comments.
19
+ import { parseReviewComment, extractSelector, submitComment } from "@slowcook-ai/review-overlay";
20
+
21
+ // React shell (mounted into the mock app's root layout).
22
+ import { SlowcookReviewOverlay } from "@slowcook-ai/review-overlay/react";
23
+ ```
24
+
25
+ Mount only the `/react` entry in the consumer's app; the core entry has zero React dependency.
26
+
27
+ ## Mounting in the mock app
28
+
29
+ The mock app scaffold (`slowcook init mock`) has a placeholder comment in `mock/src/app/layout.tsx`. Replace it with:
30
+
31
+ ```tsx
32
+ import { SlowcookReviewOverlay } from "@slowcook-ai/review-overlay/react";
33
+
34
+ export default function RootLayout({ children }: { children: ReactNode }) {
35
+ return (
36
+ <html lang="en">
37
+ <body className="bg-background text-foreground antialiased">
38
+ <ScenarioRegistryProvider registry={registry}>
39
+ {children}
40
+ <SlowcookReviewOverlay
41
+ enabled={process.env["NEXT_PUBLIC_SLOWCOOK_REVIEW"] === "1"}
42
+ owner={process.env["NEXT_PUBLIC_SLOWCOOK_OWNER"] ?? ""}
43
+ repo={process.env["NEXT_PUBLIC_SLOWCOOK_REPO"] ?? ""}
44
+ prNumber={parseInt(process.env["NEXT_PUBLIC_SLOWCOOK_PR_NUMBER"] ?? "0", 10)}
45
+ storyId={process.env["NEXT_PUBLIC_SLOWCOOK_STORY_ID"] ?? null}
46
+ />
47
+ </ScenarioRegistryProvider>
48
+ </body>
49
+ </html>
50
+ );
51
+ }
52
+ ```
53
+
54
+ The `enabled` gate keeps the overlay out of production-style builds. Slowcook's preview-deploy workflow (0.16-α.5) sets `NEXT_PUBLIC_SLOWCOOK_REVIEW=1` plus the owner/repo/PR env vars when it builds the mock for a `slowcook-mockup` PR.
55
+
56
+ ## How a comment lands in the PR
57
+
58
+ 1. PM clicks the floating toggle → **💬 Comment**.
59
+ 2. Coral tint overlays the viewport; subsequent clicks are captured (`{ capture: true, preventDefault }`) — the underlying button doesn't fire.
60
+ 3. PM clicks the element they want to comment on. Sidebar opens with the extracted selector pre-filled and a textarea.
61
+ 4. PM types prose, hits Submit.
62
+ 5. The overlay reads the GitHub PAT from `localStorage[slowcook.review-overlay.pat.{owner}/{repo}]`. First-time submits prompt for one (token scope: `public_repo` or `repo`).
63
+ 6. POST to `https://api.github.com/repos/{owner}/{repo}/issues/{pr}/comments` with body:
64
+
65
+ ```markdown
66
+ ### Review comment — `#unread-badge`
67
+
68
+ **Element:** `span` · "3"
69
+ **Viewport:** 390×844 dark (dpr 3)
70
+ **URL:** http://mock-4015.preview.example.com/u/amin?scenario=017
71
+
72
+ > Pin button looks dead.
73
+
74
+ <!--
75
+ slowcook:review-overlay
76
+ {"slowcook_overlay_version":"0.1.0","story_id":"017","element":{"selector":"#unread-badge","fallback_selector":"span.badge","strategy":"id","tag":"span","text_hint":"3","bbox":{"x":142,"y":73,"w":22,"h":22}},"viewport":{"width":390,"height":844,"colorScheme":"dark","dpr":3},"url":"...","timestamp":"...","prose":"...","user_agent":"..."}
77
+ -->
78
+ ```
79
+
80
+ 7. Plate (slowcook 0.16-α.7) reads the PR's comments, calls `parseReviewComment(body)` for each, and acts.
81
+
82
+ ## Selector strategy
83
+
84
+ Stable-selector priority (matches the design doc; first non-null wins):
85
+
86
+ | Strategy | Example | When it applies |
87
+ |---|---|---|
88
+ | `id` | `#unread-badge` | Element has a meaningful `id` (skips React `useId` patterns like `:r3:`, Radix's `radix-:r…`, Headless UI's `headlessui-…`) |
89
+ | `data-testid` | `[data-testid="save-btn"]` | Element has `data-testid` |
90
+ | `role-name` | `button[aria-label="Sign in"]` | Has explicit/implicit role + accessible name (aria-label, aria-labelledby, `<label for>`, button/link textContent) |
91
+ | `tag-classes` | `span.badge.counter:nth-child(2)` | Picks first 2 non-utility class names; adds `:nth-child(N)` when parent has multiple same-tag children. Skips Tailwind utilities, emotion `css-XXXX` hashes, CSS-modules `_XXXX` hashes |
92
+ | `xpath` | `/html/body/div/span[2]` | Last resort — always works |
93
+
94
+ The fallback (one rung lower than the chosen strategy) is also captured so plate has a degraded option when the page changes between submit and reconciliation.
95
+
96
+ ## PAT storage
97
+
98
+ Stored under `localStorage["slowcook.review-overlay.pat.{owner}/{repo}"]`. Scoped per repo — the same browser can hold multiple consumers' tokens without collision.
99
+
100
+ The PAT never leaves the browser except on a direct fetch to GitHub's API. To revoke / rotate, clear the localStorage entry (or `clearPat(window.localStorage, { owner, repo })` from the console).
101
+
102
+ A future "Mode B" — consumer-hosted submit endpoint — would let the consumer's backend hold a server-side token instead of the PM's PAT. Deferred.
103
+
104
+ ## Bundle weight
105
+
106
+ | Entry | Approx gz size |
107
+ |---|---|
108
+ | `/` (core: parser + selector + github + format) | ~3 KB |
109
+ | `/react` (overlay component) | ~6 KB |
110
+
111
+ No html2canvas yet; α.6 ships the bounding box + selector + viewport metadata and the user can paste a screenshot manually if needed. Auto-screenshot via canvas API queued for a follow-up alpha.
112
+
113
+ ## See also
114
+
115
+ - [`docs/plans/0.16-mock-app.md`](https://github.com/aminazar/slowcook/blob/main/docs/plans/0.16-mock-app.md) — the architecture this fits into
116
+ - [`docs/plans/0.13.1-review-overlay.md`](https://github.com/aminazar/slowcook/blob/main/docs/plans/0.13.1-review-overlay.md) — original design doc; this is its v1 implementation
117
+ - [`docs/operating-guide.md`](https://github.com/aminazar/slowcook/blob/main/docs/operating-guide.md) — sets up the SSH preview deploy that delivers the mock app to the PM
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Comment markdown + JSON-payload formatter — 0.16.0-α.6.
3
+ *
4
+ * The overlay submits each comment as a single GitHub PR comment whose
5
+ * body is:
6
+ *
7
+ * - human-readable markdown (selector, viewport, prose)
8
+ * - HTML-comment-hidden JSON payload (the structured data plate parses)
9
+ *
10
+ * Plate's parser greps for the payload marker; humans see the rendered
11
+ * markdown. Same idea as the cost-marker rollups slowcook already uses.
12
+ *
13
+ * Pure function; no DOM access.
14
+ */
15
+ import type { ExtractedSelector } from "./selector.js";
16
+ export interface ViewportInfo {
17
+ width: number;
18
+ height: number;
19
+ /** Color scheme: light / dark / no-preference. */
20
+ colorScheme: "light" | "dark" | "no-preference";
21
+ /** Device pixel ratio. */
22
+ dpr: number;
23
+ }
24
+ export interface ReviewCommentPayload {
25
+ slowcook_overlay_version: string;
26
+ story_id: string | null;
27
+ url: string;
28
+ timestamp: string;
29
+ prose: string;
30
+ element: {
31
+ selector: string;
32
+ fallback_selector: string | null;
33
+ strategy: ExtractedSelector["strategy"];
34
+ tag: string;
35
+ text_hint: string | null;
36
+ /** Bounding box in CSS pixels at submit time. */
37
+ bbox: {
38
+ x: number;
39
+ y: number;
40
+ w: number;
41
+ h: number;
42
+ };
43
+ };
44
+ viewport: ViewportInfo;
45
+ user_agent: string;
46
+ }
47
+ export declare const PAYLOAD_MARKER = "slowcook:review-overlay";
48
+ export interface FormatArgs {
49
+ payload: ReviewCommentPayload;
50
+ /** Optional inline screenshot data URL (image/png). */
51
+ screenshotDataUrl?: string;
52
+ }
53
+ /**
54
+ * Render the full comment body (markdown + hidden JSON). The hidden
55
+ * JSON is wrapped in an HTML comment with a stable marker so plate's
56
+ * parser can locate + decode it idempotently.
57
+ */
58
+ export declare function formatReviewComment(args: FormatArgs): string;
59
+ /**
60
+ * Reverse — given a rendered comment body, extract the JSON payload if
61
+ * present. Returns null when the body has no payload (i.e., it isn't
62
+ * a review-overlay comment).
63
+ *
64
+ * Plate uses this to ingest each review-overlay comment on a mockup PR.
65
+ */
66
+ export declare function parseReviewComment(body: string): ReviewCommentPayload | null;
67
+ export declare function buildPayload(args: {
68
+ overlayVersion: string;
69
+ storyId: string | null;
70
+ url: string;
71
+ prose: string;
72
+ selector: ExtractedSelector;
73
+ bbox: {
74
+ x: number;
75
+ y: number;
76
+ w: number;
77
+ h: number;
78
+ };
79
+ viewport: ViewportInfo;
80
+ userAgent: string;
81
+ }): ReviewCommentPayload;
82
+ //# sourceMappingURL=comment-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-format.d.ts","sourceRoot":"","sources":["../src/comment-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAChD,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,wBAAwB,EAAE,MAAM,CAAC;IACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACxC,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,iDAAiD;QACjD,IAAI,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACtD,CAAC;IACF,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,uDAAuD;IACvD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA8B5D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAgB5E;AAeD,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,oBAAoB,CAkBvB"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Comment markdown + JSON-payload formatter — 0.16.0-α.6.
3
+ *
4
+ * The overlay submits each comment as a single GitHub PR comment whose
5
+ * body is:
6
+ *
7
+ * - human-readable markdown (selector, viewport, prose)
8
+ * - HTML-comment-hidden JSON payload (the structured data plate parses)
9
+ *
10
+ * Plate's parser greps for the payload marker; humans see the rendered
11
+ * markdown. Same idea as the cost-marker rollups slowcook already uses.
12
+ *
13
+ * Pure function; no DOM access.
14
+ */
15
+ export const PAYLOAD_MARKER = "slowcook:review-overlay";
16
+ /**
17
+ * Render the full comment body (markdown + hidden JSON). The hidden
18
+ * JSON is wrapped in an HTML comment with a stable marker so plate's
19
+ * parser can locate + decode it idempotently.
20
+ */
21
+ export function formatReviewComment(args) {
22
+ const { payload, screenshotDataUrl } = args;
23
+ const lines = [];
24
+ lines.push(`### Review comment — \`${payload.element.selector}\``);
25
+ lines.push("");
26
+ lines.push(`**Element:** \`${payload.element.tag}\` ${payload.element.text_hint ? `· "${payload.element.text_hint}"` : ""}`);
27
+ lines.push(`**Viewport:** ${payload.viewport.width}×${payload.viewport.height} ${payload.viewport.colorScheme} (dpr ${payload.viewport.dpr})`);
28
+ lines.push(`**URL:** ${payload.url}`);
29
+ lines.push("");
30
+ lines.push(`> ${payload.prose.split("\n").join("\n> ")}`);
31
+ lines.push("");
32
+ if (screenshotDataUrl) {
33
+ lines.push(`![screenshot](${screenshotDataUrl})`);
34
+ lines.push("");
35
+ }
36
+ // Hidden JSON for plate. Wrap in HTML comment + payload marker so the
37
+ // parser can grep + decode without parsing markdown structure.
38
+ lines.push("<!--");
39
+ lines.push(PAYLOAD_MARKER);
40
+ lines.push(JSON.stringify(payload));
41
+ lines.push("-->");
42
+ return lines.join("\n");
43
+ }
44
+ /**
45
+ * Reverse — given a rendered comment body, extract the JSON payload if
46
+ * present. Returns null when the body has no payload (i.e., it isn't
47
+ * a review-overlay comment).
48
+ *
49
+ * Plate uses this to ingest each review-overlay comment on a mockup PR.
50
+ */
51
+ export function parseReviewComment(body) {
52
+ const idx = body.indexOf(PAYLOAD_MARKER);
53
+ if (idx < 0)
54
+ return null;
55
+ // The JSON is on the line after the marker, before `-->`.
56
+ const tail = body.slice(idx + PAYLOAD_MARKER.length);
57
+ const closeIdx = tail.indexOf("-->");
58
+ const region = closeIdx < 0 ? tail : tail.slice(0, closeIdx);
59
+ const m = region.match(/\{[\s\S]*\}/);
60
+ if (!m)
61
+ return null;
62
+ try {
63
+ const parsed = JSON.parse(m[0]);
64
+ if (!isReviewCommentPayload(parsed))
65
+ return null;
66
+ return parsed;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ function isReviewCommentPayload(v) {
73
+ if (!v || typeof v !== "object")
74
+ return false;
75
+ const o = v;
76
+ return (typeof o["slowcook_overlay_version"] === "string" &&
77
+ typeof o["url"] === "string" &&
78
+ typeof o["timestamp"] === "string" &&
79
+ typeof o["prose"] === "string" &&
80
+ typeof o["element"] === "object" && o["element"] !== null &&
81
+ typeof o["viewport"] === "object" && o["viewport"] !== null);
82
+ }
83
+ export function buildPayload(args) {
84
+ return {
85
+ slowcook_overlay_version: args.overlayVersion,
86
+ story_id: args.storyId,
87
+ url: args.url,
88
+ timestamp: new Date().toISOString(),
89
+ prose: args.prose,
90
+ element: {
91
+ selector: args.selector.selector,
92
+ fallback_selector: args.selector.fallbackSelector,
93
+ strategy: args.selector.strategy,
94
+ tag: args.selector.tag,
95
+ text_hint: args.selector.textHint,
96
+ bbox: args.bbox,
97
+ },
98
+ viewport: args.viewport,
99
+ user_agent: args.userAgent,
100
+ };
101
+ }
102
+ //# sourceMappingURL=comment-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-format.js","sourceRoot":"","sources":["../src/comment-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * GitHub API submit + PAT storage — 0.16.0-α.6.
3
+ *
4
+ * The overlay POSTs review comments directly from the browser using a
5
+ * PAT stored in localStorage. Mode A in the 0.13.1-overlay design;
6
+ * Mode B (consumer's own submit endpoint) deferred to a follow-up.
7
+ *
8
+ * Storage key is keyed by the {owner}/{repo} pair so the same browser
9
+ * can hold multiple consumers' tokens without collision.
10
+ */
11
+ export interface RepoCoord {
12
+ owner: string;
13
+ repo: string;
14
+ }
15
+ export declare function patStorageKey(repo: RepoCoord): string;
16
+ export declare function loadPat(storage: Storage, repo: RepoCoord): string | null;
17
+ export declare function savePat(storage: Storage, repo: RepoCoord, pat: string): void;
18
+ export declare function clearPat(storage: Storage, repo: RepoCoord): void;
19
+ export interface SubmitArgs extends RepoCoord {
20
+ pr: number;
21
+ pat: string;
22
+ body: string;
23
+ /** Override the API base; default https://api.github.com */
24
+ apiBase?: string;
25
+ /** Inject fetch — defaults to globalThis.fetch. Useful for tests. */
26
+ fetchImpl?: typeof fetch;
27
+ }
28
+ export interface SubmitOk {
29
+ ok: true;
30
+ commentId: number;
31
+ htmlUrl: string;
32
+ }
33
+ export interface SubmitErr {
34
+ ok: false;
35
+ status: number;
36
+ message: string;
37
+ }
38
+ export type SubmitResult = SubmitOk | SubmitErr;
39
+ /**
40
+ * POST a comment to the given PR. Returns a tagged result rather than
41
+ * throwing so the React layer can render specific UI per failure mode.
42
+ */
43
+ export declare function submitComment(args: SubmitArgs): Promise<SubmitResult>;
44
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAErD;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAEhE;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAuC3E"}
package/dist/github.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * GitHub API submit + PAT storage — 0.16.0-α.6.
3
+ *
4
+ * The overlay POSTs review comments directly from the browser using a
5
+ * PAT stored in localStorage. Mode A in the 0.13.1-overlay design;
6
+ * Mode B (consumer's own submit endpoint) deferred to a follow-up.
7
+ *
8
+ * Storage key is keyed by the {owner}/{repo} pair so the same browser
9
+ * can hold multiple consumers' tokens without collision.
10
+ */
11
+ const PAT_STORAGE_KEY_PREFIX = "slowcook.review-overlay.pat.";
12
+ export function patStorageKey(repo) {
13
+ return `${PAT_STORAGE_KEY_PREFIX}${repo.owner}/${repo.repo}`;
14
+ }
15
+ export function loadPat(storage, repo) {
16
+ return storage.getItem(patStorageKey(repo));
17
+ }
18
+ export function savePat(storage, repo, pat) {
19
+ storage.setItem(patStorageKey(repo), pat);
20
+ }
21
+ export function clearPat(storage, repo) {
22
+ storage.removeItem(patStorageKey(repo));
23
+ }
24
+ /**
25
+ * POST a comment to the given PR. Returns a tagged result rather than
26
+ * throwing so the React layer can render specific UI per failure mode.
27
+ */
28
+ export async function submitComment(args) {
29
+ const fetchImpl = args.fetchImpl ?? globalThis.fetch;
30
+ const apiBase = args.apiBase ?? "https://api.github.com";
31
+ const url = `${apiBase}/repos/${encodeURIComponent(args.owner)}/${encodeURIComponent(args.repo)}/issues/${args.pr}/comments`;
32
+ let res;
33
+ try {
34
+ res = await fetchImpl(url, {
35
+ method: "POST",
36
+ headers: {
37
+ Authorization: `Bearer ${args.pat}`,
38
+ "Content-Type": "application/json",
39
+ Accept: "application/vnd.github+json",
40
+ "X-GitHub-Api-Version": "2022-11-28",
41
+ },
42
+ body: JSON.stringify({ body: args.body }),
43
+ });
44
+ }
45
+ catch (e) {
46
+ return {
47
+ ok: false,
48
+ status: 0,
49
+ message: `network error: ${e.message}`,
50
+ };
51
+ }
52
+ if (!res.ok) {
53
+ let detail = "";
54
+ try {
55
+ const j = (await res.json());
56
+ detail = j.message ?? "";
57
+ }
58
+ catch {
59
+ // body wasn't JSON — keep detail empty
60
+ }
61
+ return {
62
+ ok: false,
63
+ status: res.status,
64
+ message: detail || res.statusText,
65
+ };
66
+ }
67
+ const j = (await res.json());
68
+ return { ok: true, commentId: j.id, htmlUrl: j.html_url };
69
+ }
70
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,sBAAsB,GAAG,8BAA8B,CAAC;AAO9D,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,GAAG,sBAAsB,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAgB,EAAE,IAAe;IACvD,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAgB,EAAE,IAAe,EAAE,GAAW;IACpE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAgB,EAAE,IAAe;IACxD,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1C,CAAC;AA0BD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAgB;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,wBAAwB,CAAC;IACzD,MAAM,GAAG,GAAG,GAAG,OAAO,UAAU,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,EAAE,WAAW,CAAC;IAC7H,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YACzB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE;gBACnC,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,6BAA6B;gBACrC,sBAAsB,EAAE,YAAY;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1C,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,kBAAmB,CAAW,CAAC,OAAO,EAAE;SAClD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;YACrD,MAAM,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,MAAM,IAAI,GAAG,CAAC,UAAU;SAClC,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqC,CAAC;IACjE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@slowcook-ai/review-overlay` — public surface.
3
+ *
4
+ * Two entry points:
5
+ *
6
+ * import { ... } from "@slowcook-ai/review-overlay" // framework-free core
7
+ * import { SlowcookReviewOverlay } from "@slowcook-ai/review-overlay/react" // React shell
8
+ *
9
+ * The core entry has zero React dependency; plate (which lives in the
10
+ * cli package, server-side) imports `parseReviewComment` from here to
11
+ * decode review comments off the PR thread.
12
+ */
13
+ export { PAYLOAD_MARKER, formatReviewComment, parseReviewComment, buildPayload, type ReviewCommentPayload, type ViewportInfo, type FormatArgs, } from "./comment-format.js";
14
+ export { extractSelector, type ExtractedSelector, } from "./selector.js";
15
+ export { loadPat, savePat, clearPat, patStorageKey, submitComment, type RepoCoord, type SubmitArgs, type SubmitResult, type SubmitOk, type SubmitErr, } from "./github.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@slowcook-ai/review-overlay` — public surface.
3
+ *
4
+ * Two entry points:
5
+ *
6
+ * import { ... } from "@slowcook-ai/review-overlay" // framework-free core
7
+ * import { SlowcookReviewOverlay } from "@slowcook-ai/review-overlay/react" // React shell
8
+ *
9
+ * The core entry has zero React dependency; plate (which lives in the
10
+ * cli package, server-side) imports `parseReviewComment` from here to
11
+ * decode review comments off the PR thread.
12
+ */
13
+ export { PAYLOAD_MARKER, formatReviewComment, parseReviewComment, buildPayload, } from "./comment-format.js";
14
+ export { extractSelector, } from "./selector.js";
15
+ export { loadPat, savePat, clearPat, patStorageKey, submitComment, } from "./github.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,GAIb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,GAEhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,OAAO,EACP,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,GAMd,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { SlowcookReviewOverlay, type SlowcookReviewOverlayProps } from "./overlay.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,KAAK,0BAA0B,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { SlowcookReviewOverlay } from "./overlay.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAmC,MAAM,cAAc,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * <SlowcookReviewOverlay /> — 0.16.0-α.6.
3
+ *
4
+ * Mounted into the consumer's mock-app root layout. Renders a floating
5
+ * mode toggle (nav / comment / approve). In comment mode, clicks on
6
+ * elements capture a selector + bbox + viewport metadata; the user
7
+ * types prose and submits to the mockup PR via PAT.
8
+ *
9
+ * Bundle weight is paid only when consumers mount it. The mock app
10
+ * scaffolded by `slowcook init mock` includes a code-comment placeholder
11
+ * but does NOT mount the overlay; consumers opt in by editing
12
+ * mock/src/app/layout.tsx after installing this package.
13
+ *
14
+ * Architecture:
15
+ * - Pure-React inline UI (no portal needed; the overlay's own
16
+ * fixed-position root sits above body)
17
+ * - Selector + comment-format + github logic lives in the framework-
18
+ * free modules in this package; the React shell wires events
19
+ * - Tailwind-style classes inlined — but every visible style is also
20
+ * set via `style={...}` so the overlay renders even without
21
+ * Tailwind in the host app
22
+ */
23
+ import { type JSX } from "react";
24
+ export interface SlowcookReviewOverlayProps {
25
+ /** GitHub owner (e.g. "aminazar"). */
26
+ owner: string;
27
+ /** GitHub repo (e.g. "slowcook"). */
28
+ repo: string;
29
+ /** Pull-request number for the mockup PR being reviewed. */
30
+ prNumber: number;
31
+ /** Optional story id; included in the JSON payload. */
32
+ storyId?: string | null;
33
+ /** Render only when truthy; useful for `process.env.NEXT_PUBLIC_SLOWCOOK_REVIEW === "1"` gating. */
34
+ enabled?: boolean;
35
+ /** Overlay package version, included in the JSON payload. */
36
+ overlayVersion?: string;
37
+ }
38
+ export declare function SlowcookReviewOverlay(props: SlowcookReviewOverlayProps): JSX.Element | null;
39
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/react/overlay.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,EAA4C,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAc3E,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAgL3F"}
@@ -0,0 +1,317 @@
1
+ /**
2
+ * <SlowcookReviewOverlay /> — 0.16.0-α.6.
3
+ *
4
+ * Mounted into the consumer's mock-app root layout. Renders a floating
5
+ * mode toggle (nav / comment / approve). In comment mode, clicks on
6
+ * elements capture a selector + bbox + viewport metadata; the user
7
+ * types prose and submits to the mockup PR via PAT.
8
+ *
9
+ * Bundle weight is paid only when consumers mount it. The mock app
10
+ * scaffolded by `slowcook init mock` includes a code-comment placeholder
11
+ * but does NOT mount the overlay; consumers opt in by editing
12
+ * mock/src/app/layout.tsx after installing this package.
13
+ *
14
+ * Architecture:
15
+ * - Pure-React inline UI (no portal needed; the overlay's own
16
+ * fixed-position root sits above body)
17
+ * - Selector + comment-format + github logic lives in the framework-
18
+ * free modules in this package; the React shell wires events
19
+ * - Tailwind-style classes inlined — but every visible style is also
20
+ * set via `style={...}` so the overlay renders even without
21
+ * Tailwind in the host app
22
+ */
23
+ "use client";
24
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
25
+ import { useEffect, useState, useRef, useCallback } from "react";
26
+ import { extractSelector } from "../selector.js";
27
+ import { buildPayload, formatReviewComment, } from "../comment-format.js";
28
+ import { loadPat, savePat, submitComment, } from "../github.js";
29
+ const APPROVE_LABEL = "slowcook-mockup-approved";
30
+ const ACCENT = "#FF6B6B";
31
+ export function SlowcookReviewOverlay(props) {
32
+ const { owner, repo, prNumber, storyId = null, enabled = true } = props;
33
+ const overlayVersion = props.overlayVersion ?? "0.1.0";
34
+ const repoCoord = { owner, repo };
35
+ // Hooks must run unconditionally — render the null AFTER all hooks
36
+ // are declared. Bails early during SSR via the typeof window check.
37
+ const [mode, setMode] = useState("nav");
38
+ const [target, setTarget] = useState(null);
39
+ const [composerOpen, setComposerOpen] = useState(false);
40
+ const [submitting, setSubmitting] = useState(false);
41
+ const [feedback, setFeedback] = useState(null);
42
+ const composerRef = useRef(null);
43
+ // ESC exits comment/approve mode + closes composer.
44
+ useEffect(() => {
45
+ if (typeof window === "undefined")
46
+ return;
47
+ function onKey(e) {
48
+ if (e.key === "Escape") {
49
+ setMode("nav");
50
+ setComposerOpen(false);
51
+ setTarget(null);
52
+ }
53
+ }
54
+ window.addEventListener("keydown", onKey);
55
+ return () => window.removeEventListener("keydown", onKey);
56
+ }, []);
57
+ // Capture clicks at the document level when in comment/approve mode.
58
+ useEffect(() => {
59
+ if (typeof window === "undefined")
60
+ return;
61
+ if (mode === "nav")
62
+ return;
63
+ function onClick(e) {
64
+ const el = e.target;
65
+ if (!el)
66
+ return;
67
+ // Don't capture clicks on the overlay's own UI.
68
+ if (composerRef.current && composerRef.current.contains(el))
69
+ return;
70
+ if (el.closest('[data-slowcook-overlay-ui="1"]'))
71
+ return;
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ if (mode === "comment") {
75
+ setTarget(el);
76
+ setComposerOpen(true);
77
+ }
78
+ else if (mode === "approve") {
79
+ void submitApproval();
80
+ }
81
+ }
82
+ document.addEventListener("click", onClick, { capture: true });
83
+ return () => document.removeEventListener("click", onClick, { capture: true });
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, [mode]);
86
+ const submitApproval = useCallback(async () => {
87
+ setSubmitting(true);
88
+ setFeedback(null);
89
+ try {
90
+ const pat = ensurePat(repoCoord);
91
+ if (!pat) {
92
+ setFeedback("Approval cancelled — no PAT.");
93
+ return;
94
+ }
95
+ const body = `### ✅ Mockup approved\n\nPM approved the mockup via the review overlay (\`${overlayVersion}\`).\n\nPlease apply the \`${APPROVE_LABEL}\` label.`;
96
+ const result = await submitComment({
97
+ owner: repoCoord.owner,
98
+ repo: repoCoord.repo,
99
+ pr: prNumber,
100
+ pat,
101
+ body,
102
+ });
103
+ if (result.ok) {
104
+ setFeedback(`Approval comment posted (#${result.commentId}).`);
105
+ setMode("nav");
106
+ }
107
+ else {
108
+ setFeedback(`Approval failed: ${result.status} ${result.message}`);
109
+ }
110
+ }
111
+ finally {
112
+ setSubmitting(false);
113
+ }
114
+ }, [overlayVersion, prNumber, repoCoord]);
115
+ const submitFromComposer = useCallback(async (prose) => {
116
+ if (!target)
117
+ return;
118
+ setSubmitting(true);
119
+ setFeedback(null);
120
+ try {
121
+ const pat = ensurePat(repoCoord);
122
+ if (!pat) {
123
+ setFeedback("Cancelled — no PAT.");
124
+ return;
125
+ }
126
+ const sel = extractSelector(target);
127
+ const rect = target.getBoundingClientRect();
128
+ const viewport = {
129
+ width: window.innerWidth,
130
+ height: window.innerHeight,
131
+ colorScheme: window.matchMedia("(prefers-color-scheme: dark)").matches
132
+ ? "dark"
133
+ : "light",
134
+ dpr: window.devicePixelRatio || 1,
135
+ };
136
+ const payload = buildPayload({
137
+ overlayVersion,
138
+ storyId,
139
+ url: window.location.href,
140
+ prose,
141
+ selector: sel,
142
+ bbox: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
143
+ viewport,
144
+ userAgent: navigator.userAgent,
145
+ });
146
+ const body = formatReviewComment({ payload });
147
+ const result = await submitComment({
148
+ owner: repoCoord.owner,
149
+ repo: repoCoord.repo,
150
+ pr: prNumber,
151
+ pat,
152
+ body,
153
+ });
154
+ if (result.ok) {
155
+ setFeedback(`Comment posted (#${result.commentId}).`);
156
+ setComposerOpen(false);
157
+ setTarget(null);
158
+ }
159
+ else {
160
+ setFeedback(`Failed: ${result.status} ${result.message}`);
161
+ }
162
+ }
163
+ finally {
164
+ setSubmitting(false);
165
+ }
166
+ }, [overlayVersion, prNumber, repoCoord, storyId, target]);
167
+ if (!enabled)
168
+ return null;
169
+ if (typeof window === "undefined")
170
+ return null;
171
+ return (_jsxs("div", { "data-slowcook-overlay-ui": "1", style: {
172
+ position: "fixed",
173
+ inset: 0,
174
+ pointerEvents: "none",
175
+ zIndex: 2147483000,
176
+ }, children: [mode !== "nav" && (_jsx("div", { style: {
177
+ position: "absolute",
178
+ inset: 0,
179
+ backgroundColor: mode === "comment"
180
+ ? "rgba(255, 107, 107, 0.05)"
181
+ : "rgba(74, 222, 128, 0.05)",
182
+ pointerEvents: "none",
183
+ }, "aria-hidden": "true" })), _jsx(ModeToggle, { mode: mode, onChange: setMode, disabled: submitting }), composerOpen && target && (_jsx(Composer, { target: target, onCancel: () => {
184
+ setComposerOpen(false);
185
+ setTarget(null);
186
+ }, onSubmit: submitFromComposer, submitting: submitting, composerRef: composerRef })), feedback && _jsx(FeedbackToast, { text: feedback, onDismiss: () => setFeedback(null) })] }));
187
+ }
188
+ function ModeToggle(props) {
189
+ const { mode, onChange, disabled } = props;
190
+ return (_jsxs("div", { "data-slowcook-overlay-ui": "1", style: {
191
+ position: "absolute",
192
+ top: 12,
193
+ right: 12,
194
+ pointerEvents: "auto",
195
+ display: "flex",
196
+ gap: 4,
197
+ background: "rgba(15, 15, 24, 0.92)",
198
+ padding: 4,
199
+ borderRadius: 999,
200
+ boxShadow: "0 4px 14px rgba(0,0,0,0.25)",
201
+ fontFamily: "system-ui, -apple-system, sans-serif",
202
+ fontSize: 13,
203
+ color: "white",
204
+ }, children: [_jsx(ToggleButton, { active: mode === "nav", onClick: () => onChange("nav"), disabled: disabled, label: "Nav" }), _jsx(ToggleButton, { active: mode === "comment", onClick: () => onChange("comment"), disabled: disabled, label: "\uD83D\uDCAC Comment", accent: true }), _jsx(ToggleButton, { active: mode === "approve", onClick: () => onChange("approve"), disabled: disabled, label: "\u2705 Approve", approve: true })] }));
205
+ }
206
+ function ToggleButton(props) {
207
+ const bg = props.active
208
+ ? props.approve
209
+ ? "#22c55e"
210
+ : props.accent
211
+ ? ACCENT
212
+ : "rgba(255,255,255,0.18)"
213
+ : "transparent";
214
+ return (_jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, style: {
215
+ background: bg,
216
+ color: "white",
217
+ border: "none",
218
+ padding: "6px 12px",
219
+ borderRadius: 999,
220
+ cursor: props.disabled ? "not-allowed" : "pointer",
221
+ opacity: props.disabled ? 0.6 : 1,
222
+ font: "inherit",
223
+ }, children: props.label }));
224
+ }
225
+ function Composer(props) {
226
+ const [prose, setProse] = useState("");
227
+ const sel = extractSelector(props.target);
228
+ const rect = props.target.getBoundingClientRect();
229
+ return (_jsxs(_Fragment, { children: [_jsx("div", { style: {
230
+ position: "absolute",
231
+ left: rect.x,
232
+ top: rect.y,
233
+ width: rect.width,
234
+ height: rect.height,
235
+ border: `2px solid ${ACCENT}`,
236
+ outlineOffset: -2,
237
+ pointerEvents: "none",
238
+ }, "aria-hidden": "true" }), _jsxs("div", { ref: props.composerRef, "data-slowcook-overlay-ui": "1", role: "dialog", "aria-label": "Review comment", style: {
239
+ position: "absolute",
240
+ right: 12,
241
+ top: 64,
242
+ width: 320,
243
+ maxHeight: "70vh",
244
+ overflow: "auto",
245
+ background: "white",
246
+ color: "#1a1a1a",
247
+ borderRadius: 8,
248
+ boxShadow: "0 12px 40px rgba(0,0,0,0.3)",
249
+ padding: 16,
250
+ pointerEvents: "auto",
251
+ fontFamily: "system-ui, -apple-system, sans-serif",
252
+ fontSize: 13,
253
+ }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: "Review comment" }), _jsx("div", { style: { fontFamily: "ui-monospace, SFMono-Regular, monospace", fontSize: 11, opacity: 0.7, marginBottom: 8, wordBreak: "break-all" }, children: sel.selector }), _jsx("textarea", { "aria-label": "Comment text", autoFocus: true, value: prose, onChange: (e) => setProse(e.target.value), placeholder: "What's off about this element?", rows: 5, style: {
254
+ width: "100%",
255
+ padding: 8,
256
+ border: "1px solid rgba(0,0,0,0.15)",
257
+ borderRadius: 6,
258
+ font: "inherit",
259
+ resize: "vertical",
260
+ boxSizing: "border-box",
261
+ } }), _jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 12 }, children: [_jsx("button", { type: "button", onClick: props.onCancel, disabled: props.submitting, style: {
262
+ background: "transparent",
263
+ border: "1px solid rgba(0,0,0,0.15)",
264
+ padding: "6px 12px",
265
+ borderRadius: 6,
266
+ cursor: "pointer",
267
+ font: "inherit",
268
+ }, children: "Cancel" }), _jsx("button", { type: "button", disabled: props.submitting || prose.trim() === "", onClick: () => void props.onSubmit(prose.trim()), style: {
269
+ background: ACCENT,
270
+ color: "white",
271
+ border: "none",
272
+ padding: "6px 14px",
273
+ borderRadius: 6,
274
+ cursor: props.submitting ? "not-allowed" : "pointer",
275
+ opacity: props.submitting || prose.trim() === "" ? 0.6 : 1,
276
+ font: "inherit",
277
+ fontWeight: 600,
278
+ }, children: props.submitting ? "Submitting…" : "Submit" })] })] })] }));
279
+ }
280
+ function FeedbackToast(props) {
281
+ useEffect(() => {
282
+ const t = setTimeout(props.onDismiss, 4000);
283
+ return () => clearTimeout(t);
284
+ }, [props]);
285
+ return (_jsx("div", { "data-slowcook-overlay-ui": "1", style: {
286
+ position: "absolute",
287
+ bottom: 24,
288
+ right: 24,
289
+ background: "rgba(15, 15, 24, 0.92)",
290
+ color: "white",
291
+ padding: "10px 16px",
292
+ borderRadius: 8,
293
+ boxShadow: "0 4px 14px rgba(0,0,0,0.3)",
294
+ fontFamily: "system-ui, -apple-system, sans-serif",
295
+ fontSize: 13,
296
+ pointerEvents: "auto",
297
+ maxWidth: 320,
298
+ }, role: "status", children: props.text }));
299
+ }
300
+ function ensurePat(repo) {
301
+ // Try localStorage first; fall back to a window.prompt() the first
302
+ // time. The PAT scopes the consumer needs are public_repo (or repo
303
+ // for private). Storing in localStorage keeps it scoped to the
304
+ // preview origin.
305
+ if (typeof window === "undefined")
306
+ return null;
307
+ let pat = loadPat(window.localStorage, repo);
308
+ if (pat)
309
+ return pat;
310
+ const entered = window.prompt(`Slowcook needs a GitHub PAT (scope: public_repo or repo) to post a comment on ${repo.owner}/${repo.repo}.\n\nIt will be stored only in this browser's localStorage for ${repo.owner}/${repo.repo}.`);
311
+ if (!entered || entered.trim() === "")
312
+ return null;
313
+ pat = entered.trim();
314
+ savePat(window.localStorage, repo, pat);
315
+ return pat;
316
+ }
317
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/react/overlay.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Element selector extraction — 0.16.0-α.6.
3
+ *
4
+ * Walks an element's attributes + ancestry to produce a stable selector
5
+ * that plate (and humans) can use to find the element later. Priority
6
+ * matches the 0.13.1-review-overlay design:
7
+ *
8
+ * 1. id → `#unread-badge` (most stable)
9
+ * 2. data-testid → `[data-testid="unread-badge"]`
10
+ * 3. role + name → `button[aria-label="Sign in"]`
11
+ * 4. tag.classes → `span.badge.bg-mint:nth-child(2)`
12
+ * 5. XPath → `/html/body/div/span[2]` (last resort)
13
+ *
14
+ * Each strategy returns null when not applicable; `extractSelector`
15
+ * tries them in order and returns the first hit. The selector is
16
+ * always non-empty for a real DOM element.
17
+ *
18
+ * Pure functions; no DOM globals beyond what each function takes as
19
+ * input. Testable in node + jsdom.
20
+ */
21
+ export interface ExtractedSelector {
22
+ /** Best stable selector. Always populated. */
23
+ selector: string;
24
+ /** Lower-priority fallback (one rung down the priority list). */
25
+ fallbackSelector: string | null;
26
+ /** Strategy that produced `selector`. */
27
+ strategy: "id" | "data-testid" | "role-name" | "tag-classes" | "xpath";
28
+ /** Raw element tag (lowercased). */
29
+ tag: string;
30
+ /** First 80 chars of trimmed text content; null when empty. */
31
+ textHint: string | null;
32
+ }
33
+ export declare function extractSelector(el: Element): ExtractedSelector;
34
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,yCAAyC;IACzC,QAAQ,EAAE,IAAI,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,GAAG,OAAO,CAAC;IACvE,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,iBAAiB,CA6C9D"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Element selector extraction — 0.16.0-α.6.
3
+ *
4
+ * Walks an element's attributes + ancestry to produce a stable selector
5
+ * that plate (and humans) can use to find the element later. Priority
6
+ * matches the 0.13.1-review-overlay design:
7
+ *
8
+ * 1. id → `#unread-badge` (most stable)
9
+ * 2. data-testid → `[data-testid="unread-badge"]`
10
+ * 3. role + name → `button[aria-label="Sign in"]`
11
+ * 4. tag.classes → `span.badge.bg-mint:nth-child(2)`
12
+ * 5. XPath → `/html/body/div/span[2]` (last resort)
13
+ *
14
+ * Each strategy returns null when not applicable; `extractSelector`
15
+ * tries them in order and returns the first hit. The selector is
16
+ * always non-empty for a real DOM element.
17
+ *
18
+ * Pure functions; no DOM globals beyond what each function takes as
19
+ * input. Testable in node + jsdom.
20
+ */
21
+ export function extractSelector(el) {
22
+ const tag = el.tagName.toLowerCase();
23
+ const textHint = textHintOf(el);
24
+ const ord = [
25
+ { strategy: "id", sel: byId(el) },
26
+ { strategy: "data-testid", sel: byTestId(el) },
27
+ { strategy: "role-name", sel: byRoleName(el) },
28
+ { strategy: "tag-classes", sel: byTagClasses(el) },
29
+ { strategy: "xpath", sel: byXPath(el) },
30
+ ];
31
+ let primary = null;
32
+ let fallback = null;
33
+ for (const o of ord) {
34
+ if (o.sel === null)
35
+ continue;
36
+ if (primary === null) {
37
+ primary = { strategy: o.strategy, sel: o.sel };
38
+ }
39
+ else {
40
+ fallback = o.sel;
41
+ break;
42
+ }
43
+ }
44
+ // Real elements always produce at least an XPath; the null branch
45
+ // here is defensive and would only fire on a detached node.
46
+ if (primary === null) {
47
+ return {
48
+ selector: tag,
49
+ fallbackSelector: null,
50
+ strategy: "tag-classes",
51
+ tag,
52
+ textHint,
53
+ };
54
+ }
55
+ return {
56
+ selector: primary.sel,
57
+ fallbackSelector: fallback,
58
+ strategy: primary.strategy,
59
+ tag,
60
+ textHint,
61
+ };
62
+ }
63
+ function textHintOf(el) {
64
+ const t = (el.textContent ?? "").trim().replace(/\s+/g, " ");
65
+ if (!t)
66
+ return null;
67
+ return t.length > 80 ? t.slice(0, 77) + "…" : t;
68
+ }
69
+ function byId(el) {
70
+ const id = el.id;
71
+ if (!id)
72
+ return null;
73
+ // Skip auto-generated id patterns React + many UI libs produce.
74
+ if (/^:r[0-9a-z]+:$/.test(id))
75
+ return null; // React useId
76
+ if (/^radix-:r/.test(id))
77
+ return null; // Radix
78
+ if (/^headlessui-/.test(id))
79
+ return null; // Headless UI
80
+ return `#${cssEscapeIdent(id)}`;
81
+ }
82
+ function byTestId(el) {
83
+ const v = el.getAttribute("data-testid");
84
+ if (!v)
85
+ return null;
86
+ return `[data-testid="${cssEscapeAttr(v)}"]`;
87
+ }
88
+ function byRoleName(el) {
89
+ const role = el.getAttribute("role") ?? implicitRoleOf(el);
90
+ if (!role)
91
+ return null;
92
+ const name = accessibleNameOf(el);
93
+ if (!name)
94
+ return null;
95
+ return `${el.tagName.toLowerCase()}[aria-label="${cssEscapeAttr(name)}"]`;
96
+ }
97
+ function byTagClasses(el) {
98
+ const classes = Array.from(el.classList).filter((c) => isMeaningfulClass(c));
99
+ if (classes.length === 0)
100
+ return null;
101
+ const head = classes.slice(0, 2).map(cssEscapeIdent).join(".");
102
+ // Add nth-child for structural disambiguation when the parent has
103
+ // more than one child of the same tag — common case.
104
+ const parent = el.parentElement;
105
+ let suffix = "";
106
+ if (parent) {
107
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === el.tagName);
108
+ if (sameTag.length > 1) {
109
+ const idx = Array.from(parent.children).indexOf(el) + 1;
110
+ suffix = `:nth-child(${idx})`;
111
+ }
112
+ }
113
+ return `${el.tagName.toLowerCase()}.${head}${suffix}`;
114
+ }
115
+ function byXPath(el) {
116
+ const parts = [];
117
+ let node = el;
118
+ while (node && node.nodeType === 1 && node.tagName.toLowerCase() !== "html") {
119
+ const parent = node.parentElement;
120
+ if (!parent)
121
+ break;
122
+ const tag = node.tagName.toLowerCase();
123
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName);
124
+ const idx = sameTag.indexOf(node) + 1;
125
+ parts.unshift(sameTag.length > 1 ? `${tag}[${idx}]` : tag);
126
+ node = parent;
127
+ }
128
+ return "/html/body/" + parts.join("/");
129
+ }
130
+ /**
131
+ * Implicit roles for the most common elements. Not a complete ARIA map
132
+ * — extends as needed. Matches the most common review-target tags.
133
+ */
134
+ function implicitRoleOf(el) {
135
+ const tag = el.tagName.toLowerCase();
136
+ switch (tag) {
137
+ case "button": return "button";
138
+ case "a": return el.hasAttribute("href") ? "link" : null;
139
+ case "nav": return "navigation";
140
+ case "main": return "main";
141
+ case "header": return "banner";
142
+ case "footer": return "contentinfo";
143
+ case "h1":
144
+ case "h2":
145
+ case "h3":
146
+ case "h4":
147
+ case "h5":
148
+ case "h6": return "heading";
149
+ case "img": return "img";
150
+ case "input": {
151
+ const type = (el.getAttribute("type") ?? "text").toLowerCase();
152
+ if (type === "checkbox")
153
+ return "checkbox";
154
+ if (type === "radio")
155
+ return "radio";
156
+ if (type === "submit" || type === "button")
157
+ return "button";
158
+ return "textbox";
159
+ }
160
+ default: return null;
161
+ }
162
+ }
163
+ function accessibleNameOf(el) {
164
+ // Order: aria-label > aria-labelledby's text > <label for> match >
165
+ // textContent (for buttons / links).
166
+ const ariaLabel = el.getAttribute("aria-label");
167
+ if (ariaLabel)
168
+ return ariaLabel.trim() || null;
169
+ const labelledBy = el.getAttribute("aria-labelledby");
170
+ if (labelledBy && el.ownerDocument) {
171
+ const ids = labelledBy.split(/\s+/);
172
+ const parts = [];
173
+ for (const id of ids) {
174
+ const ref = el.ownerDocument.getElementById(id);
175
+ if (ref)
176
+ parts.push((ref.textContent ?? "").trim());
177
+ }
178
+ if (parts.join(" ").trim())
179
+ return parts.join(" ").trim();
180
+ }
181
+ // For form controls, look up associated <label>
182
+ if (el.id && el.ownerDocument) {
183
+ const label = el.ownerDocument.querySelector(`label[for="${cssEscapeAttr(el.id)}"]`);
184
+ if (label) {
185
+ const t = (label.textContent ?? "").trim();
186
+ if (t)
187
+ return t;
188
+ }
189
+ }
190
+ // For buttons + links, textContent is the accessible name
191
+ const tag = el.tagName.toLowerCase();
192
+ if (tag === "button" || tag === "a") {
193
+ const t = (el.textContent ?? "").trim().replace(/\s+/g, " ");
194
+ if (t)
195
+ return t.length > 60 ? t.slice(0, 57) + "…" : t;
196
+ }
197
+ return null;
198
+ }
199
+ /**
200
+ * Skip Tailwind utility classes, framework-injected classes, and other
201
+ * volatile-looking class names when picking the seed for tag.classes.
202
+ *
203
+ * These rules are heuristic — false positives only cost selector
204
+ * specificity, not correctness; the XPath fallback always works.
205
+ */
206
+ function isMeaningfulClass(c) {
207
+ if (!c)
208
+ return false;
209
+ // Tailwind utilities: bg-*, text-*, p-*, m-*, w-*, h-*, flex,
210
+ // grid, items-*, justify-*, hover:*, sm:*, dark:* etc.
211
+ if (/^(bg|text|p|m|w|h|gap|space|border|rounded|font|leading|tracking|opacity|shadow|ring|grid|flex|items|justify|self|content|order|col|row|min|max|inset|top|right|bottom|left|z|object|overflow|cursor|select|pointer|filter|backdrop|transition|duration|ease|delay|animate|origin|rotate|scale|translate|skew)-/.test(c))
212
+ return false;
213
+ if (/^(flex|grid|block|inline|hidden|absolute|relative|fixed|static|sticky|truncate|antialiased|italic|underline|uppercase|lowercase|capitalize)$/.test(c))
214
+ return false;
215
+ if (/^(hover|focus|active|disabled|sm|md|lg|xl|2xl|dark):/.test(c))
216
+ return false;
217
+ // CSS modules / styled-components / emotion hashes
218
+ if (/^[a-zA-Z]+__[a-zA-Z]+_/.test(c))
219
+ return false; // foo__bar_HASH
220
+ if (/^css-[a-zA-Z0-9]{4,}$/.test(c))
221
+ return false; // emotion css-XXXX
222
+ if (/^_[a-zA-Z0-9]{6,}$/.test(c))
223
+ return false; // module-hashed _XXXX
224
+ return true;
225
+ }
226
+ function cssEscapeIdent(s) {
227
+ // Lightweight CSS.escape replacement for ident contexts. Keeps
228
+ // alnum + hyphen + underscore; backslash-escapes anything else.
229
+ return s.replace(/[^a-zA-Z0-9_-]/g, (ch) => "\\" + ch);
230
+ }
231
+ function cssEscapeAttr(s) {
232
+ // Inside double-quoted attribute values: escape backslash + quote.
233
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
234
+ }
235
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@slowcook-ai/review-overlay",
3
+ "version": "0.1.0",
4
+ "description": "Floating review overlay for slowcook mock previews — element-anchored comments, screenshot, viewport metadata; submits to the mockup PR via PAT. Mounted into the mock app's root layout. Plate parses the comments back out for amendments.",
5
+ "license": "MIT",
6
+ "author": "aminazar",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/aminazar/slowcook.git",
10
+ "directory": "packages/review-overlay"
11
+ },
12
+ "homepage": "https://github.com/aminazar/slowcook/tree/main/packages/review-overlay#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/aminazar/slowcook/issues"
15
+ },
16
+ "keywords": [
17
+ "slowcook",
18
+ "review",
19
+ "overlay",
20
+ "mock",
21
+ "annotations",
22
+ "react"
23
+ ],
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./react": {
33
+ "types": "./dist/react/index.d.ts",
34
+ "import": "./dist/react/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md"
40
+ ],
41
+ "peerDependencies": {
42
+ "react": ">=19",
43
+ "react-dom": ">=19"
44
+ },
45
+ "devDependencies": {
46
+ "react": "^19.0.0",
47
+ "react-dom": "^19.0.0",
48
+ "@types/react": "^19.0.0",
49
+ "jsdom": "^25.0.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "engines": {
55
+ "node": ">=20"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -b",
59
+ "test": "vitest run --passWithNoTests",
60
+ "typecheck": "tsc --noEmit"
61
+ }
62
+ }