chore(client): scrub style={[...]} array patterns on DOM-leaf primitives + ESLint guardrail #239
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol#239
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
PR #209 surfaced a runtime crash on the universal client: clicking certain elements threw
CSSStyleProperties doesn't have an indexed property setter for '0'(Firefox wording; same bug on Chromium). Root cause: React DOM'ssetValueForStylesiterates the style prop withfor-inand writesnode.style[key] = value. When the input is a style array (or RNW passes through a nested array via<Link asChild>/ similar merging),for-initerates numeric indices and the DOM rejects them.PR #232 and #211 adopted the safe pattern in the nav shell, login server row, and a few other places:
The bug doesn't reproduce in every
style={[...]}site — most paths through RNW flatten correctly. But the failure modes are subtle (specific Link/Pressable merging contexts; certain prop-spread chains), so leaving array sites in place is leaving land mines. The agent for #235 noted thatlogin.tsx's pre-existing form elements still usestyle={[...]}and only the newLoginServerRowadopted the safe shape — that's a representative snapshot of the codebase today.Source
Follow-up flagged in PR #238 and traced back to the runtime bug fixed in PR #232.
Scope
rg 'style={\[' apps/client/to enumerate everystyle={[...]}site. There are dozens; the migration is mechanical.[styles.a, styles.b]with no conditionals →{ ...styles.a, ...styles.b }.cond && { ... }entries → pre-compute the merged object outside the JSX, or use a ternary.const containerStyle = ...) before handoff.style={[...]}props on react-native-internal non-leaf components where the array reliably flattens (e.g.<FlatList contentContainerStyle={[...]}>); the audit's focus is DOM-leaf primitives (View, Text, Pressable, TextInput, Image, ScrollView).no-restricted-syntax-style configuration) that flagsstyle={[...]}on the DOM-leaf primitives. Allow an opt-out comment for cases where an array is the right call (e.g. animated styles via Reanimated). The rule's error message should link to this ticket so future contributors learn the pattern.The scope is limited to
apps/client/. The deleted Next.js UI's leftover styles inapps/api/app/themes/preferences.tsare pure data, not affected.Files most likely impacted
(Grep results from a recent audit — confirm during implementation.)
apps/client/app/login.tsxapps/client/app/register.tsxapps/client/app/server-setup.tsxapps/client/app/(app)/notes.tsxapps/client/app/(app)/profile.tsxapps/client/app/(app)/skills.tsxapps/client/app/(app)/experience.tsxapps/client/app/(app)/account.tsxapps/client/lib/Placeholder.tsxapps/client/lib/nav/already migrated; double-check completeness.Acceptance criteria
rg 'style=\{\[' apps/client/ --type tsxreturns nothing on DOM-leaf primitives, or returns only flagged exemptions (Reanimated-style array contexts).style={[...]}on DOM-leaf primitives with a clear error message pointing here.pnpm -F @carol/client test+export:webstay green.CONTRIBUTING.md"Conventions" pointing at the rule.Out of scope
react-native-reanimated'suseAnimatedStyle— those legitimately return objects already; if any site uses an array of an animated style + a static style, leave it but verify it doesn't trip the bug.Composes with
Part of
Standalone — no parent epic.