Skip to content

fix(whatsapp): insert placeholders at cursor - #1434

Merged
khaira777 merged 5 commits into
mainfrom
fm/flocafe-whatsapp-placeholder-cursor-r1
Nov 3, 2026
Merged

khaira777 merged 5 commits into
mainfrom
fm/flocafe-whatsapp-placeholder-cursor-r1

Conversation

@khaira777

@khaira777 khaira777 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Intent

In the WhatsApp templates editor, clicking "click to insert placeholder" always appends the placeholder to the end of the message instead of inserting it where the typing cursor/pointer is located. Fix the placeholder insertion so it inserts at the active cursor/selection position (tracked in #1432).
Follow the Obra Superpowers engineering procedure end-to-end (research, spec/plan under ~/tools/firstmate/data/flocafe-whatsapp-placeholder-cursor-r1/, TDD red-green-refactor, verification, and no-mistakes validation pipeline). Keep PR title <= 50 chars, single-line PR body paragraphs, and include Fixes #1432.

What Changed

  • Add insertPlaceholderAtSelection in shared/whatsapp-automation.ts to splice placeholder tokens at the active cursor or selection range while falling back to appending at the end when selection is unestablished or out of bounds.
  • Update the WhatsApp template editor in frontend/src/app/(dashboard)/whatsapp/page.tsx to track textarea selection state, insert clicked placeholders at the active caret or selection, and restore focus and cursor position after insertion.
  • Add unit tests in tests/whatsapp-template-placeholder-cursor.test.ts and wire them into npm run test:whatsapp-service in package.json.

Fixes #1432

Risk Assessment

✅ Low: The change is well-bounded to a pure placeholder insertion helper and local textarea selection handling in the WhatsApp template editor, with direct unit test coverage and no impact on backend or security boundaries.

Testing

Executed the targeted unit test suite (tests/whatsapp-template-placeholder-cursor.test.ts) and npm run test:script-coverage, built the backend and static desktop frontend (npm run build && npm run build:frontend), and drove all 4 end-user and adversarial scenarios live in Chromium via Playwright against an isolated FloCafe server instance (:4301–:4303), capturing 4 visual screenshot artifacts in the evidence directory; all scenarios passed and transient build/test outputs were cleaned up.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
User places the typing cursor in the middle of the WhatsApp template message body and clicks placeholder chips (including consecutive chips); the placeholders insert at the active caret and advance th… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-1-mid-cursor-and-sequential-insert.png
User highlights a text range inside the WhatsApp template message body and clicks a placeholder chip; the selected range is replaced by the placeholder token and the caret rests immediately after the… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-2-selection-range-replacement.png
User focuses the WhatsApp template message body and places the caret at index 0, then clicks a placeholder chip; the placeholder is prepended at index 0 rather than treated as an untouched field. ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-3-explicit-index-zero-prepend.png
Adversarial guard: User switches to a template or clicks 'Reset to default' and immediately clicks a placeholder chip without focusing or clicking the textarea (even when the browser reports selection… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-4-untouched-append-to-end.png

Mid-message cursor insertion & sequential placeholder clicks
Selection range replacement with clicked placeholder
Explicit cursor at index 0 prepends placeholder to start of message
Adversarial guard: untouched or freshly reset template textarea appends placeholder to end

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
User places the typing cursor in the middle of the WhatsApp template message body and clicks placeholder chips (including consecutive chips); the placeholders insert at the active caret and advance th… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-1-mid-cursor-and-sequential-insert.png
User highlights a text range inside the WhatsApp template message body and clicks a placeholder chip; the selected range is replaced by the placeholder token and the caret rests immediately after the… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-2-selection-range-replacement.png
User focuses the WhatsApp template message body and places the caret at index 0, then clicks a placeholder chip; the placeholder is prepended at index 0 rather than treated as an untouched field. ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-3-explicit-index-zero-prepend.png
Adversarial guard: User switches to a template or clicks 'Reset to default' and immediately clicks a placeholder chip without focusing or clicking the textarea (even when the browser reports selection… ✅ pass live ~/.no-mistakes/evidence/01M6H9T2XVXHC69A0DWC60PT97/scenario-4-untouched-append-to-end.png
  • ./node_modules/.bin/ts-node --transpile-only -P tests/tsconfig.json tests/whatsapp-template-placeholder-cursor.test.ts
  • npm run test:script-coverage
  • npm run build && npm run build:frontend
  • cd frontend && E2E_BASE_URL=http://127.0.0.1:4301 E2E_KDS_BASE_URL=http://127.0.0.1:4302 E2E_SERVER_APP_BASE_URL=http://127.0.0.1:4303 npx playwright test e2e/tmp-whatsapp-placeholder-live.spec.ts --config=playwright.config.ts --project=chromium
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix WhatsApp placeholder insertion at the cursor

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Insert WhatsApp template placeholders at the caret or replace selected text instead of appending.
• Restore the caret after insertion and move it to the end when templates switch or reset.
• Add regression tests for insertion positions, selection replacement, fallback, and cursor bounds.
Diagram

graph TD
  Chip["Placeholder chip"] --> Selection["Textarea selection"] --> Helper["Insertion helper"] --> Body["Updated body"] --> Caret["Restored caret"]
  Switch["Switch or reset"] --> End["Caret at end"]
Loading
High-Level Assessment

The textarea ref and pure string helper keep selection handling local to the editor and insertion logic independently testable. Mirroring selection in React state or editing the DOM directly would add complexity without a clear benefit.

Files changed (4) +191 / -5

Bug fix (2) +57 / -4
page.tsxInsert template chips at the textarea selection +35/-4

Insert template chips at the textarea selection

• Reads the body textarea’s selection when a placeholder chip is clicked, updates the template body, then refocuses the textarea with the caret after the token. Moves the caret to the end when switching or resetting templates.

frontend/src/app/(dashboard)/whatsapp/page.tsx

whatsapp-automation.tsAdd selection-aware placeholder insertion helper +22/-0

Add selection-aware placeholder insertion helper

• Adds a pure helper that replaces a selected range or inserts at the caret, clamps invalid indices, and appends when no selection is provided. Returns both the updated body and the cursor position after the token.

shared/whatsapp-automation.ts

Tests (2) +134 / -1
package.jsonRun placeholder regression tests with WhatsApp service tests +1/-1

Run placeholder regression tests with WhatsApp service tests

• Adds the new cursor-insertion test to the existing test:whatsapp-service script.

package.json

whatsapp-template-placeholder-cursor.test.tsCover placeholder insertion and cursor edge cases +133/-0

Cover placeholder insertion and cursor edge cases

• Tests beginning and mid-body insertion, selection replacement, consecutive insertions, missing selection, and out-of-bounds or inverted ranges.

tests/whatsapp-template-placeholder-cursor.test.ts

@qodo-code-review

qodo-code-review Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. First chip click inserts at the wrong edge ✓ Resolved
Description
The caret initialization effect only runs when selectedTemplateId changes, so it misses the first
mount of the lazily rendered automation editor. When the user opens that tab without first focusing
the textarea, the browser selection is 0,0, and the new insertion path places the token at the
beginning instead of preserving the previous append behavior.
Code

frontend/src/app/(dashboard)/whatsapp/page.tsx[R483-488]

+  useEffect(() => {
+    const el = templateBodyRef.current;
+    if (!el) return;
+    const end = el.value.length;
+    el.setSelectionRange(end, end);
+  }, [selectedTemplateId]);
Relevance

●●● Strong

Recent precedents accept mount-timing and stale-effect bugs; this directly breaks the PR’s stated
cursor insertion intent.

PR-#1425
PR-#922
PR-#1315

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The editor is rendered inside the automation TabsContent, while the local tabs wrapper forwards
props without enabling forceMount, so inactive content is not mounted. The effect runs only on
selectedTemplateId; its initial run can see a null ref, and switching tabs does not change that
ID. A newly mounted textarea starts with selection 0,0, which the insertion helper treats as a
valid beginning position rather than its null/undefined append fallback.

frontend/src/app/(dashboard)/whatsapp/page.tsx[483-488]
frontend/src/app/(dashboard)/whatsapp/page.tsx[325-326]
frontend/src/app/(dashboard)/whatsapp/page.tsx[1362-1382]
frontend/src/components/ui/tabs.tsx[88-97]
frontend/src/app/(dashboard)/whatsapp/page.tsx[1470-1477]
shared/whatsapp-automation.ts[395-406]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The caret-to-end effect depends only on `selectedTemplateId`, but the automation `TabsContent` is lazily mounted. If the page opens on another tab and the user first switches to automation, the textarea mounts after the effect has already run with no element, leaving its default selection at `0,0`; the first placeholder chip then inserts at the beginning rather than appending when no caret was established.

## Fix Focus Areas
- frontend/src/app/(dashboard)/whatsapp/page.tsx[483-488]
- frontend/src/app/(dashboard)/whatsapp/page.tsx[1362-1382]
- frontend/src/components/ui/tabs.tsx[88-97]

## Recommended Fix
Run the caret initialization when the textarea/editor mounts or otherwise detect the first available textarea after the automation tab becomes active. Ensure the initial selection is moved to the end only when there is no user-established selection, while retaining the existing reset and template-switch behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 5 rules
✅ Cross-repo context — repo relationships
✅ REVIEW.md
Review mode: Auto: 🚀 Fast: Localized cursor insertion fix with focused helper and regression coverage.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display settings with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@khaira777
khaira777 force-pushed the fm/flocafe-whatsapp-placeholder-cursor-r1 branch from 7398be7 to 5186a1c Compare November 3, 2026 18:30
@khaira777
khaira777 merged commit f81c735 into main Nov 3, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-whatsapp-placeholder-cursor-r1 branch November 3, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(whatsapp): insert template placeholder at cursor position instead of appending

1 participant