fix(mobile): enable habit submit button once a title is typed - #145
Conversation
The Create Habit / Save Changes buttons gated on `formState.isValid`, but the title is a buffered input that only commits to react-hook-form on blur/submit. In the default onSubmit mode, `setValue` without `shouldValidate` re-broadcasts the cached `isValid` instead of recomputing it, so the button stayed disabled even with a title filled. Web is unaffected: its inputs write to the form live, so the registered field's onChange recomputes `isValid` and the same gate works. Observe the live title draft through BufferedSheetInput's existing onDraftChange hook and gate the button on a lightweight title-present boolean. This preserves the buffer that keeps typing lag-free on Android. Also seeds the boolean from the prefilled title in the edit modal, fixing the latent case where clearing an existing title left Save enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
There was a problem hiding this comment.
The root-cause diagnosis is correct and the fix is clean. BufferedSheetInput's onDraftChange was already wired to fire on every keystroke but was previously unused at the form-fields layer; threading it up to a lightweight titleFilled boolean sidesteps the stale isValid issue without running the Zod resolver per keystroke or surfacing a premature "title required" error. Both the create-modal (resets to false on close) and the edit-modal (seeded from the prefill title before mount) handle state initialisation correctly. Web is correctly untouched — its live-binding inputs already recompute isValid through the standard RHF onChange path, so the behavioral parity (button disabled while title is empty) is preserved by different platform-appropriate mechanisms, which is the allowed difference per CLAUDE.md. Tests cover the empty→filled flip and the whitespace-trim edge case. Solid fix.
…bmit gate (#175) * fix(ui): UX/QA batch across web + mobile (26 items) + create-habit submit gate Cross-platform UX/QA pass plus shared error localization. Highlights: - Chat: voice-recording race fix; language picker moved to the chat header - Calendar: tappable centered month nav, day-detail modal, centered muted legend, removed the "x/30 dias" summary - Errors: backend errors render localized via ERROR_CODE_TO_KEY; login surfaces errors through a toast only (no reference code / inline yellow text) - Retrospective: stats-dashboard + narrative UI with an empty-period state - Upgrade: exact hero copy + Free/Pro two-column comparison - Frequency: swipe carousel; streak-freeze celebration auto-dismisses; bad = red / overdue = yellow log dots on both platforms - Preferences option legends relocated into the picker modal; AI-facts paginator inline with an icon select - Support: removed name/email fields; advanced devs: deduped MCP tab - Widget: fallback drawables + first-add render fix - pt-BR naturalization across the locale (parity preserved at 1665/1665) - fix(mobile): restore the title-presence submit gate on the create/edit habit modals. #172 reverted #145: with a buffered title input, setValue without shouldValidate re-broadcasts the cached onSubmit-mode isValid, so the button stayed disabled with a title filled. Gate on useWatch('title') instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(i18n): remove dead orbitMcp.claudeDesktop key The desktop MCP tab was dropped (MCP_CONFIG_TABS no longer includes 'desktop'), so this key has no remaining callers. Flagged by the PR review bot. Removed from both locales; en/pt-BR parity preserved at 1664/1664. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



Problem
After #144, the mobile Create Habit / Save Changes button stayed disabled even with the title filled in.
Root cause
The buttons gated on react-hook-form's
formState.isValid, but the title is a buffered input (BufferedSheetInput) that keeps keystrokes in local state and only commits to the form on blur/submit. Confirmed against the installed RHF 7.77 source: in the defaultonSubmitmode,setValue(...)withoutshouldValidatere-broadcasts the cachedisValidrather than recomputing it — soisValidnever flipped and the button stayed disabled.Web is unaffected: its inputs write to the form on every keystroke, and the registered-field
onChangepath recomputesisValid, so the same gate already works there.Fix (mobile-only)
Observe the live title through
BufferedSheetInput's existing (previously unused)onDraftChangehook and gate the button on a lightweight title-present boolean:title-section.tsx/habit-form-fields.tsx— forward the title draft up asonTitlePresenceChange(hasTitle).create-habit-modal.tsx/edit-habit-modal.tsx— gatesubmitDisabledon that boolean; seed it (false for create, from the prefilled title for edit).A boolean (not the raw string) keeps modal re-renders to the empty↔filled flip only, preserving the buffer's purpose (lag-free typing on Android). The title is intentionally not routed through live validation (
shouldValidate), which would run the resolver per keystroke and surface a "title required" field error web doesn't show.Also fixes a latent edit-modal bug: clearing an existing title now disables Save (previously it stayed enabled).
This is behavioral parity with web (button disabled while title empty / saving); the differing mechanism is the buffered-input platform adapter, which web doesn't have.
Tests
habit-form-fields.test.tsx— typing/clearing the title emitsonTitlePresenceChange(true|false)(incl. whitespace-trim).create-habit-modal.test.tsx— submit button disabled with empty title, enabled once a title is present.🤖 Generated with Claude Code