Feat/cycling events - #12
Conversation
Выбор фото профиля через системную галерею (локальный URI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
readDeviceJson/writeDeviceJson/removeDeviceValue поверх булевых флагов — slice'ы не дёргают AsyncStorage напрямую. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Аватар-плейсхолдер, мультивыбор интересов и выбор фото из галереи. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
readMyActivities/addMyActivity (дедуп по slug) + activityKeys.mine, useMyActivities, useTrackMyActivity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
readMyResponses/addMyResponse (дедуп по slug) + participantKeys.mine, useMyResponses, useTrackMyResponse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
types/schemas/utils/storage/hooks/mappers + unit-тесты. Профиль живёт в AsyncStorage, без серверного аккаунта. 4 соцсети (telegram, instagram, whatsapp, website), фото — локальный URI без URL-валидации. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…писки На успешном создании/отклике дописываем в device-list через useTrackMyActivity/useTrackMyResponse. Сбой записи не роняет операцию. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
useEditProfileForm (RHF + zodResolver + useSaveProfile, префилл через mapProfileToForm) и EditProfileForm с выбором фото и интересов. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Просмотр профиля (фото, статистика, соцсети наружу, «Мои ивенты»), экран редактирования, пустое состояние. Ссылка в шапку _layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TESTS.md дополнен секциями profile (schemas/mappers/utils). В about_the_project уточнены открытое создание ивентов и модерация. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Форма всегда даёт status «going» (литерал в схеме), выбор статуса убран. participantStatusOptions больше не нужен. Статистика и список читают старые записи maybe/cant. Тесты маппера и TESTS.md — payload всегда going. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убран Select статуса из формы отклика — остаются имя, телеграм, комментарий. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Строка статистики на странице ивента — «Идут: N» вместо трёх статусов. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (18)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (13)
📝 WalkthroughWalkthroughThis PR adds a recurring series domain, device-local profile storage/editing, activity chat/capacity/share/like support, participant simplification, merged home feeds, and updated docs/tests. ChangesShared utilities
Activity enhancements
Participant simplification
Profile feature
Series feature
App shell
Docs
Estimated code review effort: 5 (Critical) | ~150 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (13)
src/shared/lib/zodFields.ts (1)
12-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeprecated
z.string().url()chained API.Per Zod v4 docs,
z.string().url()is deprecated in favor of the top-levelz.url()and will be removed in a future major version. Since this module is newly authored against zod 4.4.3, prefer the non-deprecated form.♻️ Suggested update
export const optionalUrl = z.preprocess( emptyToNull, - z.string().url("Введите корректную ссылку").nullable(), + z.url("Введите корректную ссылку").nullable(), ); ... export const chatUrl = z.preprocess( emptyToNull, z - .string() - .url("Введите корректную ссылку") + .url("Введите корректную ссылку") .refine( isKnownChatHost, "Ссылка должна вести в Telegram, WhatsApp, Discord или Signal", ) .nullable(), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/lib/zodFields.ts` around lines 12 - 41, The url validation in optionalUrl and chatUrl still uses the deprecated z.string().url() chain, so update both schemas to use the top-level z.url() API instead. Keep the existing emptyToNull preprocessing and nullable behavior, and preserve the current error message and isKnownChatHost refinement in chatUrl so the validation logic remains unchanged.src/shared/lib/publicLink.ts (1)
24-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Math.random()suffix is fine for de-duplication, not for security.Since
generateEditTokenalready usesexpo-crypto, consider reusing a crypto-based random source for the slug suffix too for consistency, though this isn't security-sensitive.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/lib/publicLink.ts` around lines 24 - 26, The slug suffix generation in shortRandom currently uses Math.random(), which is acceptable for deduplication but inconsistent with the crypto-based approach used by generateEditToken. Update shortRandom in publicLink.ts to use the same expo-crypto random source for generating the 6-character suffix, keeping the helper’s behavior the same while aligning it with the rest of the token generation logic.src/shared/ui/ShareButton.tsx (1)
13-19: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueNo guard against rapid repeated presses.
share()can be triggered multiple times before the previous call resolves (e.g. double-tap), potentially opening multiple native share sheets or firing duplicate clipboard writes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/ui/ShareButton.tsx` around lines 13 - 19, The ShareButton share() handler can be invoked multiple times before the previous request completes, causing duplicate share actions. Update ShareButton to guard against re-entry in share(), using the existing share() function and its state so rapid taps are ignored until shareActivity({ slug, title }) resolves, then clear the guard after setNotice(noticeFor(result)) runs.src/shared/lib/storage.ts (1)
16-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider runtime validation of parsed JSON.
readDeviceJsoncastsJSON.parse(raw)directly toTwith no shape validation. If stored data becomes stale/corrupted (e.g. after a schema change), consumers will silently receive malformed objects typed as validT. Since this cohort already introduces Zod validators (zodFields.ts), consider accepting an optional schema to.safeParsethe result and returnnullon mismatch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/lib/storage.ts` around lines 16 - 24, `readDeviceJson` currently trusts `JSON.parse(raw)` and returns it as `T` without checking shape, so stale or corrupted storage can flow through as if valid. Update `readDeviceJson` in `storage.ts` to accept an optional Zod schema/validator (consistent with `zodFields.ts`), parse the JSON, and run `.safeParse` before returning. If parsing fails or the value does not match the schema, return `null`; keep the existing `null` behavior for missing or invalid JSON.src/shared/lib/shareActivity.test.ts (1)
1-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing test coverage for the web fallback path.
Only the native (
Platform.OS: "ios") branch is tested here. Consider adding a separatedescribeblock withPlatform.OS: "web"to covershouldCopyInstead/copyLink, including thenavigator.clipboardunavailable and rejection paths.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/lib/shareActivity.test.ts` around lines 1 - 45, Add test coverage for the web fallback in shareActivity tests, since only the native Platform.OS: "ios" path is covered now. Create a separate describe block that mocks Platform.OS as "web" and exercises the shouldCopyInstead/copyLink flow, including cases where navigator.clipboard is missing and where clipboard write rejects. Keep the assertions focused on the shareActivity result shape and the fallback behavior.src/features/create-activity/useCreateActivityForm.ts (1)
63-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSilent tracking failures are unobservable.
The empty
catch {}is a reasonable tradeoff to avoid blocking the create flow, but it discards all error information, making tracking failures invisible in production.♻️ Add lightweight logging without changing behavior
async function rememberCreated(activity: Activity) { try { await track.mutateAsync(activity); - } catch {} + } catch (error) { + console.warn("Не удалось сохранить активность в локальный список", error); + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/create-activity/useCreateActivityForm.ts` around lines 63 - 67, The rememberCreated helper in useCreateActivityForm currently swallows track.mutateAsync failures with an empty catch, making tracking issues invisible. Keep the non-blocking behavior, but add lightweight logging in the catch block (using the existing logger/error-reporting pattern in this feature or nearby hooks) so failures are observable without affecting the create flow.src/features/create-series/useCreateSeriesForm.ts (1)
4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSeries feature depends on activity entity for an unrelated utility.
telegramToChatUrloperates on profile/telegram data but lives inentities/activity/utilsand is now imported by the series feature. This creates unnecessary cross-domain coupling: a future change to activity's utils module for activity-specific reasons risks breaking series form prefill too.♻️ Relocate the helper
-import { telegramToChatUrl } from "../../entities/activity/utils"; +import { telegramToChatUrl } from "../../shared/lib/telegram";Move the function body from
entities/activity/utils.tsto a shared/profile-owned module and update bothuseCreateActivityForm.tsanduseCreateSeriesForm.tsaccordingly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/create-series/useCreateSeriesForm.ts` at line 4, The series form is importing telegramToChatUrl from an activity-specific utils module, creating unnecessary cross-domain coupling between series and activity. Move telegramToChatUrl out of entities/activity/utils into a shared/profile-owned module, then update both useCreateActivityForm and useCreateSeriesForm to import it from the new location. Keep the helper’s behavior unchanged and ensure the series feature no longer depends on activity-only utilities.src/features/create-series/CreateSeriesForm.tsx (1)
17-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate field set with
CreateActivityForm.tsx.Title, description, city, location_text, capacity, cover_url, and chat_url fields (labels, error wiring) are copy-pasted verbatim from
CreateActivityForm.tsx; only thestarts_atlabel differs. This will drift as fields/validation messages evolve independently in each form.♻️ Extract shared core fields
+// src/features/shared/EventCoreFields.tsx +export function EventCoreFields({ control, errors, startsAtLabel }: { + control: Control<any>; + errors: FieldErrors<any>; + startsAtLabel: string; +}) { + return ( + <> + <FormField control={control} name="title" label="название, обязательно" error={errors.title?.message} /> + <FormField control={control} name="description" label="описание" component={Textarea} error={errors.description?.message} /> + <FormField control={control} name="city" label="город, обязательно" error={errors.city?.message} /> + <FormField control={control} name="location_text" label="место текстом" error={errors.location_text?.message} /> + <FormField control={control} name="starts_at" label={startsAtLabel} component={DateTimeInput} error={errors.starts_at?.message} /> + <FormField control={control} name="capacity" label="лимит мест, число или пусто" error={errors.capacity?.message} /> + <FormField control={control} name="cover_url" label="ссылка на обложку" error={errors.cover_url?.message} /> + <FormField control={control} name="chat_url" label="ссылка на чат" error={errors.chat_url?.message} /> + </> + ); +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/create-series/CreateSeriesForm.tsx` around lines 17 - 68, The form field block in CreateSeriesForm is duplicated from the shared activity form, so it should be extracted into a reusable core fields component or helper used by both CreateSeriesForm and CreateActivityForm. Move the shared FormField wiring for title, description, city, location_text, capacity, cover_url, and chat_url into a common component, and keep only the series-specific starts_at label/behavior in CreateSeriesForm so future validation or label changes stay in sync.src/shared/ui/PhotoPicker.tsx (1)
35-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winSwallowing all errors from image picker; no explicit permission request.
The
catch {}hides every failure, not just user cancellation — this can mask real configuration problems (e.g. missing photo-library usage description) with no diagnostic trail. Official Expo docs also recommend callingrequestMediaLibraryPermissionsAsync()beforelaunchImageLibraryAsyncto avoid an unexpected permission dialog appearing mid-flow.♻️ Suggested improvement
async function pickPhoto(onChange: (uri: string) => void) { try { + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) return; const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images"], quality: 1, }); if (!result.canceled) onChange(result.assets[0].uri); - } catch {} + } catch (error) { + console.warn("Не удалось выбрать фото", error); + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/ui/PhotoPicker.tsx` around lines 35 - 43, The `pickPhoto` helper in `PhotoPicker.tsx` is swallowing all image picker failures and never requests media library permission up front. Update `pickPhoto` to call `ImagePicker.requestMediaLibraryPermissionsAsync()` before `launchImageLibraryAsync`, handle denied permission explicitly, and replace the empty `catch` with error logging or propagation so real configuration issues are visible while still ignoring user cancellation in `ImagePicker.launchImageLibraryAsync`.package.json (1)
13-13: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winСверьте версию
expo-image-pickerс рекомендованной для SDK 56.В документации SDK 56 для
expo-image-pickerсейчас указана рекомендованная версия~56.0.20, а здесь зафиксирована~56.0.18. Лучше выровнять это до рекомендованного патча или хотя бы отдельно проверить, что проект не начнёт ругаться на несовместимость модуля. (docs.expo.dev)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@package.json` at line 13, The expo-image-picker dependency is pinned to an older SDK 56 patch than the recommended version. Update the version in package.json from the current expo-image-picker entry to the SDK 56 recommended patch and verify the app still installs and runs cleanly with Expo SDK 56 so there are no module compatibility warnings.app/s/[slug].tsx (1)
158-158: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse shared date-formatting helper instead of raw
toLocaleString().Other parts of the stack introduced shared datetime helpers (
src/shared/lib/datetime.ts). UsingtoLocaleString()directly here risks inconsistent date/time formatting compared to the rest of the app.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/s/`[slug].tsx at line 158, The meeting start time in the slug page is formatted with raw Date.toLocaleString(), which can drift from the app’s shared formatting. Update the render in the [slug] page to use the datetime helper from src/shared/lib/datetime instead of formatting inline. Locate the Text element that displays meeting.starts_at and replace the direct locale formatting with the shared helper so this screen matches the rest of the app’s date/time presentation.src/entities/series/mappers.ts (1)
31-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing test coverage for
mapUpdateSeriesFormToInput.
mappers.test.tscoversmapCreateSeriesFormToInput,mapJoinFormToMemberInput, andmapSeriesToUpdateForm, but not this function, despite it performing the same date-conversion logic that's tested elsewhere and being on the critical path for the manage-series save flow.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/series/mappers.ts` around lines 31 - 38, Add test coverage for mapUpdateSeriesFormToInput in mappers.test.ts. Create a case that verifies it preserves the rest of the UpdateSeriesForm fields while converting starts_at through toIsoDate, similar to the existing tests for mapCreateSeriesFormToInput and mapSeriesToUpdateForm. Keep the test focused on the manage-series save flow path by asserting the returned UpdateSeriesInput shape from mapUpdateSeriesFormToInput.src/shared/ui/ChatLinkButton.tsx (1)
8-14: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winSilent no-op if the URL can't be opened.
No
canOpenURLcheck or user-facing feedback whenopenURLfails (e.g., unsupported scheme, no handling app). Consider surfacing a fallback message so the tap isn't a dead click.Also verify
chat_urlvalues (including those produced bytelegramToChatUrl) are alwayshttps://links rather than custom URI schemes, since custom schemes may require additional iOSLSApplicationQueriesSchemesconfiguration to open reliably.💡 Proposed fix: check openability and surface failure
export function ChatLinkButton({ url }: Props) { function openChat() { - Linking.openURL(url).catch(() => {}); + Linking.canOpenURL(url) + .then((supported) => { + if (supported) return Linking.openURL(url); + // TODO: показать пользователю сообщение о недоступной ссылке + }) + .catch(() => {}); } return <Button title="Перейти в чат" onPress={openChat} />; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/ui/ChatLinkButton.tsx` around lines 8 - 14, The ChatLinkButton openChat flow silently ignores Linking.openURL failures, so taps can become dead clicks. Update ChatLinkButton to check whether the URL can be opened before calling openURL, and surface a user-facing fallback message if it cannot; keep the logic centered around openChat and Linking. Also verify that chat_url values, including those from telegramToChatUrl, are always https:// links rather than custom URI schemes so they open reliably without extra platform configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app.json`:
- Around line 25-33: The expo-image-picker plugin config in app.json is missing
the explicit microphone opt-out, so Android may still request RECORD_AUDIO
unnecessarily. Update the expo-image-picker options alongside photosPermission
to set microphonePermission to false, keeping the change localized to the
expo-image-picker entry in the plugins array.
In `@app/index.tsx`:
- Around line 17-21: The main feed filters are only applied to useActivities, so
useSeries still loads all series and the combined feed ignores city/query.
Update app/index.tsx so the same trimmed filter values are passed into useSeries
(or otherwise applied before buildFeed), keeping the activities and series data
in sync with the current filter state.
- Around line 108-115: The nextMeetingDate() helper currently falls back to
series.starts_at, which can return a past date and incorrectly mark a series as
“ближайшая” when no future meetings exist. Update nextMeetingDate() to compute
the next occurrence from the series recurrence rule instead of using the
original starts_at fallback, and keep the existing upcoming meetings logic for
active future slots.
In `@src/entities/activity/local.ts`:
- Around line 21-26: `addMyActivity` and `toggleLike` currently do a
read-modify-write against the same device storage keys without coordination, so
parallel calls can overwrite each other’s updates. Introduce a per-key
serialized/atomic update helper or queue around the
`readMyActivities`/`writeDeviceJson` flow in `src/entities/activity/local.ts`,
and route both `addMyActivity` and `toggleLike` through it so only one mutation
for a given key runs at a time.
In `@src/entities/profile/schemas.ts`:
- Around line 3-4: `profile/schemas.ts` is re-declaring the `optionalText`
validator instead of using the shared one, which can drift from the common
empty-string-to-null behavior. Remove the local `emptyToNull`/`optionalText`
setup and import the shared `optionalText` from `src/shared/lib/zodFields.ts`,
then update the schema definitions in `schemas.ts` to use that shared validator
everywhere this field pattern appears.
In `@src/entities/series/schemas.ts`:
- Around line 19-26: The recurrence schema in `series/schemas.ts` allows
unbounded expansion because `count` only checks positivity and `until` only
checks date validity. Tighten the validators in the recurrence union so `count`
has a reasonable upper limit and `until` is capped to a maximum horizon relative
to `starts_at` (or another fixed max window). Make the same bound enforcement
visible to the occurrence generation path in `ensureUpcomingMeetings` in
`api.ts` so oversized recurrences cannot materialize excessive `meetings` rows.
In `@src/entities/series/utils.test.ts`:
- Line 20: В тестах `series/utils.test.ts` строковые названия в `describe(...)`
сейчас на английском, хотя для `.ts`-файлов весь контент должен быть на русском.
Переведите названия всех затронутых `describe(...)` в этом файле на русский,
сохранив смысл и структуру групп тестов, чтобы вывод `series/utils
listOccurrences` и связанных блоков был полностью локализован.
In `@src/features/create-series/RecurrenceFields.tsx`:
- Around line 174-181: The recurrence preview in toRecurrence uses a loose
Number(draft.interval) || 1 fallback, which incorrectly converts 0 to 1 and
still lets negative values through. Update toRecurrence in RecurrenceFields so
interval is normalized with explicit validation: treat non-numeric, zero, and
negative inputs as the default positive value (or clamp to a minimum of 1)
before listOccurrences uses it, while keeping the rest of the recurrence shape
unchanged.
- Around line 128-140: The count input in RecurrenceFields is storing
unvalidated Number(count) values, which can become NaN and then render as the
literal “NaN” through value={String(value.count)}. Update the onChangeText
handler for the count field to validate/parsing-safe the input before calling
onChange, and avoid committing NaN (for example by treating invalid/empty input
as a non-update or a safe fallback). Apply the same guard in the other affected
count input block referenced by the comment so both uses of the count field stay
consistent.
In `@src/features/join-series/useJoinSeries.ts`:
- Around line 35-50: The join flow in join() treats addMembership failures the
same as remote join failures, so a local-storage error after
joinMutation.mutateAsync succeeds is misreported. Split the try/catch in
useJoinSeries so the remote mutation is handled separately from addMembership
and setMembership; if addMembership throws, surface a storage-specific error
without implying the series join failed. Keep the existing success path around
joinMutation.mutateAsync, mapJoinFormToMemberInput, and setFormServerError, and
preserve the remote member.id/series.slug state only after the server join
completes.
- Around line 52-58: The leave() flow in useJoinSeries should handle failures
the same way join() does instead of allowing unhandled rejections. Wrap
leaveMutation.mutateAsync and removeMembership in a try/catch inside leave(),
surface the error through the same error-handling path used by join(), and only
clear membership with setMembership(null) after both operations succeed.
In `@src/features/like-activity/useLikeActivity.ts`:
- Around line 25-32: The toggle() flow in useLikeActivity currently flips
isLiked optimistically but never restores the previous value when
toggleLike(slug) fails, and it allows overlapping calls from rapid taps. Update
toggle() to keep the prior liked state, rollback the optimistic setIsLiked
change inside the catch path on persistence failure, and add a simple in-flight
guard around the toggleLike call so a second toggle() invocation is ignored
until the first completes.
In `@src/features/manage-series/ManageSeriesForm.tsx`:
- Around line 108-113: The Save/Finish actions in ManageSeriesForm are using
isSaving only for the label, so repeated taps can trigger duplicate
updateMutation.mutateAsync calls. Update the Button props in ManageSeriesForm so
both the “Сохранить” and “Завершить серию” actions are disabled while isSaving
is true, following the same pattern used by JoinSeriesForm’s
disabled={isJoining}. Keep the existing handleSubmit(onSubmit) and onFinish
handlers, but gate both buttons with the shared saving state.
In `@src/features/manage-series/useManageSeriesForm.ts`:
- Around line 66-72: The reschedule/cancel helpers in useManageSeriesForm
currently call mutateAsync without handling failures and do not expose loading
state. Update rescheduleMeeting and cancelMeeting to mirror submit/finishSeries
by catching mutation errors and surfacing them through the form’s existing error
handling flow, and expose the corresponding pending flags from
rescheduleMutation and cancelMutation so MeetingRow can disable the “Перенести”
and “Отменить встречу” actions while a request is in flight.
In `@src/shared/ui/MultiSelect.tsx`:
- Around line 6-8: The Option label type in MultiSelect is too broad for how it
is used, since the value is later coerced to a string in MultiSelect and can
turn JSX into an invalid “[object Object]” label. Tighten the Option definition
and the MultiSelect rendering logic so option.label is either a string-only
field or is rendered as a ReactNode without string interpolation; update the
code paths around Option and MultiSelect to keep text labels separate from any
node-based display.
---
Nitpick comments:
In `@app/s/`[slug].tsx:
- Line 158: The meeting start time in the slug page is formatted with raw
Date.toLocaleString(), which can drift from the app’s shared formatting. Update
the render in the [slug] page to use the datetime helper from
src/shared/lib/datetime instead of formatting inline. Locate the Text element
that displays meeting.starts_at and replace the direct locale formatting with
the shared helper so this screen matches the rest of the app’s date/time
presentation.
In `@package.json`:
- Line 13: The expo-image-picker dependency is pinned to an older SDK 56 patch
than the recommended version. Update the version in package.json from the
current expo-image-picker entry to the SDK 56 recommended patch and verify the
app still installs and runs cleanly with Expo SDK 56 so there are no module
compatibility warnings.
In `@src/entities/series/mappers.ts`:
- Around line 31-38: Add test coverage for mapUpdateSeriesFormToInput in
mappers.test.ts. Create a case that verifies it preserves the rest of the
UpdateSeriesForm fields while converting starts_at through toIsoDate, similar to
the existing tests for mapCreateSeriesFormToInput and mapSeriesToUpdateForm.
Keep the test focused on the manage-series save flow path by asserting the
returned UpdateSeriesInput shape from mapUpdateSeriesFormToInput.
In `@src/features/create-activity/useCreateActivityForm.ts`:
- Around line 63-67: The rememberCreated helper in useCreateActivityForm
currently swallows track.mutateAsync failures with an empty catch, making
tracking issues invisible. Keep the non-blocking behavior, but add lightweight
logging in the catch block (using the existing logger/error-reporting pattern in
this feature or nearby hooks) so failures are observable without affecting the
create flow.
In `@src/features/create-series/CreateSeriesForm.tsx`:
- Around line 17-68: The form field block in CreateSeriesForm is duplicated from
the shared activity form, so it should be extracted into a reusable core fields
component or helper used by both CreateSeriesForm and CreateActivityForm. Move
the shared FormField wiring for title, description, city, location_text,
capacity, cover_url, and chat_url into a common component, and keep only the
series-specific starts_at label/behavior in CreateSeriesForm so future
validation or label changes stay in sync.
In `@src/features/create-series/useCreateSeriesForm.ts`:
- Line 4: The series form is importing telegramToChatUrl from an
activity-specific utils module, creating unnecessary cross-domain coupling
between series and activity. Move telegramToChatUrl out of
entities/activity/utils into a shared/profile-owned module, then update both
useCreateActivityForm and useCreateSeriesForm to import it from the new
location. Keep the helper’s behavior unchanged and ensure the series feature no
longer depends on activity-only utilities.
In `@src/shared/lib/publicLink.ts`:
- Around line 24-26: The slug suffix generation in shortRandom currently uses
Math.random(), which is acceptable for deduplication but inconsistent with the
crypto-based approach used by generateEditToken. Update shortRandom in
publicLink.ts to use the same expo-crypto random source for generating the
6-character suffix, keeping the helper’s behavior the same while aligning it
with the rest of the token generation logic.
In `@src/shared/lib/shareActivity.test.ts`:
- Around line 1-45: Add test coverage for the web fallback in shareActivity
tests, since only the native Platform.OS: "ios" path is covered now. Create a
separate describe block that mocks Platform.OS as "web" and exercises the
shouldCopyInstead/copyLink flow, including cases where navigator.clipboard is
missing and where clipboard write rejects. Keep the assertions focused on the
shareActivity result shape and the fallback behavior.
In `@src/shared/lib/storage.ts`:
- Around line 16-24: `readDeviceJson` currently trusts `JSON.parse(raw)` and
returns it as `T` without checking shape, so stale or corrupted storage can flow
through as if valid. Update `readDeviceJson` in `storage.ts` to accept an
optional Zod schema/validator (consistent with `zodFields.ts`), parse the JSON,
and run `.safeParse` before returning. If parsing fails or the value does not
match the schema, return `null`; keep the existing `null` behavior for missing
or invalid JSON.
In `@src/shared/lib/zodFields.ts`:
- Around line 12-41: The url validation in optionalUrl and chatUrl still uses
the deprecated z.string().url() chain, so update both schemas to use the
top-level z.url() API instead. Keep the existing emptyToNull preprocessing and
nullable behavior, and preserve the current error message and isKnownChatHost
refinement in chatUrl so the validation logic remains unchanged.
In `@src/shared/ui/ChatLinkButton.tsx`:
- Around line 8-14: The ChatLinkButton openChat flow silently ignores
Linking.openURL failures, so taps can become dead clicks. Update ChatLinkButton
to check whether the URL can be opened before calling openURL, and surface a
user-facing fallback message if it cannot; keep the logic centered around
openChat and Linking. Also verify that chat_url values, including those from
telegramToChatUrl, are always https:// links rather than custom URI schemes so
they open reliably without extra platform configuration.
In `@src/shared/ui/PhotoPicker.tsx`:
- Around line 35-43: The `pickPhoto` helper in `PhotoPicker.tsx` is swallowing
all image picker failures and never requests media library permission up front.
Update `pickPhoto` to call `ImagePicker.requestMediaLibraryPermissionsAsync()`
before `launchImageLibraryAsync`, handle denied permission explicitly, and
replace the empty `catch` with error logging or propagation so real
configuration issues are visible while still ignoring user cancellation in
`ImagePicker.launchImageLibraryAsync`.
In `@src/shared/ui/ShareButton.tsx`:
- Around line 13-19: The ShareButton share() handler can be invoked multiple
times before the previous request completes, causing duplicate share actions.
Update ShareButton to guard against re-entry in share(), using the existing
share() function and its state so rapid taps are ignored until shareActivity({
slug, title }) resolves, then clear the guard after setNotice(noticeFor(result))
runs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b47d749b-14f6-4181-adc5-07485ec1b573
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (86)
TESTS.mdapp.jsonapp/_layout.tsxapp/a/[slug].tsxapp/create.tsxapp/index.tsxapp/manage/[editToken].tsxapp/manage/series/[editToken].tsxapp/profile/edit.tsxapp/profile/index.tsxapp/s/[slug].tsxdocs/about_the_project.mddocs/todo.mdpackage.jsonsrc/entities/activity/api.tssrc/entities/activity/hooks.tssrc/entities/activity/local.test.tssrc/entities/activity/local.tssrc/entities/activity/mappers.test.tssrc/entities/activity/mappers.tssrc/entities/activity/schemas.test.tssrc/entities/activity/schemas.tssrc/entities/activity/types.tssrc/entities/activity/utils.test.tssrc/entities/activity/utils.tssrc/entities/participant/hooks.tssrc/entities/participant/local.test.tssrc/entities/participant/local.tssrc/entities/participant/mappers.test.tssrc/entities/participant/mappers.tssrc/entities/participant/schemas.test.tssrc/entities/participant/schemas.tssrc/entities/participant/types.tssrc/entities/participant/utils.tssrc/entities/profile/hooks.tssrc/entities/profile/mappers.test.tssrc/entities/profile/mappers.tssrc/entities/profile/schemas.test.tssrc/entities/profile/schemas.tssrc/entities/profile/storage.tssrc/entities/profile/types.tssrc/entities/profile/utils.test.tssrc/entities/profile/utils.tssrc/entities/series/api.tssrc/entities/series/hooks.tssrc/entities/series/local.test.tssrc/entities/series/local.tssrc/entities/series/mappers.test.tssrc/entities/series/mappers.tssrc/entities/series/schemas.test.tssrc/entities/series/schemas.tssrc/entities/series/types.tssrc/entities/series/utils.test.tssrc/entities/series/utils.tssrc/features/create-activity/CreateActivityForm.tsxsrc/features/create-activity/useCreateActivityForm.tssrc/features/create-series/CreateSeriesForm.tsxsrc/features/create-series/RecurrenceFields.tsxsrc/features/create-series/useCreateSeriesForm.tssrc/features/edit-profile/EditProfileForm.tsxsrc/features/edit-profile/useEditProfileForm.tssrc/features/join-series/JoinSeriesForm.tsxsrc/features/join-series/useJoinSeries.tssrc/features/like-activity/useLikeActivity.tssrc/features/manage-activity/ManageActivityForm.tsxsrc/features/manage-activity/useManageActivityForm.tssrc/features/manage-series/ManageSeriesForm.tsxsrc/features/manage-series/useManageSeriesForm.tssrc/features/mark-meeting/useMarkMeeting.tssrc/features/respond-to-activity/ResponseForm.tsxsrc/features/respond-to-activity/useRespondToActivityForm.tssrc/shared/lib/datetime.test.tssrc/shared/lib/datetime.tssrc/shared/lib/publicLink.test.tssrc/shared/lib/publicLink.tssrc/shared/lib/publicUrl.tssrc/shared/lib/shareActivity.test.tssrc/shared/lib/shareActivity.tssrc/shared/lib/storage.tssrc/shared/lib/zodFields.tssrc/shared/ui/Avatar.tsxsrc/shared/ui/ChatLinkButton.tsxsrc/shared/ui/MultiSelect.tsxsrc/shared/ui/ParticipantList.tsxsrc/shared/ui/PhotoPicker.tsxsrc/shared/ui/ShareButton.tsx
💤 Files with no reviewable changes (3)
- src/entities/participant/types.ts
- src/entities/participant/mappers.ts
- src/entities/participant/utils.ts
- главная лента фильтрует серии по городу/тексту (index, series/api,hooks) - ближайшая встреча серии считается по правилу, прошедший старт не всплывает - запись/выход из серии обрабатывают ошибки, сбой хранилища не ломает join - ограничение числа встреч в схеме серии - защита от NaN и неположительного интервала в конструкторе повторения - дизейбл кнопок сохранения/переноса/отмены и лайка на время запроса - откат оптимистичного лайка при сбое записи + guard от двойного тапа - сериализация read-modify-write device-хранилища (updateDeviceJson) - MultiSelect.label сужен до string, дубль optionalText убран в shared - microphonePermission:false у expo-image-picker, русские describe в тестах
Summary by CodeRabbit
New Features
Improvements
Tests
Chores