yo-bug 0.1.4 → 0.2.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/dist/src/server/proxy-server.d.ts.map +1 -1
- package/dist/src/server/proxy-server.js +5 -1
- package/dist/src/server/proxy-server.js.map +1 -1
- package/dist/src/server/sdk-serve.d.ts.map +1 -1
- package/dist/src/server/sdk-serve.js +12 -5
- package/dist/src/server/sdk-serve.js.map +1 -1
- package/dist/vibe-feedback.js +713 -212
- package/package.json +1 -1
- package/src/client/annotation-mode/toolbar.ts +53 -63
- package/src/client/core/i18n.ts +23 -29
- package/src/client/core/sdk.ts +21 -48
- package/src/client/styles/icons.ts +85 -0
- package/src/client/styles/sdk.css.ts +652 -100
- package/src/client/ui/checklist-panel.ts +35 -77
- package/src/client/ui/feedback-panel.ts +7 -1
- package/src/client/ui/floating-button.ts +7 -35
- package/src/client/ui/toast.ts +12 -1
- package/src/client/ui/verify-panel.ts +14 -26
- package/src/server/proxy-server.ts +6 -1
- package/src/server/sdk-serve.ts +14 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class ChecklistPanel {
|
|
2
2
|
private container: HTMLDivElement;
|
|
3
|
-
private listEl: HTMLDivElement;
|
|
4
3
|
private headerEl: HTMLDivElement | null = null;
|
|
4
|
+
private itemsWrap: HTMLDivElement | null = null;
|
|
5
5
|
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
6
6
|
private visible = false;
|
|
7
7
|
private collapsed = false;
|
|
@@ -15,20 +15,8 @@ export class ChecklistPanel {
|
|
|
15
15
|
this.onItemFail = onItemFail || null;
|
|
16
16
|
this.baseUrl = window.location.origin;
|
|
17
17
|
this.container = document.createElement('div');
|
|
18
|
-
this.container.
|
|
19
|
-
|
|
20
|
-
max-height: 70vh; overflow: hidden;
|
|
21
|
-
background: #1e293b; border-radius: 10px;
|
|
22
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.35);
|
|
23
|
-
pointer-events: auto; display: none;
|
|
24
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
25
|
-
font-size: 13px; color: #e2e8f0; z-index: 2147483647;
|
|
26
|
-
transition: height 0.2s;
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
this.listEl = document.createElement('div');
|
|
30
|
-
this.listEl.style.cssText = `max-height: calc(70vh - 44px); overflow-y: auto;`;
|
|
31
|
-
this.container.appendChild(this.listEl);
|
|
18
|
+
this.container.className = 'vf-checklist';
|
|
19
|
+
this.container.style.display = 'none';
|
|
32
20
|
shadowRoot.appendChild(this.container);
|
|
33
21
|
}
|
|
34
22
|
|
|
@@ -51,7 +39,7 @@ export class ChecklistPanel {
|
|
|
51
39
|
if (data.items && data.items.length > 0) {
|
|
52
40
|
this.render(data);
|
|
53
41
|
if (!this.visible) {
|
|
54
|
-
this.container.style.display = '
|
|
42
|
+
this.container.style.display = '';
|
|
55
43
|
this.visible = true;
|
|
56
44
|
}
|
|
57
45
|
} else if (this.visible) {
|
|
@@ -66,29 +54,25 @@ export class ChecklistPanel {
|
|
|
66
54
|
const failed = data.items.filter((i) => i.status === 'failed').length;
|
|
67
55
|
const total = data.items.length;
|
|
68
56
|
|
|
69
|
-
this.
|
|
57
|
+
this.container.innerHTML = '';
|
|
70
58
|
|
|
71
59
|
// Header — draggable + collapsible
|
|
72
60
|
const header = document.createElement('div');
|
|
73
|
-
header.
|
|
74
|
-
padding: 10px 14px; border-bottom: 1px solid #334155;
|
|
75
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
76
|
-
cursor: move; user-select: none;
|
|
77
|
-
`;
|
|
61
|
+
header.className = 'vf-checklist-header';
|
|
78
62
|
|
|
79
63
|
const titleWrap = document.createElement('div');
|
|
80
|
-
titleWrap.
|
|
64
|
+
titleWrap.className = 'vf-checklist-title-wrap';
|
|
81
65
|
|
|
82
66
|
const title = document.createElement('span');
|
|
83
|
-
title.
|
|
67
|
+
title.className = 'vf-checklist-title';
|
|
84
68
|
title.textContent = data.title;
|
|
85
69
|
|
|
86
70
|
const badge = document.createElement('span');
|
|
87
|
-
badge.
|
|
71
|
+
badge.className = 'vf-checklist-badge';
|
|
88
72
|
badge.textContent = `${passed}/${total}`;
|
|
89
73
|
if (failed > 0) {
|
|
90
74
|
const failBadge = document.createElement('span');
|
|
91
|
-
failBadge.
|
|
75
|
+
failBadge.className = 'vf-checklist-badge-fail';
|
|
92
76
|
failBadge.textContent = `${failed} fail`;
|
|
93
77
|
badge.appendChild(failBadge);
|
|
94
78
|
}
|
|
@@ -99,15 +83,12 @@ export class ChecklistPanel {
|
|
|
99
83
|
|
|
100
84
|
// Collapse toggle
|
|
101
85
|
const toggleBtn = document.createElement('button');
|
|
102
|
-
toggleBtn.
|
|
103
|
-
background:none; border:none; color:#94a3b8; cursor:pointer;
|
|
104
|
-
font-size:16px; padding:0 4px; line-height:1;
|
|
105
|
-
`;
|
|
86
|
+
toggleBtn.className = 'vf-checklist-toggle';
|
|
106
87
|
toggleBtn.textContent = this.collapsed ? '\u25BC' : '\u25B2';
|
|
107
88
|
toggleBtn.addEventListener('click', (e) => {
|
|
108
89
|
e.stopPropagation();
|
|
109
90
|
this.collapsed = !this.collapsed;
|
|
110
|
-
this.
|
|
91
|
+
if (this.itemsWrap) this.itemsWrap.hidden = this.collapsed;
|
|
111
92
|
toggleBtn.textContent = this.collapsed ? '\u25BC' : '\u25B2';
|
|
112
93
|
});
|
|
113
94
|
header.appendChild(toggleBtn);
|
|
@@ -133,66 +114,58 @@ export class ChecklistPanel {
|
|
|
133
114
|
document.addEventListener('mouseup', onUp);
|
|
134
115
|
});
|
|
135
116
|
|
|
136
|
-
this.
|
|
117
|
+
this.container.appendChild(header);
|
|
137
118
|
this.headerEl = header;
|
|
138
119
|
|
|
139
120
|
// Progress bar
|
|
140
121
|
const progressWrap = document.createElement('div');
|
|
141
|
-
progressWrap.
|
|
122
|
+
progressWrap.className = 'vf-checklist-progress';
|
|
142
123
|
const pct = total > 0 ? (passed / total) * 100 : 0;
|
|
143
124
|
const progressBar = document.createElement('div');
|
|
144
|
-
progressBar.
|
|
125
|
+
progressBar.className = 'vf-checklist-progress-bar';
|
|
126
|
+
progressBar.style.width = `${pct}%`;
|
|
145
127
|
progressWrap.appendChild(progressBar);
|
|
146
|
-
this.
|
|
128
|
+
this.container.appendChild(progressWrap);
|
|
147
129
|
|
|
148
130
|
// Items container (collapsible)
|
|
149
|
-
|
|
150
|
-
itemsWrap.className = 'vf-checklist-items';
|
|
151
|
-
if (this.collapsed) itemsWrap.hidden = true;
|
|
131
|
+
this.itemsWrap = document.createElement('div');
|
|
132
|
+
this.itemsWrap.className = 'vf-checklist-items';
|
|
133
|
+
if (this.collapsed) this.itemsWrap.hidden = true;
|
|
152
134
|
|
|
153
135
|
for (const item of data.items) {
|
|
154
136
|
const row = document.createElement('div');
|
|
155
|
-
row.
|
|
156
|
-
padding: 8px 14px; border-bottom: 1px solid #0f172a;
|
|
157
|
-
display: flex; align-items: flex-start; gap: 8px;
|
|
158
|
-
`;
|
|
137
|
+
row.className = 'vf-checklist-row';
|
|
159
138
|
|
|
160
139
|
// Status buttons
|
|
161
140
|
const controls = document.createElement('div');
|
|
162
|
-
controls.
|
|
141
|
+
controls.className = 'vf-checklist-controls';
|
|
163
142
|
|
|
164
|
-
controls.appendChild(this.createStatusBtn('\u2713', '
|
|
143
|
+
controls.appendChild(this.createStatusBtn('\u2713', 'pass', item.status === 'passed', () => {
|
|
165
144
|
this.updateItem(item.id, 'passed');
|
|
166
145
|
}));
|
|
167
|
-
controls.appendChild(this.createStatusBtn('\u2717', '
|
|
146
|
+
controls.appendChild(this.createStatusBtn('\u2717', 'fail', item.status === 'failed', () => {
|
|
168
147
|
this.promptFeedback(item.id);
|
|
169
148
|
}));
|
|
170
149
|
row.appendChild(controls);
|
|
171
150
|
|
|
172
151
|
// Content
|
|
173
152
|
const text = document.createElement('div');
|
|
174
|
-
text.
|
|
153
|
+
text.className = 'vf-checklist-text';
|
|
175
154
|
|
|
176
155
|
// Badges row
|
|
177
156
|
const badges = document.createElement('div');
|
|
178
|
-
badges.
|
|
157
|
+
badges.className = 'vf-checklist-badges';
|
|
179
158
|
|
|
180
159
|
if (item.priority === 'critical') {
|
|
181
160
|
const badge = document.createElement('span');
|
|
182
|
-
badge.
|
|
183
|
-
display:inline-block;font-size:9px;padding:1px 5px;border-radius:3px;
|
|
184
|
-
background:rgba(239,68,68,0.15);color:#ef4444;font-weight:600;
|
|
185
|
-
`;
|
|
161
|
+
badge.className = 'vf-badge vf-badge-critical';
|
|
186
162
|
badge.textContent = 'CRITICAL';
|
|
187
163
|
badges.appendChild(badge);
|
|
188
164
|
}
|
|
189
165
|
|
|
190
166
|
if (item.dimension) {
|
|
191
167
|
const dimBadge = document.createElement('span');
|
|
192
|
-
dimBadge.
|
|
193
|
-
display:inline-block;font-size:9px;padding:1px 5px;border-radius:3px;
|
|
194
|
-
background:rgba(59,130,246,0.12);color:#60a5fa;
|
|
195
|
-
`;
|
|
168
|
+
dimBadge.className = 'vf-badge vf-badge-dim';
|
|
196
169
|
dimBadge.textContent = item.dimension;
|
|
197
170
|
badges.appendChild(dimBadge);
|
|
198
171
|
}
|
|
@@ -203,59 +176,44 @@ export class ChecklistPanel {
|
|
|
203
176
|
|
|
204
177
|
// Step (what to do)
|
|
205
178
|
const stepText = document.createElement('span');
|
|
179
|
+
stepText.className = `vf-checklist-step${item.status === 'passed' ? ' passed' : item.status === 'failed' ? ' failed' : ''}`;
|
|
206
180
|
stepText.textContent = item.step || item.text || '';
|
|
207
181
|
text.appendChild(stepText);
|
|
208
182
|
|
|
209
183
|
// Expected result
|
|
210
184
|
if (item.expect) {
|
|
211
185
|
const expectEl = document.createElement('div');
|
|
212
|
-
expectEl.
|
|
186
|
+
expectEl.className = 'vf-checklist-expect';
|
|
213
187
|
expectEl.textContent = `\u2192 ${item.expect}`;
|
|
214
188
|
text.appendChild(expectEl);
|
|
215
189
|
}
|
|
216
190
|
|
|
217
|
-
if (item.status === 'passed') {
|
|
218
|
-
stepText.style.color = '#22c55e';
|
|
219
|
-
stepText.style.textDecoration = 'line-through';
|
|
220
|
-
stepText.style.opacity = '0.6';
|
|
221
|
-
} else if (item.status === 'failed') {
|
|
222
|
-
stepText.style.color = '#ef4444';
|
|
223
|
-
}
|
|
224
|
-
|
|
225
191
|
if (item.feedback) {
|
|
226
192
|
const fb = document.createElement('div');
|
|
227
|
-
fb.
|
|
193
|
+
fb.className = 'vf-checklist-feedback';
|
|
228
194
|
fb.textContent = item.feedback;
|
|
229
195
|
text.appendChild(fb);
|
|
230
196
|
}
|
|
231
197
|
|
|
232
198
|
row.appendChild(text);
|
|
233
|
-
itemsWrap.appendChild(row);
|
|
199
|
+
this.itemsWrap.appendChild(row);
|
|
234
200
|
}
|
|
235
201
|
|
|
236
|
-
this.
|
|
202
|
+
this.container.appendChild(this.itemsWrap);
|
|
237
203
|
}
|
|
238
204
|
|
|
239
|
-
private createStatusBtn(icon: string,
|
|
205
|
+
private createStatusBtn(icon: string, type: 'pass' | 'fail', active: boolean, onClick: () => void): HTMLButtonElement {
|
|
240
206
|
const btn = document.createElement('button');
|
|
241
207
|
btn.textContent = icon;
|
|
242
|
-
btn.
|
|
243
|
-
width: 22px; height: 22px; border-radius: 4px; border: 1px solid ${color};
|
|
244
|
-
background: ${active ? color : 'transparent'};
|
|
245
|
-
color: ${active ? 'white' : color};
|
|
246
|
-
font-size: 12px; cursor: pointer; display: flex;
|
|
247
|
-
align-items: center; justify-content: center; padding: 0;
|
|
248
|
-
`;
|
|
208
|
+
btn.className = `vf-check-btn vf-check-btn-${type}${active ? ' active' : ''}`;
|
|
249
209
|
btn.addEventListener('click', onClick);
|
|
250
210
|
return btn;
|
|
251
211
|
}
|
|
252
212
|
|
|
253
213
|
private promptFeedback(itemId: number): void {
|
|
254
214
|
if (this.onItemFail) {
|
|
255
|
-
// Enter feedback mode — SDK will handle element selection/annotation
|
|
256
215
|
this.onItemFail(itemId);
|
|
257
216
|
} else {
|
|
258
|
-
// Fallback: simple prompt
|
|
259
217
|
const feedback = prompt('Describe the issue (optional):') || '';
|
|
260
218
|
this.updateItem(itemId, 'failed', feedback);
|
|
261
219
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ElementInfo } from '../element-mode/inspector.js';
|
|
2
2
|
import { t } from '../core/i18n.js';
|
|
3
|
+
import { ICON_FLAG, ICON_SCREENSHOT } from '../styles/icons.js';
|
|
3
4
|
|
|
4
5
|
const PROBLEM_TYPES = [
|
|
5
6
|
{ value: 'bug', labelKey: 'type.bug' },
|
|
@@ -52,7 +53,12 @@ export class FeedbackPanel {
|
|
|
52
53
|
// Title
|
|
53
54
|
const title = document.createElement('div');
|
|
54
55
|
title.className = 'vf-panel-title';
|
|
55
|
-
|
|
56
|
+
const titleIcon = document.createElement('span');
|
|
57
|
+
titleIcon.innerHTML = mode === 'element' ? ICON_FLAG : ICON_SCREENSHOT;
|
|
58
|
+
title.appendChild(titleIcon);
|
|
59
|
+
const titleText = document.createElement('span');
|
|
60
|
+
titleText.textContent = mode === 'element' ? t('panel.element.title') : t('panel.annotation.title');
|
|
61
|
+
title.appendChild(titleText);
|
|
56
62
|
this.container.appendChild(title);
|
|
57
63
|
|
|
58
64
|
// Element info
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t } from '../core/i18n.js';
|
|
2
|
+
import { ICON_BUG, ICON_CLOSE } from '../styles/icons.js';
|
|
2
3
|
|
|
3
4
|
const SEEN_KEY = 'yo-bug-hint-seen';
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ export class FloatingButton {
|
|
|
10
11
|
constructor(shadowRoot: ShadowRoot, private onClick: () => void) {
|
|
11
12
|
this.el = document.createElement('button');
|
|
12
13
|
this.el.className = 'vf-fab';
|
|
13
|
-
this.el.innerHTML =
|
|
14
|
+
this.el.innerHTML = ICON_BUG;
|
|
14
15
|
this.el.title = 'yo-bug';
|
|
15
16
|
this.el.addEventListener('click', () => {
|
|
16
17
|
this.dismissHint();
|
|
@@ -19,23 +20,8 @@ export class FloatingButton {
|
|
|
19
20
|
|
|
20
21
|
// Recording indicator dot
|
|
21
22
|
this.recDot = document.createElement('div');
|
|
22
|
-
this.recDot.
|
|
23
|
-
|
|
24
|
-
width: 10px; height: 10px; border-radius: 50%;
|
|
25
|
-
background: #ef4444; border: 2px solid #1e293b;
|
|
26
|
-
animation: vf-pulse 1.5s infinite;
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
const style = document.createElement('style');
|
|
30
|
-
style.textContent = `
|
|
31
|
-
@keyframes vf-pulse {
|
|
32
|
-
0%, 100% { opacity: 1; }
|
|
33
|
-
50% { opacity: 0.4; }
|
|
34
|
-
}
|
|
35
|
-
`;
|
|
36
|
-
shadowRoot.appendChild(style);
|
|
37
|
-
|
|
38
|
-
this.el.style.position = 'relative';
|
|
23
|
+
this.recDot.className = 'vf-rec-dot';
|
|
24
|
+
|
|
39
25
|
this.el.appendChild(this.recDot);
|
|
40
26
|
shadowRoot.appendChild(this.el);
|
|
41
27
|
|
|
@@ -46,7 +32,7 @@ export class FloatingButton {
|
|
|
46
32
|
setActive(active: boolean): void {
|
|
47
33
|
this.active = active;
|
|
48
34
|
this.el.classList.toggle('active', active);
|
|
49
|
-
this.el.innerHTML = active ?
|
|
35
|
+
this.el.innerHTML = active ? ICON_CLOSE : ICON_BUG;
|
|
50
36
|
this.el.appendChild(this.recDot);
|
|
51
37
|
}
|
|
52
38
|
|
|
@@ -66,25 +52,11 @@ export class FloatingButton {
|
|
|
66
52
|
} catch {}
|
|
67
53
|
|
|
68
54
|
this.hint = document.createElement('div');
|
|
69
|
-
this.hint.
|
|
70
|
-
position: fixed; bottom: 82px; right: 20px;
|
|
71
|
-
background: #1e293b; color: #e2e8f0;
|
|
72
|
-
padding: 10px 16px; border-radius: 10px;
|
|
73
|
-
font-size: 13px; pointer-events: auto;
|
|
74
|
-
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
|
|
75
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
76
|
-
animation: vf-slide-in 0.3s ease-out;
|
|
77
|
-
max-width: 200px; line-height: 1.5;
|
|
78
|
-
`;
|
|
55
|
+
this.hint.className = 'vf-hint';
|
|
79
56
|
this.hint.textContent = t('hint.firstTime');
|
|
80
57
|
|
|
81
|
-
// Arrow pointing down to the button
|
|
82
58
|
const arrow = document.createElement('div');
|
|
83
|
-
arrow.
|
|
84
|
-
position: absolute; bottom: -6px; right: 22px;
|
|
85
|
-
width: 12px; height: 12px; background: #1e293b;
|
|
86
|
-
transform: rotate(45deg);
|
|
87
|
-
`;
|
|
59
|
+
arrow.className = 'vf-hint-arrow';
|
|
88
60
|
this.hint.appendChild(arrow);
|
|
89
61
|
|
|
90
62
|
shadowRoot.appendChild(this.hint);
|
package/src/client/ui/toast.ts
CHANGED
|
@@ -6,7 +6,18 @@ export function showToast(
|
|
|
6
6
|
): void {
|
|
7
7
|
const el = document.createElement('div');
|
|
8
8
|
el.className = `vf-toast${isError ? ' error' : ''}`;
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
// Icon
|
|
11
|
+
const icon = document.createElement('span');
|
|
12
|
+
icon.style.cssText = 'font-size: 14px; flex-shrink: 0;';
|
|
13
|
+
icon.textContent = isError ? '\u26A0' : '\u2713';
|
|
14
|
+
el.appendChild(icon);
|
|
15
|
+
|
|
16
|
+
// Text
|
|
17
|
+
const text = document.createElement('span');
|
|
18
|
+
text.textContent = message;
|
|
19
|
+
el.appendChild(text);
|
|
20
|
+
|
|
10
21
|
shadowRoot.appendChild(el);
|
|
11
22
|
|
|
12
23
|
setTimeout(() => {
|
|
@@ -9,12 +9,7 @@ export class VerifyPanel {
|
|
|
9
9
|
constructor(private shadowRoot: ShadowRoot) {
|
|
10
10
|
this.baseUrl = window.location.origin;
|
|
11
11
|
this.container = document.createElement('div');
|
|
12
|
-
this.container.
|
|
13
|
-
position: fixed; bottom: 84px; left: 16px; width: 300px;
|
|
14
|
-
display: flex; flex-direction: column; gap: 8px;
|
|
15
|
-
pointer-events: none; z-index: 2147483647;
|
|
16
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
17
|
-
`;
|
|
12
|
+
this.container.className = 'vf-verify-stack';
|
|
18
13
|
shadowRoot.appendChild(this.container);
|
|
19
14
|
}
|
|
20
15
|
|
|
@@ -44,47 +39,40 @@ export class VerifyPanel {
|
|
|
44
39
|
|
|
45
40
|
private showVerifyCard(item: any): void {
|
|
46
41
|
const card = document.createElement('div');
|
|
47
|
-
card.
|
|
48
|
-
background: #1e293b; border-radius: 10px; padding: 14px 16px;
|
|
49
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.35); pointer-events: auto;
|
|
50
|
-
color: #e2e8f0; font-size: 13px;
|
|
51
|
-
border-left: 3px solid #f59e0b;
|
|
52
|
-
animation: vf-slide-in 0.3s ease-out;
|
|
53
|
-
`;
|
|
42
|
+
card.className = 'vf-verify-card';
|
|
54
43
|
|
|
55
44
|
const title = document.createElement('div');
|
|
56
|
-
title.
|
|
45
|
+
title.className = 'vf-verify-title';
|
|
57
46
|
title.textContent = t('verify.title');
|
|
58
47
|
card.appendChild(title);
|
|
59
48
|
|
|
60
49
|
const desc = document.createElement('div');
|
|
61
|
-
desc.
|
|
50
|
+
desc.className = 'vf-verify-desc';
|
|
62
51
|
desc.textContent = item.description || `${item.problemType} on ${item.element || 'page'}`;
|
|
63
52
|
card.appendChild(desc);
|
|
64
53
|
|
|
65
54
|
const btnRow = document.createElement('div');
|
|
66
|
-
btnRow.
|
|
55
|
+
btnRow.className = 'vf-verify-btns';
|
|
67
56
|
|
|
68
57
|
const passBtn = document.createElement('button');
|
|
69
|
-
passBtn.
|
|
70
|
-
flex:1; padding:6px; border:none; border-radius:6px;
|
|
71
|
-
background:#22c55e; color:white; font-size:12px; cursor:pointer; font-weight:500;
|
|
72
|
-
`;
|
|
58
|
+
passBtn.className = 'vf-verify-btn vf-verify-btn-pass';
|
|
73
59
|
passBtn.textContent = '\u2713 ' + t('verify.fixed');
|
|
74
60
|
passBtn.addEventListener('click', () => {
|
|
75
61
|
this.respond(item.feedbackId, true);
|
|
76
|
-
card.
|
|
62
|
+
card.style.opacity = '0';
|
|
63
|
+
card.style.transition = 'opacity 0.2s, transform 0.2s';
|
|
64
|
+
card.style.transform = 'translateX(-20px)';
|
|
65
|
+
setTimeout(() => card.remove(), 200);
|
|
77
66
|
});
|
|
78
67
|
|
|
79
68
|
const failBtn = document.createElement('button');
|
|
80
|
-
failBtn.
|
|
81
|
-
flex:1; padding:6px; border:none; border-radius:6px;
|
|
82
|
-
background:#ef4444; color:white; font-size:12px; cursor:pointer; font-weight:500;
|
|
83
|
-
`;
|
|
69
|
+
failBtn.className = 'vf-verify-btn vf-verify-btn-fail';
|
|
84
70
|
failBtn.textContent = '\u2717 ' + t('verify.broken');
|
|
85
71
|
failBtn.addEventListener('click', () => {
|
|
86
72
|
this.respond(item.feedbackId, false);
|
|
87
|
-
card.
|
|
73
|
+
card.style.opacity = '0';
|
|
74
|
+
card.style.transition = 'opacity 0.2s';
|
|
75
|
+
setTimeout(() => card.remove(), 200);
|
|
88
76
|
});
|
|
89
77
|
|
|
90
78
|
btnRow.appendChild(passBtn);
|
|
@@ -7,7 +7,7 @@ import { createFeedbackRouter } from './feedback-api.js';
|
|
|
7
7
|
import { createSdkRouter } from './sdk-serve.js';
|
|
8
8
|
|
|
9
9
|
const PROXY_PORT = 3695;
|
|
10
|
-
const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
|
|
10
|
+
const INJECT_SCRIPT = `<script defer src="/vibe-feedback.js"></script>`;
|
|
11
11
|
|
|
12
12
|
let server: Server | null = null;
|
|
13
13
|
let proxy: httpProxy | null = null;
|
|
@@ -38,6 +38,11 @@ export async function startProxyServer(
|
|
|
38
38
|
changeOrigin: true,
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
// Prevent upstream from sending compressed responses — we need raw HTML to inject SDK
|
|
42
|
+
proxy.on('proxyReq', (proxyReq) => {
|
|
43
|
+
proxyReq.setHeader('Accept-Encoding', 'identity');
|
|
44
|
+
});
|
|
45
|
+
|
|
41
46
|
proxy.on('error', (_err, _req, res) => {
|
|
42
47
|
if (res && 'writeHead' in res) {
|
|
43
48
|
try {
|
package/src/server/sdk-serve.ts
CHANGED
|
@@ -7,15 +7,24 @@ export function createSdkRouter(): Router {
|
|
|
7
7
|
const router = Router();
|
|
8
8
|
|
|
9
9
|
router.get('/vibe-feedback.js', (_req, res) => {
|
|
10
|
-
// Resolve SDK path relative to this file's location
|
|
11
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const sdkPath = path.resolve(__dirname, '../../dist/vibe-feedback.js');
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// Try multiple possible locations:
|
|
13
|
+
// 1. Running from dist/src/server/ → ../../vibe-feedback.js (compiled, npm global)
|
|
14
|
+
// 2. Running from src/server/ via tsx → ../../dist/vibe-feedback.js (dev mode)
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.resolve(__dirname, '../../vibe-feedback.js'), // dist/vibe-feedback.js from dist/src/server/
|
|
17
|
+
path.resolve(__dirname, '../../dist/vibe-feedback.js'), // from src/server/ (tsx dev)
|
|
18
|
+
path.resolve(__dirname, '../../../dist/vibe-feedback.js'), // fallback
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const sdkPath = candidates.find((p) => fs.existsSync(p));
|
|
22
|
+
|
|
23
|
+
if (!sdkPath) {
|
|
24
|
+
return res.status(404).send(`// SDK not found. Searched: ${candidates.join(', ')}`);
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
27
|
+
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
19
28
|
res.setHeader('Cache-Control', 'no-cache');
|
|
20
29
|
fs.createReadStream(sdkPath).pipe(res);
|
|
21
30
|
});
|