feat(habits): AI tag suggestions (#223) - #320
Conversation
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.
PR Review: feat(habits): AI tag suggestions (#223)
Decision: APPROVE
No Critical or High findings. One concretely-actionable Medium is noted below.
Rubric dimensions
Correctness: PASS - Core acceptSuggestedTagFlow logic is correct: max-tags guard, no-duplicate check for existing IDs, null-return guard for new-tag creation. Zod validates at the trust boundary (Server Action).
Cross-platform parity: PASS - Feature lands on both web and mobile with matching logic, identical component structure, parallel hook implementations.
i18n: PASS - All 5 new keys (suggestTags, suggestingTags, suggestedTagsLabel, noTagSuggestions, suggestTagsError) present in both en.json and pt-BR.json.
Backward compatibility: PASS - Additive only: new endpoint, new types, new i18n keys. Existing habits contract untouched.
API contract: NOT VERIFIABLE IN CI - orbit-api repo not mounted in this runner. Paired API PR #259 is cross-linked.
Security: PASS - suggestTags Server Action Zod-parses the raw response before returning it. Inputs serialized via JSON.stringify, no injection surface. Mobile uses apiClient (SecureStore JWT), not offline-queued, which is correct for an AI call requiring connectivity.
Tests: PASS - Shared: 5 acceptSuggestedTagFlow cases (existing-select, new-create, cap, no-duplicate, null-create-skip). Web: hook + form-fields (disabled-when-empty, suggest+accept, empty-state). Mobile: hook + form-fields (suggest+accept, empty-state).
Code standards: PASS - No any, no console.log, comments within policy.
Medium: unhandled promise rejection in handleAcceptSuggestion
Files:
- apps/web/components/habits/habit-form-fields.tsx lines 167-177
- apps/mobile/components/habits/habit-form-fields/tags-section.tsx lines 51-68
Both platforms void-discard the promise returned by tags.acceptSuggestedTag(...) while re-throwing inside the createTag callback. The throw propagates through acceptSuggestedTagFlow and surfaces as a rejected promise discarded via void, making it an unhandled rejection. In React Native this produces a yellow-box warning in dev and can be fatal in Hermes strict mode.
The UI behavior is already correct: the toast shows the error, and acceptSuggestedTagFlow guards on tagId being null before adding to selectedTagIds. Replacing "throw error" with "return null" achieves the same semantic outcome without the unhandled rejection.
This does not affect the happy path and the error is already surfaced to the user. Medium, not blocking.
Validation: Skipped per CI instructions. Build, Unit Tests, and SonarCloud run as separate required checks on this PR.
…dedupe watchedTitle (#223) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|



What
Adds AI tag suggestions to the habit create/edit form on both platforms (web + mobile). A "Suggest tags" button (sparkle) sits in the existing tag section; tapping it calls the new
POST /api/tags/suggestwith the habit's title + description, then renders 1–5 suggestion chips. Accepting a chip selects the existing tag (real id/color, no duplicate) or creates + selects a new one — flowing through the existingtagIdscreate payload /assignendpoint (no habit contract change).Shared (
packages/shared)tags.suggestendpoint constant;suggestedTagSchema+suggestTagsResponseSchemaZod types next tohabitTagSchema.acceptSuggestedTagFlowintag-selection-core(select existing id OR create+select new; honorsmaxTags, never duplicates) + tests.habits.form.*i18n keys in en.json AND pt-BR.json.Web (
apps/web) —suggestTagsServer Action (Zod-parsed at the boundary),useSuggestTags,acceptSuggestedTagonuseTagSelection,SuggestedTagsRowcomponent, wired intohabit-form-fields.tsx.Mobile (
apps/mobile) —useSuggestTags(onlineapiClientPOST, not offline-queued — an AI call needs connectivity),acceptSuggestedTag,SuggestedTagsRow(RN primitives + tokens), wired intotags-section.tsx+habit-form-fields.tsx.Why
Implements #223. Suggestions are gated/metered server-side via the shared Astra chat allowance; the button is the only trigger (never per keystroke), disabled when the title is empty or the per-habit tag limit is reached. Errors surface as a non-blocking toast and the form stays fully usable manually.
Tests
acceptSuggestedTagFlow(existing-id select, new create+select, cap respected, no-duplicate, null-create skip).useSuggestTagshook; form-fields suggest → accept-existing flow + empty-state + disabled-when-empty.useSuggestTags(onlineapiClientPOST); form-fields suggest → accept-existing flow + empty-state.type-check+lintclean across all 3 workspaces.Closes #223
Paired API PR: thomasluizon/orbit-api#259
🤖 Generated with Claude Code