Release/2.0.16#1993
Conversation
- Add avatarUrl column to users table with migration - Update API UserSchema and handlers to support avatarUrl - Add useUpdateProfile hook for saving profile changes - Fix name.tsx to use real user data and save via API - Make profile avatar tappable with image picker and upload - Add navigation from name row to name edit screen Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
- Remove misleading middle name field (no DB column for it) - Fix silent failure: check updateProfile return value after R2 upload - Fix upload spinner: overlay on TouchableOpacity so it shows over avatar images - Fix canSave baseline: use useRef to capture initial name values at mount - Add 5 MB file size validation before avatar upload - Add profile.imageTooLarge translation key Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
- Show permission-specific alert with Open Settings button when photo library access is denied (instead of silently swallowing the error) - Trim whitespace in canSave and handleSave so whitespace-only names are rejected; use useMemo to avoid duplication and redundant trims - Guard handleSave with !canSave || isLoading to prevent double-submit via keyboard Return key bypassing the disabled button state - Add permissions and common.cancel i18n keys Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
fix: upgrade nativewindui dep
Fix share button on trip
… with gemini for content analysis
Feat/Create Pack Template from Video
📝 WalkthroughWalkthroughThis PR bumps the app version to 2.0.16 across the workspace while introducing avatar upload functionality in user profiles, extending TikTok import to support video content alongside slideshows, switching the AI provider from OpenAI to Google Generative AI, adding profile update persistence via a new hook, and extending database/API schemas to accommodate avatar URLs. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as Profile Screen
participant ImagePicker as Image Picker
participant Validation as Size Validator
participant API as Backend API
participant R2 as R2 Storage
participant Store as User Store
User->>UI: Tap avatar to edit
UI->>ImagePicker: Open image picker
ImagePicker->>UI: Return selected image
UI->>Validation: Check file size (< 5 MB)
alt Size valid
Validation->>API: PUT /api/user/profile with avatarUrl
API->>R2: Upload avatar image
R2-->>API: Return public URL
API->>Store: Update user with avatarUrl
Store-->>UI: Update profile header
UI->>User: Display new avatar
else Size exceeds limit
Validation->>UI: Show error alert (translated)
UI->>User: "Image must be smaller than 5 MB"
else Upload failure
API->>UI: Return error response
UI->>User: Show error alert with details
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can get early access to new features in CodeRabbit.Enable the |
There was a problem hiding this comment.
Pull request overview
This PR advances the 2.0.16 release by adding profile editing (name + avatar) in the Expo app, extending the API/user schema to persist avatarUrl, and enhancing TikTok import/generation to support video content analyzed via Google Gemini.
Changes:
- Add
avatarUrlto the user model (DB/schema/API responses) and introduce a migration. - Add Expo profile name editing + avatar upload flow with new i18n strings and client-side upload validation.
- Update TikTok template generation/import to support video content and switch AI analysis to Gemini; bump versions/dependencies to 2.0.16.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ui/package.json | Bumps @packrat/ui version and updates @packrat-ai/nativewindui dependency. |
| packages/api/test/setup.ts | Adds test env var for Google Generative AI key. |
| packages/api/src/utils/env-validation.ts | Adds GOOGLE_GENERATIVE_AI_API_KEY to env validation schema. |
| packages/api/src/schemas/users.ts | Extends user/update schemas to include avatarUrl. |
| packages/api/src/schemas/packTemplates.ts | Updates TikTok schema copy to reflect video/slideshow support. |
| packages/api/src/routes/user/index.ts | Includes avatarUrl in profile read/update responses and update payload handling. |
| packages/api/src/routes/packTemplates/generateFromTikTok.ts | Switches TikTok generation analysis to Gemini and supports video content paths. |
| packages/api/src/db/schema.ts | Adds avatarUrl column to users table schema. |
| packages/api/package.json | Adds @ai-sdk/google dependency for Gemini usage. |
| packages/api/drizzle/meta/_journal.json | Records a new migration entry for 0034_thin_spirit. |
| packages/api/drizzle/meta/0034_snapshot.json | New Drizzle snapshot for the updated schema. |
| packages/api/drizzle/0034_thin_spirit.sql | Migration adding users.avatar_url. |
| packages/api/drizzle/0033_add_avatar_url_to_users.sql | Additional migration adding users.avatar_url (duplicates 0034). |
| packages/api/container_src/server.ts | Expands TikTok container import to handle videos and rehost video/media types. |
| packages/api/container_src/package.json | Bumps container package version to 2.0.16. |
| package.json | Bumps monorepo version to 2.0.16. |
| bun.lock | Updates dependencies (incl. nativewind, @ai-sdk/google) but workspace versions remain at 2.0.15. |
| apps/landing/package.json | Bumps landing app version to 2.0.16. |
| apps/guides/package.json | Bumps guides app version to 2.0.16. |
| apps/expo/package.json | Bumps Expo app version to 2.0.16 and updates nativewind. |
| apps/expo/lib/i18n/locales/en.json | Adds avatar/permission strings and updates TikTok copy to mention video support. |
| apps/expo/features/trips/screens/TripDetailScreen.tsx | Adds React Native Share usage (for trip sharing). |
| apps/expo/features/profile/types.ts | Adds optional avatarUrl to the client User type. |
| apps/expo/features/profile/hooks/useUpdateProfile.ts | Introduces a hook to update profile fields and sync the user store. |
| apps/expo/app/(app)/(tabs)/profile/name.tsx | Implements name edit screen with validation/loading and API update. |
| apps/expo/app/(app)/(tabs)/profile/index.tsx | Adds navigation to name edit and implements avatar upload + loading overlay. |
| apps/expo/app.config.ts | Bumps Expo app config version to 2.0.16. |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/expo/features/trips/screens/TripDetailScreen.tsx (1)
4-4:⚠️ Potential issue | 🟡 MinorRemove unused Alert component and alertRef declaration.
The
Alertcomponent on line 233 and thealertRefon line 27 are unused dead code. The Alert is rendered with an empty title and empty buttons array, andalertRefis never called to perform any action. Remove both:- const alertRef = useRef<AlertRef>(null);- type AlertRef,- <Alert title="" buttons={[]} ref={alertRef} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/expo/features/trips/screens/TripDetailScreen.tsx` at line 4, Remove the unused Alert component render and the unused alertRef declaration: delete the Alert import/usage (the Alert component rendered with empty title and buttons) and remove the alertRef variable/type (e.g., alertRef) and any related unused AlertRef type import so there is no dead code; ensure any references to alertRef or the Alert component (such as in the render tree and top-level declarations) are removed from TripDetailScreen.tsx and that imports are cleaned up accordingly.
🧹 Nitpick comments (2)
apps/expo/features/trips/screens/TripDetailScreen.tsx (1)
27-27: Consider removing unusedalertRefandAlertcomponent.The
alertRefis declared but never used in any handler or effect. TheAlertcomponent at line 233 is rendered with an empty title and empty buttons array, making it non-functional. This appears to be leftover scaffolding.🧹 Remove unused code
Remove the import of
AlertandAlertReffrom line 3-4, removeuseRefif no longer needed, and delete:- const alertRef = useRef<AlertRef>(null);- <Alert title="" buttons={[]} ref={alertRef} />Also applies to: 233-233
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/expo/features/trips/screens/TripDetailScreen.tsx` at line 27, Remove the unused alertRef and Alert component: delete the declaration alertRef (useRef<AlertRef>(null)) and remove any unused imports Alert and AlertRef (and useRef if it's not used elsewhere), then remove the rendered <Alert ... /> JSX (the empty-title, empty-buttons instance) and any related unused handlers or props referencing alertRef; ensure no remaining references to alertRef, Alert, or AlertRef remain in TripDetailScreen.tsx.apps/expo/features/profile/hooks/useUpdateProfile.ts (1)
12-34: UseuseMutationhere instead of ad-hoc request state.This new Expo feature hook is bypassing the React Query pattern the app uses elsewhere, so you lose standardized mutation state and invalidation hooks.
#!/bin/bash # Inspect existing React Query usage across Expo feature hooks. rg -n "useMutation|useQuery|useInfiniteQuery" apps/expo/features -g "**/hooks/**/*.{ts,tsx}"As per coding guidelines:
apps/expo/features/*/hooks/**/*.ts{,x}: Use React Query hooks (useQuery,useMutation,useInfiniteQuery) for data fetching in Expo feature hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/expo/features/profile/hooks/useUpdateProfile.ts` around lines 12 - 34, The hook useUpdateProfile currently implements manual loading/error state and an async updateProfile that calls axiosInstance.put; replace this with React Query's useMutation: create a mutation that calls axiosInstance.put('/api/user/profile', payload) (handling errors via handleApiError inside onError), remove local isLoading and error state and instead return the mutation object (or wrap mutation.mutateAsync to preserve updateProfile API), and in the mutation's onSuccess set userStore.set(response.data.user) and invalidate relevant queries via queryClient.invalidateQueries (e.g., the current "user" or profile query key) so other hooks update consistently; ensure you import useMutation and useQueryClient and propagate mutation status instead of bespoke flags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/expo/app/`(app)/(tabs)/profile/index.tsx:
- Around line 139-159: Import Linking from 'react-native' and remove the invalid
option passed to FileSystem.getInfoAsync: call
FileSystem.getInfoAsync(image.uri) (no { size: true } argument) so the returned
FileInfo.size is used for the AVATAR_MAX_BYTES check; also ensure the Permission
error branch that calls Linking.openSettings() compiles by adding the Linking
import. Locate references to FileSystem.getInfoAsync, AVATAR_MAX_BYTES,
uploadImage, and updateProfile in the profile component to apply the fix.
In `@apps/expo/app/`(app)/(tabs)/profile/name.tsx:
- Around line 18-24: The form state is initialized from refs
(initialFirst/initialLast) and never updates when user loads; add a
React.useEffect that watches user and calls setForm({ first: user.firstName ||
'', last: user.lastName || '' }) when user data becomes available so the form
reflects loaded values; target the component where initialFirst, initialLast,
form and setForm are defined and add the effect with [user] as the dependency
array.
In `@apps/expo/features/profile/hooks/useUpdateProfile.ts`:
- Around line 20-22: Replace the manual useState-based request handling in
useUpdateProfile.ts with React Query's useMutation (useMutation hook) to perform
the axiosInstance.put('/api/user/profile', payload) call, and in the mutation's
onSuccess handler merge the returned subset of user fields into the existing
userStore instead of replacing it (call userStore.set with a merged object
combining current userStore.get() and response.data.user so
id/role/preferredWeightUnit remain intact). Ensure the hook exports the mutation
object (mutate/mutateAsync and status) so callers use the React Query pattern.
In `@packages/api/container_src/server.ts`:
- Around line 509-516: The current code unconditionally sets
responseData.expiresAt when s3Client && env && (hasImages || hasVideo), which
can attach a TTL to fallback URLs; update the logic so expiresAt is only set for
assets actually rehosted: for video only set expiresAt when hasVideo &&
!videoFailed && finalVideoUrl points to your R2 host, and for images only set
expiresAt when hasImages and at least one image was successfully rehosted (e.g.
imageFailedCount < totalImages or by detecting rehosted image URLs);
alternatively create per-media fields (e.g. imageExpiresAt, videoExpiresAt)
instead of a global responseData.expiresAt to avoid implying TTL for fallback
URLs. Ensure you update the branch that currently sets responseData.expiresAt
and keep the existing failedImages/failedVideo fields (imageFailedCount,
videoFailed) unchanged.
- Around line 240-259: Before calling response.arrayBuffer(), guard against
oversized responses by checking response.headers.get('content-length') and
rejecting when it exceeds a configured max (e.g., MAX_VIDEO_BYTES) and also
handle missing/unknown lengths by streaming and enforcing a running byte
counter; in the block where you fetch videoUrl and use response and videoBuffer,
if Content-Length > MAX_VIDEO_BYTES throw an error and abort, otherwise stream
response.body to R2 (or consume with a reader that increments a counter and
throws once MAX_VIDEO_BYTES is exceeded) instead of awaiting
response.arrayBuffer() to avoid buffering the entire file in memory.
- Around line 355-383: Define and apply a Zod schema for the Downloader()
response before using result: create a TikTokDownloaderResultSchema that
validates top-level status and result with fields type (enum 'video'|'image'),
id (string), desc (optional string), video (optional object with playAddr as
string), and images (optional string array); then parse/validate the
Downloader() output (use safeParse or parse) and replace direct access to
result.result with the validated object; update branching in the code that sets
videoUrl, imageUrls, caption, and contentId to use the validatedResult.result
fields, handle playAddr as a string (not an array) when extracting videoUrl, and
throw or return a clear validation error if the schema parse fails so downstream
code never operates on an unexpected shape.
- Around line 452-460: Replace the inline anonymous response type with a Zod
schema (e.g., create a responseSchema using z.object(...) that matches the
suggested shape including imageUrls, optional videoUrl, caption, contentId,
expiresAt, failedImages and failedVideo) and infer the TypeScript type via
z.infer<typeof responseSchema>; then, before returning or serializing the value
currently held in responseData inside server.ts (and similarly for the block at
lines around 501-506), run responseSchema.parse(responseData) and return the
parsed result so malformed URLs, invalid dates, or negative numbers are rejected
at runtime; import z from 'zod' and reference responseSchema and the parsed
variable in the existing handler where responseData is constructed.
In `@packages/api/drizzle/0034_thin_spirit.sql`:
- Line 1: Delete the duplicate migration file 0034_thin_spirit.sql (which
repeats ALTER TABLE "users" ADD COLUMN "avatar_url" text;) so the schema change
only exists in 0033_add_avatar_url_to_users.sql, and then remove the
corresponding "0034_thin_spirit" entry from
packages/api/drizzle/meta/_journal.json (and its snapshot) so the migration
journal no longer references the deleted migration.
In `@packages/api/package.json`:
- Line 17: Tests fail because the real `@ai-sdk/google` SDK is being exercised;
update the test setup to mock the module so routes using Google Generative AI
don’t call the real SDK: add a module mock for "@ai-sdk/google" in
packages/api/test/setup.ts (or the global test setup) that exports the
clients/functions your code uses (e.g., the client constructor and any
generate/response methods) and return deterministic fixtures for calls that
reference the model name "gemini-3-flash-preview"; keep the existing
GOOGLE_GENERATIVE_AI_API_KEY env var mock but ensure the mock is registered
before tests run so routes using the SDK use the mocked implementation.
In `@packages/api/test/setup.ts`:
- Line 26: The test setup adds GOOGLE_GENERATIVE_AI_API_KEY but does not mock
the `@ai-sdk/google` module or the ai.generateObject function; update
packages/api/test/setup.ts to add a mock for createGoogleGenerativeAI from
'@ai-sdk/google' and extend the existing ai mock (the module mocked where
streamText is defined) to also provide a stubbed generateObject used by
generateFromTikTok.ts, ensuring tests that call generateFromTikTok use the
mocked generateObject and any expected return shape.
In `@packages/ui/package.json`:
- Line 6: The dependency bump of `@packrat-ai/nativewindui` to ^2.0.0 in
packages/ui/package.json breaks downstream consumers; revert this change by
restoring the previous major version for `@packrat-ai/nativewindui` in
packages/ui/package.json (or pin to the exact prior version used in lockfile)
until all consumer migration fixes (AlertRef API and theme typings) are included
in this PR; locate the dependency entry for "@packrat-ai/nativewindui" in
packages/ui/package.json and change it back to the prior major (or exact prior
version) and update package lockfiles accordingly.
---
Outside diff comments:
In `@apps/expo/features/trips/screens/TripDetailScreen.tsx`:
- Line 4: Remove the unused Alert component render and the unused alertRef
declaration: delete the Alert import/usage (the Alert component rendered with
empty title and buttons) and remove the alertRef variable/type (e.g., alertRef)
and any related unused AlertRef type import so there is no dead code; ensure any
references to alertRef or the Alert component (such as in the render tree and
top-level declarations) are removed from TripDetailScreen.tsx and that imports
are cleaned up accordingly.
---
Nitpick comments:
In `@apps/expo/features/profile/hooks/useUpdateProfile.ts`:
- Around line 12-34: The hook useUpdateProfile currently implements manual
loading/error state and an async updateProfile that calls axiosInstance.put;
replace this with React Query's useMutation: create a mutation that calls
axiosInstance.put('/api/user/profile', payload) (handling errors via
handleApiError inside onError), remove local isLoading and error state and
instead return the mutation object (or wrap mutation.mutateAsync to preserve
updateProfile API), and in the mutation's onSuccess set
userStore.set(response.data.user) and invalidate relevant queries via
queryClient.invalidateQueries (e.g., the current "user" or profile query key) so
other hooks update consistently; ensure you import useMutation and
useQueryClient and propagate mutation status instead of bespoke flags.
In `@apps/expo/features/trips/screens/TripDetailScreen.tsx`:
- Line 27: Remove the unused alertRef and Alert component: delete the
declaration alertRef (useRef<AlertRef>(null)) and remove any unused imports
Alert and AlertRef (and useRef if it's not used elsewhere), then remove the
rendered <Alert ... /> JSX (the empty-title, empty-buttons instance) and any
related unused handlers or props referencing alertRef; ensure no remaining
references to alertRef, Alert, or AlertRef remain in TripDetailScreen.tsx.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a02b790a-f4b5-4641-acbe-933af37c21ac
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (26)
apps/expo/app.config.tsapps/expo/app/(app)/(tabs)/profile/index.tsxapps/expo/app/(app)/(tabs)/profile/name.tsxapps/expo/features/profile/hooks/useUpdateProfile.tsapps/expo/features/profile/types.tsapps/expo/features/trips/screens/TripDetailScreen.tsxapps/expo/lib/i18n/locales/en.jsonapps/expo/package.jsonapps/guides/package.jsonapps/landing/package.jsonpackage.jsonpackages/api/container_src/package.jsonpackages/api/container_src/server.tspackages/api/drizzle/0033_add_avatar_url_to_users.sqlpackages/api/drizzle/0034_thin_spirit.sqlpackages/api/drizzle/meta/0034_snapshot.jsonpackages/api/drizzle/meta/_journal.jsonpackages/api/package.jsonpackages/api/src/db/schema.tspackages/api/src/routes/packTemplates/generateFromTikTok.tspackages/api/src/routes/user/index.tspackages/api/src/schemas/packTemplates.tspackages/api/src/schemas/users.tspackages/api/src/utils/env-validation.tspackages/api/test/setup.tspackages/ui/package.json
This pull request introduces user profile editing features in the Expo app, most notably allowing users to update their name and upload a profile avatar. It also includes improvements to the profile UI, error handling, and internationalization for related messages. Several package versions are also bumped to
2.0.16.Profile editing and avatar upload:
/profile/namescreen, which includes validation, loading states, and error handling. The UI is streamlined and the middle name field is removed. [1] [2] [3] [4] [5]Profile update logic:
useUpdateProfilehook is introduced to handle profile updates via the API, manage loading/error states, and update the user store on success.Usertype is updated to include an optionalavatarUrlproperty.UI and UX improvements:
Internationalization and copy updates:
Dependency and version updates:
2.0.16across the monorepo. [1] [2] [3] [4] [5] [6]nativewinddependency to^4.2.3.These changes collectively improve the user profile experience, enable avatar uploads, and enhance error handling and internationalization support.
Summary by CodeRabbit
Release Notes – v2.0.16
New Features
Chores