yo-bug 0.1.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yo-bug",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "MCP Server for visual test feedback in vibe coding — QA capability as a protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,16 @@
1
+ import {
2
+ ICON_ARROW, ICON_RECT, ICON_CIRCLE, ICON_FREEHAND, ICON_TEXT,
3
+ ICON_UNDO, ICON_CHECK, ICON_X
4
+ } from '../styles/icons.js';
5
+
1
6
  export type ToolType = 'arrow' | 'rect' | 'circle' | 'freehand' | 'text';
2
7
 
3
8
  const TOOLS: { type: ToolType; icon: string; label: string }[] = [
4
- { type: 'arrow', icon: '\u2197', label: 'Arrow' },
5
- { type: 'rect', icon: '\u25a1', label: 'Rect' },
6
- { type: 'circle', icon: '\u25cb', label: 'Circle' },
7
- { type: 'freehand', icon: '\u270e', label: 'Draw' },
8
- { type: 'text', icon: 'T', label: 'Text' },
9
+ { type: 'arrow', icon: ICON_ARROW, label: 'Arrow' },
10
+ { type: 'rect', icon: ICON_RECT, label: 'Rect' },
11
+ { type: 'circle', icon: ICON_CIRCLE, label: 'Circle' },
12
+ { type: 'freehand', icon: ICON_FREEHAND, label: 'Draw' },
13
+ { type: 'text', icon: ICON_TEXT, label: 'Text' },
9
14
  ];
10
15
 
11
16
  const COLORS = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899', '#000000', '#ffffff'];
@@ -13,6 +18,8 @@ const COLORS = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899'
13
18
  export class Toolbar {
14
19
  private container: HTMLDivElement;
15
20
  private toolBtns: HTMLButtonElement[] = [];
21
+ private colorBtns: HTMLButtonElement[] = [];
22
+ private activeColor = '#ef4444';
16
23
 
17
24
  constructor(
18
25
  parent: HTMLElement,
@@ -25,25 +32,15 @@ export class Toolbar {
25
32
  }
26
33
  ) {
27
34
  this.container = document.createElement('div');
28
- this.container.style.cssText = `
29
- position: absolute;
30
- top: 12px;
31
- left: 50%;
32
- transform: translateX(-50%);
33
- display: flex;
34
- align-items: center;
35
- gap: 6px;
36
- background: #1e293b;
37
- padding: 8px 12px;
38
- border-radius: 10px;
39
- box-shadow: 0 4px 16px rgba(0,0,0,0.4);
40
- z-index: 10;
41
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
42
- `;
35
+ this.container.className = 'vf-toolbar';
43
36
 
44
37
  // Tool buttons
45
38
  for (const tool of TOOLS) {
46
- const btn = this.createBtn(tool.icon, tool.label, () => {
39
+ const btn = document.createElement('button');
40
+ btn.className = 'vf-tool-btn';
41
+ btn.innerHTML = tool.icon;
42
+ btn.title = tool.label;
43
+ btn.addEventListener('click', () => {
47
44
  this.selectTool(tool.type);
48
45
  callbacks.onToolSelect(tool.type);
49
46
  });
@@ -56,41 +53,56 @@ export class Toolbar {
56
53
  // Color buttons
57
54
  for (const color of COLORS) {
58
55
  const btn = document.createElement('button');
59
- btn.style.cssText = `
60
- width: 22px; height: 22px; border-radius: 50%; border: 2px solid transparent;
61
- background: ${color}; cursor: pointer; padding: 0;
62
- ${color === '#ffffff' ? 'border-color: #475569;' : ''}
63
- `;
56
+ btn.className = `vf-color-btn${color === this.activeColor ? ' active' : ''}`;
57
+ btn.style.background = color;
58
+ if (color === '#ffffff') {
59
+ btn.style.borderColor = '#475569';
60
+ }
64
61
  btn.addEventListener('click', () => {
65
- // Deselect all colors, select this one
66
- this.container.querySelectorAll<HTMLElement>('[data-color]').forEach(
67
- (el) => (el.style.borderColor = el.dataset.color === '#ffffff' ? '#475569' : 'transparent')
68
- );
69
- btn.style.borderColor = '#60a5fa';
62
+ this.activeColor = color;
63
+ this.colorBtns.forEach((b) => {
64
+ b.classList.remove('active');
65
+ // Restore white's visible border
66
+ if (b.style.background === 'rgb(255, 255, 255)' || b.dataset.color === '#ffffff') {
67
+ b.style.borderColor = '#475569';
68
+ }
69
+ });
70
+ btn.classList.add('active');
70
71
  callbacks.onColorChange(color);
71
72
  });
72
73
  btn.dataset.color = color;
74
+ this.colorBtns.push(btn);
73
75
  this.container.appendChild(btn);
74
76
  }
75
77
 
76
78
  this.addSeparator();
77
79
 
78
80
  // Undo
79
- this.container.appendChild(
80
- this.createBtn('\u21a9', 'Undo', callbacks.onUndo)
81
- );
81
+ const undoBtn = document.createElement('button');
82
+ undoBtn.className = 'vf-tool-btn';
83
+ undoBtn.innerHTML = ICON_UNDO;
84
+ undoBtn.title = 'Undo (Ctrl+Z)';
85
+ undoBtn.addEventListener('click', callbacks.onUndo);
86
+ this.container.appendChild(undoBtn);
82
87
 
83
88
  this.addSeparator();
84
89
 
85
- // Done & Cancel
86
- const doneBtn = this.createBtn('\u2713', 'Done', callbacks.onDone);
90
+ // Done
91
+ const doneBtn = document.createElement('button');
92
+ doneBtn.className = 'vf-tool-action';
87
93
  doneBtn.style.background = '#22c55e';
88
- doneBtn.style.color = 'white';
94
+ doneBtn.innerHTML = ICON_CHECK;
95
+ doneBtn.title = 'Done';
96
+ doneBtn.addEventListener('click', callbacks.onDone);
89
97
  this.container.appendChild(doneBtn);
90
98
 
91
- const cancelBtn = this.createBtn('\u2715', 'Cancel', callbacks.onCancel);
99
+ // Cancel
100
+ const cancelBtn = document.createElement('button');
101
+ cancelBtn.className = 'vf-tool-action';
92
102
  cancelBtn.style.background = '#ef4444';
93
- cancelBtn.style.color = 'white';
103
+ cancelBtn.innerHTML = ICON_X;
104
+ cancelBtn.title = 'Cancel';
105
+ cancelBtn.addEventListener('click', callbacks.onCancel);
94
106
  this.container.appendChild(cancelBtn);
95
107
 
96
108
  parent.appendChild(this.container);
@@ -99,37 +111,15 @@ export class Toolbar {
99
111
  this.selectTool('arrow');
100
112
  }
101
113
 
102
- private createBtn(icon: string, title: string, onClick: () => void): HTMLButtonElement {
103
- const btn = document.createElement('button');
104
- btn.textContent = icon;
105
- btn.title = title;
106
- btn.style.cssText = `
107
- width: 34px; height: 34px; border: none; border-radius: 6px;
108
- background: transparent; color: #94a3b8; font-size: 16px;
109
- cursor: pointer; display: flex; align-items: center; justify-content: center;
110
- `;
111
- btn.addEventListener('mouseenter', () => {
112
- if (!btn.classList.contains('tool-active')) btn.style.background = '#334155';
113
- });
114
- btn.addEventListener('mouseleave', () => {
115
- if (!btn.classList.contains('tool-active')) btn.style.background = 'transparent';
116
- });
117
- btn.addEventListener('click', onClick);
118
- return btn;
119
- }
120
-
121
114
  private addSeparator(): void {
122
115
  const sep = document.createElement('div');
123
- sep.style.cssText = 'width:1px; height:24px; background:#334155; margin:0 4px;';
116
+ sep.className = 'vf-toolbar-sep';
124
117
  this.container.appendChild(sep);
125
118
  }
126
119
 
127
120
  private selectTool(type: ToolType): void {
128
121
  this.toolBtns.forEach((btn, i) => {
129
- const isActive = TOOLS[i].type === type;
130
- btn.classList.toggle('tool-active', isActive);
131
- btn.style.background = isActive ? '#3b82f6' : 'transparent';
132
- btn.style.color = isActive ? 'white' : '#94a3b8';
122
+ btn.classList.toggle('active', TOOLS[i].type === type);
133
123
  });
134
124
  }
135
125
 
@@ -1,33 +1,30 @@
1
1
  const zhCN: Record<string, string> = {
2
2
  // Floating button
3
- 'hint.firstTime': '\u53D1\u73B0\u95EE\u9898\uFF1F\u70B9\u6211\u6216\u6309 Alt+Q \u5FEB\u901F\u6807\u8BB0',
3
+ 'hint.firstTime': '发现问题?点我或按 Alt+E 报告',
4
4
  // Mode bar
5
- 'mode.quick': '\u5FEB\u901F\u6807\u8BB0',
6
- 'mode.quick.hint': '\u70B9\u51FB\u95EE\u9898\u5143\u7D20',
7
- 'mode.detail': '\u8BE6\u7EC6\u63CF\u8FF0',
8
- 'mode.detail.hint': '\u70B9\u51FB + \u586B\u5199',
9
- 'mode.screenshot': '\u622A\u56FE\u6807\u6CE8',
10
- 'mode.screenshot.hint': '\u6846\u9009\u533A\u57DF + \u753B',
5
+ 'mode.element': '元素报告',
6
+ 'mode.element.hint': '点击问题元素',
7
+ 'mode.screenshot': '截图标注',
8
+ 'mode.screenshot.hint': '框选区域 + ',
11
9
  // Feedback panel
12
- 'panel.element.title': '\u5143\u7D20\u53CD\u9988',
13
- 'panel.annotation.title': '\u622A\u56FE\u53CD\u9988',
14
- 'panel.type': '\u95EE\u9898\u7C7B\u578B',
15
- 'panel.description': '\u95EE\u9898\u63CF\u8FF0',
16
- 'panel.description.placeholder': '\u63CF\u8FF0\u4F60\u53D1\u73B0\u7684\u95EE\u9898...',
17
- 'panel.cancel': '\u53D6\u6D88',
18
- 'panel.submit': '\u63D0\u4EA4',
10
+ 'panel.element.title': '元素反馈',
11
+ 'panel.annotation.title': '截图反馈',
12
+ 'panel.type': '问题类型',
13
+ 'panel.description': '问题描述',
14
+ 'panel.description.placeholder': '描述你发现的问题...',
15
+ 'panel.cancel': '取消',
16
+ 'panel.submit': '提交',
19
17
  // Toast
20
- 'toast.flagged': '\u5DF2\u6807\u8BB0',
21
- 'toast.submitted': '\u53CD\u9988\u5DF2\u63D0\u4EA4',
22
- 'toast.failed': '\u63D0\u4EA4\u5931\u8D25',
23
- 'toast.screenshotFailed': '\u622A\u56FE\u5931\u8D25',
24
- 'toast.clickToFlag': '\u70B9\u51FB\u95EE\u9898\u5143\u7D20\u6765\u6807\u8BB0',
18
+ 'toast.submitted': '反馈已提交',
19
+ 'toast.failed': '提交失败',
20
+ 'toast.screenshotFailed': '截图失败',
21
+ 'toast.clickToFlag': '点击问题元素来标记',
25
22
  // Region selector
26
- 'region.hint': '\u62D6\u62FD\u9009\u62E9\u533A\u57DF \xB7 ESC \u53D6\u6D88',
23
+ 'region.hint': '拖拽选择区域 · ESC 取消',
27
24
  // Verify
28
- 'verify.title': '\u9A8C\u8BC1\u4FEE\u590D',
29
- 'verify.fixed': '\u5DF2\u4FEE\u590D',
30
- 'verify.broken': '\u672A\u4FEE\u590D',
25
+ 'verify.title': '验证修复',
26
+ 'verify.fixed': '已修复',
27
+ 'verify.broken': '未修复',
31
28
  // Problem types
32
29
  'type.bug': 'Bug',
33
30
  'type.ui-issue': 'UI',
@@ -37,11 +34,9 @@ const zhCN: Record<string, string> = {
37
34
  };
38
35
 
39
36
  const en: Record<string, string> = {
40
- 'hint.firstTime': 'Found a problem? Click me or press Alt+Q to flag it.',
41
- 'mode.quick': 'Quick',
42
- 'mode.quick.hint': 'Tap problem',
43
- 'mode.detail': 'Describe',
44
- 'mode.detail.hint': 'Tap + fill form',
37
+ 'hint.firstTime': 'Found a problem? Click me or press Alt+E to report.',
38
+ 'mode.element': 'Element',
39
+ 'mode.element.hint': 'Tap problem element',
45
40
  'mode.screenshot': 'Screenshot',
46
41
  'mode.screenshot.hint': 'Select area + draw',
47
42
  'panel.element.title': 'Element Feedback',
@@ -51,7 +46,6 @@ const en: Record<string, string> = {
51
46
  'panel.description.placeholder': 'Describe the issue...',
52
47
  'panel.cancel': 'Cancel',
53
48
  'panel.submit': 'Submit',
54
- 'toast.flagged': 'Flagged!',
55
49
  'toast.submitted': 'Feedback submitted',
56
50
  'toast.failed': 'Submit failed',
57
51
  'toast.screenshotFailed': 'Screenshot failed',
@@ -17,8 +17,9 @@ import { ErrorInterceptor, type ErrorEntry } from '../capture/error-interceptor.
17
17
  import { ApiClient, type FeedbackPayload } from '../api/client.js';
18
18
  import { ActionRecorder } from '../capture/action-recorder.js';
19
19
  import { t } from './i18n.js';
20
+ import { ICON_FLAG, ICON_SCREENSHOT } from '../styles/icons.js';
20
21
 
21
- type Mode = 'flag' | 'detail' | 'annotate';
22
+ type Mode = 'element' | 'annotate';
22
23
 
23
24
  export class VibeFeedback {
24
25
  private shadowHost: ShadowHost;
@@ -91,22 +92,18 @@ export class VibeFeedback {
91
92
  // Don't intercept inside our SDK panels
92
93
  if (this.isSDKElement(e.target as Element)) return;
93
94
 
94
- // All shortcuts use Alt+key (or Option+key on Mac) to avoid conflicts
95
+ // Use e.code (physical key) reliable across IME/keyboard layouts
95
96
  if (e.altKey) {
96
- switch (e.key) {
97
- case 'q': case 'Q':
97
+ switch (e.code) {
98
+ case 'KeyE':
98
99
  e.preventDefault();
99
- this.enterMode('flag');
100
+ this.enterMode('element');
100
101
  return;
101
- case 'd': case 'D':
102
- e.preventDefault();
103
- this.enterMode('detail');
104
- return;
105
- case 's': case 'S':
102
+ case 'KeyS':
106
103
  e.preventDefault();
107
104
  this.enterMode('annotate');
108
105
  return;
109
- case 'x': case 'X':
106
+ case 'KeyX':
110
107
  e.preventDefault();
111
108
  if (this.active) this.exitTestMode();
112
109
  else this.toggleModeBar();
@@ -140,7 +137,7 @@ export class VibeFeedback {
140
137
  this.hideModeBar();
141
138
  this.showModeBar();
142
139
  // Highlight active button
143
- const idx = mode === 'flag' ? 0 : mode === 'detail' ? 1 : 2;
140
+ const idx = mode === 'element' ? 0 : 1;
144
141
  this.modeBar?.querySelectorAll('.vf-mode-btn').forEach((btn, i) => {
145
142
  btn.classList.toggle('active', i === idx);
146
143
  });
@@ -171,21 +168,19 @@ export class VibeFeedback {
171
168
  this.modeBar = document.createElement('div');
172
169
  this.modeBar.className = 'vf-mode-bar';
173
170
 
174
- const modes: { mode: Mode; label: string; hint: string; key: string }[] = [
175
- { mode: 'flag', label: t('mode.quick'), hint: t('mode.quick.hint'), key: 'Alt+Q' },
176
- { mode: 'detail', label: t('mode.detail'), hint: t('mode.detail.hint'), key: 'Alt+D' },
177
- { mode: 'annotate', label: t('mode.screenshot'), hint: t('mode.screenshot.hint'), key: 'Alt+S' },
171
+ const modes: { mode: Mode; label: string; hint: string; key: string; icon: string }[] = [
172
+ { mode: 'element', label: t('mode.element'), hint: t('mode.element.hint'), key: 'E', icon: ICON_FLAG },
173
+ { mode: 'annotate', label: t('mode.screenshot'), hint: t('mode.screenshot.hint'), key: 'S', icon: ICON_SCREENSHOT },
178
174
  ];
179
175
 
180
176
  for (const m of modes) {
181
177
  const btn = document.createElement('button');
182
178
  btn.className = `vf-mode-btn${this.mode === m.mode ? ' active' : ''}`;
183
179
  btn.innerHTML = `
184
- <div style="display:flex;align-items:center;gap:6px;font-size:13px">
185
- ${m.label}
186
- <span style="font-size:10px;opacity:0.4;background:rgba(255,255,255,0.1);padding:1px 5px;border-radius:3px;">${m.key}</span>
187
- </div>
188
- <div style="font-size:10px;opacity:0.5;margin-top:1px">${m.hint}</div>
180
+ ${m.icon}
181
+ <span class="vf-mode-label">${m.label}</span>
182
+ <span class="vf-mode-hint">${m.hint}</span>
183
+ <span class="vf-mode-key">${m.key}</span>
189
184
  `;
190
185
  btn.addEventListener('click', () => {
191
186
  this.mode = m.mode;
@@ -208,7 +203,7 @@ export class VibeFeedback {
208
203
  this.highlighter.deactivate();
209
204
  if (!this.mode) return;
210
205
 
211
- if (this.mode === 'flag' || this.mode === 'detail') {
206
+ if (this.mode === 'element') {
212
207
  this.highlighter.activate();
213
208
  } else {
214
209
  this.startAnnotation();
@@ -227,37 +222,15 @@ export class VibeFeedback {
227
222
  // ──────── Element selected ────────
228
223
  private onElementSelected(el: Element): void {
229
224
  this.selectedElement = inspectElement(el);
230
-
231
- if (this.mode === 'flag') {
232
- this.quickFlag();
233
- } else {
234
- this.highlighter.deactivate();
235
- this.hideModeBar();
236
- this.panel.showForElement(this.selectedElement);
237
- }
238
- }
239
-
240
- // ──────── Quick flag ────────
241
- private async quickFlag(): Promise<void> {
242
- const payload = this.buildPayload('bug', '');
243
- try {
244
- const { id } = await this.apiClient.submitFeedback(payload);
245
- showToast(this.shadowHost.shadow, `${t('toast.flagged')} (${id})`);
246
-
247
- if (this.activeChecklistItemId !== null) {
248
- await this.linkFeedbackToChecklist(id, this.activeChecklistItemId);
249
- this.activeChecklistItemId = null;
250
- }
251
- } catch {
252
- showToast(this.shadowHost.shadow, t('toast.failed'), true);
253
- }
254
- this.selectedElement = null;
225
+ this.highlighter.deactivate();
226
+ this.hideModeBar();
227
+ this.panel.showForElement(this.selectedElement);
255
228
  }
256
229
 
257
230
  // ──────── Checklist integration ────────
258
231
  private onChecklistFail(itemId: number): void {
259
232
  this.activeChecklistItemId = itemId;
260
- this.enterMode('flag');
233
+ this.enterMode('element');
261
234
  showToast(this.shadowHost.shadow, t('toast.clickToFlag'));
262
235
  }
263
236
 
@@ -0,0 +1,85 @@
1
+ /** Inline SVG icons — crisp at any size, no external dependencies */
2
+
3
+ // yo-bug brand icon — cute beetle
4
+ export const ICON_BUG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
5
+ <ellipse cx="12" cy="15" rx="5.5" ry="6.5"/>
6
+ <circle cx="12" cy="6" r="2.5"/>
7
+ <line x1="12" y1="8.5" x2="12" y2="21.5"/>
8
+ <path d="M6.5 12L3 10"/>
9
+ <path d="M17.5 12L21 10"/>
10
+ <path d="M6.5 16L3 18"/>
11
+ <path d="M17.5 16L21 18"/>
12
+ <path d="M10 4L8 1.5"/>
13
+ <path d="M14 4L16 1.5"/>
14
+ </svg>`;
15
+
16
+ export const ICON_CLOSE = `<svg class="vf-close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
17
+ <line x1="6" y1="6" x2="18" y2="18"/>
18
+ <line x1="18" y1="6" x2="6" y2="18"/>
19
+ </svg>`;
20
+
21
+ // Mode icons
22
+ export const ICON_FLAG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
23
+ <path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
24
+ <line x1="4" y1="22" x2="4" y2="15"/>
25
+ </svg>`;
26
+
27
+ export const ICON_DETAIL = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
28
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
29
+ <polyline points="14 2 14 8 20 8"/>
30
+ <line x1="16" y1="13" x2="8" y2="13"/>
31
+ <line x1="16" y1="17" x2="8" y2="17"/>
32
+ <polyline points="10 9 9 9 8 9"/>
33
+ </svg>`;
34
+
35
+ export const ICON_SCREENSHOT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
36
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
37
+ <circle cx="12" cy="13" r="4"/>
38
+ </svg>`;
39
+
40
+ // Toolbar icons
41
+ export const ICON_ARROW = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42
+ <line x1="5" y1="19" x2="19" y2="5"/>
43
+ <polyline points="12 5 19 5 19 12"/>
44
+ </svg>`;
45
+
46
+ export const ICON_RECT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
47
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
48
+ </svg>`;
49
+
50
+ export const ICON_CIRCLE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51
+ <circle cx="12" cy="12" r="10"/>
52
+ </svg>`;
53
+
54
+ export const ICON_FREEHAND = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55
+ <path d="M12 19l7-7 3 3-7 7-3-3z"/>
56
+ <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>
57
+ <path d="M2 2l7.586 7.586"/>
58
+ <circle cx="11" cy="11" r="2"/>
59
+ </svg>`;
60
+
61
+ export const ICON_TEXT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
62
+ <polyline points="4 7 4 4 20 4 20 7"/>
63
+ <line x1="9" y1="20" x2="15" y2="20"/>
64
+ <line x1="12" y1="4" x2="12" y2="20"/>
65
+ </svg>`;
66
+
67
+ export const ICON_UNDO = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
68
+ <polyline points="1 4 1 10 7 10"/>
69
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
70
+ </svg>`;
71
+
72
+ export const ICON_CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
73
+ <polyline points="4 12 10 18 20 6"/>
74
+ </svg>`;
75
+
76
+ export const ICON_X = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round">
77
+ <line x1="6" y1="6" x2="18" y2="18"/>
78
+ <line x1="18" y1="6" x2="6" y2="18"/>
79
+ </svg>`;
80
+
81
+ // Verify icon
82
+ export const ICON_VERIFY = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
84
+ <polyline points="22 4 12 14.01 9 11.01"/>
85
+ </svg>`;