@designfever/web-review-kit 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.
@@ -0,0 +1,205 @@
1
+ # Supabase setup
2
+
3
+ Supabase는 `df-web-review-kit`의 remote QA 저장소와 team presence backend로 쓸 수 있다.
4
+
5
+ Lexus pilot:
6
+
7
+ ```txt
8
+ Supabase project: bb-qa
9
+ url: https://vhqnvfkamnpgyqclohso.supabase.co
10
+ review project id: lexus-official-v2026
11
+ source: supabase
12
+ ```
13
+
14
+ ## 역할
15
+
16
+ Supabase review item adapter:
17
+
18
+ - local draft를 remote QA row로 등록
19
+ - canonical `reviewNumber` 발급
20
+ - remote item list/get/update/delete
21
+ - deep link restore용 item JSON 저장
22
+
23
+ Supabase presence adapter:
24
+
25
+ - 같은 review project 안의 접속자 상태 공유
26
+ - shell UI에서는 현재 page에 있는 user id만 표시
27
+ - sitemap에서는 page별 presence 표시로 확장 가능
28
+
29
+ ## Required env
30
+
31
+ ```env
32
+ VITE_REVIEW_SUPABASE_URL=https://your-project.supabase.co
33
+ VITE_REVIEW_SUPABASE_ANON_KEY=
34
+ VITE_REVIEW_SUPABASE_TABLE=review_items
35
+ VITE_REVIEW_SUPABASE_PRESENCE_PRIVATE=false
36
+ ```
37
+
38
+ `anon` key만 browser env에 넣는다. `service_role` key는 browser에 넣지 않는다.
39
+
40
+ ## Database setup
41
+
42
+ 새 Supabase project에서는 [supabase-review-items.md](supabase-review-items.md)의 `Schema` SQL을 실행한다.
43
+
44
+ 이미 이전 `max(review_number) + 1` RPC를 실행한 DB에서는 [supabase-review-items.md](supabase-review-items.md)의 `Migration from max+1 RPC` 블록만 실행한다.
45
+
46
+ 현재 table:
47
+
48
+ - `review_items`: QA row와 restore payload 저장
49
+ - `review_project_counters`: `(project_id, source)`별 다음 번호 저장
50
+
51
+ 현재 RPC:
52
+
53
+ - `create_review_item`: counter row를 증가시키고 item을 insert
54
+
55
+ ## Review number policy
56
+
57
+ local `#id`는 개인 draft 번호다. 여러 사람이 local에서 동시에 만들면 번호가 겹칠 수 있다.
58
+
59
+ remote 등록 시 Supabase adapter는 local id와 local `reviewNumber`를 버린다. `create_review_item` RPC가 새 UUID와 canonical `reviewNumber`를 부여한다.
60
+
61
+ 번호 발급은 `review_project_counters`를 사용한다.
62
+
63
+ - 동시 등록 중복 방지
64
+ - 삭제 후 번호 재사용 방지
65
+ - project/source별 번호 분리
66
+
67
+ 검증된 smoke:
68
+
69
+ ```txt
70
+ concurrent create: #7, #8
71
+ delete #7/#8
72
+ next create: #9
73
+ ```
74
+
75
+ ## RLS policy
76
+
77
+ 현재 Lexus pilot RLS는 dev 검증용이다.
78
+
79
+ ```txt
80
+ project_id = 'lexus-official-v2026'
81
+ role = anon
82
+ ```
83
+
84
+ 이 방식은 anon key와 project id를 알면 row create/update/delete가 가능하다. production/package 운영에서는 다음 중 하나로 좁힌다.
85
+
86
+ - Supabase Auth + project member table 기반 RLS
87
+ - Supabase Edge Function
88
+ - project backend proxy
89
+ - private deployment 내부 service endpoint
90
+
91
+ 여러 project를 같은 `bb-qa` DB에 넣는 것은 가능하다. `review_projects`와 `review_items.project_id`로 분리한다. 예:
92
+
93
+ ```sql
94
+ insert into public.review_projects (id, label)
95
+ values
96
+ ('lexus-official-v2026', 'Lexus Official v2026'),
97
+ ('hdc-lab', 'HDC Lab')
98
+ on conflict (id) do update
99
+ set label = excluded.label;
100
+ ```
101
+
102
+ Project를 추가하면 해당 `project_id`를 허용하는 RLS policy도 같이 추가해야 한다.
103
+
104
+ ## Host code wiring
105
+
106
+ ```tsx
107
+ import {
108
+ createFallbackPresenceAdapter,
109
+ createLocalPresenceAdapter,
110
+ createSupabasePresenceAdapter,
111
+ type SupabasePresenceClient,
112
+ } from '@designfever/web-review-kit/react-shell';
113
+ import {
114
+ supabaseAdapter,
115
+ type SupabaseReviewClient,
116
+ } from '@designfever/web-review-kit';
117
+ import { createClient } from '@supabase/supabase-js';
118
+
119
+ const REVIEW_PROJECT_ID = 'lexus-official-v2026';
120
+ const REVIEW_PATH_PREFIX = '/review';
121
+
122
+ const supabaseClient = import.meta.env.VITE_REVIEW_SUPABASE_ANON_KEY
123
+ ? createClient(
124
+ import.meta.env.VITE_REVIEW_SUPABASE_URL,
125
+ import.meta.env.VITE_REVIEW_SUPABASE_ANON_KEY
126
+ )
127
+ : null;
128
+
129
+ const reviewAdapter = supabaseClient
130
+ ? supabaseAdapter({
131
+ client: supabaseClient as unknown as SupabaseReviewClient,
132
+ table: import.meta.env.VITE_REVIEW_SUPABASE_TABLE || 'review_items',
133
+ projectId: REVIEW_PROJECT_ID,
134
+ source: 'supabase',
135
+ reviewPathPrefix: REVIEW_PATH_PREFIX,
136
+ })
137
+ : null;
138
+
139
+ const localPresence = createLocalPresenceAdapter({
140
+ channelName: `${REVIEW_PROJECT_ID}:review-presence`,
141
+ });
142
+
143
+ const presence = supabaseClient
144
+ ? createFallbackPresenceAdapter(
145
+ createSupabasePresenceAdapter({
146
+ client: supabaseClient as unknown as SupabasePresenceClient,
147
+ channelPrefix: 'review-presence',
148
+ private: import.meta.env.VITE_REVIEW_SUPABASE_PRESENCE_PRIVATE === 'true',
149
+ }),
150
+ localPresence
151
+ )
152
+ : localPresence;
153
+ ```
154
+
155
+ ## Presence channel
156
+
157
+ Topic:
158
+
159
+ ```txt
160
+ review-presence-<projectId>
161
+ ```
162
+
163
+ Adapter는 topic에 안전하지 않은 문자를 `-`로 normalize한다.
164
+
165
+ `private=false`면 별도 Realtime RLS 없이 빠르게 검증할 수 있다. `private=true`로 바꾸려면 Supabase Realtime authorization policy가 필요하다.
166
+
167
+ ## Validation
168
+
169
+ After SQL:
170
+
171
+ 1. `/review?source=supabase` 접속
172
+ 2. local item 생성
173
+ 3. remote 등록
174
+ 4. local draft 삭제 확인
175
+ 5. remote source에서 item 표시 확인
176
+ 6. status dropdown 변경 확인
177
+ 7. delete 확인
178
+ 8. 새 remote item을 만들어 번호가 재사용되지 않는지 확인
179
+ 9. review page를 두 탭에서 열고 같은 page presence 표시 확인
180
+
181
+ SQL smoke:
182
+
183
+ - concurrent create 2개가 서로 다른 `review_number`를 받아야 한다.
184
+ - 두 row 삭제 후 다음 create가 더 큰 번호를 받아야 한다.
185
+
186
+ ## Troubleshooting
187
+
188
+ `create_review_item rpc is required`
189
+
190
+ - Supabase client mock이나 wrapper에 `rpc`가 없는 경우다.
191
+ - 실제 `@supabase/supabase-js` client를 넘긴다.
192
+
193
+ `permission denied for table review_project_counters`
194
+
195
+ - counter table grant 또는 RLS policy가 빠진 상태다.
196
+ - `Migration from max+1 RPC` 블록을 다시 확인한다.
197
+
198
+ remote item은 보이지만 presence가 안 보임
199
+
200
+ - `VITE_REVIEW_SUPABASE_ANON_KEY`가 비어 있으면 local presence fallback만 동작한다.
201
+ - `VITE_REVIEW_SUPABASE_PRESENCE_PRIVATE=true`인데 Realtime authorization policy가 없으면 실패할 수 있다.
202
+
203
+ remote 번호가 local 번호와 다름
204
+
205
+ - 정상이다. local 번호는 draft용이고 Supabase 번호가 canonical 번호다.
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@designfever/web-review-kit",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Designfever web page review overlay toolkit.",
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Designfever/df-web-review-kit.git"
10
+ },
11
+ "homepage": "https://github.com/Designfever/df-web-review-kit#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Designfever/df-web-review-kit/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public",
17
+ "registry": "https://registry.npmjs.org/"
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ },
29
+ "./react-shell": {
30
+ "types": "./dist/react-shell.d.ts",
31
+ "import": "./dist/react-shell.js",
32
+ "require": "./dist/react-shell.cjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "docs",
39
+ "README.md"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsup src/index.ts src/react-shell.tsx --format esm,cjs --dts --sourcemap --clean --external react --external react-dom --external react/jsx-runtime",
43
+ "dev": "tsup src/index.ts src/react-shell.tsx --format esm,cjs --dts --sourcemap --watch --external react --external react-dom --external react/jsx-runtime",
44
+ "prepare": "pnpm build",
45
+ "typecheck": "tsc --noEmit"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=18",
49
+ "react-dom": ">=18"
50
+ },
51
+ "devDependencies": {
52
+ "@types/react": "^19.2.17",
53
+ "@types/react-dom": "^19.2.3",
54
+ "lucide-react": "^1.20.0",
55
+ "react": "^19.2.7",
56
+ "react-dom": "^19.2.7",
57
+ "tsup": "^8.3.6",
58
+ "typescript": "^6.0.2"
59
+ },
60
+ "packageManager": "pnpm@9.0.0"
61
+ }