@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.
- package/README.md +241 -0
- package/dist/chunk-U5K2YGGL.js +3486 -0
- package/dist/chunk-U5K2YGGL.js.map +1 -0
- package/dist/index.cjs +3524 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/react-shell.cjs +8841 -0
- package/dist/react-shell.cjs.map +1 -0
- package/dist/react-shell.d.cts +156 -0
- package/dist/react-shell.d.ts +156 -0
- package/dist/react-shell.js +5844 -0
- package/dist/react-shell.js.map +1 -0
- package/dist/types-D_mNjOHx.d.cts +183 -0
- package/dist/types-D_mNjOHx.d.ts +183 -0
- package/docs/README.md +37 -0
- package/docs/adapter-handoff.md +146 -0
- package/docs/concept.md +102 -0
- package/docs/df-sheet-adapter.md +336 -0
- package/docs/df-sheet-next.md +222 -0
- package/docs/initial-plan.md +226 -0
- package/docs/installation.md +222 -0
- package/docs/package-split-checkpoint.md +79 -0
- package/docs/presence-handoff.md +138 -0
- package/docs/review-feedback-2026-06-20.md +267 -0
- package/docs/smoke-baseline-2026-06-20.md +41 -0
- package/docs/stabilize-ui-work-guide.md +243 -0
- package/docs/supabase-presence.md +198 -0
- package/docs/supabase-review-items.md +365 -0
- package/docs/supabase.md +205 -0
- package/package.json +61 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
type ReviewItemKind = 'note' | 'area';
|
|
2
|
+
type ReviewItemScope = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'dom';
|
|
3
|
+
type ReviewWorkflowStatus = 'todo' | 'doing' | 'review' | 'hold' | 'done';
|
|
4
|
+
type ReviewItemStatus = 'open' | 'resolved' | ReviewWorkflowStatus;
|
|
5
|
+
type ReviewMode = 'idle' | 'note' | 'element' | 'area';
|
|
6
|
+
type ReviewSource = 'local' | 'df-sheet' | 'supabase' | (string & {});
|
|
7
|
+
type ReviewSubmitStatus = 'idle' | 'submitting' | 'submitted' | 'failed';
|
|
8
|
+
type ReviewViewportScope = Exclude<ReviewItemScope, 'dom'>;
|
|
9
|
+
type DomAnchorStrategy = 'configured-attribute' | 'id' | 'class' | 'dom-path';
|
|
10
|
+
interface ReviewRulerConfig {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
unit?: string;
|
|
13
|
+
}
|
|
14
|
+
interface DomAnchorCandidate {
|
|
15
|
+
selector: string;
|
|
16
|
+
strategy: DomAnchorStrategy;
|
|
17
|
+
textFingerprint?: string;
|
|
18
|
+
confidence?: number;
|
|
19
|
+
}
|
|
20
|
+
interface DomSourceHint {
|
|
21
|
+
component?: string;
|
|
22
|
+
file?: string;
|
|
23
|
+
sectionId?: string;
|
|
24
|
+
sectionIndex?: string;
|
|
25
|
+
}
|
|
26
|
+
interface DomAnchor extends DomAnchorCandidate {
|
|
27
|
+
candidates?: DomAnchorCandidate[];
|
|
28
|
+
htmlSnippet?: string;
|
|
29
|
+
source?: DomSourceHint;
|
|
30
|
+
}
|
|
31
|
+
interface RelativeSelection {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
interface ViewportSize {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}
|
|
41
|
+
interface ReviewPoint {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
}
|
|
45
|
+
interface ReviewMarker {
|
|
46
|
+
viewport: ReviewPoint;
|
|
47
|
+
relative?: ReviewPoint;
|
|
48
|
+
}
|
|
49
|
+
interface ReviewSelection {
|
|
50
|
+
viewport: RelativeSelection;
|
|
51
|
+
relative?: RelativeSelection;
|
|
52
|
+
}
|
|
53
|
+
interface ReviewItem {
|
|
54
|
+
id: string;
|
|
55
|
+
reviewNumber?: number;
|
|
56
|
+
projectId: string;
|
|
57
|
+
routeKey: string;
|
|
58
|
+
pageUrl: string;
|
|
59
|
+
originalUrl?: string;
|
|
60
|
+
normalizedPath: string;
|
|
61
|
+
scope?: ReviewItemScope;
|
|
62
|
+
kind: ReviewItemKind;
|
|
63
|
+
title?: string;
|
|
64
|
+
comment: string;
|
|
65
|
+
status: ReviewItemStatus;
|
|
66
|
+
viewport: ViewportSize;
|
|
67
|
+
devicePixelRatio?: number;
|
|
68
|
+
scroll?: {
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
};
|
|
72
|
+
anchor?: DomAnchor;
|
|
73
|
+
marker?: ReviewMarker;
|
|
74
|
+
selection?: ReviewSelection;
|
|
75
|
+
externalIssueId?: string;
|
|
76
|
+
externalIssueUrl?: string;
|
|
77
|
+
submittedAt?: string;
|
|
78
|
+
submitStatus?: ReviewSubmitStatus;
|
|
79
|
+
submitError?: string;
|
|
80
|
+
createdAt: string;
|
|
81
|
+
updatedAt: string;
|
|
82
|
+
}
|
|
83
|
+
interface ReviewItemQuery {
|
|
84
|
+
projectId: string;
|
|
85
|
+
pageId?: string;
|
|
86
|
+
routeKey?: string;
|
|
87
|
+
normalizedPath?: string;
|
|
88
|
+
status?: ReviewItemStatus;
|
|
89
|
+
source?: string;
|
|
90
|
+
}
|
|
91
|
+
interface WebReviewKitAdapter {
|
|
92
|
+
get(id: string): Promise<ReviewItem | null>;
|
|
93
|
+
list(query: ReviewItemQuery): Promise<ReviewItem[]>;
|
|
94
|
+
create(item: ReviewItem): Promise<ReviewItem>;
|
|
95
|
+
update(id: string, patch: Partial<Omit<ReviewItem, 'id' | 'createdAt'>>): Promise<ReviewItem>;
|
|
96
|
+
remove(id: string): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
interface LocalAdapterOptions {
|
|
99
|
+
storageKey?: string;
|
|
100
|
+
}
|
|
101
|
+
interface DfSheetAdapterOptions {
|
|
102
|
+
baseUrl?: string;
|
|
103
|
+
projectId: string;
|
|
104
|
+
pageId: string;
|
|
105
|
+
reviewProjectId?: string;
|
|
106
|
+
reviewPathPrefix?: string;
|
|
107
|
+
source?: string;
|
|
108
|
+
issueType?: string;
|
|
109
|
+
priority?: string;
|
|
110
|
+
token?: string;
|
|
111
|
+
}
|
|
112
|
+
interface SupabaseReviewClient {
|
|
113
|
+
from(table: string): any;
|
|
114
|
+
rpc?: (fn: string, args?: Record<string, unknown>) => any;
|
|
115
|
+
}
|
|
116
|
+
interface SupabaseReviewAdapterOptions {
|
|
117
|
+
client: SupabaseReviewClient;
|
|
118
|
+
table?: string;
|
|
119
|
+
projectId: string;
|
|
120
|
+
source?: ReviewSource;
|
|
121
|
+
createRpc?: string;
|
|
122
|
+
reviewPathPrefix?: string;
|
|
123
|
+
unsafeClientReviewNumberFallback?: boolean;
|
|
124
|
+
}
|
|
125
|
+
interface ReviewViewportPreset {
|
|
126
|
+
label: string;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
scope?: Exclude<ReviewItemScope, 'dom'>;
|
|
130
|
+
}
|
|
131
|
+
interface NumberedReviewItem {
|
|
132
|
+
item: ReviewItem;
|
|
133
|
+
scope: ReviewItemScope;
|
|
134
|
+
label: string;
|
|
135
|
+
number: number;
|
|
136
|
+
displayLabel: string;
|
|
137
|
+
}
|
|
138
|
+
interface WebReviewKitOptions {
|
|
139
|
+
projectId: string;
|
|
140
|
+
adapter?: WebReviewKitAdapter;
|
|
141
|
+
target?: WebReviewKitTarget | (() => WebReviewKitTarget | undefined);
|
|
142
|
+
viewports?: {
|
|
143
|
+
presets?: ReviewViewportPreset[];
|
|
144
|
+
};
|
|
145
|
+
ruler?: ReviewRulerConfig;
|
|
146
|
+
hotkeys?: {
|
|
147
|
+
qa?: string;
|
|
148
|
+
};
|
|
149
|
+
anchors?: {
|
|
150
|
+
attribute?: string;
|
|
151
|
+
};
|
|
152
|
+
onRestoreItem?: (item: ReviewItem) => void | Promise<void>;
|
|
153
|
+
onItemsChange?: (items: ReviewItem[]) => void;
|
|
154
|
+
onModeChange?: (mode: ReviewMode) => void;
|
|
155
|
+
ui?: {
|
|
156
|
+
panel?: boolean;
|
|
157
|
+
};
|
|
158
|
+
modules?: {
|
|
159
|
+
qa?: boolean;
|
|
160
|
+
grid?: boolean;
|
|
161
|
+
figma?: boolean;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
interface WebReviewKitController {
|
|
165
|
+
open(): void;
|
|
166
|
+
close(): void;
|
|
167
|
+
toggle(): void;
|
|
168
|
+
setMode(mode: ReviewMode): void;
|
|
169
|
+
getMode(): ReviewMode;
|
|
170
|
+
highlightItem(itemId?: string): void;
|
|
171
|
+
setHiddenItemIds(itemIds?: string[]): void;
|
|
172
|
+
reload(): Promise<ReviewItem[]>;
|
|
173
|
+
getItems(): ReviewItem[];
|
|
174
|
+
destroy(): void;
|
|
175
|
+
}
|
|
176
|
+
interface WebReviewKitTarget {
|
|
177
|
+
window: Window;
|
|
178
|
+
document: Document;
|
|
179
|
+
getViewportRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
180
|
+
getOverlayRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type { DfSheetAdapterOptions as D, LocalAdapterOptions as L, NumberedReviewItem as N, ReviewWorkflowStatus as R, SupabaseReviewAdapterOptions as S, ViewportSize as V, WebReviewKitAdapter as W, ReviewItemStatus as a, WebReviewKitOptions as b, WebReviewKitController as c, ReviewViewportPreset as d, ReviewItem as e, ReviewItemScope as f, DomAnchor as g, DomAnchorCandidate as h, DomAnchorStrategy as i, DomSourceHint as j, RelativeSelection as k, ReviewItemKind as l, ReviewItemQuery as m, ReviewMarker as n, ReviewMode as o, ReviewPoint as p, ReviewRulerConfig as q, ReviewSelection as r, ReviewSource as s, ReviewSubmitStatus as t, ReviewViewportScope as u, SupabaseReviewClient as v, WebReviewKitTarget as w };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
type ReviewItemKind = 'note' | 'area';
|
|
2
|
+
type ReviewItemScope = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'dom';
|
|
3
|
+
type ReviewWorkflowStatus = 'todo' | 'doing' | 'review' | 'hold' | 'done';
|
|
4
|
+
type ReviewItemStatus = 'open' | 'resolved' | ReviewWorkflowStatus;
|
|
5
|
+
type ReviewMode = 'idle' | 'note' | 'element' | 'area';
|
|
6
|
+
type ReviewSource = 'local' | 'df-sheet' | 'supabase' | (string & {});
|
|
7
|
+
type ReviewSubmitStatus = 'idle' | 'submitting' | 'submitted' | 'failed';
|
|
8
|
+
type ReviewViewportScope = Exclude<ReviewItemScope, 'dom'>;
|
|
9
|
+
type DomAnchorStrategy = 'configured-attribute' | 'id' | 'class' | 'dom-path';
|
|
10
|
+
interface ReviewRulerConfig {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
unit?: string;
|
|
13
|
+
}
|
|
14
|
+
interface DomAnchorCandidate {
|
|
15
|
+
selector: string;
|
|
16
|
+
strategy: DomAnchorStrategy;
|
|
17
|
+
textFingerprint?: string;
|
|
18
|
+
confidence?: number;
|
|
19
|
+
}
|
|
20
|
+
interface DomSourceHint {
|
|
21
|
+
component?: string;
|
|
22
|
+
file?: string;
|
|
23
|
+
sectionId?: string;
|
|
24
|
+
sectionIndex?: string;
|
|
25
|
+
}
|
|
26
|
+
interface DomAnchor extends DomAnchorCandidate {
|
|
27
|
+
candidates?: DomAnchorCandidate[];
|
|
28
|
+
htmlSnippet?: string;
|
|
29
|
+
source?: DomSourceHint;
|
|
30
|
+
}
|
|
31
|
+
interface RelativeSelection {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
interface ViewportSize {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}
|
|
41
|
+
interface ReviewPoint {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
}
|
|
45
|
+
interface ReviewMarker {
|
|
46
|
+
viewport: ReviewPoint;
|
|
47
|
+
relative?: ReviewPoint;
|
|
48
|
+
}
|
|
49
|
+
interface ReviewSelection {
|
|
50
|
+
viewport: RelativeSelection;
|
|
51
|
+
relative?: RelativeSelection;
|
|
52
|
+
}
|
|
53
|
+
interface ReviewItem {
|
|
54
|
+
id: string;
|
|
55
|
+
reviewNumber?: number;
|
|
56
|
+
projectId: string;
|
|
57
|
+
routeKey: string;
|
|
58
|
+
pageUrl: string;
|
|
59
|
+
originalUrl?: string;
|
|
60
|
+
normalizedPath: string;
|
|
61
|
+
scope?: ReviewItemScope;
|
|
62
|
+
kind: ReviewItemKind;
|
|
63
|
+
title?: string;
|
|
64
|
+
comment: string;
|
|
65
|
+
status: ReviewItemStatus;
|
|
66
|
+
viewport: ViewportSize;
|
|
67
|
+
devicePixelRatio?: number;
|
|
68
|
+
scroll?: {
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
};
|
|
72
|
+
anchor?: DomAnchor;
|
|
73
|
+
marker?: ReviewMarker;
|
|
74
|
+
selection?: ReviewSelection;
|
|
75
|
+
externalIssueId?: string;
|
|
76
|
+
externalIssueUrl?: string;
|
|
77
|
+
submittedAt?: string;
|
|
78
|
+
submitStatus?: ReviewSubmitStatus;
|
|
79
|
+
submitError?: string;
|
|
80
|
+
createdAt: string;
|
|
81
|
+
updatedAt: string;
|
|
82
|
+
}
|
|
83
|
+
interface ReviewItemQuery {
|
|
84
|
+
projectId: string;
|
|
85
|
+
pageId?: string;
|
|
86
|
+
routeKey?: string;
|
|
87
|
+
normalizedPath?: string;
|
|
88
|
+
status?: ReviewItemStatus;
|
|
89
|
+
source?: string;
|
|
90
|
+
}
|
|
91
|
+
interface WebReviewKitAdapter {
|
|
92
|
+
get(id: string): Promise<ReviewItem | null>;
|
|
93
|
+
list(query: ReviewItemQuery): Promise<ReviewItem[]>;
|
|
94
|
+
create(item: ReviewItem): Promise<ReviewItem>;
|
|
95
|
+
update(id: string, patch: Partial<Omit<ReviewItem, 'id' | 'createdAt'>>): Promise<ReviewItem>;
|
|
96
|
+
remove(id: string): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
interface LocalAdapterOptions {
|
|
99
|
+
storageKey?: string;
|
|
100
|
+
}
|
|
101
|
+
interface DfSheetAdapterOptions {
|
|
102
|
+
baseUrl?: string;
|
|
103
|
+
projectId: string;
|
|
104
|
+
pageId: string;
|
|
105
|
+
reviewProjectId?: string;
|
|
106
|
+
reviewPathPrefix?: string;
|
|
107
|
+
source?: string;
|
|
108
|
+
issueType?: string;
|
|
109
|
+
priority?: string;
|
|
110
|
+
token?: string;
|
|
111
|
+
}
|
|
112
|
+
interface SupabaseReviewClient {
|
|
113
|
+
from(table: string): any;
|
|
114
|
+
rpc?: (fn: string, args?: Record<string, unknown>) => any;
|
|
115
|
+
}
|
|
116
|
+
interface SupabaseReviewAdapterOptions {
|
|
117
|
+
client: SupabaseReviewClient;
|
|
118
|
+
table?: string;
|
|
119
|
+
projectId: string;
|
|
120
|
+
source?: ReviewSource;
|
|
121
|
+
createRpc?: string;
|
|
122
|
+
reviewPathPrefix?: string;
|
|
123
|
+
unsafeClientReviewNumberFallback?: boolean;
|
|
124
|
+
}
|
|
125
|
+
interface ReviewViewportPreset {
|
|
126
|
+
label: string;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
scope?: Exclude<ReviewItemScope, 'dom'>;
|
|
130
|
+
}
|
|
131
|
+
interface NumberedReviewItem {
|
|
132
|
+
item: ReviewItem;
|
|
133
|
+
scope: ReviewItemScope;
|
|
134
|
+
label: string;
|
|
135
|
+
number: number;
|
|
136
|
+
displayLabel: string;
|
|
137
|
+
}
|
|
138
|
+
interface WebReviewKitOptions {
|
|
139
|
+
projectId: string;
|
|
140
|
+
adapter?: WebReviewKitAdapter;
|
|
141
|
+
target?: WebReviewKitTarget | (() => WebReviewKitTarget | undefined);
|
|
142
|
+
viewports?: {
|
|
143
|
+
presets?: ReviewViewportPreset[];
|
|
144
|
+
};
|
|
145
|
+
ruler?: ReviewRulerConfig;
|
|
146
|
+
hotkeys?: {
|
|
147
|
+
qa?: string;
|
|
148
|
+
};
|
|
149
|
+
anchors?: {
|
|
150
|
+
attribute?: string;
|
|
151
|
+
};
|
|
152
|
+
onRestoreItem?: (item: ReviewItem) => void | Promise<void>;
|
|
153
|
+
onItemsChange?: (items: ReviewItem[]) => void;
|
|
154
|
+
onModeChange?: (mode: ReviewMode) => void;
|
|
155
|
+
ui?: {
|
|
156
|
+
panel?: boolean;
|
|
157
|
+
};
|
|
158
|
+
modules?: {
|
|
159
|
+
qa?: boolean;
|
|
160
|
+
grid?: boolean;
|
|
161
|
+
figma?: boolean;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
interface WebReviewKitController {
|
|
165
|
+
open(): void;
|
|
166
|
+
close(): void;
|
|
167
|
+
toggle(): void;
|
|
168
|
+
setMode(mode: ReviewMode): void;
|
|
169
|
+
getMode(): ReviewMode;
|
|
170
|
+
highlightItem(itemId?: string): void;
|
|
171
|
+
setHiddenItemIds(itemIds?: string[]): void;
|
|
172
|
+
reload(): Promise<ReviewItem[]>;
|
|
173
|
+
getItems(): ReviewItem[];
|
|
174
|
+
destroy(): void;
|
|
175
|
+
}
|
|
176
|
+
interface WebReviewKitTarget {
|
|
177
|
+
window: Window;
|
|
178
|
+
document: Document;
|
|
179
|
+
getViewportRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
180
|
+
getOverlayRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type { DfSheetAdapterOptions as D, LocalAdapterOptions as L, NumberedReviewItem as N, ReviewWorkflowStatus as R, SupabaseReviewAdapterOptions as S, ViewportSize as V, WebReviewKitAdapter as W, ReviewItemStatus as a, WebReviewKitOptions as b, WebReviewKitController as c, ReviewViewportPreset as d, ReviewItem as e, ReviewItemScope as f, DomAnchor as g, DomAnchorCandidate as h, DomAnchorStrategy as i, DomSourceHint as j, RelativeSelection as k, ReviewItemKind as l, ReviewItemQuery as m, ReviewMarker as n, ReviewMode as o, ReviewPoint as p, ReviewRulerConfig as q, ReviewSelection as r, ReviewSource as s, ReviewSubmitStatus as t, ReviewViewportScope as u, SupabaseReviewClient as v, WebReviewKitTarget as w };
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# df-web-review-kit docs
|
|
2
|
+
|
|
3
|
+
현재 문서의 읽는 순서:
|
|
4
|
+
|
|
5
|
+
1. [Concept](concept.md)
|
|
6
|
+
2. [Installation](installation.md)
|
|
7
|
+
3. [Supabase setup](supabase.md)
|
|
8
|
+
4. [Supabase review item SQL](supabase-review-items.md)
|
|
9
|
+
5. [Supabase presence](supabase-presence.md)
|
|
10
|
+
6. [Adapter handoff](adapter-handoff.md)
|
|
11
|
+
7. [df-sheet next](df-sheet-next.md)
|
|
12
|
+
8. [Review feedback 2026-06-20](review-feedback-2026-06-20.md)
|
|
13
|
+
9. [Stabilize UI work guide](stabilize-ui-work-guide.md)
|
|
14
|
+
10. [Smoke baseline 2026-06-20](smoke-baseline-2026-06-20.md)
|
|
15
|
+
11. [Package split checkpoint](package-split-checkpoint.md)
|
|
16
|
+
|
|
17
|
+
## 문서 역할
|
|
18
|
+
|
|
19
|
+
- `concept.md`: 왜 이 package가 있고 어떤 문제를 해결하는지.
|
|
20
|
+
- `installation.md`: host project에 설치하고 `/review` route에 붙이는 방법.
|
|
21
|
+
- `supabase.md`: Supabase를 remote QA 저장소와 presence backend로 연결하는 방법.
|
|
22
|
+
- `supabase-review-items.md`: 실제 table/RPC/RLS SQL.
|
|
23
|
+
- `supabase-presence.md`: Supabase Realtime Presence adapter 구조.
|
|
24
|
+
- `adapter-handoff.md`: package repo로 옮길 때 필요한 adapter contract 정리.
|
|
25
|
+
- `df-sheet-next.md`: df-sheet를 source of record로 쓸 때 필요한 제품/API 방향.
|
|
26
|
+
- `review-feedback-2026-06-20.md`: 빵빵/팡팡/오빵 package 리뷰 메모와 우선순위.
|
|
27
|
+
- `stabilize-ui-work-guide.md`: anchor 안정화, UI token화, package split 전 작업 순서.
|
|
28
|
+
- `smoke-baseline-2026-06-20.md`: 761 현재 기능 smoke 기준선 결과.
|
|
29
|
+
- `package-split-checkpoint.md`: 765 package export/file/peer dependency와 host 소비 경계.
|
|
30
|
+
- `initial-plan.md`: 초기 계획 기록. 현재 기준 문서가 아니다.
|
|
31
|
+
|
|
32
|
+
## 현재 기준
|
|
33
|
+
|
|
34
|
+
- `local`: 개인 draft 저장소.
|
|
35
|
+
- `supabase`: 팀 공유 remote 저장소.
|
|
36
|
+
- `df-sheet`: 나중에 issue workflow로 연결할 수 있는 remote destination.
|
|
37
|
+
- local 번호는 개인 draft용이고, remote 번호는 remote source가 새로 발급하는 canonical 번호다.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# review-kit adapter handoff
|
|
2
|
+
|
|
3
|
+
## 목적
|
|
4
|
+
|
|
5
|
+
`df-web-review-kit`를 Lexus repo 안에서 검증한 뒤 별도 package repo로 옮기기 위한 handoff 문서다.
|
|
6
|
+
|
|
7
|
+
현재 방향은 review shell/core와 저장소를 분리하고, project page에서는 adapter array만 선언해서 local, remote, Supabase 같은 저장소를 교체 가능하게 만드는 것이다.
|
|
8
|
+
|
|
9
|
+
## 현재 상태
|
|
10
|
+
|
|
11
|
+
- Mount 위치: `page/review/index.tsx`
|
|
12
|
+
- Package 위치: `packages/df-web-review-kit`
|
|
13
|
+
- 현재 source: `local`, `df-sheet`
|
|
14
|
+
- 현재 shell-facing adapter config: array
|
|
15
|
+
- core-facing internal adapter: 기존 `WebReviewKitAdapter`
|
|
16
|
+
- local item id: uuid
|
|
17
|
+
- local 표시 번호: 개인 draft용 `reviewNumber`
|
|
18
|
+
- remote 표시 번호: remote source가 새로 배정하는 canonical `reviewNumber`
|
|
19
|
+
- remote submit 성공 시 local item은 삭제하고, remote source에서 새 canonical id/number item으로 조회한다.
|
|
20
|
+
|
|
21
|
+
현재 public shell adapter는 generic `update`를 노출하지 않는다.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
type ReviewShellAdapter = {
|
|
25
|
+
label: ReviewSource;
|
|
26
|
+
pageId?: string;
|
|
27
|
+
get: WebReviewKitAdapter['get'];
|
|
28
|
+
list: WebReviewKitAdapter['list'];
|
|
29
|
+
create: WebReviewKitAdapter['create'];
|
|
30
|
+
statusOptions?: readonly ReviewShellStatusOption[];
|
|
31
|
+
updateStatus?: (input: ReviewShellUpdateStatusInput) => Promise<ReviewItem>;
|
|
32
|
+
syncSubmission?: (input: ReviewShellSyncSubmissionInput) => Promise<ReviewItem>;
|
|
33
|
+
remove?: WebReviewKitAdapter['remove'];
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
의도:
|
|
38
|
+
|
|
39
|
+
- `create`: QA 생성 또는 remote 등록
|
|
40
|
+
- `list/get`: source별 QA 조회와 deep link restore
|
|
41
|
+
- `statusOptions + updateStatus`: 상태 변경 UI
|
|
42
|
+
- `remove`: 삭제 버튼 노출 조건
|
|
43
|
+
- `syncSubmission`: remote 등록 중/실패 상태를 local item에 임시 기록하는 좁은 bookkeeping
|
|
44
|
+
|
|
45
|
+
## 결정된 원칙
|
|
46
|
+
|
|
47
|
+
- shell public adapter에는 generic `update`를 넣지 않는다.
|
|
48
|
+
- 상태 변경은 `updateStatus`로만 다룬다.
|
|
49
|
+
- `statusIndex`는 identity가 아니라 외부 시스템 매핑 보조값이다.
|
|
50
|
+
- 저장 기준은 stable status value다.
|
|
51
|
+
- source가 기능을 제공하지 않으면 shell은 해당 버튼/UI를 숨기거나 readonly badge로 보여준다.
|
|
52
|
+
- `local`은 생성, 상태 변경, 삭제, remote submit 중/실패 상태 기록을 담당한다.
|
|
53
|
+
- `df-sheet`는 현재 review-kit 안에서는 remote 등록과 읽기 중심으로 둔다.
|
|
54
|
+
|
|
55
|
+
## Supabase adapter
|
|
56
|
+
|
|
57
|
+
Supabase adapter를 하나 붙이면 adapter 계약 검증에 좋다. df-sheet보다 DB schema를 직접 통제할 수 있어서 `statusOptions`, `updateStatus`, `remove`의 동작 경계를 확인하기 쉽다.
|
|
58
|
+
|
|
59
|
+
초기 source 이름은 `supabase`로 잡았다.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
{
|
|
63
|
+
label: 'supabase',
|
|
64
|
+
get,
|
|
65
|
+
list,
|
|
66
|
+
create,
|
|
67
|
+
statusOptions,
|
|
68
|
+
updateStatus,
|
|
69
|
+
remove,
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
현재 구현:
|
|
74
|
+
|
|
75
|
+
- Adapter: `packages/df-web-review-kit/src/adapters/supabase.ts`
|
|
76
|
+
- Lexus wiring: `page/review/index.tsx`
|
|
77
|
+
- SQL/runbook: `packages/df-web-review-kit/docs/supabase-review-items.md`
|
|
78
|
+
- `ReviewSource`는 string 확장 가능 타입으로 풀었다.
|
|
79
|
+
- route query의 `source` 처리는 `source !== 'local'` 기준으로 일반화했다.
|
|
80
|
+
- shell은 adapter array의 첫 non-local adapter를 remote source로 사용한다.
|
|
81
|
+
- source별 list title, empty text도 label 기반으로 바꿨다.
|
|
82
|
+
|
|
83
|
+
## Supabase schema
|
|
84
|
+
|
|
85
|
+
브라우저에서 직접 붙이는 빠른 검증은 anon key + RLS 기준으로 시작한다. service role key는 브라우저에 절대 넣지 않는다.
|
|
86
|
+
|
|
87
|
+
현재 schema/runbook은 `supabase-review-items.md`가 source of truth다.
|
|
88
|
+
|
|
89
|
+
핵심 구조:
|
|
90
|
+
|
|
91
|
+
- `review_items`: QA row. 실제 review item payload는 `item jsonb`에 저장한다.
|
|
92
|
+
- `review_project_counters`: `(project_id, source)`별 다음 `review_number`를 보관한다.
|
|
93
|
+
- `create_review_item`: counter row를 짧게 증가시키고 canonical `review_number`를 item에 주입해서 insert한다.
|
|
94
|
+
|
|
95
|
+
주의:
|
|
96
|
+
|
|
97
|
+
- 이 RLS는 dev 검증용이다.
|
|
98
|
+
- production/package용으로는 Supabase Edge Function 또는 project-scoped backend proxy가 더 안전하다.
|
|
99
|
+
- RLS 조건에 쓰는 컬럼에는 인덱스를 둔다.
|
|
100
|
+
|
|
101
|
+
## Supabase adapter draft
|
|
102
|
+
|
|
103
|
+
Adapter option:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
type SupabaseReviewAdapterOptions = {
|
|
107
|
+
url: string;
|
|
108
|
+
anonKey: string;
|
|
109
|
+
table?: string;
|
|
110
|
+
projectId: string;
|
|
111
|
+
source?: string;
|
|
112
|
+
createRpc?: string;
|
|
113
|
+
unsafeClientReviewNumberFallback?: boolean;
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
API mapping:
|
|
118
|
+
|
|
119
|
+
- `get(id)`: `select().eq('id', id).maybeSingle()`
|
|
120
|
+
- `list(query)`: `project_id`, `route_key`, optional `status` filter
|
|
121
|
+
- `create(item)`: `create_review_item` RPC로 canonical id/number 발급 후 review item 반환
|
|
122
|
+
- `updateStatus({ id, status })`: `status`, `updated_at`만 patch
|
|
123
|
+
- `remove(id)`: row delete
|
|
124
|
+
|
|
125
|
+
local `reviewNumber`는 사람별 draft 번호라 서로 겹칠 수 있다. remote submit 시 local 번호를 보존하지 않고 Supabase adapter가 새 `id`와 새 canonical `reviewNumber`를 배정한다.
|
|
126
|
+
|
|
127
|
+
현재 구현은 `create_review_item` RPC가 `review_project_counters` row를 `insert ... on conflict ... do update returning`으로 증가시켜 `review_number`를 발급하고 row를 insert한다. 동시 등록 충돌과 삭제 후 번호 재사용을 피하기 위한 구조다. `unsafeClientReviewNumberFallback` option을 켜면 예전 client `max(review_number) + 1` 방식을 쓸 수 있지만 dev 임시 용도다.
|
|
128
|
+
|
|
129
|
+
## 작업 순서
|
|
130
|
+
|
|
131
|
+
1. `ReviewSource`/route/source select를 adapter label 기반으로 일반화한다.
|
|
132
|
+
2. `supabaseAdapter` 파일을 추가한다.
|
|
133
|
+
3. `page/review/index.tsx`에 Supabase adapter config를 선택적으로 추가한다.
|
|
134
|
+
4. `.env` 예시를 문서화한다.
|
|
135
|
+
5. local, df-sheet, supabase source 전환을 브라우저에서 확인한다.
|
|
136
|
+
6. `pnpm --dir packages/df-web-review-kit typecheck`
|
|
137
|
+
7. `pnpm typecheck:review`
|
|
138
|
+
8. `pnpm review-kit:build`
|
|
139
|
+
|
|
140
|
+
## Open questions
|
|
141
|
+
|
|
142
|
+
- Supabase는 dev 검증만 할지, package 정식 adapter로 포함할지.
|
|
143
|
+
- remote source에서 delete를 허용할지.
|
|
144
|
+
- remote source의 `reviewNumber`를 사람이 쓰는 canonical id로 확정할지.
|
|
145
|
+
- auth를 anon+RLS로 유지할지, Edge Function/proxy로 감쌀지.
|
|
146
|
+
- status option을 package default로 둘지, project page config에서만 주입할지.
|
package/docs/concept.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Concept
|
|
2
|
+
|
|
3
|
+
`df-web-review-kit`는 웹 프로젝트 안에 넣는 review shell이다. QA 전용 SaaS나 Chrome extension이 아니라, 프로젝트가 가진 route, viewport, DOM, style context를 그대로 사용해 검수 이슈를 남기는 toolkit이다.
|
|
4
|
+
|
|
5
|
+
## 해결하려는 문제
|
|
6
|
+
|
|
7
|
+
웹 QA는 보통 다음 정보가 빠져서 다시 확인하는 시간이 길어진다.
|
|
8
|
+
|
|
9
|
+
- 정확한 page route
|
|
10
|
+
- viewport size
|
|
11
|
+
- scroll position
|
|
12
|
+
- DOM anchor
|
|
13
|
+
- 깨진 영역의 좌표
|
|
14
|
+
- 사람이 남긴 comment
|
|
15
|
+
- 누가 같은 page를 보고 있는지
|
|
16
|
+
|
|
17
|
+
review-kit은 이 정보를 review item 하나에 묶어서 저장하고, `/review?...` link로 다시 복원한다.
|
|
18
|
+
|
|
19
|
+
## 기본 UX
|
|
20
|
+
|
|
21
|
+
1. `/review`를 연다.
|
|
22
|
+
2. sitemap에서 target page를 고른다.
|
|
23
|
+
3. mobile/tablet/desktop/wide viewport를 고른다.
|
|
24
|
+
4. iframe 안에서 note, DOM note, area item을 만든다.
|
|
25
|
+
5. 개인이 처리할 것은 `local` draft에 둔다.
|
|
26
|
+
6. 팀 공유가 필요한 것만 remote source에 등록한다.
|
|
27
|
+
7. remote item link를 공유하거나 AI prompt로 복사한다.
|
|
28
|
+
|
|
29
|
+
## Source 개념
|
|
30
|
+
|
|
31
|
+
### local
|
|
32
|
+
|
|
33
|
+
개인 draft 저장소다. `localStorage` 기반이라 빠르고, 실험/개인 처리용 QA를 remote에 섞지 않는다.
|
|
34
|
+
|
|
35
|
+
local item의 `#id`는 개인 브라우저 안에서만 의미가 있다. 여러 사람이 같은 번호를 가질 수 있다.
|
|
36
|
+
|
|
37
|
+
### remote
|
|
38
|
+
|
|
39
|
+
팀이 같이 보는 source of record다. 현재 검증 source는 `supabase`다.
|
|
40
|
+
|
|
41
|
+
local item을 remote에 등록하면 local id/number를 보존하지 않고 remote source가 새 id와 canonical `reviewNumber`를 발급한다. 등록 성공 후 local draft는 삭제한다.
|
|
42
|
+
|
|
43
|
+
### df-sheet
|
|
44
|
+
|
|
45
|
+
df-sheet는 issue workflow와 연결되는 remote destination 후보다. review-kit이 df-sheet issue editor가 되면 안 되고, local QA를 issue로 등록하고 restore link를 제공하는 역할이 맞다.
|
|
46
|
+
|
|
47
|
+
## Item 종류
|
|
48
|
+
|
|
49
|
+
- `note`: 화면 특정 지점에 남기는 comment.
|
|
50
|
+
- `dom`: DOM element에 anchor를 둔 note.
|
|
51
|
+
- `area`: 사용자가 드래그한 영역.
|
|
52
|
+
|
|
53
|
+
표시 범위는 mobile/tablet/desktop/wide와 연결된다. DOM item은 viewport가 달라도 anchor를 찾아 복원하는 것을 목표로 한다.
|
|
54
|
+
|
|
55
|
+
## Presence
|
|
56
|
+
|
|
57
|
+
Presence는 저장소가 아니다. 현재 접속한 사용자의 page/source/viewport 상태만 공유한다.
|
|
58
|
+
|
|
59
|
+
현재 UI는 같은 page에 들어온 사용자 id만 page header 쪽에 보여준다. sitemap 같은 상위 화면에서는 page별 접속자 표시로 확장할 수 있다.
|
|
60
|
+
|
|
61
|
+
## 패키지 경계
|
|
62
|
+
|
|
63
|
+
Package가 담당하는 것:
|
|
64
|
+
|
|
65
|
+
- review shell UI
|
|
66
|
+
- iframe target loading
|
|
67
|
+
- marker 생성과 restore
|
|
68
|
+
- prompt generation
|
|
69
|
+
- adapter contract
|
|
70
|
+
- local adapter
|
|
71
|
+
- Supabase adapter
|
|
72
|
+
- presence adapter contract
|
|
73
|
+
|
|
74
|
+
Host project가 담당하는 것:
|
|
75
|
+
|
|
76
|
+
- `/review` route 생성
|
|
77
|
+
- page glob 전달
|
|
78
|
+
- viewport preset 전달
|
|
79
|
+
- project id와 storage key 결정
|
|
80
|
+
- remote adapter env와 auth 결정
|
|
81
|
+
- Supabase client 생성
|
|
82
|
+
|
|
83
|
+
## 0.1 목표
|
|
84
|
+
|
|
85
|
+
0.1은 완전한 review platform이 아니라, 프로젝트별로 붙여 쓸 수 있는 최소 안정 package다.
|
|
86
|
+
|
|
87
|
+
포함:
|
|
88
|
+
|
|
89
|
+
- local draft
|
|
90
|
+
- Supabase remote CRUD
|
|
91
|
+
- Supabase Presence
|
|
92
|
+
- stable remote review number
|
|
93
|
+
- prompt copy
|
|
94
|
+
- route/viewport/scroll/marker restore
|
|
95
|
+
|
|
96
|
+
보류:
|
|
97
|
+
|
|
98
|
+
- screenshot upload
|
|
99
|
+
- full df-sheet workflow
|
|
100
|
+
- auth/member 기반 production RLS
|
|
101
|
+
- sitemap presence dashboard
|
|
102
|
+
- Chrome extension
|