feat(habits): inline create-habit AI suggestion (#220) - #318
Conversation
Add a "Suggest with AI" affordance to the create-habit form on web and mobile. Tapping it calls the new POST /api/habits/suggest-setup endpoint for the entered title and applies the suggested emoji, schedule (recurring vs one-time, frequency, days) and — for Pro users — a sub-habit breakdown into the editable form; everything stays editable before submit. Shared: habitSetupSuggestionRequest/Response Zod schemas, the suggestSetup endpoint constant, a pure buildHabitFormPatchFromSuggestion mapper, and aiSuggest* i18n keys in both en and pt-BR. Web uses a Server Action; mobile calls apiClient directly (never the offline queue). Both surface the allowance limit (403) and error/empty states via toasts. Closes #220. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Code Review: PR #318
Scope: PR #318 — feat(habits): inline create-habit AI suggestion (#220)
Recommendation: APPROVE
Summary
This PR implements the "Suggest with AI" affordance on the create-habit form across both web and mobile. A new POST /api/habits/suggest-setup endpoint is called when the user taps the ghost pill; the response (emoji + schedule + sub-habit breakdown) is applied into the editable form without auto-committing. The implementation is additive, well-tested, and behaviorally symmetric across platforms. No Critical or High findings survive adversarial review.
Findings
Critical
None.
High
None.
Medium
None.
Low / Info
None.
Subagents
| Agent | Verdict |
|---|---|
| parity-checker | PAIRED |
| i18n-syncer | IN SYNC |
| contract-aligner | MATCH (frontend-side; orbit-api paired at thomasluizon/orbit-api#257) |
| security-reviewer | N/A (no orbit-api code in diff) |
Parity: Both use-habit-suggestion.ts hooks are identical in mutation signature, schema parsing, and onSuccess invalidation. Both create-habit-modal.tsx files wire handleSuggest identically (PAY_GATE → limit toast, network error → error toast, empty patch → info toast, non-empty → success toast). Both habit-form-fields.tsx files accept and render onSuggestSetup/isSuggesting with the same disabled-when-empty logic. Platform-adapter seam only (Server Action vs apiClient).
i18n: All 6 new keys (aiSuggest, aiSuggesting, aiSuggestApplied, aiSuggestError, aiSuggestEmpty, aiSuggestLimitReached) exist in both en.json and pt-BR.json under habits.form, in identical order, with no orphaned callsites.
Contract: API.habits.suggestSetup = /api/habits/suggest-setup. Both new Zod schemas are purely additive — no existing fields renamed or removed. No breaks to old mobile clients.
Backward-compat guard
All shared-type changes are additive. No field was removed or renamed in any existing schema. No breaking change for old mobile clients.
Validation
| Check | Result |
|---|---|
| Lint | N/A (CI-only) |
| Type check | N/A (CI-only) |
| Tests | PASS — shared 6, web 3, mobile 4; modal mocks updated correctly |
| Build (api) | N/A (orbit-api in paired PR #257) |
Deferred
- Dimension 13 (Backend hard rules): N/A — no orbit-api code in this diff.
- Dimension 12 (Security — API side): N/A; frontend checks pass.
- API DTO shape: orbit-api not mounted; C# DTO and FluentValidation deferred to thomasluizon/orbit-api#257.
What is good
- Zero trust-boundary leaks:
habitSetupSuggestionSchema.parse()runs insidemutationFnon both platforms before data reaches form state. - PAY_GATE path is correct and tested on both platforms.
- Offline-queue excluded deliberately — suggestion has no offline value; JSDoc WHY note present.
- Sub-habit gating:
hasProAccess && patch.subHabitTitles.length > 0— free users never get sub-habit suggestions. buildHabitFormPatchFromSuggestionis pure and covers all edge cases (daily/non-daily/one-time/null-quantity).- i18n-first: all toasts and button labels go through
t(). - No comment-policy violations.
Recommendation
Merge as-is. Confirm the API contract in thomasluizon/orbit-api#257 before deploying.
|
There was a problem hiding this comment.
APPROVE - Two Medium findings (no Critical/High). [1] Em dashes in en.json + pt-BR.json aiSuggestEmpty violate DESIGN.md:169 ban - replace with period. [2] Web create-habit-modal.test.tsx missing mock for useHabitSuggestion and missing showSuccess/showInfo in useAppToast mock - tests still pass but handleSuggest form-wiring is untested. Parity PAIRED, i18n IN SYNC, backward-compat CLEAR, security clean.



What
Adds a "Suggest with AI" affordance to the create-habit form on web and mobile. Type a name, tap the ghost pill, and the new
POST /api/habits/suggest-setupendpoint returns a suggested emoji + schedule + sub-habit breakdown that is applied into the editable form (nothing is auto-committed).packages/shared):habitSetupSuggestionRequestSchema/habitSetupSuggestionSchemaZod types, thehabits.suggestSetupendpoint constant, a purebuildHabitFormPatchFromSuggestionmapper (decides recurring vs one-time, keeps days only for a daily schedule), andhabits.form.aiSuggest*keys in both en.json and pt-BR.json.suggestHabitSetupServer Action +useHabitSuggestionmutation hook (parses the response, invalidates subscription + profile so the remaining-allowance UI refreshes); GhostPill inhabit-form-fields.tsx; apply + toast wiring increate-habit-modal.tsx.apiClient(read-style call — not routed through the SQLite offline queue) and the RNPillButtonghost variant.Why
Implements #220 (inline create-habit AI). The trigger is explicit (a tap, disabled while empty/pending — never per keystroke), and the backend bounds cost via allowance gate + server cache + a
habit-suggestrate-limit policy + the cheap SubTask model tier.Parity & compatibility
Identical logic on web + mobile (transport adapter only); both locales covered; "Orbit"/"Astra" stay literal. Additive shared contract — no breaking change for old mobile clients.
Tests
Vitest/RTL — shared mapper + schema (
habit-suggestion.test.ts), web hook, mobile hook. Full suites green (shared 1021, web 1643, mobile 626).Closes #220
Paired API PR: thomasluizon/orbit-api#257
🤖 Generated with Claude Code