🤖 fix: stop iPad taps from selecting the whole chat pane - #3761
🤖 fix: stop iPad taps from selecting the whole chat pane#3761ibetitsmike wants to merge 38 commits into
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4176b9c78b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a1816d752
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 90f32629d3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7c663ace8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb948595da
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3272a521c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dce3acaac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 025080804b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18400eb8fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c73ce372ff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f684290df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4320c36d29
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb82ee70c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b47d74ed5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
Tailwind scans files named by @source directives for utility classes, so a selection class there would compile like one written in TSX. The opt-in scan now covers those files, keyed by repo-relative path, and throws on any @source form it cannot model (not, inline, globs, directories). A path that matches nothing scans as nothing, exactly as Tailwind treats it.
The arbitrary-value and variable forms (select-[text], select-(--x)) compile to nothing in this Tailwind version, where select-* is a static utility, but tracking them costs one regex alternative and closes the gap a Tailwind upgrade would otherwise open. The @apply refusal also moves from an anchored token match to a contains match, so variant chains it did not model ([&:hover]:) and important markers (select-text!, !select-text), which do compile, can no longer hide a selection utility from it.
Browsers match CSS property names and keyword values ASCII-case- insensitively, so USER-SELECT: TEXT defeats the guard while a lowercase-only pattern never collects it. Properties and values are lowercased at collection, escaped property names (user-sele\63t) are refused rather than decoded, and the @apply/@source walks accept any name case. The TSX scan stays case-sensitive on purpose: it models Tailwind's candidate parser, which compiles SELECT-TEXT and [USER-SELECT:text] to nothing, only arbitrary-property values keep their case and those were already matched by wildcard.
A React style object or DOM style assignment puts the declaration on the element just like a utility class, invisible to both the stylesheet parse and a class-only scan. The opt-in pattern now matches camelCase (userSelect, WebkitUserSelect, DOM webkitUserSelect) and kebab-case string spellings; literal none values and the empty-string reset stay untracked as suppression, and a value the pattern cannot see counts as an opt-in so review decides.
cssText and setProperty parse CSS, which is ASCII-case-insensitive, so USER-SELECT: text in a string overrides the guard while a lowercase-only alternative never sees it. Each alternative now carries its parser's case posture: Tailwind candidates and JS property lookups stay case-sensitive (SELECT-TEXT and [USER-SELECT:text] compile to nothing or fail the build, style.USERSELECT is inert), and the kebab spellings are built letter by letter because one regex flag cannot vary per alternative.
A per-file count cannot tell a reviewed opt-in from a different one that replaced it in the same file: removing one of ReviewPanel's four metadata select-all classes while adding select-text to a transcript container leaves the count at four. Each occurrence is now pinned by the trimmed line that carries it, so moving, replacing, or editing a carrying line resubmits it for review, while unrelated edits elsewhere in the file leave the entry alone. Identity comes from the line's text, keeping the exact-match posture, no TSX parsing.
ReviewPanel wraps four metadata values in byte-identical divs, so a carrying line alone cannot tell them apart: moving one to a transcript container in the same file leaves the multiset unchanged. Each occurrence is now the carrying line plus the next non-empty line, which in JSX is the content the opt-in exposes, so identical wrappers are distinguished by what they make selectable.
One class or id token is not scoping when the element it names wraps the application: #root contains everything React renders, so suppressing selection there is app-wide by inheritance. Shell ids are read from index.html rather than hardcoded, so a renamed or added container stays covered.
select-none on a control is safe while the same class on the App.tsx shell disables desktop selection for everything under it, and a static scan cannot tell those apart from the class name. The untracked posture held for one direction only as long as no one could name a broad suppression site; the shell is one, so both directions now go through the same enumeration machinery: every suppression spelling, pinned per occurrence with line-plus-content identity, 38 sites across 33 files generated from the current tree.
The class-name scan globbed src/**/*.{ts,tsx}, but Tailwind's automatic
source detection reads far more, including index.html, whose elements
are real DOM the shell id check just made interesting. Hand-listing
that set is another approximation, so the scan now asks
@tailwindcss/oxide (the scanner underneath the declared tailwindcss
packages) for the file list and applies the same patterns to each file.
A source() clause on the Tailwind import would change the scan base, so
it throws until modelled. Three suppression sites outside src/ join the
map (.design-sync previews, vscode's webview.css); index.html is clean
today.
Pinning each site's line text made behavior-neutral refactors (reordering classes on a line, renaming a nearby expression) fail the contract, which is the tautological-test shape AGENTS.md forbids. The enumeration now records only normalized matched tokens per file with a count: the full Tailwind candidate including variants and important markers, or the inline property spelling, with quotes, whitespace, and the =/: difference folded and only the case-insensitive kebab forms lowercased. Adds, removals, kind and variant changes, and cross-file moves still fail; moving an already-reviewed token within a file is an accepted, documented residual, since telling identical tokens apart needs surrounding text or TSX parsing, and the PR diff shows what a moved class newly wraps.
The named-utility alternatives dropped important markers and bracketed variants from the match, so select-text to select-text! or [&:hover]: variants changed nothing the inventory saw, contradicting its comment. The variant chain (named or arbitrary) and either important-marker spelling are now part of the token, which also surfaced that ReviewPanel's suppression is [&_summary]:select-none, previously conflated with a bare select-none.
…tors A single class token does not guarantee component scope: .mobile-layout sits on the App.tsx application-wide wrapper, so auto-accepting every one-token selector let a broad suppressor pass. Reading class names out of shell files cannot classify tokens either (titlebar-drag appears in App.tsx on a scoped strip), so selectors are now a reviewed enumeration with the index.html shell ids as a hard floor no entry can override.
Fixtures, Storybook stories, design previews, docs, and the VS Code webview were scanned as if their text styled the Electron renderer, so editing a fixture's sample payload failed the contract without any behavior change. The scan now keeps only sources that can style a rendered element: the entry HTML files from vite.config.ts and the src tree minus test/story/fixture conventions. Tailwind's scanner still supplies the file list, so file discovery stays delegated.
Backend-only src/node, src/cli, and src/desktop were still inventoried, so a prompt or comment string mentioning select-none there failed the contract without any renderer change. The scan now covers a reviewed segment list (browser, common, constants, version). Transitive closure cannot compute this: src/common runtime-imports @/node in server-only corners the renderer never reaches, so reachability at segment granularity would pull node back in. A drift guard extracts the browser segment's direct runtime imports (type-only statements stripped) and fails when a segment outside the list appears.
auto is user-select's initial value and resolves through the parent's used value, and globals.css guard rules are unlayered while utilities live in @layer utilities, so a normal select-auto cannot override the guard or the editable opt-backs; adding it is behavior-neutral and failed the contract anyway. Important-marked forms stay tracked since author-important utilities beat the unlayered normal guard rules, as do the inline auto spellings, which sit above every stylesheet rule. The co-location residual (select-auto outranking a same-element select-all in compiled order) is documented alongside the token-move residual.
Selection tokens mentioned only in runtime-source comments are behavior-neutral, but the raw matchAll scan treated them as declarations. Parse TypeScript-family sources and remove comments only from syntax-token gaps so strings, regex literals, template text, and JSX text remain visible; strip HTML comments from markup sources. The regression covers URLs in JSX, JSX and template-expression comments, shebangs, regexes, and real selection tokens after comment-like text.
The runtime-source predicate still admitted src/browser/stories helpers and settingsStoryUtils.tsx, so behavior-neutral Storybook changes could alter the golden inventory. Exclude the Storybook directory and the remaining colocated Storybook helper while keeping a real Settings section in the scan.
Renderer-adjacent Markdown, SVG, JSON, and declaration files were still admitted by the selection inventory, so documentation or asset text could change its golden counts without affecting UI behavior. Keep entry HTML explicit, restrict renderer segments to executable JS and TS sources, and preserve positive coverage for TSX and CJS.
Replace the renderer-segment regex with TypeScript AST traversal so comments, strings, and type-only imports do not create false positives while runtime imports, exports, import-equals, dynamic imports, and require calls remain visible. Preserve language-specific empty-import semantics and wrapped literal arguments. Assert fine-pointer selectability for every vendor property the stylesheet parser recognizes.
--- _Generated with `mux` • Model: `openai:gpt-5.6-sol` • Thinking: `xhigh`_ <!-- mux-attribution: model=openai:gpt-5.6-sol thinking=xhigh -->
fe24921 to
0e215a0
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e215a0ca0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
--- _Generated with `mux` • Model: `openai:gpt-5.6-sol` • Thinking: `xhigh`_ <!-- mux-attribution: model=openai:gpt-5.6-sol thinking=xhigh -->
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c63d7e59cb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
--- _Generated with `mux` • Model: `openai:gpt-5.6-sol` • Thinking: `xhigh`_ <!-- mux-attribution: model=openai:gpt-5.6-sol thinking=xhigh -->
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Fixes accidental whole-chat text selection on iPadOS by applying the existing touch selection guard to all coarse-pointer widths instead of phones only. The disproven JavaScript focus workaround is removed.
Background
The existing guard lived inside
@media (max-width: 768px) and (pointer: coarse). iPadOS reports a coarse primary pointer even with a Magic Keyboard trackpad attached, but iPads exceed the phone breakpoint, so the guard never applied there.Implementation
bodyselection guard to@media (pointer: coarse).user-select: text.mousedownfocus workaround and its helper tests.Validation
make static-check: passed.Risks
Touch-primary tablets can no longer select arbitrary transcript text by drag or long-press, matching existing phone behavior. Editable controls and explicit copy actions remain available. Fine-pointer desktop behavior remains selectable.
Generated with
mux• Model:openai:gpt-5.6-sol• Thinking:xhigh