input fix for chome

This commit is contained in:
2026-03-27 22:51:02 -04:00
parent f1683e2ff5
commit 7476ca5cd1
9 changed files with 1088 additions and 9 deletions

View File

@@ -858,6 +858,39 @@ docker compose exec redis redis-cli monitor
---
## Mobile Input / Auto-Fill Fixes
### CSS (`index.css`)
**Auto-fill styling** — prevents browser yellow/blue autofill background from breaking the theme:
```css
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px var(--surface) inset !important;
-webkit-text-fill-color: var(--text-primary) !important;
transition: background-color 5000s ease-in-out 0s !important;
}
```
**Prevent iOS zoom on input focus** — iOS zooms in if font-size < 16px:
```css
@media (max-width: 768px) {
input:focus, textarea:focus, select:focus { font-size: 16px !important; }
}
```
### Input Attributes
| File | Change |
|---|---|
| `MessageInput.jsx` | `autoComplete="off"`, `autoCorrect="off"`, `autoCapitalize="sentences"`, `spellCheck="true"` on message textarea |
| `PasswordInput.jsx` | Default `autoComplete="new-password"`, `autoCorrect="off"`, `autoCapitalize="off"`, `spellCheck="false"` (callers can override via props) |
| `Login.jsx` | Email: `autoComplete="email"`, `autoCorrect="off"`, `autoCapitalize="off"`, `spellCheck="false"`; Password: `autoComplete="current-password"` |
| `ChangePassword.jsx` | Current password: `autoComplete="current-password"`; new/confirm: inherit `new-password` default |
| `UserManagerPage.jsx` | Email: `autoComplete="email"`; First/Last name: `given-name`/`family-name`; Phone: `autoComplete="tel"` |
| `GroupManagerPage.jsx` | Fixed duplicate `autoComplete` attributes; search/name inputs use `autoComplete="off"` |
---
## Phase 3 — Read Replicas (Future)
When write load on Postgres becomes a bottleneck (typically >100,000 concurrent active users):

1026
CLAUDE_input_update.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -386,6 +386,10 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on
onKeyDown={handleKeyDown}
onFocus={onInputFocus}
rows={1}
autoComplete="off"
autoCorrect="off"
autoCapitalize="sentences"
spellCheck="true"
style={{ resize: 'none' }} />
</div>

View File

@@ -12,6 +12,10 @@ export default function PasswordInput({ className, style, wrapperStyle, ...input
return (
<div style={{ position: 'relative', display: 'flex', alignItems: 'center', ...wrapperStyle }}>
<input
autoComplete="new-password"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
{...inputProps}
type={show ? 'text' : 'password'}
className={className ?? 'input'}

View File

@@ -51,6 +51,18 @@ a { color: inherit; text-decoration: none; }
/* Focus */
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* Auto-fill styling — prevents browser yellow/blue override from breaking the theme */
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px var(--surface) inset !important;
-webkit-text-fill-color: var(--text-primary) !important;
transition: background-color 5000s ease-in-out 0s !important;
}
/* Prevent iOS from zooming in on focused inputs (requires font-size >= 16px) */
@media (max-width: 768px) {
input:focus, textarea:focus, select:focus { font-size: 16px !important; }
}
/* Utils */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }

View File

@@ -41,7 +41,7 @@ export default function ChangePassword() {
<form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Current Password</label>
<PasswordInput value={current} onChange={e => setCurrent(e.target.value)} autoComplete="new-password" required />
<PasswordInput value={current} onChange={e => setCurrent(e.target.value)} autoComplete="current-password" required />
</div>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>New Password</label>

View File

@@ -26,7 +26,7 @@ function UserCheckList({ allUsers, selectedIds, onChange, onIF, onIB }) {
.sort((a, b) => (a.display_name||a.name).localeCompare(b.display_name||b.name));
return (
<div>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} autoComplete="new-password" style={{ marginBottom:8 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} autoComplete="off" style={{ marginBottom:8 }} onFocus={onIF} onBlur={onIB} />
<div style={{ maxHeight:220, overflowY:'auto', border:'1px solid var(--border)', borderRadius:'var(--radius)' }}>
{filtered.map(u => (
<label key={u.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 12px', borderBottom:'1px solid var(--border)', cursor:'pointer' }}>
@@ -201,7 +201,7 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
<div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth: isMobile ? '100%' : 520 }}>
<div>
<label className="settings-section-label">Group Name</label>
<input className="input" value={editName} onChange={e => setEditName(e.target.value)} autoComplete="new-password" placeholder="e.g. Coaches" style={{ marginTop:6 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
<input className="input" value={editName} onChange={e => setEditName(e.target.value)} autoComplete="off" placeholder="e.g. Coaches" style={{ marginTop:6 }} onFocus={onIF} onBlur={onIB} />
{isCreating && !noDm && <p style={{ fontSize:12, color:'var(--text-tertiary)', marginTop:5 }}>A matching Direct Message group will be created automatically.</p>}
<label style={{ display:'flex', alignItems:'center', gap:8, marginTop:8, cursor: (selected && selected.dm_group_id) ? 'not-allowed' : 'pointer', opacity: (selected && selected.dm_group_id) ? 0.5 : 1 }}>
<input

View File

@@ -93,11 +93,11 @@ export default function Login() {
<form onSubmit={handleSubmit} className="login-form">
<div className="field">
<label>Email</label>
<input className="input" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoFocus placeholder="your@email.com" />
<input className="input" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoFocus placeholder="your@email.com" autoComplete="email" autoCorrect="off" autoCapitalize="off" spellCheck="false" />
</div>
<div className="field">
<label>Password</label>
<PasswordInput value={password} onChange={e => setPassword(e.target.value)} required placeholder="••••••••" />
<PasswordInput value={password} onChange={e => setPassword(e.target.value)} required placeholder="••••••••" autoComplete="current-password" />
</div>
<label className="remember-me">

View File

@@ -241,7 +241,7 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
value={email} onChange={e => setEmail(e.target.value)}
disabled={isEdit}
style={{ width:'100%', ...(isEdit ? { opacity:0.6, cursor:'not-allowed' } : {}) }}
autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
autoComplete="email" autoCorrect="off" autoCapitalize="off" spellCheck="false" onFocus={onIF} onBlur={onIB} />
</div>
{/* Row 2: First Name + Last Name */}
@@ -250,13 +250,13 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
{lbl('First Name', true)}
<input className="input" placeholder="Jane"
value={firstName} onChange={e => setFirstName(e.target.value)}
autoComplete="new-password" autoCapitalize="words" onFocus={onIF} onBlur={onIB} />
autoComplete="given-name" autoCapitalize="words" onFocus={onIF} onBlur={onIB} />
</div>
<div>
{lbl('Last Name', true)}
<input className="input" placeholder="Smith"
value={lastName} onChange={e => setLastName(e.target.value)}
autoComplete="new-password" autoCapitalize="words" onFocus={onIF} onBlur={onIB} />
autoComplete="family-name" autoCapitalize="words" onFocus={onIF} onBlur={onIB} />
</div>
</div>
@@ -266,7 +266,7 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
{lbl('Phone', false, '(optional)')}
<input className="input" type="tel" placeholder="+1 555 000 0000"
value={phone} onChange={e => setPhone(e.target.value)}
autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
autoComplete="tel" onFocus={onIF} onBlur={onIB} />
</div>
<div>
{lbl('App Role', true)}