Repository navigation
feat: seamless inline editing of generated prose - #8
Conversation
Click any main prose field on the Edit tab and type in place — no input boxes, no edit/save buttons. Edits commit on blur and persist automatically via the run's existing autosave; Preview reflects them with no extra wiring. - New EditableText primitive: uncontrolled contentEditable, commit-on-blur, Enter commits single-line / inserts newline multiline, paste-as-plain-text, reverts to the previous value if cleared. Subtle hover tint + focus ring, no chrome. - New immutable setIn deep-set + EDIT_FIELD reducer action update the active platform's content by path (supports array indices, e.g. social tweets). - Editable fields: PH tagline/description/first-comment, HN title/post/first-comment, Reddit title/body, AppSumo headline/pitch, each Social thread tweet. Lists, hints, Launch ops, and the Launch Plan stay read-only (per agreed scope). Tests: setIn, EDIT_FIELD reducer, EditableText behavior, and a section edit-path test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR adds inline field editing to the post-kit wizard. It introduces a ChangesInline Field Editing for Post-Kit
Sequence Diagram(s)sequenceDiagram
participant User
participant EditableText
participant PlatformSections
participant PostKit
participant RunPage
participant wizardReducer
User->>EditableText: blur after editing text
EditableText->>EditableText: normalize & validate (trim, revert if blank)
EditableText->>PlatformSections: onCommit(newValue)
PlatformSections->>PostKit: onEdit(fieldPath, newValue)
PostKit->>RunPage: onEditField(platform, fieldPath, newValue)
RunPage->>wizardReducer: dispatch EDIT_FIELD { platform, path, value }
wizardReducer->>wizardReducer: setIn(generation.platforms, [platform, ...path], value)
wizardReducer-->>RunPage: updated state with new generation.platforms
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/common/EditableText/EditableText.tsx`:
- Around line 41-45: In the handlePaste function, replace the deprecated
document.execCommand('insertText') call with a modern approach that prioritizes
the Selection and Range APIs. First attempt to retrieve the current selection
using window.getSelection(), create a range, delete the current content, and
insert the new text using the Range API. If the selection is unavailable or the
modern approach fails, fall back to document.execCommand('insertText') as a
compatibility measure to ensure reliable paste handling across all browsers.
In `@lib/wizard/set-in.ts`:
- Around line 6-9: The setIn function currently accepts any numeric key without
validation, allowing negative numbers and non-integer values like -1 or 1.5 to
be set as array properties instead of proper indices. Add a guard condition
after the typeof check for 'number' in the setIn function to validate that the
key is a non-negative integer (e.g., using Number.isInteger and checking that
key >= 0). If the key fails validation, throw an error or handle it
appropriately to prevent invalid array shapes from being created silently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: aff72f65-5491-4568-8c7a-458954f848eb
📒 Files selected for processing (23)
app/runs/[id]/page.tsxcomponents/common/EditableText/EditableText.test.tsxcomponents/common/EditableText/EditableText.tsxcomponents/common/EditableText/EditableText.types.tscomponents/common/EditableText/index.tscomponents/post-kit/PostKit/PostKit.test.tsxcomponents/post-kit/PostKit/PostKit.tsxcomponents/post-kit/PostKit/PostKit.types.tscomponents/post-kit/platforms/AppSumoSections/AppSumoSections.tsxcomponents/post-kit/platforms/HackerNewsSections/HackerNewsSections.tsxcomponents/post-kit/platforms/ProductHuntSections/ProductHuntSections.tsxcomponents/post-kit/platforms/RedditSections/RedditSections.tsxcomponents/post-kit/platforms/SocialSections/SocialSections.test.tsxcomponents/post-kit/platforms/SocialSections/SocialSections.tsxcomponents/post-kit/platforms/platform-sections.types.tscomponents/post-kit/sections/CopySection/CopySection.tsxcomponents/post-kit/sections/CopySection/CopySection.types.tscomponents/post-kit/sections/TopicsCommentSection/TopicsCommentSection.tsxcomponents/post-kit/sections/TopicsCommentSection/TopicsCommentSection.types.tslib/wizard/set-in.test.tslib/wizard/set-in.tslib/wizard/wizard-reducer.test.tslib/wizard/wizard-reducer.ts
| function handlePaste(e: ClipboardEvent) { | ||
| e.preventDefault() | ||
| const text = e.clipboardData.getData('text/plain') | ||
| document.execCommand('insertText', false, text) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Current browser support and deprecation status for Document.execCommand('insertText') in contentEditable elements (Chrome, Firefox, Safari, iOS Safari), and recommended modern replacement API.
💡 Result:
As of June 2026, document.execCommand('insertText') is officially deprecated and non-standard [1][2][3]. It is no longer on a standards track, and its use is discouraged by browser vendors and documentation platforms [1][4]. Browser Support Status Despite being deprecated, the method remains widely implemented across major browsers for compatibility reasons [1][5]. - Chrome, Edge, and Opera: Full support [6]. - Firefox: Supported, though with some known historical caveats regarding nested calls and input manipulation in specific older versions [6][7][2]. - Safari (including iOS Safari): Supported [6]. Because it is not standardized, browser vendors have no obligation to maintain it, and it may cease to function at any time [1][4]. Developers should treat it as an unstable feature [1]. Modern Replacement and Recommendations There is no single direct, drop-in replacement API for document.execCommand('insertText') that provides the same behavior, particularly regarding the preservation of the browser's native undo/redo history [1][4][5]. - For general text editing: Developers are encouraged to use the Selection and Range APIs to manually manipulate the DOM or, for complex editors, leverage established open-source rich-text editing libraries (e.g., CKEditor, TinyMCE, ProseMirror, Slate) which abstract away the inconsistencies of native browser editing [4][8][5]. - For advanced custom editing: The EditContext API is the modern, standardized alternative designed to give developers granular control over text input, composition, and selection, without relying on the DOM-heavy contentEditable attribute [9][10][11]. However, EditContext is primarily for building custom editing experiences rather than a direct replacement for simple text insertion [10]. - For undo/redo preservation: If you must maintain native undo/redo behavior, many developers are currently forced to continue using execCommand as a legacy fallback due to the lack of a standardized modern API that satisfies this specific requirement [12][13][5]. Some implementers build custom undo/redo stacks using MutationObserver or application-level state management, though this significantly increases implementation complexity [4][5].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
- 2: https://bcd-training.deno.dev/feature?id=api.Document.execCommand
- 3: mdn/browser-compat-data@2d3890a
- 4: https://stackoverflow.com/questions/61520602/is-there-a-replacement-for-document-execcommand-or-is-it-safe-to-use-document
- 5: https://medium.com/@python-javascript-php-html-css/how-to-update-content-in-a-contenteditable-element-while-maintaining-the-undo-stack-e17c48e36466
- 6: https://caniuse.com/document-execcommand
- 7: https://caniuse.com/?search=Document+API%3A+execCommand
- 8: https://www.codestudy.net/blog/execcommand-is-now-obsolete-what-s-the-alternative/
- 9: https://developer.mozilla.org/en-US/docs/Web/API/EditContext_API/Guide
- 10: https://developer.chrome.com/blog/introducing-editcontext-api
- 11: https://w3c.github.io/edit-context/
- 12:
document.execCommandhas valid use cases without viable alternatives mdn/content#40245 - 13: https://stackoverflow.com/questions/78015279/alternative-for-document-execcommandinserttext-false-text
🏁 Script executed:
# First, let's examine the EditableText component to understand the context
fd -t f "EditableText.tsx" --type fileRepository: AhmedFr/launch-kit
Length of output: 110
🏁 Script executed:
# Once found, examine the file around lines 41-45
cat -n components/common/EditableText/EditableText.tsx | head -60 | tail -30Repository: AhmedFr/launch-kit
Length of output: 1172
🏁 Script executed:
# Check if there are any tests for this component
fd -t f "EditableText" --type file | grep -i testRepository: AhmedFr/launch-kit
Length of output: 115
🏁 Script executed:
# Check the test file to understand the expected behavior
cat -n components/common/EditableText/EditableText.test.tsxRepository: AhmedFr/launch-kit
Length of output: 1813
🏁 Script executed:
# Get more context about the component - check the beginning and full structure
head -40 components/common/EditableText/EditableText.tsx | cat -nRepository: AhmedFr/launch-kit
Length of output: 1766
🏁 Script executed:
# Check if there's any type information about the ref
rg -A 5 -B 5 "useRef" components/common/EditableText/EditableText.tsxRepository: AhmedFr/launch-kit
Length of output: 857
Replace deprecated paste insertion API with a fallback-safe approach.
Line 44 uses document.execCommand('insertText', ...) after preventing default paste at Line 42. This API is officially deprecated and non-standard as of June 2026; if the browser blocks or removes support for it, pasted text will be lost. Switch to the Selection and Range APIs with execCommand as a fallback to ensure reliable paste handling.
Suggested patch
function handlePaste(e: ClipboardEvent) {
e.preventDefault()
const text = e.clipboardData.getData('text/plain')
- document.execCommand('insertText', false, text)
+ const selection = window.getSelection()
+ if (selection && selection.rangeCount > 0) {
+ const range = selection.getRangeAt(0)
+ range.deleteContents()
+ range.insertNode(document.createTextNode(text))
+ range.collapse(false)
+ selection.removeAllRanges()
+ selection.addRange(range)
+ return
+ }
+ // Fallback for engines that still support it
+ document.execCommand('insertText', false, text)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function handlePaste(e: ClipboardEvent) { | |
| e.preventDefault() | |
| const text = e.clipboardData.getData('text/plain') | |
| document.execCommand('insertText', false, text) | |
| } | |
| function handlePaste(e: ClipboardEvent) { | |
| e.preventDefault() | |
| const text = e.clipboardData.getData('text/plain') | |
| const selection = window.getSelection() | |
| if (selection && selection.rangeCount > 0) { | |
| const range = selection.getRangeAt(0) | |
| range.deleteContents() | |
| range.insertNode(document.createTextNode(text)) | |
| range.collapse(false) | |
| selection.removeAllRanges() | |
| selection.addRange(range) | |
| return | |
| } | |
| // Fallback for engines that still support it | |
| document.execCommand('insertText', false, text) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/common/EditableText/EditableText.tsx` around lines 41 - 45, In the
handlePaste function, replace the deprecated document.execCommand('insertText')
call with a modern approach that prioritizes the Selection and Range APIs. First
attempt to retrieve the current selection using window.getSelection(), create a
range, delete the current content, and insert the new text using the Range API.
If the selection is unavailable or the modern approach fails, fall back to
document.execCommand('insertText') as a compatibility measure to ensure reliable
paste handling across all browsers.
| if (typeof key === 'number') { | ||
| const arr = Array.isArray(obj) ? obj.slice() : [] | ||
| arr[key] = setIn(arr[key], rest, value) | ||
| return arr as T |
There was a problem hiding this comment.
Reject negative/non-integer array indices in setIn.
setIn currently accepts any numeric key; invalid indices (e.g., -1, 1.5) become non-index array properties and silently produce wrong shapes. Add an explicit guard before assignment.
Proposed fix
if (typeof key === 'number') {
+ if (!Number.isInteger(key) || key < 0) {
+ throw new RangeError(`setIn path index must be a non-negative integer, got ${key}`)
+ }
const arr = Array.isArray(obj) ? obj.slice() : []
arr[key] = setIn(arr[key], rest, value)
return arr as T
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (typeof key === 'number') { | |
| const arr = Array.isArray(obj) ? obj.slice() : [] | |
| arr[key] = setIn(arr[key], rest, value) | |
| return arr as T | |
| if (typeof key === 'number') { | |
| if (!Number.isInteger(key) || key < 0) { | |
| throw new RangeError(`setIn path index must be a non-negative integer, got ${key}`) | |
| } | |
| const arr = Array.isArray(obj) ? obj.slice() : [] | |
| arr[key] = setIn(arr[key], rest, value) | |
| return arr as T |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/wizard/set-in.ts` around lines 6 - 9, The setIn function currently
accepts any numeric key without validation, allowing negative numbers and
non-integer values like -1 or 1.5 to be set as array properties instead of
proper indices. Add a guard condition after the typeof check for 'number' in the
setIn function to validate that the key is a non-negative integer (e.g., using
Number.isInteger and checking that key >= 0). If the key fails validation, throw
an error or handle it appropriately to prevent invalid array shapes from being
created silently.
What & why
The Edit tab rendered generated copy as read-only cards. This makes the main prose directly editable in place — click the text and type. No input boxes, no edit mode, no save/edit buttons; edits persist automatically and Preview reflects them.
Design spec was brainstormed and approved first (kept locally at
docs/superpowers/specs/2026-06-21-inline-editing-design.md, which is gitignored).How it works
EditableText(components/common/EditableText) — the seamless primitive. UncontrolledcontentEditable(React rendersvalueonce and never mid-edit, so the caret never jumps), commits the trimmed text on blur. Single-line: Enter commits + blurs; multiline: Enter = newline. Paste is forced to plain text. Cleared-to-empty reverts to the previous value. Affordance: subtle hover tint +cursor-textand a soft focus ring — no buttons, no boxes. Renders read-only when noonCommitis passed.setIndeep-set (lib/wizard/set-in.ts) + anEDIT_FIELD { platform, path, value }reducer action that updates the active platform's content by path (supports array indices, e.g.['thread','tweets',2]). Persistence is free —useRunalready autosavesstatetolocalStorage, so edits survive reload and flow into Export. Preview reads the same state.PlatformSectionsPropsgainsonEdit;PostKittakesonEditField(platform, path, value)and binds the active platform; the run page dispatchesEDIT_FIELD.Scope (per agreed design — main prose only)
Editable: Product Hunt tagline / description / first comment; Hacker News title / post body / first comment; Reddit title / body; AppSumo deal headline / pitch; each Social thread tweet. Read-only: lists, topics, checklists, gallery/video, outreach hints, the Launch ops tab, and the Launch Plan. No add/remove of list items.
How it was tested
Verified the real round-trip in the browser (throwaway harness, since deleted): typed into the tagline, clicked away → the value committed and the live
(38/60)counter updated, with no caret disruption. The edited text looks identical to static text.pnpm test— 100 passed (18 files)pnpm typecheckpnpm lintpnpm buildChecklist
EditableText,set-in).envvalues committedScreenshots / notes
setIn,EDIT_FIELDreducer,EditableTextbehavior, and aSocialSectionsedit-path test (asserts the correct['thread','tweets',i]path).Summary by CodeRabbit