Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
17 changes: 14 additions & 3 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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:

Expand All @@ -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.
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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<version>.zip` and publishes it as a GitHub Release asset.

See [CHANGELOG.md](CHANGELOG.md) for release notes.
Expand All @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions ai-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`
: '';
Expand Down
2 changes: 1 addition & 1 deletion background.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
170 changes: 170 additions & 0 deletions content-proposal.js
Original file line number Diff line number Diff line change
@@ -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
};
});
Loading
Loading