@designfever/web-review-kit 0.1.0 → 0.3.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/README.md +77 -179
- package/dist/{chunk-U5K2YGGL.js → chunk-I76WEDLA.js} +2248 -2722
- package/dist/chunk-I76WEDLA.js.map +1 -0
- package/dist/index.cjs +2346 -2603
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +220 -7
- package/dist/index.js.map +1 -1
- package/dist/react-shell.cjs +8953 -6632
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +8 -3
- package/dist/react-shell.d.ts +8 -3
- package/dist/react-shell.js +5956 -3636
- package/dist/react-shell.js.map +1 -1
- package/dist/{types-D_mNjOHx.d.cts → types-Cf2x5ky6.d.cts} +8 -14
- package/dist/{types-D_mNjOHx.d.ts → types-Cf2x5ky6.d.ts} +8 -14
- package/dist/vite.cjs +186 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +16 -0
- package/dist/vite.d.ts +16 -0
- package/dist/vite.js +161 -0
- package/dist/vite.js.map +1 -0
- package/docs/README.md +21 -30
- package/docs/adaptor.sample.ts +182 -0
- package/docs/architecture.md +125 -0
- package/docs/db-setup.md +253 -0
- package/docs/figma-overlay.md +52 -0
- package/docs/grid-overlay.md +38 -0
- package/docs/installation.md +108 -40
- package/package.json +22 -6
- package/dist/chunk-U5K2YGGL.js.map +0 -1
- package/docs/adapter-handoff.md +0 -146
- package/docs/concept.md +0 -102
- package/docs/df-sheet-adapter.md +0 -336
- package/docs/df-sheet-next.md +0 -222
- package/docs/initial-plan.md +0 -226
- package/docs/package-split-checkpoint.md +0 -79
- package/docs/presence-handoff.md +0 -138
- package/docs/review-feedback-2026-06-20.md +0 -267
- package/docs/smoke-baseline-2026-06-20.md +0 -41
- package/docs/stabilize-ui-work-guide.md +0 -243
- package/docs/supabase-presence.md +0 -198
- package/docs/supabase-review-items.md +0 -365
- package/docs/supabase.md +0 -205
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
3
|
+
type ReviewItem,
|
|
4
|
+
type ReviewItemQuery,
|
|
5
|
+
type ReviewItemStatus,
|
|
6
|
+
type ReviewSource,
|
|
7
|
+
type WebReviewKitAdapter,
|
|
8
|
+
} from '@designfever/web-review-kit';
|
|
9
|
+
import type {
|
|
10
|
+
ReviewShellAdapter,
|
|
11
|
+
} from '@designfever/web-review-kit/react-shell';
|
|
12
|
+
|
|
13
|
+
type RemoteReviewAdapterOptions = {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
projectId: string;
|
|
16
|
+
source?: ReviewSource;
|
|
17
|
+
token?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type RemoteReviewItemResponse = ReviewItem | { item: ReviewItem };
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* WebReviewKitAdapter is the core storage contract.
|
|
24
|
+
*
|
|
25
|
+
* ReviewItem is the full QA payload. Persist it as structured JSON so marker,
|
|
26
|
+
* anchor, selection, viewport, scroll, status, and external issue fields survive.
|
|
27
|
+
*
|
|
28
|
+
* ReviewItemQuery is used by the shell for current-page lists and sitemap counts.
|
|
29
|
+
* A remote backend should support at least projectId, routeKey, status, source,
|
|
30
|
+
* and pageId when the host project needs page-level grouping.
|
|
31
|
+
*
|
|
32
|
+
* Keep private tokens, admin keys, numbering, and permission checks in your
|
|
33
|
+
* backend. Browser code should only call browser-safe endpoints.
|
|
34
|
+
*/
|
|
35
|
+
export function createRemoteReviewAdapter(
|
|
36
|
+
options: RemoteReviewAdapterOptions
|
|
37
|
+
): WebReviewKitAdapter {
|
|
38
|
+
const source = options.source ?? 'remote';
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
async get(id) {
|
|
42
|
+
return readReviewItem(
|
|
43
|
+
await requestJson<RemoteReviewItemResponse>(
|
|
44
|
+
`/review-items/${encodeURIComponent(id)}`,
|
|
45
|
+
options
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async list(query) {
|
|
51
|
+
const rows = await requestJson<RemoteReviewItemResponse[]>(
|
|
52
|
+
`/review-items?${createListParams(query, source).toString()}`,
|
|
53
|
+
options
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return rows.map(readReviewItem);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async create(item) {
|
|
60
|
+
return readReviewItem(
|
|
61
|
+
await requestJson<RemoteReviewItemResponse>('/review-items', options, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
project_id: options.projectId,
|
|
65
|
+
source,
|
|
66
|
+
item,
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async update(id, patch) {
|
|
73
|
+
return readReviewItem(
|
|
74
|
+
await requestJson<RemoteReviewItemResponse>(
|
|
75
|
+
`/review-items/${encodeURIComponent(id)}`,
|
|
76
|
+
options,
|
|
77
|
+
{
|
|
78
|
+
method: 'PATCH',
|
|
79
|
+
body: JSON.stringify({ patch }),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async remove(id) {
|
|
86
|
+
await requestJson<void>(
|
|
87
|
+
`/review-items/${encodeURIComponent(id)}`,
|
|
88
|
+
options,
|
|
89
|
+
{ method: 'DELETE' }
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* ReviewShellAdapter is the React shell wiring.
|
|
97
|
+
*
|
|
98
|
+
* label becomes the URL source, for example /review?source=remote.
|
|
99
|
+
* create controls whether the shell can write to this adapter.
|
|
100
|
+
* canWrite can be true or limited to ['dom', 'note', 'area'].
|
|
101
|
+
* updateStatus drives the status buttons in the QA panel.
|
|
102
|
+
* remove enables delete actions for this source.
|
|
103
|
+
*/
|
|
104
|
+
export function createRemoteReviewShellAdapter(
|
|
105
|
+
options: RemoteReviewAdapterOptions
|
|
106
|
+
): ReviewShellAdapter {
|
|
107
|
+
const adapter = createRemoteReviewAdapter(options);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
label: options.source ?? 'remote',
|
|
111
|
+
get: (id) => adapter.get(id),
|
|
112
|
+
list: (query) => adapter.list(query),
|
|
113
|
+
create: (item) => adapter.create(item),
|
|
114
|
+
canWrite: true,
|
|
115
|
+
statusOptions: REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
116
|
+
updateStatus: ({ id, status }) =>
|
|
117
|
+
adapter.update(id, { status: normalizeRemoteStatus(status) }),
|
|
118
|
+
remove: (id) => adapter.remove(id),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function appendParam(
|
|
123
|
+
params: URLSearchParams,
|
|
124
|
+
key: string,
|
|
125
|
+
value: string | undefined
|
|
126
|
+
) {
|
|
127
|
+
if (value) params.set(key, value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createListParams(query: ReviewItemQuery, source: ReviewSource) {
|
|
131
|
+
const params = new URLSearchParams();
|
|
132
|
+
params.set('project_id', query.projectId);
|
|
133
|
+
params.set('source', query.source ?? source);
|
|
134
|
+
appendParam(params, 'page_id', query.pageId);
|
|
135
|
+
appendParam(params, 'route_key', query.routeKey ?? query.normalizedPath);
|
|
136
|
+
appendParam(params, 'status', query.status);
|
|
137
|
+
return params;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function requestJson<T>(
|
|
141
|
+
path: string,
|
|
142
|
+
options: RemoteReviewAdapterOptions,
|
|
143
|
+
init: RequestInit = {}
|
|
144
|
+
) {
|
|
145
|
+
const url = new URL(path, ensureTrailingSlash(options.baseUrl));
|
|
146
|
+
const headers = new Headers(init.headers);
|
|
147
|
+
|
|
148
|
+
headers.set('Accept', 'application/json');
|
|
149
|
+
if (init.body && !headers.has('Content-Type')) {
|
|
150
|
+
headers.set('Content-Type', 'application/json');
|
|
151
|
+
}
|
|
152
|
+
if (options.token) {
|
|
153
|
+
headers.set('Authorization', `Bearer ${options.token}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const response = await fetch(url, {
|
|
157
|
+
...init,
|
|
158
|
+
headers,
|
|
159
|
+
credentials: 'include',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
throw new Error(`remote review adapter request failed: ${response.status}`);
|
|
164
|
+
}
|
|
165
|
+
if (response.status === 204) return undefined as T;
|
|
166
|
+
|
|
167
|
+
return response.json() as Promise<T>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readReviewItem(response: RemoteReviewItemResponse) {
|
|
171
|
+
return 'item' in response ? response.item : response;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function ensureTrailingSlash(value: string) {
|
|
175
|
+
return value.endsWith('/') ? value : `${value}/`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeRemoteStatus(status: ReviewItemStatus) {
|
|
179
|
+
if (status === 'open') return 'todo';
|
|
180
|
+
if (status === 'resolved') return 'done';
|
|
181
|
+
return status;
|
|
182
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Architecture and Runtime Logic
|
|
2
|
+
|
|
3
|
+
`df-web-review-kit` has two main runtime surfaces:
|
|
4
|
+
|
|
5
|
+
- `core`: a vanilla DOM runtime that can mount review overlays on a same-origin target page.
|
|
6
|
+
- `react-shell`: a React review app that hosts pages in an iframe and controls the core runtime.
|
|
7
|
+
|
|
8
|
+
This split keeps the target-page overlay independent from React while allowing the review shell to provide richer workflow UI.
|
|
9
|
+
|
|
10
|
+
## High-Level Flow
|
|
11
|
+
|
|
12
|
+
```txt
|
|
13
|
+
React shell
|
|
14
|
+
-> renders topbar, QA panel, iframe, ruler, settings
|
|
15
|
+
-> creates core runtime with createWebReviewKit()
|
|
16
|
+
-> passes iframe target geometry through getReviewKitTarget()
|
|
17
|
+
|
|
18
|
+
Core runtime
|
|
19
|
+
-> mounts a shadow DOM overlay
|
|
20
|
+
-> handles note/area/DOM selection
|
|
21
|
+
-> creates markers and highlights over the target viewport
|
|
22
|
+
-> persists ReviewItem records through the configured adapter
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
In the React shell, core is created with:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
createWebReviewKit({
|
|
29
|
+
target: () => getReviewKitTarget({ frameScrollRef, iframeRef }),
|
|
30
|
+
ui: {
|
|
31
|
+
panel: false,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`ui.panel: false` means the React shell owns the side panel and toolbar. Core still owns target overlays such as note pins, area selection boxes, DOM hover outlines, saved item markers, and highlights.
|
|
37
|
+
|
|
38
|
+
## Core Modules
|
|
39
|
+
|
|
40
|
+
- `web.review.kit.app.ts`: controller lifecycle, state transitions, adapter calls, item creation, restore flow.
|
|
41
|
+
- `web.review.kit.view.ts`: vanilla DOM renderer for core overlay UI.
|
|
42
|
+
- `dom.anchor.ts`: selector candidate generation, anchor rebinding, text fingerprint matching.
|
|
43
|
+
- `geometry.ts`: target-space and host-space coordinate conversion.
|
|
44
|
+
- `review/item.ts`: marker, selection, highlight, and fallback resolution.
|
|
45
|
+
- `review/scope.ts`: viewport scope grouping and numbering.
|
|
46
|
+
- `review/format.ts`: compact item/draft metadata labels.
|
|
47
|
+
- `scroll.ts`: scroll restore helpers.
|
|
48
|
+
- `location.ts`: public URL and route key helpers.
|
|
49
|
+
|
|
50
|
+
## Coordinate Spaces
|
|
51
|
+
|
|
52
|
+
Core uses two coordinate spaces:
|
|
53
|
+
|
|
54
|
+
- **Target space**: coordinates relative to the iframe or target page viewport.
|
|
55
|
+
- **Host space**: coordinates relative to the review shell page that contains the iframe.
|
|
56
|
+
|
|
57
|
+
Persisted review data uses target-space viewport values plus optional anchor-relative values. Rendering converts target-space markers and selections into host-space overlay positions.
|
|
58
|
+
|
|
59
|
+
## Anchor and Restore Logic
|
|
60
|
+
|
|
61
|
+
When a user creates a DOM or area item, core tries to capture:
|
|
62
|
+
|
|
63
|
+
- an explicit configured anchor such as `data-qa-id`
|
|
64
|
+
- a meaningful `id`
|
|
65
|
+
- a meaningful class
|
|
66
|
+
- a DOM path fallback
|
|
67
|
+
- a short text fingerprint
|
|
68
|
+
|
|
69
|
+
Restore prefers anchor-relative coordinates because absolute viewport coordinates drift after layout changes. If an anchor cannot be resolved, core falls back to the original viewport coordinate adjusted by saved scroll position.
|
|
70
|
+
|
|
71
|
+
## Adapter Boundary
|
|
72
|
+
|
|
73
|
+
Core never owns persistence storage directly. It only calls the configured `WebReviewKitAdapter`:
|
|
74
|
+
|
|
75
|
+
- `list`
|
|
76
|
+
- `get`
|
|
77
|
+
- `create`
|
|
78
|
+
- `update`
|
|
79
|
+
- `remove`
|
|
80
|
+
|
|
81
|
+
The default local adapter is for draft/local review work. Supabase is optional host wiring, not a required backend.
|
|
82
|
+
|
|
83
|
+
## React Shell Boundary
|
|
84
|
+
|
|
85
|
+
`react-shell` owns reviewer workflow UI:
|
|
86
|
+
|
|
87
|
+
- iframe target routing
|
|
88
|
+
- QA list and item actions
|
|
89
|
+
- viewport presets
|
|
90
|
+
- sitemap modal
|
|
91
|
+
- settings modal
|
|
92
|
+
- ruler UI
|
|
93
|
+
- presence UI
|
|
94
|
+
- host overlay toggles such as grid and Figma
|
|
95
|
+
|
|
96
|
+
React shell should call the core controller instead of duplicating target overlay logic.
|
|
97
|
+
|
|
98
|
+
## Figma Overlay Direction
|
|
99
|
+
|
|
100
|
+
Figma overlay work should stay outside core unless it needs target runtime primitives.
|
|
101
|
+
|
|
102
|
+
Preferred direction:
|
|
103
|
+
|
|
104
|
+
```txt
|
|
105
|
+
src/react-shell/figma/
|
|
106
|
+
controls.tsx
|
|
107
|
+
sync.ts
|
|
108
|
+
overlay.state.ts
|
|
109
|
+
|
|
110
|
+
src/figma/
|
|
111
|
+
matching.ts
|
|
112
|
+
types.ts
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Shared coordinate math can reuse `core/geometry.ts` or move to a future shared module if both core and Figma need it heavily.
|
|
116
|
+
|
|
117
|
+
Avoid turning `core` into a feature bucket. Core should stay focused on target review runtime behavior.
|
|
118
|
+
|
|
119
|
+
## Extension Rules
|
|
120
|
+
|
|
121
|
+
- Put target overlay primitives in `core`.
|
|
122
|
+
- Put reviewer workflow UI in `react-shell`.
|
|
123
|
+
- Put feature-specific integration logic, such as Figma matching, in its own module.
|
|
124
|
+
- Keep adapter contracts storage-agnostic.
|
|
125
|
+
- Prefer anchor-relative data for anything that must survive layout changes.
|
package/docs/db-setup.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# DB Setup
|
|
2
|
+
|
|
3
|
+
Supabase is an optional backend adapter. A host project may use it for canonical QA items and Realtime Presence, but the public package does not own the Supabase project or any operator secrets.
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
```env
|
|
8
|
+
VITE_REVIEW_PROJECT_ID=df-web-review-kit
|
|
9
|
+
VITE_REVIEW_SUPABASE_URL=https://your-project.supabase.co
|
|
10
|
+
VITE_REVIEW_SUPABASE_ANON_KEY=
|
|
11
|
+
VITE_REVIEW_SUPABASE_TABLE=review_items
|
|
12
|
+
VITE_REVIEW_SUPABASE_PRESENCE_PRIVATE=false
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Use only an `anon` key in browser env. Keep `service_role`, OpenClaw `KUKU_*`, and future admin keys in an operator layer or backend service.
|
|
16
|
+
|
|
17
|
+
## Review Item Schema
|
|
18
|
+
|
|
19
|
+
Run this in the Supabase SQL editor to create the tables, indexes, RPC, and grants.
|
|
20
|
+
|
|
21
|
+
```sql
|
|
22
|
+
create table if not exists public.review_items (
|
|
23
|
+
id text primary key,
|
|
24
|
+
project_id text not null,
|
|
25
|
+
route_key text not null,
|
|
26
|
+
source text not null default 'supabase',
|
|
27
|
+
review_number integer,
|
|
28
|
+
status text not null default 'todo',
|
|
29
|
+
item jsonb not null,
|
|
30
|
+
created_at timestamptz not null default now(),
|
|
31
|
+
updated_at timestamptz not null default now()
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
create unique index if not exists review_items_project_review_number_idx
|
|
35
|
+
on public.review_items (project_id, source, review_number)
|
|
36
|
+
where review_number is not null;
|
|
37
|
+
|
|
38
|
+
create index if not exists review_items_project_route_updated_idx
|
|
39
|
+
on public.review_items (project_id, source, route_key, updated_at desc);
|
|
40
|
+
|
|
41
|
+
create index if not exists review_items_project_status_idx
|
|
42
|
+
on public.review_items (project_id, source, status);
|
|
43
|
+
|
|
44
|
+
create table if not exists public.review_project_counters (
|
|
45
|
+
project_id text not null,
|
|
46
|
+
source text not null default 'supabase',
|
|
47
|
+
next_review_number integer not null default 1,
|
|
48
|
+
updated_at timestamptz not null default now(),
|
|
49
|
+
primary key (project_id, source),
|
|
50
|
+
constraint review_project_counters_next_review_number_check
|
|
51
|
+
check (next_review_number > 0)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
create or replace function public.create_review_item(
|
|
55
|
+
p_id text,
|
|
56
|
+
p_project_id text,
|
|
57
|
+
p_route_key text,
|
|
58
|
+
p_source text,
|
|
59
|
+
p_status text,
|
|
60
|
+
p_item jsonb
|
|
61
|
+
)
|
|
62
|
+
returns public.review_items
|
|
63
|
+
language plpgsql
|
|
64
|
+
security invoker
|
|
65
|
+
set search_path = public
|
|
66
|
+
as $$
|
|
67
|
+
declare
|
|
68
|
+
v_review_number integer;
|
|
69
|
+
v_now timestamptz := now();
|
|
70
|
+
v_row public.review_items;
|
|
71
|
+
begin
|
|
72
|
+
insert into public.review_project_counters (
|
|
73
|
+
project_id,
|
|
74
|
+
source,
|
|
75
|
+
next_review_number,
|
|
76
|
+
updated_at
|
|
77
|
+
)
|
|
78
|
+
select
|
|
79
|
+
p_project_id,
|
|
80
|
+
p_source,
|
|
81
|
+
coalesce(max(review_number), 0) + 2,
|
|
82
|
+
v_now
|
|
83
|
+
from public.review_items
|
|
84
|
+
where project_id = p_project_id
|
|
85
|
+
and source = p_source
|
|
86
|
+
on conflict (project_id, source) do update
|
|
87
|
+
set next_review_number = greatest(
|
|
88
|
+
public.review_project_counters.next_review_number + 1,
|
|
89
|
+
excluded.next_review_number
|
|
90
|
+
),
|
|
91
|
+
updated_at = excluded.updated_at
|
|
92
|
+
returning next_review_number - 1 into v_review_number;
|
|
93
|
+
|
|
94
|
+
insert into public.review_items (
|
|
95
|
+
id,
|
|
96
|
+
project_id,
|
|
97
|
+
route_key,
|
|
98
|
+
source,
|
|
99
|
+
review_number,
|
|
100
|
+
status,
|
|
101
|
+
item,
|
|
102
|
+
created_at,
|
|
103
|
+
updated_at
|
|
104
|
+
)
|
|
105
|
+
values (
|
|
106
|
+
p_id,
|
|
107
|
+
p_project_id,
|
|
108
|
+
p_route_key,
|
|
109
|
+
p_source,
|
|
110
|
+
v_review_number,
|
|
111
|
+
p_status,
|
|
112
|
+
p_item || jsonb_build_object(
|
|
113
|
+
'id', p_id,
|
|
114
|
+
'reviewNumber', v_review_number,
|
|
115
|
+
'projectId', p_project_id,
|
|
116
|
+
'routeKey', p_route_key,
|
|
117
|
+
'normalizedPath', coalesce(nullif(p_item->>'normalizedPath', ''), p_route_key),
|
|
118
|
+
'status', p_status,
|
|
119
|
+
'externalIssueId', p_id,
|
|
120
|
+
'submittedAt', coalesce(p_item->>'submittedAt', v_now::text),
|
|
121
|
+
'submitStatus', coalesce(p_item->>'submitStatus', 'submitted'),
|
|
122
|
+
'createdAt', v_now::text,
|
|
123
|
+
'updatedAt', v_now::text
|
|
124
|
+
),
|
|
125
|
+
v_now,
|
|
126
|
+
v_now
|
|
127
|
+
)
|
|
128
|
+
returning * into v_row;
|
|
129
|
+
|
|
130
|
+
return v_row;
|
|
131
|
+
end;
|
|
132
|
+
$$;
|
|
133
|
+
|
|
134
|
+
grant execute on function public.create_review_item(
|
|
135
|
+
text,
|
|
136
|
+
text,
|
|
137
|
+
text,
|
|
138
|
+
text,
|
|
139
|
+
text,
|
|
140
|
+
jsonb
|
|
141
|
+
) to anon;
|
|
142
|
+
|
|
143
|
+
grant select, insert, update, delete on public.review_items to anon;
|
|
144
|
+
grant select, insert, update on public.review_project_counters to anon;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## RLS Policies
|
|
148
|
+
|
|
149
|
+
Run this after the schema SQL. Replace `df-web-review-kit` if `VITE_REVIEW_PROJECT_ID` uses a different value.
|
|
150
|
+
|
|
151
|
+
```sql
|
|
152
|
+
alter table public.review_items enable row level security;
|
|
153
|
+
alter table public.review_project_counters enable row level security;
|
|
154
|
+
|
|
155
|
+
drop policy if exists review_items_sample_read on public.review_items;
|
|
156
|
+
create policy review_items_sample_read
|
|
157
|
+
on public.review_items
|
|
158
|
+
for select
|
|
159
|
+
to anon
|
|
160
|
+
using (project_id = 'df-web-review-kit');
|
|
161
|
+
|
|
162
|
+
drop policy if exists review_items_sample_insert on public.review_items;
|
|
163
|
+
create policy review_items_sample_insert
|
|
164
|
+
on public.review_items
|
|
165
|
+
for insert
|
|
166
|
+
to anon
|
|
167
|
+
with check (project_id = 'df-web-review-kit');
|
|
168
|
+
|
|
169
|
+
drop policy if exists review_items_sample_update on public.review_items;
|
|
170
|
+
create policy review_items_sample_update
|
|
171
|
+
on public.review_items
|
|
172
|
+
for update
|
|
173
|
+
to anon
|
|
174
|
+
using (project_id = 'df-web-review-kit')
|
|
175
|
+
with check (project_id = 'df-web-review-kit');
|
|
176
|
+
|
|
177
|
+
drop policy if exists review_items_sample_delete on public.review_items;
|
|
178
|
+
create policy review_items_sample_delete
|
|
179
|
+
on public.review_items
|
|
180
|
+
for delete
|
|
181
|
+
to anon
|
|
182
|
+
using (project_id = 'df-web-review-kit');
|
|
183
|
+
|
|
184
|
+
drop policy if exists review_project_counters_sample_read on public.review_project_counters;
|
|
185
|
+
create policy review_project_counters_sample_read
|
|
186
|
+
on public.review_project_counters
|
|
187
|
+
for select
|
|
188
|
+
to anon
|
|
189
|
+
using (project_id = 'df-web-review-kit');
|
|
190
|
+
|
|
191
|
+
drop policy if exists review_project_counters_sample_insert on public.review_project_counters;
|
|
192
|
+
create policy review_project_counters_sample_insert
|
|
193
|
+
on public.review_project_counters
|
|
194
|
+
for insert
|
|
195
|
+
to anon
|
|
196
|
+
with check (project_id = 'df-web-review-kit');
|
|
197
|
+
|
|
198
|
+
drop policy if exists review_project_counters_sample_update on public.review_project_counters;
|
|
199
|
+
create policy review_project_counters_sample_update
|
|
200
|
+
on public.review_project_counters
|
|
201
|
+
for update
|
|
202
|
+
to anon
|
|
203
|
+
using (project_id = 'df-web-review-kit')
|
|
204
|
+
with check (project_id = 'df-web-review-kit');
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Adapter Behavior
|
|
208
|
+
|
|
209
|
+
- `list`: reads by `project_id`, `source`, optional `route_key`, optional `status`.
|
|
210
|
+
- `create`: discards local draft number and calls `create_review_item` for a canonical `review_number`.
|
|
211
|
+
- `update`: updates row columns and the nested `item` JSON.
|
|
212
|
+
- `remove`: deletes the row.
|
|
213
|
+
|
|
214
|
+
Remote numbers are canonical. Local draft numbers can overlap between browsers and are not reused as remote numbers.
|
|
215
|
+
|
|
216
|
+
## Presence
|
|
217
|
+
|
|
218
|
+
Supabase Presence is optional session state. It is not QA item storage.
|
|
219
|
+
|
|
220
|
+
Default topic:
|
|
221
|
+
|
|
222
|
+
```txt
|
|
223
|
+
review-presence-<projectId>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The adapter normalizes unsafe topic characters. `private=false` is enough for quick dev validation. `private=true` requires an authenticated Supabase session and Realtime authorization policies for `realtime.messages`.
|
|
227
|
+
|
|
228
|
+
Use project-level channels instead of page-level channels so the sitemap can show who is on each page.
|
|
229
|
+
|
|
230
|
+
## Security
|
|
231
|
+
|
|
232
|
+
The RLS policies above are sample/dev policies. Anyone with the anon key and allowed `project_id` can create, update, and delete QA rows.
|
|
233
|
+
|
|
234
|
+
For production, use one of these instead:
|
|
235
|
+
|
|
236
|
+
- Supabase Auth plus project member table based RLS
|
|
237
|
+
- Supabase Edge Function
|
|
238
|
+
- host project backend proxy
|
|
239
|
+
- private admin service
|
|
240
|
+
|
|
241
|
+
Never put a `service_role` key in browser env.
|
|
242
|
+
|
|
243
|
+
## Validation
|
|
244
|
+
|
|
245
|
+
1. Open `/review?source=supabase`.
|
|
246
|
+
2. Create a local item.
|
|
247
|
+
3. Submit it to remote.
|
|
248
|
+
4. Confirm the local draft disappears.
|
|
249
|
+
5. Confirm remote list/get works.
|
|
250
|
+
6. Change status.
|
|
251
|
+
7. Delete the remote item.
|
|
252
|
+
8. Create another remote item and confirm the number is not reused.
|
|
253
|
+
9. If presence is enabled, open two browser tabs and confirm each user appears on the active page or sitemap.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Figma Overlay
|
|
2
|
+
|
|
3
|
+
The Figma overlay button in the review shell toggles a host-page helper. The package does not fetch Figma data by itself and does not own a server-side Figma token.
|
|
4
|
+
|
|
5
|
+
## User Flow
|
|
6
|
+
|
|
7
|
+
- Open `/review`.
|
|
8
|
+
- Click the settings button.
|
|
9
|
+
- Enter a Figma token if the host helper requires one.
|
|
10
|
+
- Click the Figma overlay button or press `f`.
|
|
11
|
+
|
|
12
|
+
The token is stored in browser localStorage with this key:
|
|
13
|
+
|
|
14
|
+
```txt
|
|
15
|
+
figma-token
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Treat this as a dev/debug convenience. Do not use it for private server tokens.
|
|
19
|
+
|
|
20
|
+
## Availability
|
|
21
|
+
|
|
22
|
+
The shell currently enables the Figma overlay on viewport presets whose `kind` is:
|
|
23
|
+
|
|
24
|
+
- `mobile`
|
|
25
|
+
- `wide`
|
|
26
|
+
|
|
27
|
+
Other viewport kinds show the unavailable message.
|
|
28
|
+
|
|
29
|
+
## Host Requirements
|
|
30
|
+
|
|
31
|
+
The target page inside the iframe must already support the Figma helper.
|
|
32
|
+
|
|
33
|
+
Expected behavior:
|
|
34
|
+
|
|
35
|
+
- The target page reacts to a `KeyboardEvent` with `code: 'KeyF'`.
|
|
36
|
+
- The target page mounts a visible Figma helper layer.
|
|
37
|
+
- The helper root uses one of these selectors:
|
|
38
|
+
- `.helper-figma-root`
|
|
39
|
+
- `.helper-figma-loading-backdrop`
|
|
40
|
+
|
|
41
|
+
The review shell uses those selectors only to detect whether the overlay is active.
|
|
42
|
+
|
|
43
|
+
## Troubleshooting
|
|
44
|
+
|
|
45
|
+
If the button does nothing:
|
|
46
|
+
|
|
47
|
+
- Confirm the iframe target page is same-origin and can receive dispatched keyboard events.
|
|
48
|
+
- Confirm the host helper listens for `KeyF`.
|
|
49
|
+
- Confirm the helper renders `.helper-figma-root` or `.helper-figma-loading-backdrop`.
|
|
50
|
+
- Confirm the current viewport preset is `mobile` or `wide`.
|
|
51
|
+
|
|
52
|
+
If the overlay needs a private Figma integration, move that work to a backend/admin service and expose only browser-safe state to the host page.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Grid Overlay
|
|
2
|
+
|
|
3
|
+
The grid overlay button in the review shell toggles a host-page layout/helper overlay. It is for visual alignment while reviewing pages.
|
|
4
|
+
|
|
5
|
+
## User Flow
|
|
6
|
+
|
|
7
|
+
- Open `/review`.
|
|
8
|
+
- Click the grid button or press `g`.
|
|
9
|
+
- The target page toggles its own grid/helper layer.
|
|
10
|
+
|
|
11
|
+
## Host Requirements
|
|
12
|
+
|
|
13
|
+
The target page inside the iframe must already support the grid helper.
|
|
14
|
+
|
|
15
|
+
Expected behavior:
|
|
16
|
+
|
|
17
|
+
- The target page reacts to a `KeyboardEvent` with `code: 'KeyG'`.
|
|
18
|
+
- The target page toggles a visible grid/helper layer.
|
|
19
|
+
- The active state is reflected by one of these:
|
|
20
|
+
- `document.body.classList.contains('is-help')`
|
|
21
|
+
- `.helper.onShow`
|
|
22
|
+
|
|
23
|
+
The review shell uses those signals to keep the toolbar button state in sync.
|
|
24
|
+
|
|
25
|
+
## Notes
|
|
26
|
+
|
|
27
|
+
- The package does not prescribe the grid design.
|
|
28
|
+
- The grid overlay is not persisted as QA data.
|
|
29
|
+
- Use project-specific CSS/helper code in the host page when the grid differs by brand or design system.
|
|
30
|
+
|
|
31
|
+
## Troubleshooting
|
|
32
|
+
|
|
33
|
+
If the icon state does not change:
|
|
34
|
+
|
|
35
|
+
- Confirm the target page helper is mounted.
|
|
36
|
+
- Confirm it listens for `KeyG`.
|
|
37
|
+
- Confirm it sets `body.is-help` or `.helper.onShow`.
|
|
38
|
+
- Confirm the iframe is same-origin and keyboard events are not blocked.
|