diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e443f..fb768b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.7.2 (Prepared August 3, 2026) + +- Added Add Content mode for anchoring proposed text, image placeholders, lists, and safe HTML/embed-frame placeholders to existing page elements. +- Added reversible before, after, inside-start, and inside-end previews with explicit filler, support intent, and acceptance checks. +- Stored Add Content results as sanitized `insert` mutations with original/proposed evidence while restoring the live page after Save, Cancel, navigation, or close. +- Updated History, AI Bundle formatting, package verification, local documentation, and Store-facing discovery copy for the new workflow. +- Kept the HTML/embed frame non-executable, all feedback local-first, the existing three permissions unchanged, and the separate v1.8 Feedback Session work out of this release. + ## 1.7.1 (Submitted August 3, 2026) - Added a plain-language Store title and short description around picking UI, annotating regions, and producing AI-ready prompts and visual change specs. diff --git a/QUICKSTART.md b/QUICKSTART.md index 5664589..75fff19 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -40,7 +40,18 @@ Optional for local PDFs: 5. Add the implementation note and optional acceptance checks, then save the spec. 6. The live page is restored; the original/proposed evidence and requested mutations remain in local History. -## 4. Compile an annotated region spec +## 4. Propose new page content + +1. Open an injectable webpage and choose `Add` in the extension popup. +2. Click the existing element that should anchor the new block. +3. Choose Text, Image placeholder, List, or HTML/embed frame and select its placement. +4. Add filler content or explain what the new block should communicate or support. +5. Add optional acceptance checks, then save the insert spec. +6. The temporary block is removed from the live page while its anchor, intent, structured insert mutation, and before/proposed evidence remain in History. + +The HTML/embed frame is a safe placeholder; it does not execute supplied HTML or load remote content. + +## 5. Compile an annotated region spec 1. Open the target page or PDF in the browser. 2. Open the extension popup and switch to `Region`. @@ -50,7 +61,7 @@ Optional for local PDFs: 6. Describe the requested change and optionally add one acceptance check per line. 7. Save the visual change spec. -## 5. Export saved feedback +## 6. Export saved feedback Open the extension popup and select `Open History & Export`. This extension-owned page works for captures from normal pages, PDFs, and other surfaces where the in-page panel is unavailable. From History, you can: @@ -62,7 +73,7 @@ Open the extension popup and select `Open History & Export`. This extension-owne AI Prompt is text-only. Use AI Bundle when the implementation handoff needs its numbered evidence images. -## 6. Give a local agent project-scoped feedback +## 7. Give a local agent project-scoped feedback 1. In History, choose `Download JSON for MCP`. 2. Configure the MCP companion with the absolute target project path and the folder containing that export. diff --git a/README.md b/README.md index 70ec2a8..4cb4337 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Dev Feedback Capture -Pick and annotate browser elements or regions, then copy AI-ready prompts or export local visual change specs for Codex, Claude Code, Cursor, or another developer. Dev Feedback Capture supports three evidence-rich workflows: +Pick and annotate browser elements or regions, propose new content blocks, then copy AI-ready prompts or export local visual change specs for Codex, Claude Code, Cursor, or another developer. Dev Feedback Capture supports four evidence-rich workflows: -> [Chrome Web Store v1.7 is public](https://chromewebstore.google.com/detail/dev-feedback-capture/hhdmfaaplpiokafjieefpgoppckijafc). This source checkout prepares a behavior-neutral v1.7.1 discovery refresh; the latest GitHub Release ZIP remains v1.2.0 as a manual fallback. +> [Chrome Web Store v1.7 is public](https://chromewebstore.google.com/detail/dev-feedback-capture/hhdmfaaplpiokafjieefpgoppckijafc). This source checkout prepares the v1.7.2 Add Content update; the latest GitHub Release ZIP remains v1.2.0 as a manual fallback. - `Element` mode injects a lightweight in-page UI so you can click DOM elements and save selectors, styles, and notes. - `Visual` mode lets you directly drag and resize one live DOM element, records original versus proposed intent, and restores the page after Save or Cancel. +- `Add` mode anchors a proposed text, image, list, or HTML/embed frame to an existing element and saves a structured insert request without persisting the preview into the page. - `Region` mode captures the visible viewport and compiles a crop, vector annotations, best-effort DOM anchors, requested change, and acceptance checks into one visual change spec. All feedback stays local in extension storage. Open History to review captures, use the legacy standalone exports, or download one AI Bundle with instructions, structured data, page context, and before/annotated images. @@ -15,6 +16,7 @@ All feedback stays local in extension storage. Open History to review captures, - Element capture with selector, text, styles, and note metadata - Pointer-first Visual Edit previews with drag-to-move, corner-handle resize, undo, redo, and reset - Original/proposed evidence plus explicit requested-mutation data; the live page is always restored +- Reversible Add Content previews for text, image placeholders, lists, and safe HTML/embed frame placeholders - Visual Change Spec editor with crop, arrow, rectangle, ellipse, pin, text, blur/redact, color, undo, and redo - DOM-linked vector annotations with selector fallbacks, roles, surrounding text, geometry, and parent-layout context when the source DOM is available - Optional acceptance checks plus browser, viewport, scroll, zoom, DPR, and source metadata @@ -83,12 +85,23 @@ The cropped image, viewport rectangle, and source context are saved into the sam Visual Edit does not change source files, persist mutations into the page, or replay saved edits automatically. It records intent so a developer or coding agent can implement the change in the correct source layer. +### Add Content Mode + +1. Open an injectable webpage and choose `Add` in the popup. +2. Click the existing element that should anchor the new content. +3. Choose Text, Image placeholder, List, or HTML/embed frame, then choose before, after, inside-start, or inside-end placement when the anchor supports children. +4. Add basic filler, an optional heading, and what the content should communicate or support. +5. Save the insert spec, or Cancel to remove the preview. Either path restores the original live page. + +The HTML/embed frame is intentionally a non-executable placeholder. Add Content never runs supplied markup, scripts, or remote embeds; it records the requested content and placement for implementation in source. + ## Data Model Stored feedback items use a discriminated shape: - `type: "element"` items include selector, element info, and position. - v1.5 element items may also include a sanitized `changeRequest`, original/proposed element state, and local evidence images. Older element items normalize as visual suggestions without invented mutations. +- Add Content items remain compatible `type: "element"` records and use a sanitized `insert` mutation containing an anchor, placement, content type, filler, support intent, and optional acceptance checks. - `type: "region"` items include one evidence crop, vector annotations, DOM anchors when available, acceptance criteria, and page context. Annotated PNGs are rendered locally when the AI Bundle is built. Older element-only captures are still loaded and normalized automatically. @@ -132,6 +145,7 @@ The extension does not use static host permissions, always-on content scripts, t - `background.js`: runtime injection and region-capture session orchestration - `content.js`: in-page panel and element capture - `visual-edit.js`: dependency-free reversible mutation engine used by Visual Edit Mode +- `content-proposal.js`: dependency-free safe placeholder builder used by Add Content Mode - `mcp/`: project-scoped stdio MCP companion and filesystem sidecar store - `capture.html` / `capture.js`: screenshot region selection editor - `popup.html` / `popup.js`: mode switch, current-tab actions, and History entry point @@ -151,7 +165,7 @@ The extension does not use static host permissions, always-on content scripts, t 1. Confirm `package.json` and `manifest.json` versions match. 2. Run `npm test`, `npm run check`, and `npm run package`. `npm test` covers both extension and MCP contracts. -3. Complete the package, listing, and manual unpacked-extension gates in `docs/manual-release-checklist.md`, then create and push the matching `v1.7.1` tag. +3. Complete the package, listing, and manual unpacked-extension gates in `docs/manual-release-checklist.md`, then create and push the matching version tag when publishing a GitHub Release. 4. The release workflow builds `dist/dev-feedback-capture-v.zip` and publishes it as a GitHub Release asset. See [CHANGELOG.md](CHANGELOG.md) for release notes. @@ -160,6 +174,7 @@ See [CHANGELOG.md](CHANGELOG.md) for release notes. - Element mode depends on DOM/script injection and is not intended for browser-internal surfaces. - Visual Edit is intentionally limited to direct move and resize of one normal-page DOM target. Text, visibility, order, style, matching, alignment, cross-origin frames, arbitrary CSS, responsive breakpoints, animation, and reparenting are outside this release. +- Add Content previews basic content structure and placement only. It does not generate production markup, upload image assets, load remote embeds, or execute user-supplied HTML. - Region mode stores one crop plus vector metadata in local storage; very large capture histories will still increase storage usage. - Blur/redact masks are applied to the saved crop before the transient viewport screenshot is discarded, so AI Bundle “before” evidence does not restore redacted pixels. - DOM annotation anchors are best-effort and are unavailable for protected browser pages, PDFs without an accessible DOM, cross-origin frames, and pages that move after capture. diff --git a/ai-bundle.js b/ai-bundle.js index 2e79be0..be932f1 100644 --- a/ai-bundle.js +++ b/ai-bundle.js @@ -369,6 +369,12 @@ || target.tag || target.text || formatRect(target.rect); + if (mutation?.action === 'insert') { + const content = mutation.parameters?.content || {}; + const placement = mutation.parameters?.placement || 'after'; + const details = [content.title, content.body, content.support].filter(Boolean).join(' | '); + return `insert ${content.type || 'content'} ${placement} ${identity || 'unknown target'}${details ? `; ${details}` : ''}`; + } const parameters = mutation?.parameters && Object.keys(mutation.parameters).length ? `; parameters=${JSON.stringify(mutation.parameters)}` : ''; diff --git a/background.js b/background.js index 788e4b6..eb45263 100644 --- a/background.js +++ b/background.js @@ -127,7 +127,7 @@ try { await chrome.scripting.executeScript({ target: { tabId }, - files: ['shared.js', 'visual-edit.js', 'content.js'] + files: ['shared.js', 'visual-edit.js', 'content-proposal.js', 'content.js'] }); return { ok: true }; } catch (error) { diff --git a/content-proposal.js b/content-proposal.js new file mode 100644 index 0000000..5cb55ce --- /dev/null +++ b/content-proposal.js @@ -0,0 +1,170 @@ +/** + * Dev Feedback Capture - Safe proposed-content previews + * + * User input is rendered with DOM text nodes only. The HTML frame option is a + * visual placeholder and never executes markup, scripts, or remote content. + */ + +(function(root, factory) { + const api = factory(); + root.DevFeedbackContentProposal = api; + if (typeof module === 'object' && module.exports) { + module.exports = api; + } +})(typeof globalThis !== 'undefined' ? globalThis : this, function() { + 'use strict'; + + const BLOCK_TYPES = Object.freeze(['text', 'image', 'list', 'frame']); + const PLACEMENTS = Object.freeze(['before', 'after', 'inside-start', 'inside-end']); + const CONTAINER_TAGS = new Set([ + 'article', 'aside', 'blockquote', 'body', 'dd', 'div', 'dl', 'dt', + 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'li', + 'main', 'nav', 'ol', 'section', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul' + ]); + + function sanitizeDefinition(input) { + const raw = input && typeof input === 'object' ? input : {}; + const type = BLOCK_TYPES.includes(raw.type) ? raw.type : 'text'; + const placement = PLACEMENTS.includes(raw.placement) ? raw.placement : 'after'; + const body = cleanText(raw.body, 2000); + const items = (Array.isArray(raw.items) ? raw.items : body.split(/\r?\n/)) + .map((item) => cleanText(item, 280)) + .filter(Boolean) + .slice(0, 12); + + return { + type, + placement, + title: cleanText(raw.title, 160), + body, + items, + altText: cleanText(raw.altText, 500), + support: cleanText(raw.support, 1000) + }; + } + + function createPreviewElement(documentRef, input) { + if (!documentRef?.createElement) { + throw new TypeError('createPreviewElement requires a document.'); + } + + const definition = sanitizeDefinition(input); + const preview = documentRef.createElement('section'); + preview.className = `dev-feedback-content-preview dev-feedback-content-preview-${definition.type}`; + preview.dataset.devFeedbackContentPreview = 'true'; + preview.setAttribute('aria-label', `Proposed ${getTypeLabel(definition.type)} content`); + + const proposalLabel = documentRef.createElement('div'); + proposalLabel.className = 'dev-feedback-content-preview-label'; + proposalLabel.textContent = `Proposed ${getTypeLabel(definition.type)}`; + preview.appendChild(proposalLabel); + + if (definition.title) { + const title = documentRef.createElement('strong'); + title.className = 'dev-feedback-content-preview-title'; + title.textContent = definition.title; + preview.appendChild(title); + } + + if (definition.type === 'image') { + const imagePlaceholder = documentRef.createElement('div'); + imagePlaceholder.className = 'dev-feedback-content-preview-image'; + imagePlaceholder.textContent = 'Image placeholder'; + preview.appendChild(imagePlaceholder); + + const caption = documentRef.createElement('span'); + caption.className = 'dev-feedback-content-preview-copy'; + caption.textContent = definition.altText || definition.body || 'Describe the image, subject, or asset needed.'; + preview.appendChild(caption); + } else if (definition.type === 'list') { + const list = documentRef.createElement('ul'); + const items = definition.items.length ? definition.items : ['First item', 'Second item', 'Third item']; + items.forEach((item) => { + const listItem = documentRef.createElement('li'); + listItem.textContent = item; + list.appendChild(listItem); + }); + preview.appendChild(list); + } else if (definition.type === 'frame') { + const frame = documentRef.createElement('div'); + frame.className = 'dev-feedback-content-preview-frame'; + frame.textContent = definition.body || 'HTML / embed frame placeholder'; + preview.appendChild(frame); + } else { + const paragraph = documentRef.createElement('p'); + paragraph.className = 'dev-feedback-content-preview-copy'; + paragraph.textContent = definition.body || 'Add supporting copy here.'; + preview.appendChild(paragraph); + } + + if (definition.support) { + const support = documentRef.createElement('small'); + support.className = 'dev-feedback-content-preview-support'; + support.textContent = `Supports: ${definition.support}`; + preview.appendChild(support); + } + + return preview; + } + + function insertPreview(anchor, preview, placement) { + if (!anchor?.parentNode || !preview) { + throw new TypeError('insertPreview requires a connected anchor and preview.'); + } + + const normalizedPlacement = PLACEMENTS.includes(placement) ? placement : 'after'; + if (normalizedPlacement.startsWith('inside-') && !canPlaceInside(anchor)) { + throw new Error(`Cannot place content inside a ${String(anchor.tagName || 'void element').toLowerCase()}.`); + } + + if (normalizedPlacement === 'before') { + anchor.parentNode.insertBefore(preview, anchor); + } else if (normalizedPlacement === 'inside-start') { + anchor.insertBefore(preview, anchor.firstChild || null); + } else if (normalizedPlacement === 'inside-end') { + anchor.appendChild(preview); + } else { + anchor.parentNode.insertBefore(preview, anchor.nextSibling || null); + } + + return preview; + } + + function removePreview(preview) { + if (preview?.parentNode) { + preview.parentNode.removeChild(preview); + } + } + + function canPlaceInside(anchor) { + const tag = String(anchor?.tagName || '').toLowerCase(); + return CONTAINER_TAGS.has(tag); + } + + function getTypeLabel(type) { + return ({ + text: 'text', + image: 'image', + list: 'list', + frame: 'HTML frame' + })[type] || 'text'; + } + + function cleanText(value, maxLength) { + return String(value ?? '') + .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '') + .trim() + .slice(0, maxLength); + } + + return { + BLOCK_TYPES, + PLACEMENTS, + canPlaceInside, + createPreviewElement, + getTypeLabel, + insertPreview, + removePreview, + sanitizeDefinition + }; +}); diff --git a/content.js b/content.js index e0f0b36..a466655 100644 --- a/content.js +++ b/content.js @@ -43,7 +43,9 @@ OFF: 'off', ELEMENT: 'element', VISUAL_PICK: 'visual-pick', - VISUAL_EDIT: 'visual-edit' + VISUAL_EDIT: 'visual-edit', + CONTENT_PICK: 'content-pick', + CONTENT_EDIT: 'content-edit' }); let feedbackMode = false; @@ -67,6 +69,12 @@ let visualLastRect = null; let visualBusy = false; let visualGesture = null; + let contentAnchor = null; + let contentAnchorInfo = null; + let contentBeforeViewport = null; + let contentInitialContext = null; + let contentPreview = null; + let contentBusy = false; function init() { if (!document.body) { @@ -94,6 +102,7 @@ 0 V* + C*
@@ -106,6 +115,7 @@ +
@@ -143,6 +153,58 @@ +
+
+
+
Add Content
+
Pick a placement anchor
+
+ +
+

Click an existing page element to choose where the new content belongs.

+ +
+ Proposed block +
+ + + + +
+ + + + + + + + + + + + + + + +
+ + +
+
+
`; document.body.appendChild(feedbackPanel); @@ -155,8 +217,10 @@ feedbackPanel.querySelector('#dev-feedback-copy-ai').addEventListener('click', copyAsAiPrompt); feedbackPanel.querySelector('#dev-feedback-capture-region').addEventListener('click', startRegionCapture); feedbackPanel.querySelector('#dev-feedback-start-visual').addEventListener('click', startVisualEditMode); + feedbackPanel.querySelector('#dev-feedback-start-content').addEventListener('click', startAddContentMode); feedbackPanel.querySelector('#dev-feedback-clear').addEventListener('click', clearAllFeedback); bindVisualInspector(); + bindContentInspector(); } function togglePanelCollapsed() { @@ -192,6 +256,26 @@ feedbackPanel.querySelector('#dev-feedback-cancel-visual').addEventListener('click', cancelVisualEdit); } + function bindContentInspector() { + const ids = [ + 'dev-feedback-content-type', + 'dev-feedback-content-placement', + 'dev-feedback-content-heading', + 'dev-feedback-content-body', + 'dev-feedback-content-alt', + 'dev-feedback-content-support' + ]; + ids.forEach((id) => { + const field = feedbackPanel.querySelector(`#${id}`); + field.addEventListener(field.tagName === 'SELECT' ? 'change' : 'input', updateContentPreview); + }); + feedbackPanel.querySelector('#dev-feedback-content-type').addEventListener('change', syncContentFieldLabels); + feedbackPanel.querySelector('#dev-feedback-content-pick-again').addEventListener('click', pickAnotherContentAnchor); + feedbackPanel.querySelector('#dev-feedback-save-content').addEventListener('click', saveContentSpec); + feedbackPanel.querySelector('#dev-feedback-cancel-content').addEventListener('click', cancelContentProposal); + syncContentFieldLabels(); + } + function clampPanelToViewport() { const rect = feedbackPanel.getBoundingClientRect(); feedbackPanel.style.left = `${clamp(rect.left, 8, Math.max(8, window.innerWidth - rect.width - 8))}px`; @@ -317,6 +401,12 @@ return; } + if (event.key === 'Escape' && contentPreview && !contentBusy) { + event.preventDefault(); + cancelContentProposal(); + return; + } + const editable = /^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement?.tagName || ''); if (!editable && visualSession && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') { event.preventDefault(); @@ -364,7 +454,7 @@ if (interactionMode === nextMode) { return true; } - if (visualBusy && visualSession) { + if ((visualBusy && visualSession) || contentBusy) { showNotification('Wait for the current visual evidence capture to finish.', 'error'); return false; } @@ -380,10 +470,22 @@ if (visualSession && !String(nextMode).startsWith('visual-')) { restoreVisualSession(); } + if ( + contentPreview + && !String(nextMode).startsWith('content-') + && !options.discardContent + ) { + showNotification('Save or Cancel the content proposal before changing modes.', 'error'); + return false; + } + if (contentPreview && !String(nextMode).startsWith('content-')) { + restoreContentProposal(); + } interactionMode = nextMode; feedbackMode = nextMode === INTERACTION_MODES.ELEMENT; feedbackPanel.classList.toggle('visible', nextMode !== INTERACTION_MODES.OFF); feedbackPanel.classList.toggle('visual-active', String(nextMode).startsWith('visual-')); + feedbackPanel.classList.toggle('content-active', String(nextMode).startsWith('content-')); closeCaptureModal(); if (nextMode === INTERACTION_MODES.OFF) { disableElementHighlighting(); @@ -391,6 +493,7 @@ enableElementHighlighting(); } renderVisualInspector(); + renderContentInspector(); scheduleDecorationRefresh(); if (nextMode !== INTERACTION_MODES.OFF) { window.requestAnimationFrame(() => { @@ -405,7 +508,7 @@ } function stopInteractionMode() { - setInteractionMode(INTERACTION_MODES.OFF, { discardVisual: true }); + setInteractionMode(INTERACTION_MODES.OFF, { discardVisual: true, discardContent: true }); } function startVisualEditMode() { @@ -428,6 +531,28 @@ return { ok: true }; } + function startAddContentMode() { + if (!globalThis.DevFeedbackContentProposal?.createPreviewElement) { + showNotification('Add Content did not load. Refresh the page and try again.', 'error'); + return { ok: false, reason: 'Add Content engine unavailable.' }; + } + if (visualBusy || contentBusy) { + return { ok: false, reason: 'Wait for the current evidence capture to finish.' }; + } + if (visualSession?.getState().dirty) { + return { ok: false, reason: 'Save or Cancel the visual preview before adding content.' }; + } + if (contentPreview) { + return { ok: false, reason: 'Save or Cancel the current content proposal before starting another.' }; + } + if (!setInteractionMode(INTERACTION_MODES.CONTENT_PICK)) { + return { ok: false, reason: 'Finish the current preview before adding content.' }; + } + setPanelCollapsed(true); + showNotification('Add Content: pick the element that should anchor the new block.'); + return { ok: true }; + } + function enableElementHighlighting() { disableElementHighlighting(); document.addEventListener('mouseover', handleMouseOver); @@ -446,7 +571,7 @@ } function handleMouseOver(event) { - if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT || isOurElement(event.target)) { + if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT || interactionMode === INTERACTION_MODES.CONTENT_EDIT || isOurElement(event.target)) { return; } @@ -454,7 +579,7 @@ } function handleMouseOut(event) { - if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT || isOurElement(event.target)) { + if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT || interactionMode === INTERACTION_MODES.CONTENT_EDIT || isOurElement(event.target)) { return; } @@ -462,7 +587,7 @@ } function handleElementClick(event) { - if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT) { + if (interactionMode === INTERACTION_MODES.OFF || interactionMode === INTERACTION_MODES.VISUAL_EDIT || interactionMode === INTERACTION_MODES.CONTENT_EDIT) { return; } @@ -477,6 +602,8 @@ captureElement(target); } else if (interactionMode === INTERACTION_MODES.VISUAL_PICK) { selectVisualTarget(target); + } else if (interactionMode === INTERACTION_MODES.CONTENT_PICK) { + selectContentAnchor(target); } } @@ -487,7 +614,8 @@ (element.id && element.id.startsWith('dev-feedback')) || (typeof element.closest === 'function' && element.closest(SELECTORS.panel)) || (typeof element.closest === 'function' && element.closest(SELECTORS.modal)) || - (typeof element.closest === 'function' && element.closest(SELECTORS.markerLayer)) + (typeof element.closest === 'function' && element.closest(SELECTORS.markerLayer)) || + (typeof element.closest === 'function' && element.closest('[data-dev-feedback-content-preview]')) ) ); } @@ -545,8 +673,308 @@ } } + async function selectContentAnchor(element) { + if (contentBusy || isOurElement(element)) { + return; + } + if (element === document.body || element === document.documentElement || element.contains(feedbackPanel)) { + showNotification('Pick a specific page element to anchor the new content.', 'error'); + return; + } + + contentBusy = true; + element.classList.remove('dev-feedback-highlight'); + try { + const anchorInfo = buildElementSnapshot(element); + const beforeViewport = await captureVisualViewport(); + if (!beforeViewport) { + throw new Error('Unable to capture before evidence. Keep this tab visible and try again.'); + } + restoreContentProposal(); + contentBusy = true; + contentAnchor = element; + contentAnchor.classList.add('dev-feedback-selected'); + contentAnchorInfo = anchorInfo; + contentBeforeViewport = beforeViewport; + contentInitialContext = buildPageContext(); + clearContentFields(); + interactionMode = INTERACTION_MODES.CONTENT_EDIT; + feedbackPanel.classList.add('content-active'); + placePanelOppositeTarget(anchorInfo.rect); + setPanelCollapsed(false); + updateContentPreview(); + renderContentInspector('Preview this block, add its purpose, then save the insert spec.'); + scheduleDecorationRefresh(); + } catch (error) { + restoreContentProposal(); + interactionMode = INTERACTION_MODES.CONTENT_PICK; + showNotification(error.message || 'Unable to start a content proposal for this element.', 'error'); + } finally { + contentBusy = false; + renderContentInspector(); + } + } + + function clearContentFields() { + feedbackPanel.querySelector('#dev-feedback-content-type').value = 'text'; + feedbackPanel.querySelector('#dev-feedback-content-placement').value = 'after'; + feedbackPanel.querySelector('#dev-feedback-content-heading').value = ''; + feedbackPanel.querySelector('#dev-feedback-content-body').value = ''; + feedbackPanel.querySelector('#dev-feedback-content-alt').value = ''; + feedbackPanel.querySelector('#dev-feedback-content-support').value = ''; + feedbackPanel.querySelector('#dev-feedback-content-acceptance').value = ''; + syncContentFieldLabels(); + } + + function readContentDefinition() { + const body = feedbackPanel.querySelector('#dev-feedback-content-body').value; + return globalThis.DevFeedbackContentProposal.sanitizeDefinition({ + type: feedbackPanel.querySelector('#dev-feedback-content-type').value, + placement: feedbackPanel.querySelector('#dev-feedback-content-placement').value, + title: feedbackPanel.querySelector('#dev-feedback-content-heading').value, + body, + items: body.split(/\r?\n/), + altText: feedbackPanel.querySelector('#dev-feedback-content-alt').value, + support: feedbackPanel.querySelector('#dev-feedback-content-support').value + }); + } + + function syncContentFieldLabels() { + const type = feedbackPanel.querySelector('#dev-feedback-content-type').value; + const bodyLabel = feedbackPanel.querySelector('#dev-feedback-content-body-label'); + const bodyField = feedbackPanel.querySelector('#dev-feedback-content-body'); + const altGroup = feedbackPanel.querySelector('#dev-feedback-content-alt-group'); + altGroup.hidden = type !== 'image'; + + if (type === 'list') { + bodyLabel.textContent = 'List items (one per line)'; + bodyField.placeholder = 'First item\nSecond item\nThird item'; + } else if (type === 'frame') { + bodyLabel.textContent = 'Frame placeholder details'; + bodyField.placeholder = 'Describe the widget, embed, HTML fragment, or external content area...'; + } else if (type === 'image') { + bodyLabel.textContent = 'Caption or filler copy'; + bodyField.placeholder = 'Optional caption or supporting text...'; + } else { + bodyLabel.textContent = 'Filler content'; + bodyField.placeholder = 'Draft the copy or basic filler...'; + } + } + + function updateContentPreview() { + syncContentFieldLabels(); + globalThis.DevFeedbackContentProposal.removePreview(contentPreview); + contentPreview = null; + if (!contentAnchor?.isConnected) { + renderContentInspector(); + return; + } + + const definition = readContentDefinition(); + try { + const preview = globalThis.DevFeedbackContentProposal.createPreviewElement(document, definition); + globalThis.DevFeedbackContentProposal.insertPreview(contentAnchor, preview, definition.placement); + contentPreview = preview; + renderContentInspector(); + scheduleDecorationRefresh(); + } catch (error) { + renderContentInspector(error.message || 'That placement is not supported for this anchor.'); + } + } + + function renderContentInspector(statusMessage) { + if (!feedbackPanel) { + return; + } + const inspector = feedbackPanel.querySelector('.dev-feedback-content-inspector'); + if (!inspector) { + return; + } + const contentActive = String(interactionMode).startsWith('content-'); + const canPlaceInside = globalThis.DevFeedbackContentProposal?.canPlaceInside(contentAnchor); + feedbackPanel.querySelectorAll('#dev-feedback-content-placement option').forEach((option) => { + option.disabled = option.value.startsWith('inside-') && !canPlaceInside; + }); + feedbackPanel.classList.toggle('content-active', contentActive); + feedbackPanel.classList.toggle('content-dirty', Boolean(contentPreview)); + feedbackPanel.querySelector('#dev-feedback-content-title').textContent = contentAnchor + ? `${contentAnchorInfo?.tag || 'element'} · ${contentAnchorInfo?.selector || ''}` + : 'Pick a placement anchor'; + const status = feedbackPanel.querySelector('#dev-feedback-content-status'); + if (statusMessage) { + status.textContent = statusMessage; + } else if (!contentAnchor) { + status.textContent = 'Click an existing page element to choose where the new content belongs.'; + } else if (contentPreview) { + status.textContent = 'Reversible content preview is active.'; + } + inspector.querySelector('fieldset').disabled = !contentAnchor || contentBusy; + feedbackPanel.querySelector('#dev-feedback-save-content').disabled = !contentPreview || contentBusy; + feedbackPanel.querySelector('#dev-feedback-content-pick-again').disabled = !contentAnchor; + const closeButton = feedbackPanel.querySelector('#dev-feedback-panel-close'); + if (contentActive) { + closeButton.title = 'Stop Add Content and restore page'; + closeButton.setAttribute('aria-label', closeButton.title); + } + } + + function pickAnotherContentAnchor() { + if (contentBusy) { + showNotification('Wait for the current evidence capture to finish.', 'error'); + return; + } + restoreContentProposal(); + interactionMode = INTERACTION_MODES.CONTENT_PICK; + feedbackPanel.classList.add('content-active'); + setPanelCollapsed(true); + renderContentInspector('Click another page element to choose a new anchor.'); + scheduleDecorationRefresh(); + } + + function cancelContentProposal() { + if (contentBusy) { + showNotification('Wait for the current evidence capture to finish.', 'error'); + return; + } + restoreContentProposal(); + interactionMode = INTERACTION_MODES.CONTENT_PICK; + feedbackPanel.classList.add('content-active'); + setPanelCollapsed(true); + renderContentInspector('Preview removed. Pick another placement anchor.'); + showNotification('Content proposal cancelled and page restored.'); + scheduleDecorationRefresh(); + } + + function restoreContentProposal() { + globalThis.DevFeedbackContentProposal?.removePreview(contentPreview); + contentAnchor?.classList.remove('dev-feedback-selected'); + contentPreview = null; + contentAnchor = null; + contentAnchorInfo = null; + contentBeforeViewport = null; + contentInitialContext = null; + contentBusy = false; + feedbackPanel?.classList.remove('content-dirty'); + } + + async function saveContentSpec() { + if (!contentAnchor || !contentPreview || contentBusy) { + return; + } + const definition = readContentDefinition(); + const hasUsefulInput = Boolean( + definition.title || definition.body || definition.altText || definition.items.length || definition.support + ); + if (!hasUsefulInput) { + const supportField = feedbackPanel.querySelector('#dev-feedback-content-support'); + showNotification('Add filler content or describe what this block should support.', 'error'); + supportField.focus(); + return; + } + if (!contentAnchor.isConnected || !contentPreview.isConnected || !sameContentViewport()) { + showNotification('The page moved or changed. Pick the placement anchor again.', 'error'); + return; + } + + contentBusy = true; + renderContentInspector('Capturing proposed evidence...'); + try { + const proposedViewport = await captureVisualViewport(); + if (!proposedViewport?.dataUrl || !contentBeforeViewport?.dataUrl) { + throw new Error('Could not capture both before and proposed evidence. Nothing was saved.'); + } + const evidenceRect = buildEvidenceRect(contentAnchorInfo.rect, getViewportRect(contentPreview)); + const [beforeDataUrl, proposedDataUrl] = await Promise.all([ + cropViewportImage(contentBeforeViewport.dataUrl, evidenceRect), + cropViewportImage(proposedViewport.dataUrl, evidenceRect) + ]); + if (!beforeDataUrl || !proposedDataUrl) { + throw new Error('Content proposal evidence could not be cropped. Nothing was saved.'); + } + + const summary = definition.support + || definition.title + || `Add a ${globalThis.DevFeedbackContentProposal.getTypeLabel(definition.type)} block`; + const acceptance = feedbackPanel.querySelector('#dev-feedback-content-acceptance').value + .split(/\r?\n/) + .map((criterion) => criterion.trim()) + .filter(Boolean) + .slice(0, 12); + const item = { + specVersion: 2, + id: buildFeedbackId(), + type: CAPTURE_TYPE_ELEMENT, + captureType: CAPTURE_TYPE_ELEMENT, + selector: contentAnchorInfo.selector, + pageUrl: window.location.href, + pageTitle: document.title, + elementInfo: toStoredElementInfo(contentAnchorInfo), + proposedElementInfo: toStoredElementInfo(buildElementSnapshot(contentPreview)), + position: contentAnchorInfo.position, + pageContext: contentInitialContext, + changeRequest: { + kind: 'requested-mutation', + summary: summary.slice(0, MAX_NOTE_LENGTH), + requestedMutations: [{ + id: `insert-${Date.now()}`, + action: 'insert', + target: toMutationTargetSnapshot(contentAnchorInfo), + parameters: { + placement: definition.placement, + content: definition + } + }] + }, + evidence: { + before: { mimeType: 'image/png', dataUrl: beforeDataUrl, source: { kind: 'captured' } }, + proposed: { mimeType: 'image/png', dataUrl: proposedDataUrl, source: { kind: 'rendered-preview' } } + }, + acceptance, + note: summary.slice(0, MAX_NOTE_LENGTH), + timestamp: new Date().toISOString() + }; + + restoreContentProposal(); + contentBusy = true; + const nextItems = await runFeedbackMutation('add-feedback-item', { item }); + if (!nextItems) { + throw new Error('Unable to store the content insertion spec.'); + } + feedbackItems = sanitizeFeedbackItems(nextItems, window.location.href, document.title); + interactionMode = INTERACTION_MODES.CONTENT_PICK; + feedbackPanel.classList.add('content-active'); + setPanelCollapsed(true); + updateFeedbackPanel(); + renderContentInspector('Saved locally. Pick another placement anchor.'); + scheduleDecorationRefresh(); + showNotification('Content insert spec saved and page restored.'); + } catch (error) { + restoreContentProposal(); + interactionMode = INTERACTION_MODES.CONTENT_PICK; + renderContentInspector('Save failed after restoring the page. Pick the anchor again.'); + showNotification(error.message || 'Unable to save the content insert spec.', 'error'); + } finally { + contentBusy = false; + renderContentInspector(); + } + } + + function sameContentViewport() { + const viewport = contentInitialContext?.viewport; + return contentInitialContext?.url === window.location.href + && viewport + && Math.abs(viewport.scrollX - window.scrollX) <= 2 + && Math.abs(viewport.scrollY - window.scrollY) <= 2 + && Math.abs(viewport.width - window.innerWidth) <= 2 + && Math.abs(viewport.height - window.innerHeight) <= 2 + && Math.abs(viewport.devicePixelRatio - window.devicePixelRatio) <= 0.02; + } + function buildVisualTargetSnapshot(element) { - const snapshot = buildElementSnapshot(element); + return toMutationTargetSnapshot(buildElementSnapshot(element)); + } + + function toMutationTargetSnapshot(snapshot) { return { selectors: snapshot.selectors, tag: snapshot.tag, @@ -659,28 +1087,35 @@ } function restoreVisualOnPageExit() { - if (!visualSession) { + if (!visualSession && !contentPreview) { return; } restoreVisualSession(); + restoreContentProposal(); interactionMode = INTERACTION_MODES.OFF; feedbackMode = false; disableElementHighlighting(); if (feedbackPanel) { - feedbackPanel.classList.remove('visible', 'visual-active'); + feedbackPanel.classList.remove('visible', 'visual-active', 'content-active'); } } function restoreVisualAfterSameDocumentNavigation() { - if (!visualSession) { + if (!visualSession && !contentPreview) { return; } + const wasContent = Boolean(contentPreview); restoreVisualSession(); - interactionMode = INTERACTION_MODES.VISUAL_PICK; + restoreContentProposal(); + interactionMode = wasContent ? INTERACTION_MODES.CONTENT_PICK : INTERACTION_MODES.VISUAL_PICK; feedbackMode = false; - feedbackPanel.classList.add('visible', 'visual-active'); + feedbackPanel.classList.add('visible', wasContent ? 'content-active' : 'visual-active'); setPanelCollapsed(true); - renderVisualInspector('Page navigation restored the preview. Pick an element again.'); + if (wasContent) { + renderContentInspector('Page navigation restored the preview. Pick an anchor again.'); + } else { + renderVisualInspector('Page navigation restored the preview. Pick an element again.'); + } scheduleDecorationRefresh(); } @@ -1231,7 +1666,7 @@ } async function startRegionCapture() { - if (visualBusy) { + if (visualBusy || contentBusy) { const reason = 'Wait for the current visual evidence capture to finish.'; showNotification(reason, 'error'); return { ok: false, reason }; @@ -1241,8 +1676,15 @@ showNotification(reason, 'error'); return { ok: false, reason }; } + if (contentPreview) { + const reason = 'Save or Cancel the content proposal before starting Region capture.'; + showNotification(reason, 'error'); + return { ok: false, reason }; + } if (visualSession || String(interactionMode).startsWith('visual-')) { setInteractionMode(INTERACTION_MODES.OFF, { discardVisual: true }); + } else if (String(interactionMode).startsWith('content-')) { + setInteractionMode(INTERACTION_MODES.OFF, { discardContent: true }); } const visibility = [feedbackPanel, captureModal, markerLayer].map((element) => element?.style.visibility || ''); [feedbackPanel, captureModal, markerLayer].forEach((element) => { @@ -1367,7 +1809,10 @@ function populateElementItem(itemElement, item) { const selector = document.createElement('div'); selector.className = 'dev-feedback-item-selector'; - selector.textContent = item.selector; + const insertMutation = item.changeRequest?.requestedMutations?.find((mutation) => mutation.action === 'insert'); + selector.textContent = insertMutation + ? `Add ${insertMutation.parameters.content?.type || 'content'} ${insertMutation.parameters.placement || 'after'} ${item.selector}` + : item.selector; itemElement.appendChild(selector); } @@ -1922,6 +2367,11 @@ return; } + if (request.action === 'start-add-content') { + sendResponse(startAddContentMode()); + return; + } + if (request.action === 'refresh-feedback') { loadFeedbackItems(); sendResponse({ feedbackMode, itemCount: feedbackItems.length }); diff --git a/docs/add-content-mode-plan.html b/docs/add-content-mode-plan.html new file mode 100644 index 0000000..13639a1 --- /dev/null +++ b/docs/add-content-mode-plan.html @@ -0,0 +1,195 @@ + + + + + + Add Content Mode - Product and UX Contract + + + +
+
+
Add Content Mode
+

Propose what the page needs, not only what should change.

+

Choose an existing element as the implementation anchor, preview a safe content block, and save an explicit insert request with placement, filler, purpose, acceptance criteria, and before/proposed evidence.

+
+ +
+
+ +
+
Workflow software
+

Turn browser feedback into a buildable change spec.

+

Capture context, describe intent, and hand a developer the evidence needed to act.

+ Start a capture +
+
+ Existing anchor: feature summary + This element remains untouched after the proposal is saved. +
+
+
+ + +
+ +
+

Saved requirement

insert mutation with DOM anchor, placement, typed content, support intent, and acceptance checks.

+

Reversible preview

The temporary block appears only for evidence capture and is removed after Save, Cancel, navigation, or mode exit.

+

Safe frame

HTML/embed content is represented as a placeholder. User markup, scripts, and remote frames are never executed.

+
+
+ + + diff --git a/docs/chrome-web-store-submission-draft.md b/docs/chrome-web-store-submission-draft.md index 626737d..f2183ae 100644 --- a/docs/chrome-web-store-submission-draft.md +++ b/docs/chrome-web-store-submission-draft.md @@ -1,32 +1,32 @@ # Chrome Web Store Submission Record -Status: v1.7.0 is public in the Chrome Web Store. The exact v1.7.1 discovery package was uploaded and submitted for normal review on August 3, 2026 with no API warnings; it will publish automatically after approval. Google distributes the Browser Code icon inside the public v1.7 CRX, but the Store listing still rendered the retired purple-flag asset before this submission. +Status: v1.7.0 is public in the Chrome Web Store. The exact v1.7.1 discovery package was submitted for normal review on August 3, 2026, and v1.7.2 is now the prepared Add Content replacement candidate. Replacing the active submission requires an explicit cancel, upload, publish, and API readback sequence. Google distributes the Browser Code icon inside the public v1.7 CRX, but the Store listing still rendered the retired purple-flag asset before these submissions. -The next package is a behavior-neutral v1.7.1 discovery refresh. It changes the manifest title, short description, package version, and icon encoding; it does not add permissions or extension behavior. Keep the separate v1.8 Feedback Session work out of this package. The existing Store approval does not prove the deferred PDF/export, Visual Edit, local MCP, or compact-panel runtime gates in `docs/manual-release-checklist.md`. +The next package is v1.7.2. It adds the user-triggered Add Content workflow plus the v1.7.1 discovery metadata and refreshed icon encoding without adding permissions, remote code, or network services. Keep the separate v1.8 Feedback Session work out of this package. The existing Store approval and automated checks do not prove the deferred browser runtime gates in `docs/manual-release-checklist.md`. ## Ready inputs - Publisher: `FlyingChanges Code` -- Candidate extension package: `dist/dev-feedback-capture-v1.7.1.zip` +- Candidate extension package: `dist/dev-feedback-capture-v1.7.2.zip` - Store icon: `icon128.png` (`128x128` PNG) - Public product page: `https://monroes.tech/software/dev-feedback-capture/` - Privacy policy: `https://monroes.tech/software/dev-feedback-capture/privacy/` - Support email: `monroe@flyingchangesfarm.net` - Single purpose: Capture structured, local visual change specifications from the current browser-visible page or PDF and export them for implementation. -## Discovery metadata for v1.7.1 +## Discovery metadata for v1.7.2 Manifest title, 44 of 45 characters: > Dev Feedback Capture: AI UI Review & Prompts -Short description, 120 of 132 characters: +Short description, 126 of 132 characters: -> Pick and annotate elements or regions. Copy AI-ready prompts and visual change specs for Codex, Claude Code, and Cursor. +> Pick elements, propose new page content, and annotate regions. Export AI-ready prompts and visual change specs for developers. Store overview: -> Pick a webpage element, annotate a region or PDF, or preview a visual layout change. Dev Feedback Capture turns your feedback into AI-ready prompts and local visual change specs for Codex, Claude Code, Cursor, or another developer. +> Pick a webpage element, propose new page content, annotate a region or PDF, or preview a visual layout change. Dev Feedback Capture turns your feedback into AI-ready prompts and local visual change specs for Codex, Claude Code, Cursor, or another developer. > > **Capture the evidence** > @@ -34,6 +34,7 @@ Store overview: > - Capture a visible region from a webpage or browser-rendered PDF. > - Add arrows, shapes, numbered pins, text, and opaque redact marks. > - Preview a direct move or resize while preserving original-versus-proposed evidence and restoring the live page afterward. +> - Propose text, image placeholders, lists, or safe HTML/embed frames before, after, or inside an existing page element. > - Add acceptance checks so the requested outcome is explicit. > > **Hand it off clearly** @@ -61,11 +62,11 @@ These values were entered in the Chrome Web Store Privacy practices tab and are `activeTab` -> Used only after the user starts a capture. It grants temporary access to the current tab so the extension can identify the selected page, capture the visible viewport, or start Element, Visual, or Region mode. +> Used only after the user starts a capture. It grants temporary access to the current tab so the extension can identify the selected page, capture the visible viewport, or start Element, Visual, Add Content, or Region mode. `scripting` -> Used only after a user action to inject the capture overlay or visual-edit interface into the current tab. The extension does not use always-on content scripts. +> Used only after a user action to inject the capture overlay, visual-edit interface, or reversible Add Content preview into the current tab. The extension does not use always-on content scripts. `storage` @@ -168,6 +169,7 @@ Target: 75-90 seconds, `1920x1080`, recorded in a clean Edge window on the publi - Current public package: v1.7.0. - Submitted package: `dist/dev-feedback-capture-v1.7.1.zip`; SHA-256 `440b41157386ae75e42a15e0f7f12c9bcbe645966e57330ec97f55fdb61a1dc3`. - API readback on August 3, 2026: v1.7.1 is `PENDING_REVIEW` at a 100% target with no warning or takedown flag. +- Replacement candidate: `dist/dev-feedback-capture-v1.7.2.zip`; final SHA-256 and API state must be recorded after the release package is built and submitted. - Category: Developer Tools; language: English (United States). - The single purpose, permission justifications, no-remote-code declaration, data-use categories, privacy-policy URL, homepage, and support URL were saved before submission. - One valid 1280x800 screenshot remains on the Store listing. Monroe explicitly waived replacement screenshots for this submission; the fresh five-shot set remains optional follow-up work. @@ -175,4 +177,4 @@ Target: 75-90 seconds, `1920x1080`, recorded in a clean Edge window on the publi - Confirm the dashboard's publication timing choice before an approved item goes live; that setting was not independently recorded here. - Monroe explicitly authorized the v1.7.1 upload without a new unpacked-browser smoke or replacement screenshots. Those checks remain open and the release must not be described as runtime-verified. - The feature video, 440x280 promotional tile, and backup publisher Admin remain optional follow-up work. -- Keep payments, authentication, network sync, additional permissions, and v1.8 Feedback Sessions out of v1.7.1. +- Keep payments, authentication, network sync, additional permissions, and v1.8 Feedback Sessions out of v1.7.2. diff --git a/docs/manual-release-checklist.md b/docs/manual-release-checklist.md index 64ff13e..6862dae 100644 --- a/docs/manual-release-checklist.md +++ b/docs/manual-release-checklist.md @@ -2,10 +2,21 @@ Automated checks are necessary but do not replace the unpacked-extension gate. -Store status on August 3, 2026: v1.7.0 is public in the Chrome Web Store. The distributed CRX contains the Browser Code icon, while the Store listing still renders the retired purple-flag artwork. Store approval is not proof that the deferred checks below passed. Keep them open and do not call v1.7 or v1.7.1 runtime-verified until the relevant evidence is recorded. +Store status on August 3, 2026: v1.7.0 is public in the Chrome Web Store, v1.7.1 is pending review, and v1.7.2 is the prepared Add Content replacement. The distributed CRX contains the Browser Code icon, while the Store listing still renders the retired purple-flag artwork. Store approval is not proof that the deferred checks below passed. Keep them open and do not call these releases runtime-verified until the relevant evidence is recorded. Submission record: Monroe explicitly waived the new unpacked-browser smoke and replacement screenshot gates. The exact v1.7.1 ZIP with SHA-256 `440b41157386ae75e42a15e0f7f12c9bcbe645966e57330ec97f55fdb61a1dc3` was uploaded successfully and submitted for normal review on August 3, 2026. The API readback showed `PENDING_REVIEW` at a 100% target with no warning or takedown flag. The unchecked runtime and screenshot items below remain open proof gaps, not blockers to that authorized submission. +## v1.7.2 Add Content package and submission + +Before submitting v1.7.2: + +- Confirm the package adds only the reviewed Add Content feature plus the v1.7.1 discovery refresh; do not include v1.8 Feedback Session files or new permissions. +- Confirm Text, Image placeholder, List, and HTML/embed frame definitions are rendered with DOM text nodes and that the frame remains a non-executable placeholder. +- Confirm saved content requests normalize to a structured `insert` mutation and remain compatible with existing History and AI Bundle exports. +- Run `npm test`, `npm run check`, `npm run package`, `npm run verify:package`, JavaScript syntax checks, and `git diff --check` from the isolated release worktree. +- Because Monroe waived the new unpacked-browser smoke and replacement screenshots, record those as open proof gaps rather than claiming runtime verification. +- Read the live Store API state, cancel the active v1.7.1 submission, upload the exact verified v1.7.2 ZIP, submit with review enabled and warnings blocking, then read the status back. + ## v1.7.1 discovery package and listing check Before tagging or uploading v1.7.1: diff --git a/history.js b/history.js index 788e7b7..eeb5db0 100644 --- a/history.js +++ b/history.js @@ -540,6 +540,12 @@ || target.tag || target.text || formatMutationRect(target.rect); + if (mutation?.action === 'insert') { + const content = mutation.parameters?.content || {}; + const placement = mutation.parameters?.placement || 'after'; + const details = [content.title, content.body, content.support].filter(Boolean).join(' | '); + return `insert ${content.type || 'content'} ${placement} ${identity || 'unknown target'}${details ? `; ${details}` : ''}`; + } const parameters = mutation?.parameters && Object.keys(mutation.parameters).length ? `; parameters=${JSON.stringify(mutation.parameters)}` : ''; diff --git a/manifest.json b/manifest.json index 38dcb30..e233603 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Dev Feedback Capture: AI UI Review & Prompts", - "version": "1.7.1", - "description": "Pick and annotate elements or regions. Copy AI-ready prompts and visual change specs for Codex, Claude Code, and Cursor.", + "version": "1.7.2", + "description": "Pick elements, propose new page content, and annotate regions. Export AI-ready prompts and visual change specs for developers.", "permissions": [ "storage", "activeTab", diff --git a/package-lock.json b/package-lock.json index bc3f7bb..c14a7cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dev-feedback-capture", - "version": "1.7.1", + "version": "1.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dev-feedback-capture", - "version": "1.7.1", + "version": "1.7.2", "license": "UNLICENSED", "dependencies": { "@modelcontextprotocol/sdk": "1.29.0", diff --git a/package.json b/package.json index 0a7a790..b3721ce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "dev-feedback-capture", "private": true, - "version": "1.7.1", + "version": "1.7.2", "license": "UNLICENSED", "description": "Pick and annotate browser UI for AI-ready prompts and local visual change specs", "scripts": { diff --git a/popup.html b/popup.html index 6f292f0..594ac09 100644 --- a/popup.html +++ b/popup.html @@ -125,7 +125,7 @@ .mode-switch { display: grid; - grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; padding: 4px; border: 1px solid var(--line); @@ -390,6 +390,13 @@

Dev Feedback Capture

Drag and resize one selected element. +