Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions .cursor/rules/git-commits-ru.mdc

This file was deleted.

29 changes: 29 additions & 0 deletions .cursor/rules/git-commits.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
description: Conventional Commits (агент и ✨ — см. также .cursorrules)
alwaysApply: true
---

# Git: Conventional Commits

**Кнопка ✨ в Source Control** читает правила из **`.cursorrules` в корне репозитория**, а не из этого файла. Если ✨ игнорирует формат — правь `.cursorrules`.

**Агент в чате** — следуй и этому файлу, и `.cursorrules`.

## Формат

- `<тип>[область]: <описание>`, одна строка, до 72 символов, без тела коммита.
- Тип: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
- Описание на английском, повелительное наклонение, без точки в конце.
- Одна суть: не перечисляй все файлы и подзадачи.

## Примеры

- `feat(pay): add monthly payout API`
- `fix(drawer): panel follows finger on left-edge drag`
- `refactor: simplify PayMonthlyChart`

## Нельзя

- Длинные «отчёты» на полэкрана с перечислением моделей, маршрутов и стилей.
- `Обновил PayMonthlyChart` — старый формат, без типа
- `drawer gestures` — нет типа и глагола-действия
22 changes: 11 additions & 11 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Сообщения git-коммита (кнопка ✨ в Source Control)

При генерации сообщения коммита (`cursor.generateGitCommitMessage`) — только это:
При генерации сообщения коммита (`cursor.generateGitCommitMessage`) — формат
Conventional Commits:

- Язык: русский, без английского в теме.
- Длина: одна строка, не длиннее 72 символов (без переносов и второго абзаца).
- Содержание: только одна главная мысль; максимально кратко. Не перечисляй файлы, классы, параметры или детали реализации.
- Время: прошедшее — «Добавил», «Исправил», «Обновил», «Убрал», «Перенес».
- Без префиксов feat:, fix:, chore:.
- `<тип>[область]: <описание>`, одна строка, до 72 символов, без тела коммита.
- Тип: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
- Описание на английском, повелительное наклонение, без точки в конце.
- Только одна главная мысль; не перечисляй файлы, классы, параметры или детали реализации.

Примеры (копируй длину и тон):

- Добавил API ежемесячных выплат Pay
- Исправил жесты drawer с левого края
- Обновил PayMonthlyChart
- feat(pay): add monthly payout API
- fix(drawer): handle left-edge gesture correctly
- refactor: simplify PayMonthlyChart

Запрещено:

- «Добавлены новые модели и маршруты… Обновлены стили…»
- любое сообщение длиннее одной короткой фразы.
- «Added new models and routes… Updated styles…» — несколько мыслей сразу
- любое сообщение длиннее одной короткой строки.
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

## Чек-лист

- [ ] Коммиты по [правилам проекта](../blob/main/CONTRIBUTING.md#правила-коммитов):
одна строка, до 72 символов, по-русски, глагол в прошедшем времени
- [ ] Коммиты по [Conventional Commits](https://www.conventionalcommits.org/) —
см. [правила проекта](../blob/main/CONTRIBUTING.md#правила-коммитов)
- [ ] `./gradlew :app:testDebugUnitTest` зелёный
- [ ] Заметки к релизу лежат в разделе «Не выпущено»
[CHANGELOG.md](../blob/main/CHANGELOG.md) — их пишет агент по команде
Expand Down
40 changes: 37 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,43 @@

## [Не выпущено]

Пока пусто. Сюда дописывается изменение, заметное пользователю, — тем же PR,
которым оно вносится, а не по памяти в день выпуска. Заголовки внутри:
«Добавлено», «Изменено», «Исправлено», «Убрано».
### Добавлено

- **Оплата видна отдельно от прихода** — как в YClients: клиент пришёл —
на записи восклицательный знак, заплатил — плюсик. «Проведено» считает
всех, кто пришёл, а в «Заработано» занятие попадает только после оплаты;
неоплаченное стоит в «Ожидается», пока деньги не пришли.

### Исправлено

- **Занятия дня больше не пропадают после уведомления** — когда приходило
уведомление про запись на следующий месяц, в этом дне оставалась только она
одна, а остальные занятия исчезали до следующей полной синхронизации.
- **Налог из настроек применяется ко всем месяцам** — исправленная сумма
налога теперь меняет прибыль и в прошлых месяцах. Раньше первая введённая
цифра оставалась в истории навсегда, и месяц показывал доход, посчитанный
по ней.
- **Кнопка «Сегодня» не закрывает числа** — при увеличенном системном шрифте
она перекрывала нижний ряд календаря, и по этим дням нельзя было нажать.
Место под кнопку теперь отводится по её настоящему размеру.
- **Видно, когда уведомления идут с задержкой** — если телефон не получил от
Google ключ мгновенной доставки, приложение говорит об этом на экране
«Уведомления о занятиях» и предлагает проверить снова. Раньше уведомления
молча приходили с опозданием до получаса.
- **Вход не отключает пуши** — если при входе телефон ещё не получил ключ от
Google, сервер больше не стирает рабочий: уведомления продолжают приходить
мгновенно.

### Изменено

- **Уведомление об обновлении ведёт прямо к кнопке** — нажатие на «доступно
обновление» открывает «О программе», где новая версия уже найдена: видно, что
изменилось, и стоят кнопки «Обновить» и «Пропустить». Раньше на этом экране
приходилось заново нажимать «Проверить обновления», хотя телефон про версию
уже знал.
- **Найденное обновление не теряется** — если приложение закрыть, предложение
обновиться остаётся на экране «О программе» и показывается даже без сети,
пока версию не поставят или не пропустят.

## [0.2.3] — 2026-08-21

Expand Down
29 changes: 11 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# Правила для агента

## Git-коммиты — только на русском, кратко

- Одна строка, до 72 символов, без тела коммита и переносов.
- Одна главная мысль — не перечислять файлы, классы, параметры, детали реализации.
- Глагол в прошедшем времени: «Добавил», «Исправил», «Обновил», «Убрал», «Перенес».
- Без префиксов `feat:`, `fix:`, `chore:`.
- Имена в коде можно латиницей, описание — только русский.

Примеры:
- Добавил API ежемесячных выплат Pay
- Исправил жесты drawer с левого края
- Обновил PayMonthlyChart

Нельзя:
- «Добавлены новые модели и маршруты… Обновлены стили…»
- `Refactor CalendarHeader for improved usability`
- любое сообщение длиннее одной короткой фразы

## Тронул код — дописал CHANGELOG

Любая правка, которую видно пользователю, идёт вместе с записью в разделе
Expand All @@ -38,6 +20,17 @@ CI.
Выпускать нечего (правка тестов, чистый рефакторинг, работа только с
документацией) — метка «без-релиза» на PR, тогда ни релиза, ни проверки.

## Тронул код — актуализируй документацию проекта

Правки, которые меняют поведение, архитектуру или процесс, описанные в
`README.md`, `docs/*.md`, `CONTRIBUTING.md` или другой документации
проекта, — идут вместе с правкой этой документации, **тем же изменением**,
а не потом. Это отдельно от CHANGELOG (для него своё правило выше): здесь
речь про то, что документация продолжает описывать код таким, какой он есть.

Почему: документация, разошедшаяся с кодом, вреднее её отсутствия — по ней
ориентируются и человек, и агент в следующей сессии.

## Без Gradle-сборки

Никогда не запускать `./gradlew` или любые Gradle-команды (`assembleDebug`, `build`, `test` и т.п.).
Expand Down
23 changes: 13 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,25 @@ Neiro — личный инструмент одного педагога, а н

## Правила коммитов

Одна строка, до 72 символов, по-русски, глагол в прошедшем времени. Без тела
коммита, без префиксов `feat:` / `fix:`, без перечисления файлов и классов —
одна главная мысль. Имена в коде можно латиницей, описание — только русский.
Те же правила действуют для агентов, они записаны в [CLAUDE.md](CLAUDE.md).
Формат — [Conventional Commits](https://www.conventionalcommits.org/):
`<тип>[область]: <описание>`. Одна строка, до 72 символов, без тела коммита.

Тип: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`,
`ci`, `chore`, `revert`. Описание — на английском, повелительное наклонение,
без точки в конце, одна главная мысль на коммит. Те же правила действуют для
агентов — скилл `git-commit`.

Так:

- `Добавил API ежемесячных выплат Pay`
- `Исправил жесты drawer с левого края`
- `Обновил PayMonthlyChart`
- `feat(pay): add monthly payout API`
- `fix(drawer): handle left-edge gesture correctly`
- `refactor: simplify PayMonthlyChart`

Не так:

- `Добавлены новые модели и маршруты… Обновлены стили…` — несколько мыслей
- `Refactor CalendarHeader for improved usability` — английский и префикс
- `Правки` — не сказано, что изменилось
- `Added new models and routes… Updated styles…` — несколько мыслей
- `Обновил PayMonthlyChart` — старый формат, без типа
- `fix bug` — не сказано, что именно исправлено

## Ветки и PR

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/ru/greemlab/neiro/NeiroApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import ru.greemlab.neiro.data.CalendarDataStoreProvider
import ru.greemlab.neiro.notifications.SessionNotificationCoordinator
import ru.greemlab.neiro.push.PushDeliveryDiagnostics
import ru.greemlab.neiro.push.PushRegistrar
import ru.greemlab.neiro.sync.AutoSyncCoordinator
import ru.greemlab.neiro.sync.LiveApiCoordinator
Expand Down Expand Up @@ -34,6 +35,9 @@ class NeiroApplication : Application() {
LiveApiCoordinator.initialize(this@NeiroApplication)

SessionNotificationCoordinator.initialize(this@NeiroApplication)
// До PushRegistrar: тот сразу пойдёт за токеном FCM и запишет
// результат, а прогрев поверх него вернул бы сохранённое старое.
PushDeliveryDiagnostics.warmUp(this@NeiroApplication)
PushRegistrar.initialize(this@NeiroApplication)

// Суточная проверка новой версии на GitHub. В debug выходит сразу:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ fun mergeFact(
?: profile.pricePerDiagnostics,
priceIntensiveChild = existing?.priceIntensiveChild?.takeIf { it > 0.0 }
?: profile.pricePerIntensiveChild,
tax = existing?.tax?.takeIf { it > 0.0 } ?: profile.monthlyTaxAmount,
// След, а не источник: расчёт месяца берёт налог из профиля
// (`MonthRatesResolver`). Держим здесь свежее значение, чтобы запись
// месяца не хранила давно исправленную сумму.
tax = profile.monthlyTaxAmount,
factGross = if (hasFact) fact.gross else existing?.factGross,
factSessions = if (hasFact) fact.services else existing?.factSessions,
origin = existing?.origin ?: PriceOrigin.AUTO,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ data class RecordData(
@SerializedName("seance_length") val seanceLength: Int?,
val length: Int?,
@SerializedName("visit_attendance") val visitAttendance: Int?,
/**
* Оплачено ли занятие — плюсик на записи в YClients. `1` — да.
*
* Отдельно от [attendance]: «пришёл» деньгами не является, и в заработок
* занятие попадает только с оплатой (решение от 01.09.2026).
*/
@SerializedName("paid_full") val paidFull: Int? = null,
val deleted: Boolean? = null,
@SerializedName("last_change_date") val lastChangeDate: String? = null,
/** Групповое событие (интенсив и прочие группы). У обычной записи `null`. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger
import ru.greemlab.neiro.BuildConfig
import ru.greemlab.neiro.auth.LogoutCoordinator
import ru.greemlab.neiro.push.PushConfig
import ru.greemlab.neiro.push.PushDeliveryDiagnostics
import ru.greemlab.neiro.push.PushDeviceId
import ru.greemlab.neiro.push.PushEventsCursor
import ru.greemlab.neiro.push.PushFcmToken
Expand Down Expand Up @@ -107,7 +108,7 @@ class YClientsRepository(context: Context) {
// Токен FCM берём до входа: сервер регистрирует устройство тем же
// запросом. Не дал — не беда, вход пройдёт и без пушей, а токен
// донесёт PushRegistrar, когда Firebase его выдаст.
val fcmToken = PushFcmToken.fetch().orEmpty()
val fcmToken = PushFcmToken.fetch(appContext).orEmpty()
try {
val response = neiroApi.login(
LoginRequest(
Expand Down Expand Up @@ -152,6 +153,9 @@ class YClientsRepository(context: Context) {
// Курсор событий: новое устройство стартует с конца журнала,
// известное — со своего сохранённого места (API.md § Вход).
PushEventsCursor.setIfAbsent(appContext, body.lastEventId)
// Устройство регистрируется тем же запросом: непустой токен,
// уехавший с логином, сервер уже принял.
if (fcmToken.isNotBlank()) PushDeliveryDiagnostics.onTokenDelivered(appContext)
ApiResult.Success(account)
} catch (e: CancellationException) {
throw e
Expand Down Expand Up @@ -237,6 +241,11 @@ class YClientsRepository(context: Context) {
.onSuccess { handleAuthFailure(it.code(), generation) }
.map { it.isSuccessful }
.getOrDefault(false)
.also { delivered ->
// Пуши идут только на устройство, чей токен доехал до сервера;
// до этого момента приложение живёт на догоне.
if (delivered) PushDeliveryDiagnostics.onTokenDelivered(appContext)
}
}

/**
Expand Down
Loading
Loading