Skip to content

fix(comet-ai): use matched input selector, tighten finalCheck, doc stability semantics#21

Merged
RapierCraft merged 1 commit into
RapierCraft:mainfrom
sergei-aronsen:fix/comet-ai-correctness
May 16, 2026
Merged

fix(comet-ai): use matched input selector, tighten finalCheck, doc stability semantics#21
RapierCraft merged 1 commit into
RapierCraft:mainfrom
sergei-aronsen:fix/comet-ai-correctness

Conversation

@sergei-aronsen
Copy link
Copy Markdown

Summary

Three small correctness fixes in `src/comet-ai.ts`.

1. `findInputElement` return value was ignored

`sendPrompt` called `findInputElement()` (which iterates candidate selectors and returns the first that matches), then discarded the result and hardcoded `[contenteditable="true"]` for the typing logic. On pages where the real input is a `<textarea>` and a different unrelated `contenteditable` exists somewhere on the page (e.g. a hidden search box), focus / typing landed on the wrong element.

Now we route through the selector that the discovery loop matched, and branch on `el.isContentEditable` / `'value' in el` so contenteditable elements get `execCommand('insertText', ...)` and form controls get `.value =` + `input` event.

2. `finalCheck` matched `[class*="animate"]` — too broad

Perplexity's brand mark uses `animate-pulse` permanently. The "submission succeeded?" probe used `[class*="animate"]`, which matches that brand mark, so `hasLoading` was effectively always `true` on any Perplexity page. The `form.submit()` last-resort branch — meant to fire when neither key dispatch nor button click worked — therefore never fired.

Tightened to specific progress indicators: `animate-spin`, `loading`, `thinking`.

3. `isResponseStable` doc-vs-code off-by-one

The constant was named `STABILITY_THRESHOLD` with the comment "Response must be same for 2 checks". The actual behaviour: counter increments only AFTER the first matching call, so the function returns `true` on the 3rd consecutive identical poll (which is what the existing unit test asserts).

Renamed to `STABLE_REPEATS_REQUIRED` and rewrote the comment to describe the true semantics. The constant value (2) is unchanged, the test still passes, no behavioural change — just an accurate name + comment.

Compatibility

No public API change. The matched-selector fix is the only one with a visible runtime delta, and that's the intended behaviour (the previous version was a latent bug).

Test plan

  • `npm run test:unit` passes
  • Manual: `comet_ask` on a fresh Perplexity tab still types into the correct input
  • Manual: `comet_ask` on a page with both `<textarea>` and other `contenteditable` elements correctly targets the search input

🤖 Generated with Claude Code

…ability semantics

### `findInputElement` return value was ignored

`sendPrompt` called `findInputElement()` (which iterates a list of
candidate selectors and returns the first one that matches), then
discarded the result and hardcoded `[contenteditable="true"]` for the
typing logic. On pages where the real input is a `<textarea>` AND a
different unrelated `contenteditable` exists somewhere on the page
(e.g. a hidden search box), focus / typing landed on the wrong
element. Now we route through the selector that the discovery loop
actually matched, and branch on `el.isContentEditable` / `'value' in el`
so contenteditable elements get `execCommand('insertText', ...)` and
form controls get `.value =` + `input` event.

### `finalCheck` matched `[class*="animate"]` — too broad

Perplexity's brand mark uses `animate-pulse` permanently. The
"submission succeeded?" probe used `[class*="animate"]`, which
matches that brand mark, so `hasLoading` was effectively always
`true` on any Perplexity page. The `form.submit()` last-resort
branch — meant to fire when neither key dispatch nor button click
worked — therefore never fired. Tightened to specific progress
indicators: `animate-spin`, `loading`, `thinking`.

### `isResponseStable` semantics were off-by-one in the doc

The constant was named `STABILITY_THRESHOLD` with the comment
"Response must be same for 2 checks". The actual behaviour: counter
increments only AFTER the first matching call, so the function
returns `true` on the 3rd consecutive identical poll (which is what
the existing unit test asserts). Renamed to `STABLE_REPEATS_REQUIRED`
and rewrote the comment to describe the true semantics — "unchanged
for `STABLE_REPEATS_REQUIRED + 1` consecutive polls". The constant
value (2) is unchanged; the test still passes; no behavioural
change, just an accurate name + comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@RapierCraft
Copy link
Copy Markdown
Owner

Code Review: PR #21 — fix(comet-ai): use matched input selector, tighten finalCheck, doc stability semantics

Reviewed commit: 732ec7c


Overall Assessment

This is a well-motivated correctness PR with clear explanations. The three changes are logically sound and the PR description accurately describes the problems being fixed. I have a few observations ranging from a potential bug to minor suggestions.


1. Matched Input Selector — Correctness (LGTM)

The fix is correct: using inputSelector (the selector that findInputElement() matched) instead of hardcoding [contenteditable="true"] is the right approach.

The branching logic (el.isContentEditable vs 'value' in el) correctly handles both contenteditable elements and form controls. The use of JSON.stringify for safeSelector and safePrompt is correct for preventing injection in the evaluated code string. No security concerns.

Edge case confirmed safe: if a matched selector returns an element that is neither contenteditable nor has a value property, the IIFE falls through both if branches and implicitly returns undefined, which will cause the outer code to throw "Failed to type into input element" — correct error handling.


2. finalCheck Tightening — Correct, but Inconsistency Remains (Medium)

The fix for finalCheck is correct and well-reasoned. Tightening from [class*="animate"] to [class*="animate-spin"], [class*="loading"], [class*="thinking"] prevents the Perplexity brand mark's animate-pulse from always triggering a false positive.

However, the PR introduces an inconsistency:

The earlier submission check at line ~140 (inside submitPrompt, the first verification after Enter key dispatch) still uses:

const hasLoading = document.querySelector('[class*="animate-spin"], [class*="animate-pulse"]') !== null;

This check still matches animate-pulse — the exact class the PR identifies as permanently present on Perplexity pages. This means:

  • The early check (line 140) will still report hasLoading = true even when no actual loading is happening
  • It causes the function to return early (treating submission as successful even if it wasn't)
  • The code may never reach the tightened finalCheck because the earlier check already short-circuits

Recommendation: Apply the same tightening to line 140. Replace [class*="animate-pulse"] with [class*="loading"], [class*="thinking"] for consistency with the finalCheck fix.

Similarly, getAgentStatus() at line 316 uses [class*="animate-pulse"]. If that's intentional for working-state detection (where pulse animation is a valid signal distinct from the brand mark), it's worth a comment explaining why.


3. Stability Semantics Rename — Correct

The rename from STABILITY_THRESHOLD to STABLE_REPEATS_REQUIRED accurately describes the behavior. The new docstring ("returns true once the response text has been observed unchanged for STABLE_REPEATS_REQUIRED + 1 consecutive polls") is correct: with value 2, it takes 3 consecutive identical polls to return true. Clear improvement.


Summary Table

Finding Severity Blocking?
animate-pulse still in earlier submission check (line 140) — same false-positive the PR fixes in finalCheck Medium No, but should be fixed in this PR or immediate follow-up
No unit tests for the selector-routing logic Low No

Verdict: The PR's changes are correct and improve the codebase. The animate-pulse inconsistency at line 140 should ideally be addressed in this PR since it suffers from the same root cause. Otherwise, the logic is sound. LGTM with that caveat.

@RapierCraft RapierCraft merged commit 448f151 into RapierCraft:main May 16, 2026
1 check passed
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.

2 participants