← All prompts
My Context

Build Your Own WYSIWYG HTML Editor

Point it at a folder of HTML files, click the thing, change it, hit save — a zero-dependency editor that writes every change straight back to the source file. One prompt in, working tool out.

Prompt text
Build My WYSIWYG HTML Editor Intent - Chat round-trips are the wrong tool for visual tweaks. - I want to edit the rendered page directly and have saves write back to the source file. - Then a human and an agent can edit the same file from opposite ends. What to do - Confirm you have terminal and filesystem access. If you don't, stop and tell me to reopen this prompt in a coding agent (Claude Code or similar). - Ask me which folder of .html files to point at. If I don't have one, generate two or three realistic sample pages. - Build the complete editor in one pass. No checkpoints, no explanations, no tutorials — just deliver the working tool. - Generate a welcome document: an HTML page that lists every editor feature with a one-line "try it here" beside each — an editable headline, a box whose color I change via right-click, a card I Cmd+click and drag-resize, a CSS variable that rethemes the page. Give it real embedded CSS (multiple rules and custom properties) so the inspector has something to show. Put it in the target folder. - git init, commit the editor, commit the welcome doc, start the server, and open the welcome doc in my browser. Architecture (non-negotiable) - One self-contained file, zero dependencies. `bun editor.js <dir>` → http://localhost:4734. Bind to 127.0.0.1 only. If 4734 is taken, use the next free port and say so. Bun preferred; fall back to Node's built-in http module if Bun is missing. - The document being edited lives in an iframe served same-origin. All editor chrome (toolbar, tabs, save button, status) lives in the parent page, so the document's own CSS can never touch it. - Auto-detect editable text: an element is editable iff it has at least one direct child text node with non-whitespace content. No special markup required in my files. - Save serializes a clean clone: deep-clone the document, then strip from the clone every injected style tag, every editor-UI element, every contenteditable/spellcheck attribute you added, and any extension-injected attributes that weren't in the source. Never mutate the live document. - Fragment files (source has no doctype and no html tag) are saved back as fragments — head innerHTML plus body innerHTML, no invented skeleton. Full documents get doctype plus documentElement, with newlines between the doctype, html, head, and body boundaries so a save doesn't join lines the source kept separate. Parser normalization the browser forces (tbody insertion, entity forms) is tolerable; the serializer must add no noise of its own. - CSS edits never modify the original stylesheet. Every rule edit is persisted into a single `<style id="doc-overrides">` appended to head. - Right-click opens a style inspector. Cmd/Ctrl+click selects an element with drag-resize handles. One uniform undo/redo covers every mutation type. Server - Routes: `/` redirects to the first doc; `/editor?doc=NAME` serves the editor shell; `/doc/NAME` serves the raw file and any sibling assets (css, images, fonts) with correct MIME types; `POST /save?doc=NAME` writes to disk. - One tab per .html file in the folder, skipping dotfiles. - Reject any path containing `..`. Never read or write outside the target directory. Editing behavior - Enter inserts a line break, never a new block element. - Excluded from editing: anything inside an svg, the editor's own UI, and script/style/iframe/canvas/video/audio elements. - Affordances via an injected style tag carrying an editor marker attribute: dashed outline plus faint tint on hover, solid outline on focus. - Status indicator: loaded / unsaved changes / saving… / saved. beforeunload guard while dirty. - First save of each file backs up the original to `.backup/<name>.<timestamp>.html` — once per file per session, not on every save. - Cmd+S / Ctrl+S bound in both the parent page and the iframe document. Inspector behavior - Breadcrumbs up the ancestor chain; clicking a crumb retargets the inspector. - Applied rules read from document.styleSheets inside the iframe, cascade-winner first, grouped by selector, every declaration in an editable input, each group labeled with its source sheet. Wrap selector matching in try/catch — real stylesheets contain selectors that make .matches() throw. - Show each rule's declarations as authored, parsed from its cssText — never by enumerating the style object, which explodes shorthands into longhands and renders var()-backed shorthands as rows of empty inputs. - Inherited typography (color, font-family, font-size, font-weight, font-style, line-height, letter-spacing, text-align, text-transform), each labeled with the ancestor it came from. - CSS custom properties, editable document-wide. Color pickers on any property whose name contains "color", seeded from the computed value converted to hex. - Delete element (undoable), and a collapsed "more properties" list of common element-level props. - Value edits default to rewriting the rule, so every element matching the selector updates; an "el" button applies the change to just this element as an inline style instead. Keep that distinction visible in the UI. - Re-setting a property in the overrides block replaces the existing line instead of appending a duplicate. If the block is empty at save time, remove it so untouched files stay byte-clean. - Right-clicking inside the inspector itself keeps the native context menu. Select and resize - Cmd/Ctrl+click draws an overlay box around the element with a tag.class label and three handles: right (width), bottom (height), corner (both). Dragging writes inline width/height onto the element. - The overlay is pointer-events: none with pointer-events: auto only on the handles. Reposition it on scroll, resize, and input. Use pointer events, not mouse events. Escape deselects and closes any open menu. - Mark the overlay with the editor-UI attribute so the clean clone strips it on save. Undo/redo - A snapshot is body.innerHTML plus the overrides block text. Push on any change, debounced ~350ms so a typing burst is one undo step. Undo and redo restore both halves together. - After any restore, re-run the editable-detection pass — innerHTML restore silently drops listeners and attributes. - Bind Cmd+Z / Cmd+Shift+Z in the capture phase in both parent and iframe, and preventDefault — except when the event target is inside the inspector's own inputs, where native undo is correct. - Never take a snapshot while restoring. Take a baseline snapshot on load so the first undo has somewhere to go. Test it yourself before handing it to me - Do not tell me it's done until you have verified it works — "the code looks right" doesn't count. - With the server running, verify every route: each tab's editor page loads, each raw doc serves, a POST to /save round-trips, the first save creates the backup, and a traversal path is rejected. - If you have browser automation available, drive the real UI: click text and type, make a rule edit through the inspector, Cmd+click and drag-resize, delete an element, run the undo chain back and redo forward, save, then diff the file on disk and confirm it's clean and fragments stayed fragments. If you have no browser, say so, and walk me through a two-minute manual check of those same steps instead — don't skip verification silently. - Anything you found broken, fix and re-verify before handoff. - Hand off with the server running. The last thing you print is the URL and the folder being served — and if you ever stop the server for your own testing, restart it before you finish. Done means - Text, CSS rules, CSS variables, element size, and deletion are all editable by clicking, and all uniformly undoable. - Save writes clean HTML back to the source file: no editor attributes anywhere in it, fragments stay fragments, and a diff shows only the edits I made. - The welcome document is open in my browser and everything it tells me to try actually works.