Skip to content

Feat/notifications - #13

Open
anu-mdl wants to merge 39 commits into
mainfrom
feat/notifications
Open

Feat/notifications#13
anu-mdl wants to merge 39 commits into
mainfrom
feat/notifications

Conversation

@anu-mdl

@anu-mdl anu-mdl commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Browse activities and recurring series together, with city/title filtering and upcoming-date sorting.
    • Create and manage recurring event series, meetings, memberships, and attendance marks.
    • Added profile viewing/editing with avatar, interests, social links, statistics, and logout.
    • Added sharing, likes, chat links, capacity notices, photo selection, and local activity tracking.
    • Added event reminders and confirmation messages for scheduled notifications.
  • Improvements
    • Simplified activity responses to confirmed attendance.
    • Improved form validation, Telegram links, date handling, and error messaging.
  • Documentation
    • Updated testing guidance, project overview, and product backlog.

anu-mdl and others added 30 commits June 28, 2026 18:05
Выбор фото профиля через системную галерею (локальный 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>
Форма всегда даёт 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>
anu-mdl added 9 commits June 28, 2026 22:02
- главная лента фильтрует серии по городу/тексту (index, series/api,hooks)
- ближайшая встреча серии считается по правилу, прошедший старт не всплывает
- запись/выход из серии обрабатывают ошибки, сбой хранилища не ломает join
- ограничение числа встреч в схеме серии
- защита от NaN и неположительного интервала в конструкторе повторения
- дизейбл кнопок сохранения/переноса/отмены и лайка на время запроса
- откат оптимистичного лайка при сбое записи + guard от двойного тапа
- сериализация read-modify-write device-хранилища (updateDeviceJson)
- MultiSelect.label сужен до string, дубль optionalText убран в shared
- microphonePermission:false у expo-image-picker, русские describe в тестах
Добавлен entity reminder (планирование/отмена уведомлений, настройки)
и подключение к откликам на активности/серии и отметке "иду".
@anu-mdl
anu-mdl requested a review from lukivan8 July 12, 2026 08:48
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds recurring series creation and management, profile editing, local activity and response tracking, reminders, sharing, likes, chat links, feed filtering, reusable validation/UI components, and native notification configuration.

Changes

Application and activity experience

Layer / File(s) Summary
Activity and feed experience
app/index.tsx, app/a/[slug].tsx, app/create.tsx, src/entities/activity/*
The home feed filters and combines activities with series; activity pages show capacity, likes, sharing, chat links, and reminder confirmation; creation supports activity or series modes.
Shared platform and UI
src/shared/lib/*, src/shared/ui/*, app.json, package.json, app/_layout.tsx
Shared datetime, storage, public-link, sharing, validation, notification, avatar, picker, selection, and link components are added or updated.
Participant and profile flows
src/entities/participant/*, src/entities/profile/*, app/profile/*, src/features/edit-profile/*
Participant responses are limited to going; profile persistence, editing, social links, categories, avatar/photo selection, and profile navigation are added.

Recurring series

Layer / File(s) Summary
Series domain and recurrence
src/entities/series/types.ts, src/entities/series/schemas.ts, src/entities/series/utils.ts, src/entities/series/api.ts, src/entities/series/hooks.ts
Series, meetings, recurrence rules, memberships, marks, Supabase operations, recurrence generation, and React Query integration are introduced.
Series creation and management
src/features/create-series/*, src/features/manage-series/*, app/manage/series/[editToken].tsx
Forms support recurrence previews, validation, series creation, editing, meeting rescheduling/cancellation, finishing, and member lists.
Series participation
src/features/join-series/*, src/features/mark-meeting/*, app/s/[slug].tsx
Users can join or leave series, view meetings and attendance counts, toggle meeting marks, and move reminders after skipping meetings.

Reminder and supporting documentation

Layer / File(s) Summary
Reminder lifecycle
src/entities/reminder/*, src/shared/lib/notifications.ts, src/features/respond-to-activity/useRespondToActivityForm.ts
Reminder settings, scheduling, cancellation, local persistence, notification permissions, and response cancellation behavior are implemented.
Documentation and test expectations
TESTS.md, docs/about_the_project.md, docs/todo.md, src/**/*.test.ts
Test descriptions, unit coverage, project documentation, and the prioritized product backlog are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • linkerkz/project-e#6: Earlier implementation and refactoring of shared activity utilities, mappers, and participant behavior continued by this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the main theme of the PR, which adds notifications and reminder-related behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/notifications

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (5)
src/shared/lib/zodFields.ts (1)

3-5: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider trimming whitespace in emptyToNull.

emptyToNull converts "" and undefined to null, but a whitespace-only string like " " passes through as a valid value. For optionalText fields (e.g., city, location_text, socials), this means whitespace-only input is stored rather than treated as empty. URL and capacity fields are unaffected since downstream validation rejects whitespace.

♻️ Proposed refactor
-const emptyToNull = (v: unknown) => (v === "" || v === undefined ? null : v);
+const emptyToNull = (v: unknown) => {
+  if (typeof v === "string") {
+    const trimmed = v.trim();
+    return trimmed === "" ? null : trimmed;
+  }
+  return v === undefined ? null : v;
+};
🤖 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 3 - 5, Update emptyToNull to treat
whitespace-only strings as empty by checking the trimmed string value before
returning null, while preserving existing handling of undefined and non-empty
strings. Keep optionalText using emptyToNull so affected text fields receive the
normalized null value.
src/shared/ui/Avatar.tsx (1)

8-23: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add onError fallback to the Image so broken photo URLs show the letter avatar.

If photoUrl is stale or unreachable, the Image renders blank with no visual fallback. Swapping to the letter avatar on error is a small change that meaningfully improves resilience.

♻️ Proposed refactor with onError fallback
 export function Avatar({ photoUrl, name }: Props) {
+  const [imgFailed, setImgFailed] = useState(false);
+
   if (photoUrl != null && !imgFailed) {
     return (
       <Image
         source={{ uri: photoUrl }}
         className="h-20 w-20 border border-gray-400"
+        onError={() => setImgFailed(true)}
       />
     );
   }
🤖 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/Avatar.tsx` around lines 8 - 23, Update the Image rendered by
Avatar to track load failure via onError and switch to the existing
letter-avatar View using firstLetter(name) when the photo URL is stale or
unreachable. Preserve the current image rendering for successful loads and the
existing photoUrl-null fallback.
src/shared/lib/storage.ts (1)

30-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Объявите writeQueues до updateDeviceJson для читаемости.

writeQueues (строка 51) объявлена после функции updateDeviceJson (строки 32–49), которая её использует. Это безопасно во время выполнения — функция вызывается уже после инициализации модуля, — но нарушает естественный порядок «сначала объявление, потом использование».

♻️ Предлагаемый рефакторинг
+const writeQueues = new Map<string, Promise<unknown>>();
+
 // Read-modify-write по ключу, сериализованный в очередь: параллельные апдейты
 // не затирают друг друга, а выполняются по одному. Возвращает новое значение.
 export async function updateDeviceJson<T>(
@@ -48,8 +49,6 @@
   return result;
 }
-
-const writeQueues = new Map<string, Promise<unknown>>();
🤖 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 30 - 51, Переместите объявление
константы writeQueues перед функцией updateDeviceJson, сохранив её тип,
инициализацию и остальную логику очереди без изменений.
src/features/mark-meeting/useMarkMeeting.ts (1)

30-31: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Silent error swallowing hides reminder failures.

.catch(() => {}) discards all errors from moveReminderPastSkip. If cancelReminder succeeds but scheduleReminder fails, the user silently loses their reminder with no diagnostic trace. Add a console.warn to aid debugging without changing control flow.

🔊 Proposed fix: log swallowed errors
-    if (status === "skip")
-      await moveReminderPastSkip(meetingId).catch(() => {});
+    if (status === "skip")
+      await moveReminderPastSkip(meetingId).catch((error) => {
+        console.warn("moveReminderPastSkip failed", 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/mark-meeting/useMarkMeeting.ts` around lines 30 - 31, Update the
moveReminderPastSkip error handler in the status === "skip" branch to emit a
console.warn containing the caught error before swallowing it. Preserve the
existing non-blocking control flow and do not change reminder handling behavior.
src/entities/series/hooks.ts (1)

163-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Используйте seriesKeys вместо захардкоженного ключа.

useUpsertMark инвалидирует ["series", "marks"] напрямую, тогда как остальной код использует seriesKeys.*. Добавьте allMarks в seriesKeys для консистентности.

♻️ Предлагаемый рефакторинг
 export const seriesKeys = {
   all: ["series"] as const,
+  allMarks: ["series", "marks"] as const,
   list: (filter: SeriesFilter) => ["series", "list", filter] as const,
   // ...
 };

Then update useUpsertMark:

     onSuccess: () =>
-      queryClient.invalidateQueries({ queryKey: ["series", "marks"] }),
+      queryClient.invalidateQueries({ queryKey: seriesKeys.allMarks }),
🤖 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/hooks.ts` around lines 163 - 170, Добавьте ключ allMarks
в объект seriesKeys и обновите onSuccess в useUpsertMark, чтобы
invalidateQueries использовал seriesKeys.allMarks вместо захардкоженного
["series", "marks"], сохранив текущую инвалидизацию после успешного upsertMark.
🤖 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/index.tsx`:
- Around line 113-127: Update nextMeetingDate so its listOccurrences call does
not use the default horizon of 5; pass a sufficiently large dedicated horizon or
use an unbounded way to find the first future recurrence. Preserve the existing
active-meeting candidates, filtering, sorting, and null behavior when no future
date exists.

In `@src/entities/activity/utils.test.ts`:
- Line 8: Переведите название набора тестов в describe("activity/utils", ...) на
русский язык, сохранив область тестов и остальную структуру без изменений.

In `@src/entities/profile/schemas.test.ts`:
- Line 13: Переведите название набора тестов в describe("profile/schemas") на
русский язык, сохранив охватываемый набор тестов и остальную структуру без
изменений.

In `@src/entities/profile/utils.ts`:
- Around line 50-57: Update formatRegisteredAt to use getUTCDate(),
getUTCMonth(), and getUTCFullYear() when formatting valid ISO timestamps, while
preserving the existing invalid-date fallback and padding behavior.

In `@src/entities/reminder/schedule.ts`:
- Around line 21-44: Update scheduleReminder to cancel any existing reminder for
the same target.slug before scheduling a new notification, reusing the existing
reminder lookup and cancellation APIs. Perform this cancellation before
scheduleNotificationAt, while preserving the current success and early-return
behavior.

In `@src/entities/series/hooks.ts`:
- Around line 143-162: Update the onSuccess handlers in useJoinSeries and
useLeaveSeries to invalidate both seriesKeys.members(seriesId) and
seriesKeys.memberships, ensuring useMyMemberships refreshes after joining or
leaving while preserving the existing member-list invalidation.

In `@src/entities/series/utils.test.ts`:
- Line 20: Переведите названия наборов тестов в describe-блоках, включая наборы
вокруг series/utils и связанные блоки на строках 97 и 111, с английского на
русский язык; изменяйте только текст названий, сохранив тестовую логику без
изменений.
- Around line 39-41: Remove the fixed spacing(dates, 0) assertion from the
weekly test. Keep the dates.map(ourWeekday) validation so the test verifies the
expected weekdays without assuming a constant millisecond interval across DST
transitions.

In `@src/entities/series/utils.ts`:
- Around line 17-24: В функции, формирующей список дат вокруг occurrenceLimit,
добавьте ранний возврат пустого списка при нулевом лимите до запуска
occurrenceSequence и добавления первой даты. Сохраните текущую обработку
положительных лимитов, until и ограничения по количеству встреч.
- Around line 101-105: Update the recurrence loop around current and advance so
monthly generation does not roll dates 29–31 into the following month. Add a
dedicated month calculation that clamps the target day to the last day of the
target month, while preserving the existing advance behavior for other
recurrence units.

In `@src/features/create-series/RecurrenceFields.tsx`:
- Around line 41-45: Переведите латинскую метку «N встреч» в массиве endOptions
на русский язык, сохранив смысл варианта количества встреч, и замените
англоязычный термин `coerce` в комментарии возле соответствующего кода на
русский эквивалент. Не изменяйте поведение компонента.

In `@src/features/create-series/useCreateSeriesForm.ts`:
- Around line 65-69: Добавьте минимальное предупреждающее логирование в пустой
блок catch функции rememberCreated, сохранив подавление ошибки и основной
сценарий работы без изменений.

In `@src/features/edit-profile/useEditProfileForm.ts`:
- Around line 40-41: Переведите комментарий, описывающий передачу валидированной
формы напрямую в хранилище, на русский язык, сохранив его смысл и расположение
рядом с соответствующим payload.

In `@src/features/join-series/useJoinSeries.ts`:
- Around line 51-55: Переведите оставшийся англоязычный термин в комментариях
функции useJoinSeries, включая комментарий около строки 98, на русский язык; не
изменяйте поведение addMembership и planReminder.
- Around line 66-69: Update the exit flow containing leaveMutation.mutateAsync,
removeMembership, and setMembership so a failure from removeMembership does not
prevent clearing the local membership state. Preserve the completed server-side
leave and reminder cancellation behavior, and ensure setMembership(null) runs
even when local removal rejects.
- Around line 102-115: В `useEffect` вокруг `findMembership` сбрасывайте
`membership` в `null` сразу при каждом изменении `slug`, до проверки на пустое
значение и запуска запроса. Для пустого `slug` после сброса сразу возвращайтесь;
для непустого сохраняйте текущую защиту `active`, чтобы результат предыдущей
серии не применялся.

In `@src/features/respond-to-activity/useRespondToActivityForm.ts`:
- Around line 49-52: Update the cancellation branch in the useEffect to set
reminderScheduled to false when activity.status is "cancelled", alongside
cancelReminder(activity.slug). Keep the existing dependency list and reminder
cancellation behavior unchanged.

In `@src/shared/lib/publicLink.test.ts`:
- Line 9: Переведите человекочитаемое название набора тестов в describe для
shared/publicLink на русский язык, сохранив структуру тестов и идентификатор
модуля без изменений.

In `@src/shared/ui/ChatLinkButton.tsx`:
- Around line 9-11: Update openChat in ChatLinkButton so failures from
Linking.openURL(url) are no longer silently swallowed; provide user-visible
feedback when opening the chat fails, while preserving the existing successful
URL-opening behavior.

In `@src/shared/ui/PhotoPicker.tsx`:
- Around line 37-41: Инициализируйте восстановление отложенного результата через
ImagePicker.getPendingResultAsync() в компоненте PhotoPicker или общем
провайдере, чтобы результат после пересоздания MainActivity передавался в
onChange. Сохраните текущую обработку результата launchImageLibraryAsync и
применяйте восстановленный asset только при наличии результата и URI.

---

Nitpick comments:
In `@src/entities/series/hooks.ts`:
- Around line 163-170: Добавьте ключ allMarks в объект seriesKeys и обновите
onSuccess в useUpsertMark, чтобы invalidateQueries использовал
seriesKeys.allMarks вместо захардкоженного ["series", "marks"], сохранив текущую
инвалидизацию после успешного upsertMark.

In `@src/features/mark-meeting/useMarkMeeting.ts`:
- Around line 30-31: Update the moveReminderPastSkip error handler in the status
=== "skip" branch to emit a console.warn containing the caught error before
swallowing it. Preserve the existing non-blocking control flow and do not change
reminder handling behavior.

In `@src/shared/lib/storage.ts`:
- Around line 30-51: Переместите объявление константы writeQueues перед функцией
updateDeviceJson, сохранив её тип, инициализацию и остальную логику очереди без
изменений.

In `@src/shared/lib/zodFields.ts`:
- Around line 3-5: Update emptyToNull to treat whitespace-only strings as empty
by checking the trimmed string value before returning null, while preserving
existing handling of undefined and non-empty strings. Keep optionalText using
emptyToNull so affected text fields receive the normalized null value.

In `@src/shared/ui/Avatar.tsx`:
- Around line 8-23: Update the Image rendered by Avatar to track load failure
via onError and switch to the existing letter-avatar View using
firstLetter(name) when the photo URL is stale or unreachable. Preserve the
current image rendering for successful loads and the existing photoUrl-null
fallback.
🪄 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: d21f2ce6-4305-42dc-99e8-8d3ce8fe87e2

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4b49a and 587f9da.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (95)
  • TESTS.md
  • app.json
  • app/_layout.tsx
  • app/a/[slug].tsx
  • app/create.tsx
  • app/index.tsx
  • app/manage/[editToken].tsx
  • app/manage/series/[editToken].tsx
  • app/profile/edit.tsx
  • app/profile/index.tsx
  • app/s/[slug].tsx
  • docs/about_the_project.md
  • docs/todo.md
  • package.json
  • src/entities/activity/api.ts
  • src/entities/activity/hooks.ts
  • src/entities/activity/local.test.ts
  • src/entities/activity/local.ts
  • src/entities/activity/mappers.test.ts
  • src/entities/activity/mappers.ts
  • src/entities/activity/schemas.test.ts
  • src/entities/activity/schemas.ts
  • src/entities/activity/types.ts
  • src/entities/activity/utils.test.ts
  • src/entities/activity/utils.ts
  • src/entities/participant/hooks.ts
  • src/entities/participant/local.test.ts
  • src/entities/participant/local.ts
  • src/entities/participant/mappers.test.ts
  • src/entities/participant/mappers.ts
  • src/entities/participant/schemas.test.ts
  • src/entities/participant/schemas.ts
  • src/entities/participant/types.ts
  • src/entities/participant/utils.ts
  • src/entities/profile/hooks.ts
  • src/entities/profile/mappers.test.ts
  • src/entities/profile/mappers.ts
  • src/entities/profile/schemas.test.ts
  • src/entities/profile/schemas.ts
  • src/entities/profile/storage.ts
  • src/entities/profile/types.ts
  • src/entities/profile/utils.test.ts
  • src/entities/profile/utils.ts
  • src/entities/reminder/hooks.ts
  • src/entities/reminder/local.test.ts
  • src/entities/reminder/local.ts
  • src/entities/reminder/schedule.ts
  • src/entities/reminder/settings.ts
  • src/entities/reminder/types.ts
  • src/entities/reminder/utils.test.ts
  • src/entities/reminder/utils.ts
  • src/entities/series/api.ts
  • src/entities/series/hooks.ts
  • src/entities/series/local.test.ts
  • src/entities/series/local.ts
  • src/entities/series/mappers.test.ts
  • src/entities/series/mappers.ts
  • src/entities/series/schemas.test.ts
  • src/entities/series/schemas.ts
  • src/entities/series/types.ts
  • src/entities/series/utils.test.ts
  • src/entities/series/utils.ts
  • src/features/create-activity/CreateActivityForm.tsx
  • src/features/create-activity/useCreateActivityForm.ts
  • src/features/create-series/CreateSeriesForm.tsx
  • src/features/create-series/RecurrenceFields.tsx
  • src/features/create-series/useCreateSeriesForm.ts
  • src/features/edit-profile/EditProfileForm.tsx
  • src/features/edit-profile/useEditProfileForm.ts
  • src/features/join-series/JoinSeriesForm.tsx
  • src/features/join-series/useJoinSeries.ts
  • src/features/like-activity/useLikeActivity.ts
  • src/features/manage-activity/ManageActivityForm.tsx
  • src/features/manage-activity/useManageActivityForm.ts
  • src/features/manage-series/ManageSeriesForm.tsx
  • src/features/manage-series/useManageSeriesForm.ts
  • src/features/mark-meeting/useMarkMeeting.ts
  • src/features/respond-to-activity/ResponseForm.tsx
  • src/features/respond-to-activity/useRespondToActivityForm.ts
  • src/shared/lib/datetime.test.ts
  • src/shared/lib/datetime.ts
  • src/shared/lib/notifications.ts
  • src/shared/lib/publicLink.test.ts
  • src/shared/lib/publicLink.ts
  • src/shared/lib/publicUrl.ts
  • src/shared/lib/shareActivity.test.ts
  • src/shared/lib/shareActivity.ts
  • src/shared/lib/storage.ts
  • src/shared/lib/zodFields.ts
  • src/shared/ui/Avatar.tsx
  • src/shared/ui/ChatLinkButton.tsx
  • src/shared/ui/MultiSelect.tsx
  • src/shared/ui/ParticipantList.tsx
  • src/shared/ui/PhotoPicker.tsx
  • src/shared/ui/ShareButton.tsx
💤 Files with no reviewable changes (3)
  • src/entities/participant/mappers.ts
  • src/entities/participant/types.ts
  • src/entities/participant/utils.ts

Comment thread app/index.tsx
Comment on lines +113 to +127
// Ближайшая встреча в будущем: активный слот или следующая дата по правилу.
// Будущих встреч нет — возвращаем null, чтобы не поднимать серию прошедшим стартом.
function nextMeetingDate(series: SeriesWithMeetings): Date | null {
const now = Date.now();
const fromMeetings = series.meetings
.filter((meeting) => meeting.status === "active")
.map((meeting) => new Date(meeting.starts_at).getTime());
const fromRule = listOccurrences(series.recurrence, series.starts_at).map(
(iso) => new Date(iso).getTime(),
);
const upcoming = [...fromMeetings, ...fromRule]
.filter((time) => time >= now)
.sort((a, b) => a - b);
return upcoming.length > 0 ? new Date(upcoming[0]) : null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Найти определения и использования listOccurrences, а также участок с nextMeetingDate.
git ls-files | rg '(^|/)(app/index\.tsx|.*recurrence.*|.*occurrence.*|.*meeting.*|.*series.*)\.(ts|tsx|js|jsx)$' || true
rg -n "function nextMeetingDate|const nextMeetingDate|listOccurrences\\(" app/index.tsx .

Repository: linkerkz/project-e

Length of output: 2251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Посмотреть сигнатуру listOccurrences и связанные значения horizon.
rg -n "horizon\\s*=|listOccurrences\\s*\\(" . --glob '!**/node_modules/**'

Repository: linkerkz/project-e

Length of output: 1300


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Понять, как listOccurrences использует horizon и что именно покрывают тесты.
wc -l src/entities/series/utils.ts src/entities/series/utils.test.ts app/index.tsx
sed -n '1,220p' src/entities/series/utils.ts
printf '\n--- TESTS ---\n'
sed -n '1,220p' src/entities/series/utils.test.ts
printf '\n--- APP ---\n'
sed -n '100,145p' app/index.tsx

Repository: linkerkz/project-e

Length of output: 10590


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Понять реализацию listOccurrences и поведение horizon в тестах.
wc -l src/entities/series/utils.ts src/entities/series/utils.test.ts app/index.tsx
sed -n '1,220p' src/entities/series/utils.ts
printf '\n--- TESTS ---\n'
sed -n '1,220p' src/entities/series/utils.test.ts
printf '\n--- APP ---\n'
sed -n '100,145p' app/index.tsx

Repository: linkerkz/project-e

Length of output: 10590


nextMeetingDate не должен использовать дефолтный horizon = 5

Для длинных повторяющихся серий listOccurrences(...) обрежет будущие даты, и лента покажет «ближайших встреч нет», хотя они есть. Передайте здесь отдельный больший горизонт или ищите первую будущую дату без фиксированного лимита.

🤖 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/index.tsx` around lines 113 - 127, Update nextMeetingDate so its
listOccurrences call does not use the default horizon of 5; pass a sufficiently
large dedicated horizon or use an unbounded way to find the first future
recurrence. Preserve the existing active-meeting candidates, filtering, sorting,
and null behavior when no future date exists.

telegramToChatUrl,
} from "./utils";

describe("activity/utils", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Переведите название набора тестов.

Предлагаемое исправление
-describe("activity/utils", () => {
+describe("активность/утилиты", () => {

As per coding guidelines, «All content, comments, and documentation must be in Russian language».

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("activity/utils", () => {
describe("активность/утилиты", () => {
🤖 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/activity/utils.test.ts` at line 8, Переведите название набора
тестов в describe("activity/utils", ...) на русский язык, сохранив область
тестов и остальную структуру без изменений.

Source: Coding guidelines

website: "",
};

describe("profile/schemas", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Переведите название набора тестов.

Предлагаемое исправление
-describe("profile/schemas", () => {
+describe("профиль/схемы", () => {

As per coding guidelines, «All content, comments, and documentation must be in Russian language».

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("profile/schemas", () => {
describe("профиль/схемы", () => {
🤖 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/profile/schemas.test.ts` at line 13, Переведите название набора
тестов в describe("profile/schemas") на русский язык, сохранив охватываемый
набор тестов и остальную структуру без изменений.

Source: Coding guidelines

Comment on lines +50 to +57
export function formatRegisteredAt(value: string) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;

return `${padDatePart(date.getDate())}.${padDatePart(
date.getMonth() + 1,
)}.${date.getFullYear()}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how registeredAt is stored in profile storage
rg -n 'registeredAt' src/entities/profile/storage.ts src/entities/profile/mappers.ts

Repository: linkerkz/project-e

Length of output: 417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## storage.ts\n'
cat -n src/entities/profile/storage.ts

printf '\n## utils.ts\n'
cat -n src/entities/profile/utils.ts

printf '\n## mappers.ts\n'
if [ -f src/entities/profile/mappers.ts ]; then cat -n src/entities/profile/mappers.ts; fi

Repository: linkerkz/project-e

Length of output: 4309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from datetime import datetime, timezone
import os, subprocess, textwrap, json, sys

# Probe JS date behavior for a UTC ISO string and a date-only string across timezones.
script = r"""
const value = '2024-01-01T00:00:00.000Z';
const d = new Date(value);
console.log(JSON.stringify({
  iso: value,
  valid: !Number.isNaN(d.getTime()),
  local: `${d.getDate()}.${d.getMonth()+1}.${d.getFullYear()}`,
  utc: `${d.getUTCDate()}.${d.getUTCMonth()+1}.${d.getUTCFullYear()}`
}));
"""
for tz in ['UTC', 'Pacific/Honolulu', 'America/Los_Angeles', 'Asia/Tokyo']:
    env = os.environ.copy()
    env['TZ'] = tz
    out = subprocess.check_output(['node', '-e', script], env=env, text=True).strip()
    print(tz, out)
PY

Repository: linkerkz/project-e

Length of output: 548


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## src/entities/profile/storage.ts\n'
cat -n src/entities/profile/storage.ts

printf '\n## src/entities/profile/utils.ts\n'
cat -n src/entities/profile/utils.ts

Repository: linkerkz/project-e

Length of output: 3328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import os, subprocess

script = r"""
const value = '2024-01-01T00:00:00.000Z';
const d = new Date(value);
console.log(JSON.stringify({
  valid: !Number.isNaN(d.getTime()),
  local: `${d.getDate()}.${d.getMonth()+1}.${d.getFullYear()}`,
  utc: `${d.getUTCDate()}.${d.getUTCMonth()+1}.${d.getUTCFullYear()}`
}));
"""

for tz in ['UTC', 'Pacific/Honolulu', 'America/Los_Angeles', 'Asia/Tokyo']:
    env = os.environ.copy()
    env['TZ'] = tz
    out = subprocess.check_output(['node', '-e', script], env=env, text=True).strip()
    print(tz, out)
PY

Repository: linkerkz/project-e

Length of output: 416


Используйте UTC-методы в formatRegisteredAt

registeredAt сохраняется как UTC ISO-строка через toISOString(), а getDate()/getMonth()/getFullYear() показывают локальный календарный день. В отрицательных часовых поясах дата может сдвигаться на предыдущие сутки — лучше форматировать через getUTCDate()/getUTCMonth()/getUTCFullYear().

🤖 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/profile/utils.ts` around lines 50 - 57, Update
formatRegisteredAt to use getUTCDate(), getUTCMonth(), and getUTCFullYear() when
formatting valid ISO timestamps, while preserving the existing invalid-date
fallback and padding behavior.

Comment on lines +21 to +44
export async function scheduleReminder(target: Target): Promise<boolean> {
const { enabled, offsetHours } = await readReminderSettings();
if (!enabled) return false;

const remindAt = getReminderDate(target.startsAt, offsetHours);
if (remindAt == null) return false;

const granted = await ensureNotificationPermission();
if (!granted) return false;

const notificationId = await scheduleNotificationAt(
remindAt,
target.title,
`Начало через ${offsetHours} ${hoursWord(offsetHours)}`,
);
if (notificationId == null) return false;

await addReminder({
slug: target.slug,
notificationId,
startsAt: target.startsAt,
});
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find all call sites of scheduleReminder to verify whether they cancel first.
rg -n -C3 '\bscheduleReminder\s*\(' --type ts --type tsx src/

Repository: linkerkz/project-e

Length of output: 184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== scheduleReminder call sites =="
rg -n -C3 '\bscheduleReminder\s*\(' src --glob '!**/*.tsx'

echo
echo "== reminder-related files =="
git ls-files 'src/**/reminder*' 'src/**/useMarkMeeting*' 'src/**/schedule*' | sed 's#^`#-` #'

echo
echo "== relevant definitions =="
rg -n -C3 '\b(addReminder|cancelReminder|scheduleReminder|readReminderSettings|scheduleNotificationAt|getReminderDate|ensureNotificationPermission)\b' src

echo
echo "== file sizes (for focused reading) =="
for f in \
  src/entities/reminder/schedule.ts \
  src/entities/reminder/*.ts \
  src/features/**/useMarkMeeting*.ts \
  src/**/useMarkMeeting*.ts
do
  [ -f "$f" ] && wc -l "$f"
done

Repository: linkerkz/project-e

Length of output: 18395


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/entities/reminder/schedule.ts =="
cat -n src/entities/reminder/schedule.ts

echo
echo "== src/entities/reminder/local.ts =="
cat -n src/entities/reminder/local.ts

echo
echo "== src/features/join-series/useJoinSeries.ts (relevant section) =="
sed -n '1,130p' src/features/join-series/useJoinSeries.ts | cat -n

echo
echo "== src/features/respond-to-activity/useRespondToActivityForm.ts (relevant section) =="
sed -n '1,170p' src/features/respond-to-activity/useRespondToActivityForm.ts | cat -n

Repository: linkerkz/project-e

Length of output: 12160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/features/join-series/useJoinSeries.ts =="
sed -n '1,130p' src/features/join-series/useJoinSeries.ts | cat -n

echo
echo "== src/features/respond-to-activity/useRespondToActivityForm.ts =="
sed -n '1,170p' src/features/respond-to-activity/useRespondToActivityForm.ts | cat -n

echo
echo "== src/entities/reminder/schedule.ts =="
sed -n '1,120p' src/entities/reminder/schedule.ts | cat -n

Repository: linkerkz/project-e

Length of output: 11008


Отменять предыдущее напоминание перед повторным планированием. scheduleReminder должен сам снимать уже запланированное уведомление для того же slug: сейчас addReminder лишь перезаписывает локальную запись, а старый notificationId теряется, так что повторный вызов оставляет дубликат в системе. Это уже возможно из useRespondToActivityForm, где перед новым планированием нет cancelReminder.

🤖 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/reminder/schedule.ts` around lines 21 - 44, Update
scheduleReminder to cancel any existing reminder for the same target.slug before
scheduling a new notification, reusing the existing reminder lookup and
cancellation APIs. Perform this cancellation before scheduleNotificationAt,
while preserving the current success and early-return behavior.

Comment on lines +102 to +115
useEffect(() => {
if (slug === "") return;
let active = true;
findMembership(slug)
.then((found) => {
if (active) setMembership(found);
})
.catch(() => {
if (active) setMembership(null);
});
return () => {
active = false;
};
}, [slug]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Не используйте членство от предыдущей серии после смены slug.

При новом или пустом slug состояние сохраняет прежнюю запись до завершения эффекта. Тогда leave может передать memberId старой серии в mutation новой серии.

Предлагаемое исправление
 function useMembership(slug: string) {
-  const [membership, setMembership] = useState<Membership | null>(null);
+  const [storedMembership, setMembership] = useState<Membership | null>(null);
+  const membership =
+    storedMembership?.seriesSlug === slug ? storedMembership : null;

   useEffect(() => {
-    if (slug === "") return;
+    if (slug === "") {
+      setMembership(null);
+      return;
+    }
🤖 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/join-series/useJoinSeries.ts` around lines 102 - 115, В
`useEffect` вокруг `findMembership` сбрасывайте `membership` в `null` сразу при
каждом изменении `slug`, до проверки на пустое значение и запуска запроса. Для
пустого `slug` после сброса сразу возвращайтесь; для непустого сохраняйте
текущую защиту `active`, чтобы результат предыдущей серии не применялся.

Comment on lines +49 to +52
useEffect(() => {
if (activity?.status !== "cancelled") return;
cancelReminder(activity.slug).catch(() => {});
}, [activity?.status, activity?.slug]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

reminderScheduled не сбрасывается при отмене активности.

useEffect отменяет напоминание через cancelReminder, но не сбрасывает reminderScheduled в false. В результате ActivityPage показывает «Напомним за 2 часа до начала.» одновременно с «ОТМЕНЕНО», что противоречит друг другу.

🔧 Предлагаемое исправление
  useEffect(() => {
    if (activity?.status !== "cancelled") return;
    cancelReminder(activity.slug).catch(() => {});
+   setReminderScheduled(false);
  }, [activity?.status, activity?.slug]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (activity?.status !== "cancelled") return;
cancelReminder(activity.slug).catch(() => {});
}, [activity?.status, activity?.slug]);
useEffect(() => {
if (activity?.status !== "cancelled") return;
cancelReminder(activity.slug).catch(() => {});
setReminderScheduled(false);
}, [activity?.status, activity?.slug]);
🤖 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/respond-to-activity/useRespondToActivityForm.ts` around lines 49
- 52, Update the cancellation branch in the useEffect to set reminderScheduled
to false when activity.status is "cancelled", alongside
cancelReminder(activity.slug). Keep the existing dependency list and reminder
cancellation behavior unchanged.


import { generateEditToken, generateSlug, slugifyTitle } from "./publicLink";

describe("shared/publicLink", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Переведите название набора тестов.

"shared/publicLink" — человекочитаемый текст на английском.

Предлагаемое исправление
-describe("shared/publicLink", () => {
+describe("общая публичная ссылка", () => {

As per coding guidelines, «All content, comments, and documentation must be in Russian language».

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("shared/publicLink", () => {
describe("общая публичная ссылка", () => {
🤖 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.test.ts` at line 9, Переведите человекочитаемое
название набора тестов в describe для shared/publicLink на русский язык,
сохранив структуру тестов и идентификатор модуля без изменений.

Source: Coding guidelines

Comment on lines +9 to +11
function openChat() {
Linking.openURL(url).catch(() => {});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Пользователь не получит обратной связи при ошибке открытия чата.

Linking.openURL(url).catch(() => {}) полностью подавляет любые ошибки. Если URL невалидный или приложение-обработчик недоступно, пользователь нажимает кнопку и ничего не происходит.

Предлагаемое исправление
 import { Linking } from "react-native";
 import { Button } from "./Button";

 type Props = {
   url: string;
 };

 export function ChatLinkButton({ url }: Props) {
   function openChat() {
-    Linking.openURL(url).catch(() => {});
+    Linking.openURL(url).catch(() => {
+      Alert.alert("Ошибка", "Не удалось открыть чат");
+    });
   }

   return <Button title="Перейти в чат" onPress={openChat} />;
 }
+import { Alert, Linking } from "react-native";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function openChat() {
Linking.openURL(url).catch(() => {});
}
import { Alert, Linking } from "react-native";
import { Button } from "./Button";
type Props = {
url: string;
};
export function ChatLinkButton({ url }: Props) {
function openChat() {
Linking.openURL(url).catch(() => {
Alert.alert("Ошибка", "Не удалось открыть чат");
});
}
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 9 - 11, Update openChat in
ChatLinkButton so failures from Linking.openURL(url) are no longer silently
swallowed; provide user-visible feedback when opening the chat fails, while
preserving the existing successful URL-opening behavior.

Comment on lines +37 to +41
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
quality: 1,
});
if (!result.canceled) onChange(result.assets[0].uri);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

rg -n -C 3 'launchImageLibraryAsync|getPendingResultAsync' src/shared/ui/PhotoPicker.tsx

Repository: linkerkz/project-e

Length of output: 431


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- search pending-result usage ---'
rg -n -C 2 'getPendingResultAsync|launchImageLibraryAsync|launchCameraAsync' src . --glob '!**/node_modules/**' || true

echo
echo '--- inspect PhotoPicker.tsx ---'
sed -n '1,220p' src/shared/ui/PhotoPicker.tsx

echo
echo '--- locate PhotoPicker usages ---'
rg -n 'PhotoPicker' src --glob '!**/node_modules/**' || true

Repository: linkerkz/project-e

Length of output: 2556


🌐 Web query:

Expo SDK 56 imagepicker getPendingResultAsync destroyed Android Activity docs

💡 Result:

When using expo-image-picker on Android, the operating system may terminate the MainActivity due to low memory or when the "Don't keep activities" developer option is enabled [1][2]. When this happens, the original promise returned by launchCameraAsync or launchImageLibraryAsync is lost, and the application does not receive the expected result [3][1][4]. To handle this scenario, Expo provides the getPendingResultAsync method [1]. Key information regarding this method: - Purpose: It is designed to retrieve media data that was selected or captured but lost due to the activity being destroyed by the Android system [1][5]. - Implementation: When the activity is recreated, you should call getPendingResultAsync to check if a result is waiting in persistent storage [1]. If the picker finished successfully before the activity was killed, this method returns the result; otherwise, it returns an error result or null [1][6]. - Known Limitations: The mechanism has historically been subject to bugs, including instances where it returns an empty array or fails to capture the result depending on the specific Android version and device memory conditions [7][3][6][2]. Some community reports indicate that even with this method, low-memory scenarios can cause the process to be completely killed without successfully persisting the necessary data [3][4]. Official documentation advises that developers should always account for MainActivity destruction on Android by implementing this check [1]. You can test your implementation by enabling "Don't keep activities" in your Android device's Developer Options [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

# Inspect current repository file tree around shared UI and relevant consumers
git ls-files 'src/shared/ui/*' 'src/**/PhotoPicker*' 'src/**' | sed -n '1,200p'

Repository: linkerkz/project-e

Length of output: 3790


Добавьте ImagePicker.getPendingResultAsync() при инициализации экрана или общего провайдера: после уничтожения MainActivity результат launchImageLibraryAsync() может потеряться, и выбранное фото не восстановится.

🤖 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 37 - 41, Инициализируйте
восстановление отложенного результата через ImagePicker.getPendingResultAsync()
в компоненте PhotoPicker или общем провайдере, чтобы результат после
пересоздания MainActivity передавался в onChange. Сохраните текущую обработку
результата launchImageLibraryAsync и применяйте восстановленный asset только при
наличии результата и URI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant