feedback-collector 0.1.1 → 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.
@@ -1,461 +1,23 @@
1
- /**
2
- * feedback-collector.js — POC de coletor de feedback visual para Claude Code
3
- * ---------------------------------------------------------------------------
4
- * USO
5
- * 1. (Recomendado) Instale o react-dev-inspector para source mapping:
6
- * npm i -D react-dev-inspector
7
- * e adicione o plugin ao seu bundler (Babel/SWC/Vite — ver docs do pacote).
8
- * Ele injeta data-inspector-relative-path / -line / -column no DOM em dev.
9
- * Sem ele o script ainda funciona, mas cai pro fallback (fiber _debugSource)
10
- * e, em último caso, só seletor CSS.
11
- *
12
- * 2. Carregue este arquivo SOMENTE em dev. Exemplos:
13
- * - Vite/Next (em um componente raiz ou _app):
14
- * if (process.env.NODE_ENV === 'development') import('./feedback-collector.js')
15
- * - Ou <script src="/feedback-collector.js"></script> no index.html de dev.
16
- *
17
- * INTERAÇÃO
18
- * - Segure ALT: entra em modo picker (elementos ganham highlight no hover).
19
- * - ALT + clique: captura o elemento e abre campo pra sua instrução.
20
- * - Painel flutuante (canto inferior direito): lista, edita, remove itens.
21
- * - "Copiar backlog": gera markdown numerado no clipboard, pronto pra colar
22
- * no Claude Code.
23
- * - Itens persistem em localStorage (sobrevivem reload e navegação SPA).
24
- *
25
- * ESCOPO (POC): sem screenshot, sem extensão, sem backend. Teste primeiro se
26
- * source mapping + DOM + instrução bastam — provavelmente sim pra layout/estilo.
27
- */
28
- (function () {
29
- 'use strict';
30
- if (window.__FEEDBACK_COLLECTOR__) return;
31
- window.__FEEDBACK_COLLECTOR__ = true;
32
-
33
- var STORAGE_KEY = '__fbc_items_v1';
34
-
35
- /* ------------------------------------------------------------------ */
36
- /* Estado */
37
- /* ------------------------------------------------------------------ */
38
- var items = load();
39
- var pickerActive = false;
40
- var hoverEl = null;
41
-
42
- function load() {
43
- try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; }
44
- catch (e) { return []; }
45
- }
46
- function save() {
47
- try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); }
48
- catch (e) { /* quota — ignora na POC */ }
49
- }
50
-
51
- /* ------------------------------------------------------------------ */
52
- /* Source mapping */
53
- /* ------------------------------------------------------------------ */
54
- function getSourceInfo(el) {
55
- // 1) react-dev-inspector (data attrs no próprio elemento ou ancestral)
56
- var node = el;
57
- while (node && node !== document.body) {
58
- var p = node.getAttribute && (
59
- node.getAttribute('data-inspector-relative-path') ||
60
- node.getAttribute('data-source') // convenção alternativa
61
- );
62
- if (p) {
63
- var line = node.getAttribute('data-inspector-line') || '';
64
- var col = node.getAttribute('data-inspector-column') || '';
65
- return { file: p, line: line, column: col, via: 'data-attr', ownerHops: node === el ? 0 : 1 };
66
- }
67
- node = node.parentElement;
68
- }
69
-
70
- // 2) Fallback: React fiber _debugSource (funciona em dev builds do React <19)
71
- var fiber = getFiber(el);
72
- var hops = 0;
73
- while (fiber && hops < 20) {
74
- var src = fiber._debugSource;
75
- if (src && src.fileName) {
76
- return {
77
- file: relativize(src.fileName),
78
- line: src.lineNumber || '',
79
- column: src.columnNumber || '',
80
- via: 'fiber',
81
- componentName: fiberName(fiber)
82
- };
83
- }
84
- fiber = fiber._debugOwner || fiber.return;
85
- hops++;
86
- }
87
- return null;
88
- }
89
-
90
- function getFiber(el) {
91
- for (var k in el) {
92
- if (k.indexOf('__reactFiber$') === 0 || k.indexOf('__reactInternalInstance$') === 0) {
93
- return el[k];
94
- }
95
- }
96
- return null;
97
- }
98
-
99
- function fiberName(fiber) {
100
- var f = fiber;
101
- while (f) {
102
- var t = f.type;
103
- if (typeof t === 'function') return t.displayName || t.name || '';
104
- if (t && typeof t === 'object' && t.displayName) return t.displayName;
105
- f = f._debugOwner || f.return;
106
- }
107
- return '';
108
- }
109
-
110
- function componentChain(el) {
111
- // Nome dos 3 componentes React mais próximos, do mais interno pro externo
112
- var fiber = getFiber(el);
113
- var names = [];
114
- var hops = 0;
115
- while (fiber && names.length < 3 && hops < 30) {
116
- var t = fiber.type;
117
- var name = null;
118
- if (typeof t === 'function') name = t.displayName || t.name;
119
- else if (t && typeof t === 'object' && t.displayName) name = t.displayName;
120
- if (name && names.indexOf(name) === -1) names.push(name);
121
- fiber = fiber._debugOwner || fiber.return;
122
- hops++;
123
- }
124
- return names;
125
- }
126
-
127
- function relativize(fileName) {
128
- // Corta caminho absoluto até algo legível (src/, app/, pages/, components/)
129
- var m = fileName.match(/(?:^|\/)((?:src|app|pages|components|lib|features)\/.*)$/);
130
- return m ? m[1] : fileName;
131
- }
132
-
133
- /* ------------------------------------------------------------------ */
134
- /* Seletor CSS estável */
135
- /* ------------------------------------------------------------------ */
136
- function cssSelector(el) {
137
- if (el.id) return '#' + CSS.escape(el.id);
138
- var parts = [];
139
- var node = el;
140
- while (node && node.nodeType === 1 && node !== document.body && parts.length < 5) {
141
- var part = node.tagName.toLowerCase();
142
- var stableAttrs = ['data-testid', 'data-test', 'aria-label', 'name'];
143
- var got = false;
144
- for (var i = 0; i < stableAttrs.length; i++) {
145
- var v = node.getAttribute(stableAttrs[i]);
146
- if (v) { part += '[' + stableAttrs[i] + '="' + v + '"]'; got = true; break; }
147
- }
148
- if (!got && node.classList.length) {
149
- // usa até 2 classes que não pareçam geradas (hash/módulo CSS)
150
- var cls = Array.prototype.filter.call(node.classList, function (c) {
151
- return !/^[a-z]*[_-][a-zA-Z0-9]{5,}$/.test(c) && !/^\d/.test(c) && c.length < 30;
152
- }).slice(0, 2);
153
- if (cls.length) { part += '.' + cls.map(function(c){ return CSS.escape(c); }).join('.'); got = true; }
154
- }
155
- if (!got) {
156
- var idx = 1, sib = node;
157
- while ((sib = sib.previousElementSibling)) {
158
- if (sib.tagName === node.tagName) idx++;
159
- }
160
- if (idx > 1) part += ':nth-of-type(' + idx + ')';
161
- }
162
- parts.unshift(part);
163
- // se o seletor parcial já é único, para
164
- try {
165
- if (document.querySelectorAll(parts.join(' > ')).length === 1) break;
166
- } catch (e) { /* seletor inválido, segue */ }
167
- node = node.parentElement;
168
- }
169
- return parts.join(' > ');
170
- }
171
-
172
- /* ------------------------------------------------------------------ */
173
- /* Computed styles curados */
174
- /* ------------------------------------------------------------------ */
175
- var BASE_PROPS = ['display', 'position', 'width', 'height', 'margin', 'padding',
176
- 'color', 'background-color', 'font-size', 'font-weight', 'line-height',
177
- 'border', 'border-radius', 'gap', 'flex-direction', 'justify-content',
178
- 'align-items', 'grid-template-columns', 'overflow', 'z-index', 'opacity',
179
- 'box-shadow', 'text-align'];
180
-
181
- function curatedStyles(el) {
182
- var cs = getComputedStyle(el);
183
- var out = {};
184
- BASE_PROPS.forEach(function (p) {
185
- var v = cs.getPropertyValue(p);
186
- if (!v) return;
187
- // pula valores default barulhentos
188
- if (v === 'none' || v === 'normal' || v === 'auto' || v === 'rgba(0, 0, 0, 0)' ||
189
- v === '0px' || v === 'visible' || v === 'static' || v === 'start') return;
190
- out[p] = v;
191
- });
192
- return out;
193
- }
194
-
195
- function truncatedHTML(el, max) {
196
- var html = el.outerHTML || '';
197
- if (html.length <= max) return html;
198
- // mantém a tag de abertura inteira + começo do conteúdo
199
- return html.slice(0, max) + '… [truncado]';
200
- }
201
-
202
- /* ------------------------------------------------------------------ */
203
- /* Captura */
204
- /* ------------------------------------------------------------------ */
205
- function capture(el) {
206
- var rect = el.getBoundingClientRect();
207
- var source = getSourceInfo(el);
208
- var item = {
209
- id: Date.now() + '_' + Math.random().toString(36).slice(2, 7),
210
- url: location.pathname + location.search + location.hash,
211
- tag: el.tagName.toLowerCase(),
212
- selector: cssSelector(el),
213
- source: source,
214
- components: componentChain(el),
215
- text: (el.textContent || '').trim().slice(0, 120),
216
- html: truncatedHTML(el, 600),
217
- styles: curatedStyles(el),
218
- rect: {
219
- x: Math.round(rect.x), y: Math.round(rect.y),
220
- w: Math.round(rect.width), h: Math.round(rect.height)
221
- },
222
- viewport: window.innerWidth + 'x' + window.innerHeight,
223
- instruction: '',
224
- ts: new Date().toISOString()
225
- };
226
- items.push(item);
227
- save();
228
- renderPanel();
229
- // foca o textarea do item recém-criado
230
- setTimeout(function () {
231
- var ta = panel.querySelector('[data-fbc-ta="' + item.id + '"]');
232
- if (ta) ta.focus();
233
- }, 50);
234
- flashElement(el);
235
- }
236
-
237
- function flashElement(el) {
238
- var prev = el.style.outline;
239
- el.style.outline = '3px solid #16a34a';
240
- setTimeout(function () { el.style.outline = prev; }, 400);
241
- }
242
-
243
- /* ------------------------------------------------------------------ */
244
- /* Export markdown */
245
- /* ------------------------------------------------------------------ */
246
- function toMarkdown() {
247
- var lines = ['# Backlog de feedback visual', '',
248
- 'Gerado em ' + new Date().toLocaleString() + ' — ' + items.length + ' itens.',
249
- 'Cada item indica o arquivo:linha quando disponível. Execute na ordem, agrupando edits do mesmo arquivo.', ''];
250
- items.forEach(function (it, i) {
251
- lines.push('## ' + (i + 1) + '. ' + (it.instruction ? it.instruction.split('\n')[0] : '(sem instrução)'));
252
- lines.push('');
253
- if (it.instruction && it.instruction.indexOf('\n') !== -1) {
254
- lines.push(it.instruction);
255
- lines.push('');
256
- }
257
- lines.push('- **Rota:** `' + it.url + '`');
258
- if (it.source) {
259
- var loc = it.source.file + (it.source.line ? ':' + it.source.line : '');
260
- lines.push('- **Source:** `' + loc + '`' + (it.source.componentName ? ' (componente `' + it.source.componentName + '`)' : ''));
261
- } else if (it.components && it.components.length) {
262
- lines.push('- **Componentes (do mais interno):** ' + it.components.map(function (c) { return '`' + c + '`'; }).join(' → '));
263
- }
264
- lines.push('- **Elemento:** `<' + it.tag + '>` — seletor: `' + it.selector + '`');
265
- if (it.text) lines.push('- **Texto visível:** "' + it.text + '"');
266
- lines.push('- **Posição:** ' + it.rect.w + 'x' + it.rect.h + ' @ (' + it.rect.x + ',' + it.rect.y + '), viewport ' + it.viewport);
267
- var styleKeys = Object.keys(it.styles);
268
- if (styleKeys.length) {
269
- lines.push('- **Styles atuais:** ' + styleKeys.map(function (k) {
270
- return '`' + k + ': ' + it.styles[k] + '`';
271
- }).join(', '));
272
- }
273
- lines.push('');
274
- lines.push('```html');
275
- lines.push(it.html);
276
- lines.push('```');
277
- lines.push('');
278
- });
279
- return lines.join('\n');
280
- }
281
-
282
- function copyBacklog() {
283
- var md = toMarkdown();
284
- navigator.clipboard.writeText(md).then(function () {
285
- toast('Backlog copiado (' + items.length + ' itens). Cola no Claude Code.');
286
- }, function () {
287
- // fallback: mostra num prompt
288
- window.prompt('Copie manualmente:', md);
289
- });
290
- }
291
-
292
- /* ------------------------------------------------------------------ */
293
- /* Picker (highlight + clique) */
294
- /* ------------------------------------------------------------------ */
295
- var highlight = document.createElement('div');
296
- highlight.style.cssText = 'position:fixed;pointer-events:none;z-index:2147483645;' +
297
- 'border:2px solid #2563eb;background:rgba(37,99,235,.08);border-radius:2px;' +
298
- 'display:none;transition:all .05s linear;';
299
- var highlightLabel = document.createElement('div');
300
- highlightLabel.style.cssText = 'position:absolute;top:-22px;left:0;background:#2563eb;' +
301
- 'color:#fff;font:11px/1.4 ui-monospace,monospace;padding:1px 6px;border-radius:2px;white-space:nowrap;';
302
- highlight.appendChild(highlightLabel);
303
-
304
- function onKeyDown(e) {
305
- if (e.key === 'Alt' && !pickerActive) {
306
- pickerActive = true;
307
- document.body.style.cursor = 'crosshair';
308
- }
309
- }
310
- function onKeyUp(e) {
311
- if (e.key === 'Alt') deactivatePicker();
312
- }
313
- function deactivatePicker() {
314
- pickerActive = false;
315
- hoverEl = null;
316
- highlight.style.display = 'none';
317
- document.body.style.cursor = '';
318
- }
319
-
320
- function onMouseMove(e) {
321
- if (!pickerActive) return;
322
- var el = document.elementFromPoint(e.clientX, e.clientY);
323
- if (!el || panel.contains(el) || el === highlight || highlight.contains(el)) {
324
- highlight.style.display = 'none';
325
- hoverEl = null;
326
- return;
327
- }
328
- hoverEl = el;
329
- var r = el.getBoundingClientRect();
330
- highlight.style.display = 'block';
331
- highlight.style.left = r.left + 'px';
332
- highlight.style.top = r.top + 'px';
333
- highlight.style.width = r.width + 'px';
334
- highlight.style.height = r.height + 'px';
335
- var src = getSourceInfo(el);
336
- highlightLabel.textContent = '<' + el.tagName.toLowerCase() + '>' +
337
- (src ? ' ' + src.file.split('/').pop() + (src.line ? ':' + src.line : '') : '');
338
- }
339
-
340
- function onClick(e) {
341
- if (!pickerActive || !hoverEl) return;
342
- if (panel.contains(e.target)) return;
343
- e.preventDefault();
344
- e.stopPropagation();
345
- capture(hoverEl);
346
- }
347
-
348
- /* ------------------------------------------------------------------ */
349
- /* Painel */
350
- /* ------------------------------------------------------------------ */
351
- var panel = document.createElement('div');
352
- panel.style.cssText = 'position:fixed;bottom:16px;right:16px;width:340px;max-height:70vh;' +
353
- 'z-index:2147483646;background:#1c1c1e;color:#e5e5e7;border-radius:10px;' +
354
- 'box-shadow:0 8px 30px rgba(0,0,0,.4);font:13px/1.45 system-ui,sans-serif;' +
355
- 'display:flex;flex-direction:column;overflow:hidden;';
356
-
357
- var toastEl = document.createElement('div');
358
- toastEl.style.cssText = 'position:fixed;bottom:16px;left:50%;transform:translateX(-50%);' +
359
- 'z-index:2147483647;background:#15803d;color:#fff;padding:8px 16px;border-radius:8px;' +
360
- 'font:13px system-ui,sans-serif;display:none;box-shadow:0 4px 14px rgba(0,0,0,.3);';
361
- function toast(msg) {
362
- toastEl.textContent = msg;
363
- toastEl.style.display = 'block';
364
- clearTimeout(toastEl.__t);
365
- toastEl.__t = setTimeout(function () { toastEl.style.display = 'none'; }, 2500);
366
- }
367
-
368
- var collapsed = false;
369
-
370
- function renderPanel() {
371
- var srcCount = items.filter(function (it) { return it.source; }).length;
372
- panel.innerHTML =
373
- '<div data-fbc="header" style="display:flex;align-items:center;gap:8px;padding:10px 12px;' +
374
- 'background:#2c2c2e;cursor:pointer;user-select:none;">' +
375
- '<span style="font-weight:600;">Feedback</span>' +
376
- '<span style="background:#2563eb;border-radius:10px;padding:0 8px;font-size:11px;">' + items.length + '</span>' +
377
- (items.length ? '<span style="font-size:11px;color:#a8a8ae;">' + srcCount + ' com source</span>' : '') +
378
- '<span style="margin-left:auto;color:#a8a8ae;font-size:11px;">segure ALT + clique</span>' +
379
- '<span data-fbc="toggle" style="color:#a8a8ae;">' + (collapsed ? '▸' : '▾') + '</span>' +
380
- '</div>' +
381
- (collapsed ? '' :
382
- '<div style="overflow-y:auto;flex:1;padding:8px 12px;display:flex;flex-direction:column;gap:8px;">' +
383
- (items.length === 0
384
- ? '<div style="color:#a8a8ae;padding:12px 0;text-align:center;">Nenhum item. Segure ALT e clique num elemento da página.</div>'
385
- : items.map(renderItem).join('')) +
386
- '</div>' +
387
- '<div style="display:flex;gap:8px;padding:10px 12px;background:#2c2c2e;">' +
388
- '<button data-fbc="copy" style="flex:1;background:#2563eb;color:#fff;border:0;border-radius:6px;' +
389
- 'padding:8px;font:600 13px system-ui;cursor:pointer;"' + (items.length ? '' : ' disabled style="flex:1;background:#3a3a3c;color:#a8a8ae;border:0;border-radius:6px;padding:8px;font:600 13px system-ui;"') + '>Copiar backlog</button>' +
390
- '<button data-fbc="clear" style="background:#3a3a3c;color:#e5e5e7;border:0;border-radius:6px;' +
391
- 'padding:8px 12px;font:13px system-ui;cursor:pointer;">Limpar</button>' +
392
- '</div>');
393
- }
394
-
395
- function esc(s) {
396
- return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
397
- }
398
-
399
- function renderItem(it, i) {
400
- var loc = it.source
401
- ? it.source.file.split('/').slice(-2).join('/') + (it.source.line ? ':' + it.source.line : '')
402
- : (it.components && it.components[0] ? '<' + it.components[0] + '>' : it.selector.slice(0, 40));
403
- return '<div style="background:#2c2c2e;border-radius:8px;padding:8px;">' +
404
- '<div style="display:flex;align-items:baseline;gap:6px;margin-bottom:6px;">' +
405
- '<span style="color:#a8a8ae;font-size:11px;">#' + (i + 1) + '</span>' +
406
- '<span style="font-family:ui-monospace,monospace;font-size:11px;color:#64d2ff;overflow:hidden;' +
407
- 'text-overflow:ellipsis;white-space:nowrap;flex:1;" title="' + esc(it.selector) + '">' + esc(loc) + '</span>' +
408
- '<span data-fbc-del="' + it.id + '" style="cursor:pointer;color:#a8a8ae;padding:0 4px;" title="Remover">✕</span>' +
409
- '</div>' +
410
- '<textarea data-fbc-ta="' + it.id + '" placeholder="O que mudar aqui?" ' +
411
- 'style="width:100%;box-sizing:border-box;background:#1c1c1e;color:#e5e5e7;border:1px solid #3a3a3c;' +
412
- 'border-radius:6px;padding:6px;font:13px system-ui;resize:vertical;min-height:40px;">' +
413
- esc(it.instruction) + '</textarea>' +
414
- '</div>';
415
- }
416
-
417
- panel.addEventListener('click', function (e) {
418
- var t = e.target;
419
- if (t.getAttribute('data-fbc') === 'copy') { copyBacklog(); return; }
420
- if (t.getAttribute('data-fbc') === 'clear') {
421
- if (confirm('Limpar todos os ' + items.length + ' itens?')) {
422
- items = []; save(); renderPanel();
423
- }
424
- return;
425
- }
426
- if (t.hasAttribute('data-fbc-del')) {
427
- var id = t.getAttribute('data-fbc-del');
428
- items = items.filter(function (it) { return it.id !== id; });
429
- save(); renderPanel();
430
- return;
431
- }
432
- var header = t.closest('[data-fbc="header"]');
433
- if (header) { collapsed = !collapsed; renderPanel(); }
434
- });
435
-
436
- panel.addEventListener('input', function (e) {
437
- var t = e.target;
438
- if (t.hasAttribute('data-fbc-ta')) {
439
- var id = t.getAttribute('data-fbc-ta');
440
- var it = items.find(function (x) { return x.id === id; });
441
- if (it) { it.instruction = t.value; save(); }
442
- }
443
- });
444
-
445
- /* ------------------------------------------------------------------ */
446
- /* Boot */
447
- /* ------------------------------------------------------------------ */
448
- document.addEventListener('keydown', onKeyDown, true);
449
- document.addEventListener('keyup', onKeyUp, true);
450
- window.addEventListener('blur', deactivatePicker);
451
- document.addEventListener('mousemove', onMouseMove, true);
452
- document.addEventListener('click', onClick, true);
453
-
454
- document.body.appendChild(highlight);
455
- document.body.appendChild(panel);
456
- document.body.appendChild(toastEl);
457
- renderPanel();
458
-
459
- console.log('[feedback-collector] ativo. Segure ALT + clique pra capturar. ' +
460
- items.length + ' item(ns) restaurado(s) do localStorage.');
461
- })();
1
+ var FeedbackCollector=(function(exports){'use strict';var ue=["display","position","width","height","margin","padding","color","background-color","font-size","font-weight","line-height","border","border-radius","gap","flex-direction","justify-content","align-items","grid-template-columns","overflow","z-index","opacity","box-shadow","text-align"],pe=new Set(["none","normal","auto","rgba(0, 0, 0, 0)","0px","visible","static","start"]);function V(t){let n=t;for(let e in n)if(e.startsWith("__reactFiber$")||e.startsWith("__reactInternalInstance$"))return n[e];return null}function fe(t){let n=t;for(;n;){let e=n.type;if(typeof n.type=="function")return e?.displayName||e?.name||"";if(e&&typeof e=="object"&&e.displayName)return e.displayName;n=n._debugOwner||n.return;}return ""}function me(t){return t.match(/(?:^|\/)((?:src|app|pages|components|lib|features)\/.*)$/)?.[1]||t}function ge(t){let n=t.match(/^(.*):(\d+):(\d+)$/);if(n)return {file:n[1]||t,line:n[2]||"",column:n[3]||""};let e=t.match(/^(.*):(\d+)$/);return {file:e?.[1]||t,line:e?.[2]||"",column:""}}function I(t){let n=t,e=0;for(;n&&n!==document.body;){let a=n.getAttribute("data-inspector-relative-path"),s=n.getAttribute("data-source");if(a||s){let l=s?ge(s):null;return {file:a||l?.file||"",line:n.getAttribute("data-inspector-line")||l?.line||"",column:n.getAttribute("data-inspector-column")||l?.column||"",via:"data-attr",ownerHops:e}}n=n.parentElement,e+=1;}let r=V(t),u=0;for(;r&&u<20;){let a=r._debugSource;if(a?.fileName)return {file:me(a.fileName),line:a.lineNumber||"",column:a.columnNumber||"",via:"fiber",componentName:fe(r)};r=r._debugOwner||r.return||null,u+=1;}return null}function be(t){let n=V(t),e=[],r=0;for(;n&&e.length<3&&r<30;){let u=n.type,a=typeof n.type=="function"?u?.displayName||u?.name:u?.displayName;a&&!e.includes(a)&&e.push(a),n=n._debugOwner||n.return||null,r+=1;}return e}function W(t){return typeof CSS<"u"&&CSS.escape?CSS.escape(t):t.replace(/[^a-zA-Z0-9_-]/g,"\\$&")}function he(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function ye(t){if(t.id)return `#${W(t.id)}`;let n=[],e=t;for(;e&&e!==document.body&&n.length<5;){let r=e.tagName.toLowerCase(),u=false;for(let a of ["data-testid","data-test","aria-label","name"]){let s=e.getAttribute(a);if(s){r+=`[${a}="${he(s)}"]`,u=true;break}}if(!u&&e.classList.length){let a=Array.from(e.classList).filter(s=>!/^[a-z]*[_-][a-zA-Z0-9]{5,}$/.test(s)&&!/^\d/.test(s)&&s.length<30).slice(0,2);a.length&&(r+=`.${a.map(W).join(".")}`,u=true);}if(!u){let a=1,s=e.previousElementSibling;for(;s;)s.tagName===e.tagName&&(a+=1),s=s.previousElementSibling;a>1&&(r+=`:nth-of-type(${a})`);}n.unshift(r);try{if(document.querySelectorAll(n.join(" > ")).length===1)break}catch{}e=e.parentElement;}return n.join(" > ")}function xe(t){let n=getComputedStyle(t),e={};for(let r of ue){let u=n.getPropertyValue(r);u&&!pe.has(u)&&(e[r]=u);}return e}function ve(t,n){let e=t.outerHTML||"";return e.length<=n?e:`${e.slice(0,n)}\u2026 [truncado]`}function J(t){let n=t.getBoundingClientRect();return {id:`${Date.now()}_${Math.random().toString(36).slice(2,7)}`,url:location.pathname+location.search+location.hash,tag:t.tagName.toLowerCase(),selector:ye(t),source:I(t),components:be(t),text:(t.textContent||"").trim().slice(0,120),html:ve(t,600),styles:xe(t),rect:{x:Math.round(n.x),y:Math.round(n.y),w:Math.round(n.width),h:Math.round(n.height)},viewport:`${window.innerWidth}x${window.innerHeight}`,instruction:"",ts:new Date().toISOString()}}function g(t){return String(t).replace(/`/g,"\\`")}function A(t,n=new Date){let e=["# Backlog de feedback visual","",`Gerado em ${n.toLocaleString()} \u2014 ${t.length} itens.`,"Cada item indica o arquivo:linha quando dispon\xEDvel. Execute na ordem, agrupando edits do mesmo arquivo.",""];return t.forEach((r,u)=>{if(e.push(`## ${u+1}. ${r.instruction?r.instruction.split(`
2
+ `)[0]:"(sem instru\xE7\xE3o)"}`),e.push(""),r.instruction.includes(`
3
+ `)&&e.push(r.instruction,""),e.push(`- **Rota:** \`${g(r.url)}\``),r.source){let s=`${r.source.file}${r.source.line?`:${r.source.line}`:""}`,l=r.source.componentName?` (componente \`${g(r.source.componentName)}\`)`:"";e.push(`- **Source:** \`${g(s)}\`${l}`);}else r.components.length&&e.push(`- **Componentes (do mais interno):** ${r.components.map(s=>`\`${g(s)}\``).join(" \u2192 ")}`);e.push(`- **Elemento:** \`<${g(r.tag)}>\` \u2014 seletor: \`${g(r.selector)}\``),r.text&&e.push(`- **Texto vis\xEDvel:** "${r.text}"`),e.push(`- **Posi\xE7\xE3o:** ${r.rect.w}x${r.rect.h} @ (${r.rect.x},${r.rect.y}), viewport ${r.viewport}`);let a=Object.entries(r.styles);a.length&&e.push(`- **Styles atuais:** ${a.map(([s,l])=>`\`${g(s)}: ${g(l)}\``).join(", ")}`),e.push("","```html",r.html,"```","");}),e.join(`
4
+ `)}var Z="__fbc_state_v2",we="__fbc_items_v1";var Q=()=>({version:2,items:[]});function U(t){if(!t)return null;try{return JSON.parse(t)}catch{return null}}function Ee(t){return !!t&&typeof t=="object"&&Array.isArray(t.items)}function Se(t,n){let e=U(t);if(Ee(e)&&e.version===2)return {state:{version:2,items:e.items},migrated:false};let r=U(n);return Array.isArray(r)?{state:{version:2,items:r},migrated:true}:{state:Q(),migrated:false}}function O(t,n){try{return t.setItem(Z,JSON.stringify(n)),!0}catch{return false}}function ee(t){let n=null,e=null;try{n=t.getItem(Z),e=t.getItem(we);}catch{return Q()}let r=Se(n,e);return r.migrated&&O(t,r.state),r.state}var ke="position:fixed;pointer-events:none;z-index:2147483645;border:2px solid #2563eb;background:rgba(37,99,235,.08);border-radius:2px;display:none;transition:all .05s linear;",$e="position:absolute;top:-22px;left:0;background:#2563eb;color:#fff;font:11px/1.4 ui-monospace,monospace;padding:1px 6px;border-radius:2px;white-space:nowrap;",Le="position:fixed;bottom:16px;right:16px;width:340px;max-width:calc(100vw - 32px);max-height:70vh;z-index:2147483646;background:#1c1c1e;color:#e5e5e7;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.4);font:13px/1.45 system-ui,sans-serif;display:flex;flex-direction:column;overflow:hidden;transition:opacity .08s ease;",Ce="position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#15803d;color:#fff;padding:8px 16px;border-radius:8px;font:13px system-ui,sans-serif;display:none;box-shadow:0 4px 14px rgba(0,0,0,.3);",te="__fbc_panel_corner_v1",m=16,_e=4;function Te(t){return t==="top-left"||t==="top-right"||t==="bottom-left"||t==="bottom-right"}function E(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/"/g,"&quot;")}function Ie(t,n){let e=t.source?`${t.source.file.split("/").slice(-2).join("/")}${t.source.line?`:${t.source.line}`:""}`:t.components[0]?`<${t.components[0]}>`:t.selector.slice(0,40);return `<div style="background:#2c2c2e;border-radius:8px;padding:8px;">
5
+ <div style="display:flex;align-items:baseline;gap:6px;margin-bottom:6px;">
6
+ <span style="color:#a8a8ae;font-size:11px;">#${n+1}</span>
7
+ <span style="font-family:ui-monospace,monospace;font-size:11px;color:#64d2ff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;" title="${E(t.selector)}">${E(e)}</span>
8
+ <span data-fbc-del="${E(t.id)}" style="cursor:pointer;color:#a8a8ae;padding:0 4px;" title="Remover">\u2715</span>
9
+ </div>
10
+ <textarea data-fbc-ta="${E(t.id)}" placeholder="O que mudar aqui?" style="width:100%;box-sizing:border-box;background:#1c1c1e;color:#e5e5e7;border:1px solid #3a3a3c;border-radius:6px;padding:6px;font:13px system-ui;resize:vertical;min-height:40px;">${E(t.instruction)}</textarea>
11
+ </div>`}function ne(t={}){if(!document.body)throw new Error("feedback-collector: mount() requer document.body dispon\xEDvel.");let n=t.storage||window.localStorage,e=ee(n),r=false,u=null,a=false,s="bottom-right",l=null,S=false,_=true,v,y,k=new Set,re=document.body.style.cursor,p=document.createElement("div");p.dataset.fbcRoot="highlight",p.style.cssText=ke;let T=document.createElement("div");T.style.cssText=$e,p.append(T);let c=document.createElement("div");c.dataset.fbcRoot="panel",c.style.cssText=Le;let h=document.createElement("div");h.dataset.fbcRoot="toast",h.style.cssText=Ce;try{let o=n.getItem(te);Te(o)&&(s=o);}catch{}function P(o,i){let d=Math.max(m,window.innerWidth-c.offsetWidth-m),f=Math.max(m,window.innerHeight-c.offsetHeight-m);c.style.left=`${Math.min(Math.max(m,o),d)}px`,c.style.top=`${Math.min(Math.max(m,i),f)}px`,c.style.right="auto",c.style.bottom="auto";}function $(){let o=s.endsWith("right")?window.innerWidth-c.offsetWidth-m:m,i=s.startsWith("bottom")?window.innerHeight-c.offsetHeight-m:m;P(o,i);}function ie(){try{n.setItem(te,s);}catch{}}function M(o){c.style.opacity=o?"0":"1",c.style.pointerEvents=o?"none":"auto";}function L(){O(n,e);}function ae(o){h.textContent=o,h.style.display="block",v&&clearTimeout(v),v=setTimeout(()=>{h.style.display="none";},2500);}function w(){let o=e.items.filter(i=>i.source).length;c.innerHTML=`<div data-fbc="header" title="Arraste para mover \xB7 clique para recolher" style="display:flex;align-items:center;gap:8px;padding:10px 12px;background:#2c2c2e;cursor:grab;user-select:none;touch-action:none;">
12
+ <span style="font-weight:600;">Feedback</span>
13
+ <span style="background:#2563eb;border-radius:10px;padding:0 8px;font-size:11px;">${e.items.length}</span>
14
+ ${e.items.length?`<span style="font-size:11px;color:#a8a8ae;">${o} com source</span>`:""}
15
+ <span style="margin-left:auto;color:#a8a8ae;font-size:11px;">segure ALT + clique</span>
16
+ <span data-fbc="toggle" style="color:#a8a8ae;">${a?"\u25B8":"\u25BE"}</span>
17
+ </div>${a?"":`<div style="overflow-y:auto;flex:1;padding:8px 12px;display:flex;flex-direction:column;gap:8px;">
18
+ ${e.items.length?e.items.map(Ie).join(""):'<div style="color:#a8a8ae;padding:12px 0;text-align:center;">Nenhum item. Segure ALT e clique num elemento da p\xE1gina.</div>'}
19
+ </div>
20
+ <div style="display:flex;gap:8px;padding:10px 12px;background:#2c2c2e;">
21
+ <button data-fbc="copy" ${e.items.length?"":"disabled"} style="flex:1;background:${e.items.length?"#2563eb":"#3a3a3c"};color:${e.items.length?"#fff":"#a8a8ae"};border:0;border-radius:6px;padding:8px;font:600 13px system-ui;cursor:${e.items.length?"pointer":"default"};">Copiar backlog</button>
22
+ <button data-fbc="clear" style="background:#3a3a3c;color:#e5e5e7;border:0;border-radius:6px;padding:8px 12px;font:13px system-ui;cursor:pointer;">Limpar</button>
23
+ </div>`}`,l||$();}function C(){r=false,u=null,p.style.display="none",M(false),document.body.style.cursor=re;}function se(o){let i=o.style.outline;o.style.outline="3px solid #16a34a";let d=setTimeout(()=>{o.style.outline=i,k.delete(d);},400);k.add(d);}function le(o){let i=J(o);e.items.push(i),L(),w(),setTimeout(()=>c.querySelector(`[data-fbc-ta="${CSS.escape(i.id)}"]`)?.focus(),50),o instanceof HTMLElement&&se(o);}async function ce(){let o=A(e.items);try{await navigator.clipboard.writeText(o),ae(`Backlog copiado (${e.items.length} itens). Cola no seu agente de c\xF3digo.`);}catch{window.prompt("Copie manualmente:",o);}}function N(o){o.key==="Alt"&&!r&&(r=true,M(true),document.body.style.cursor="crosshair");}function F(o){o.key==="Alt"&&C();}function D(o){if(!r)return;let i=document.elementFromPoint(o.clientX,o.clientY);if(!i||c.contains(i)||p.contains(i)||i===p){p.style.display="none",u=null;return}u=i;let d=i.getBoundingClientRect();p.style.display="block",p.style.left=`${d.left}px`,p.style.top=`${d.top}px`,p.style.width=`${d.width}px`,p.style.height=`${d.height}px`;let f=I(i);T.textContent=`<${i.tagName.toLowerCase()}>${f?` ${f.file.split("/").pop()}${f.line?`:${f.line}`:""}`:""}`;}function H(o){!r||!u||!(o.target instanceof Node)||c.contains(o.target)||(o.preventDefault(),o.stopPropagation(),le(u));}function Y(o){if(!(o.target instanceof Element))return;let i=o.target,d=i.getAttribute("data-fbc");if(d==="copy"){ce();return}if(d==="clear"){e.items.length&&window.confirm(`Limpar todos os ${e.items.length} itens?`)&&(e.items=[],L(),w());return}let f=i.getAttribute("data-fbc-del");if(f){e.items=e.items.filter(X=>X.id!==f),L(),w();return}if(i.closest('[data-fbc="header"]')){if(S){S=false;return}a=!a,w();}}function z(o){if(o.button!==0||!(o.target instanceof Element))return;let i=o.target.closest('[data-fbc="header"]');if(!i)return;let d=c.getBoundingClientRect();l={pointerId:o.pointerId,startX:o.clientX,startY:o.clientY,panelX:d.left,panelY:d.top,moved:false,header:i},i.style.cursor="grabbing";}function j(o){if(!l||o.pointerId!==l.pointerId)return;let i=o.clientX-l.startX,d=o.clientY-l.startY;!l.moved&&Math.hypot(i,d)<_e||(l.moved=true,o.preventDefault(),P(l.panelX+i,l.panelY+d));}function B(o){if(!l||o.pointerId!==l.pointerId)return;let i=l;if(l=null,i.header.style.cursor="grab",!i.moved)return;let d=c.getBoundingClientRect(),f=d.left+d.width/2<window.innerWidth/2?"left":"right";s=`${d.top+d.height/2<window.innerHeight/2?"top":"bottom"}-${f}`,S=true,y&&clearTimeout(y),y=setTimeout(()=>{S=false,y=void 0;},0),$(),ie();}function G(o){!l||o.pointerId!==l.pointerId||(l.header.style.cursor="grab",l=null,$());}function q(){l||$();}function K(o){if(!(o.target instanceof HTMLTextAreaElement))return;let i=o.target.getAttribute("data-fbc-ta"),d=e.items.find(f=>f.id===i);d&&(d.instruction=o.target.value,L());}document.addEventListener("keydown",N,true),document.addEventListener("keyup",F,true),window.addEventListener("blur",C),document.addEventListener("mousemove",D,true),document.addEventListener("click",H,true),c.addEventListener("click",Y),c.addEventListener("input",K),c.addEventListener("pointerdown",z),window.addEventListener("pointermove",j,{passive:false}),window.addEventListener("pointerup",B),window.addEventListener("pointercancel",G),window.addEventListener("resize",q),document.body.append(p,c,h),w();let de={get mounted(){return _},get items(){return e.items},exportMarkdown(){return A(e.items)},destroy(){if(_){_=false,C(),document.removeEventListener("keydown",N,true),document.removeEventListener("keyup",F,true),window.removeEventListener("blur",C),document.removeEventListener("mousemove",D,true),document.removeEventListener("click",H,true),c.removeEventListener("click",Y),c.removeEventListener("input",K),c.removeEventListener("pointerdown",z),window.removeEventListener("pointermove",j),window.removeEventListener("pointerup",B),window.removeEventListener("pointercancel",G),window.removeEventListener("resize",q),v&&clearTimeout(v),y&&clearTimeout(y);for(let o of k)clearTimeout(o);k.clear(),p.remove(),c.remove(),h.remove();}}};return console.log(`[feedback-collector] ativo. Segure ALT + clique pra capturar. ${e.items.length} item(ns) restaurado(s) do localStorage.`),de}var b=null,x=null;function R(t={}){if(typeof window>"u"||typeof document>"u")throw new Error("feedback-collector: mount() s\xF3 pode ser chamado no navegador.");if(b?.mounted)return b;let n=window.__FEEDBACK_COLLECTOR__;if(n&&typeof n=="object"&&n.mounted)return b=n,n;if(n===true)throw new Error("feedback-collector: uma vers\xE3o anterior j\xE1 est\xE1 montada nesta p\xE1gina.");return b=ne(t),window.__FEEDBACK_COLLECTOR__=b,b}function Ae(){x&&(document.removeEventListener("DOMContentLoaded",x),x=null),b?.destroy(),b=null,typeof window<"u"&&delete window.__FEEDBACK_COLLECTOR__;}function oe(){if(!(typeof window>"u"||typeof document>"u")&&window.__FEEDBACK_COLLECTOR__!==true){if(document.readyState==="loading"){x=()=>{x=null,R();},document.addEventListener("DOMContentLoaded",x,{once:true});return}R();}}oe();exports.destroy=Ae;exports.mount=R;return exports;})({});
package/src/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * O package é um side effect puro (injeta o picker no DOM ao ser importado).
3
- * Não há API pública — este .d.ts existe só pra TypeScript aceitar
4
- * `import('feedback-collector')` sem reclamar de declaração ausente.
5
- */
6
- export {};
package/src/index.js DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * Entry ESM do package. O script em si é um IIFE auto-executável (side effect):
3
- * importar este módulo injeta o picker/painel na página. Não há API exportada.
4
- *
5
- * Guard de SSR: importar este módulo em Node/server (Next SSR, testes) é no-op
6
- * — o script só carrega quando há `window`. O feedback-collector.js fica sem
7
- * `export` de propósito, pra também funcionar cru via <script src> em páginas
8
- * sem bundler (ver "./script" no exports do package.json).
9
- */
10
- if (typeof window !== "undefined") {
11
- void import("./feedback-collector.js");
12
- }
13
-
14
- export {};