docs(mobile): add mobile app plan and link it from README roadmap
Add docs/mobile-app-plan.md capturing the mobile-app research: current stack, existing responsive web inventory, why the editor must stay in a WebView, comparison of native/Capacitor/hybrid paths, the recommended Capacitor route, required backend changes (token-in-body login, CORS whitelist, APNs/FCM push, optional Swagger), Android/iOS specifics, and the offline outlook (referencing docs/offline-sync-plan.md). Enrich the "Mobile app" roadmap entry in README.md and README.ru.md to point to the new plan and correct the inaccurate "native" wording (Capacitor wrapper of the existing web UI, iOS first, Android to follow).
This commit is contained in:
236
docs/mobile-app-plan.md
Normal file
236
docs/mobile-app-plan.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Мобильное приложение gitmost — исследование и план
|
||||
|
||||
> Статус: исследовательский + проектный документ.
|
||||
> Контекст: gitmost — форк Docmost, чистое веб-приложение. Отдельного
|
||||
> мобильного (нативного/устанавливаемого) приложения **нет**.
|
||||
> Цель: определить путь к мобильным приложениям — **iOS обязательно, Android
|
||||
> как пойдёт** — с заделом на оффлайн в будущем (оффлайн сейчас не требуется).
|
||||
|
||||
Документ фиксирует, что уже есть в коде, почему путь к мобилке предопределён
|
||||
устройством продукта, сравнивает варианты и описывает рекомендуемый план с
|
||||
привязкой к файлам.
|
||||
|
||||
---
|
||||
|
||||
## 1. TL;DR
|
||||
|
||||
1. **Нативного приложения нет.** В проекте отсутствуют Capacitor, React Native,
|
||||
Cordova и т.п. Мобильного клиента ещё не начинали.
|
||||
2. **Адаптивная веб-версия — есть, и довольно проработанная.** Веб-клиент
|
||||
открывается с телефона как mobile-friendly сайт: сворачиваемый сайдбар-drawer,
|
||||
отдельные мобильные компоненты (история, поиск, хлебные крошки), responsive-
|
||||
примитивы Mantine, mobile-tuned `viewport`. Это готовый фундамент UI.
|
||||
3. **Ядро продукта — веб-редактор — нативно не воспроизвести.** TipTap 3
|
||||
(ProseMirror) + совместное редактирование на Yjs/Hocuspocus плотно сшиты с
|
||||
React. Production-порта Yjs под Swift/Kotlin нет. Любой реалистичный путь
|
||||
оставляет редактор в **WebView**.
|
||||
4. **API уже готов к нативному клиенту.** Сервер принимает JWT не только из
|
||||
cookie, но и из заголовка `Authorization: Bearer`. Есть точка входа для
|
||||
вебсокета совместного редактирования (`POST /auth/collab-token`).
|
||||
5. **Рекомендуемый путь — Capacitor:** обернуть существующий React-SPA в
|
||||
нативную оболочку (iOS + Android из одного кода), добавить нативные плагины
|
||||
(push, биометрия, share, файлы). Эволюция в гибрид (нативная навигация +
|
||||
WebView-редактор) делается потом инкрементально, без переписывания.
|
||||
6. **Оффлайн-будущее уже заложено** (Yjs + `y-indexeddb`). Детальный план —
|
||||
в [offline-sync-plan.md](offline-sync-plan.md); мобильное приложение этот
|
||||
план переиспользует, а не дублирует.
|
||||
|
||||
---
|
||||
|
||||
## 2. Текущее состояние (как есть)
|
||||
|
||||
### 2.1. Стек
|
||||
|
||||
| Слой | Технологии |
|
||||
|---|---|
|
||||
| Бэкенд | NestJS 11 + Fastify, Kysely/Postgres, Redis/BullMQ. API в стиле RPC-POST (соглашение Docmost). Аутентификация — JWT. |
|
||||
| Фронт | React 18 + Vite + Mantine + TanStack Query + i18next. Обычный SPA. |
|
||||
| Ядро (редактор) | TipTap 3 (ProseMirror) + совместное редактирование на Yjs через Hocuspocus — см. [page-editor.tsx](../apps/client/src/features/editor/page-editor.tsx). |
|
||||
| Оффлайн-фундамент | `yjs` + `y-indexeddb` уже в зависимостях клиента (локальная CRDT-копия тела документа). |
|
||||
|
||||
### 2.2. Мобильного приложения нет
|
||||
|
||||
В `package.json` и `apps/*/package.json` нет `capacitor`, `react-native`,
|
||||
`cordova`, `expo`. Нативной оболочки в репозитории не заведено.
|
||||
|
||||
### 2.3. Адаптивная веб-версия — есть
|
||||
|
||||
| Что | Где |
|
||||
|---|---|
|
||||
| Адаптивная оболочка Mantine `AppShell` с `breakpoint: "sm"`, раздельные состояния `collapsed.mobile` / `collapsed.desktop` | [global-app-shell.tsx](../apps/client/src/components/layouts/global/global-app-shell.tsx) (L85–99) |
|
||||
| Отдельный мобильный сайдбар-drawer (`mobileSidebarAtom` отделён от `desktopSidebarAtom`), авто-закрытие при навигации по дереву | [sidebar-atom.ts](../apps/client/src/components/layouts/global/hooks/atoms/sidebar-atom.ts), [space-tree-row.tsx](../apps/client/src/features/page/tree/components/space-tree-row.tsx) (L147–148) |
|
||||
| Мобильная модалка истории + свой CSS | [history-modal.tsx](../apps/client/src/features/page-history/components/history-modal.tsx) (L17–19), `history-modal-mobile.tsx` |
|
||||
| Мобильный контрол поиска | [search-control.tsx](../apps/client/src/features/search/components/search-control.tsx) (L38–42) |
|
||||
| Мобильный рендер хлебных крошек через `useMediaQuery` | [breadcrumb.tsx](../apps/client/src/features/page/components/breadcrumbs/breadcrumb.tsx) (L41) |
|
||||
| Responsive-примитивы `hiddenFrom`/`visibleFrom` (~16 мест), медиа-запросы в CSS-модулях | по всему `apps/client/src` |
|
||||
| Mobile-tuned viewport (`width=device-width, user-scalable=no`) | [index.html](../apps/client/index.html) (L8) |
|
||||
|
||||
> Важно: адаптив проверялся в мобильном **браузере**, а не в WebView нативной
|
||||
> оболочки. Перед сборкой приложения нужно прогнать UI как PWA/в WebView и
|
||||
> отловить отличия (жесты, экранная клавиатура/IME в редакторе, safe-area).
|
||||
|
||||
### 2.4. Готовность API к нативному клиенту
|
||||
|
||||
- **Bearer-токен уже поддержан.** JWT извлекается из cookie **или** из заголовка
|
||||
`Authorization`: см. [jwt.strategy.ts](../apps/server/src/core/auth/strategies/jwt.strategy.ts) (L27–29).
|
||||
Серверная сторона нативной авторизации менять не нужно.
|
||||
- **Токен сейчас не возвращается в теле логина.** [`login`](../apps/server/src/core/auth/auth.controller.ts)
|
||||
(L55–105) кладёт JWT только в `httpOnly`-cookie ([`setAuthCookie`](../apps/server/src/core/auth/auth.controller.ts) L222–230).
|
||||
- **Точка входа вебсокета коллаборации:** [`POST /auth/collab-token`](../apps/server/src/core/auth/auth.controller.ts) (L187–193).
|
||||
- **CORS открыт без конфигурации:** [`app.enableCors()`](../apps/server/src/main.ts) (L144).
|
||||
- **OpenAPI/Swagger отсутствует** (`@nestjs/swagger` не подключён) — авто-генерации
|
||||
типизированного клиента сейчас нет.
|
||||
|
||||
---
|
||||
|
||||
## 3. Почему путь к мобилке предопределён
|
||||
|
||||
Три факта диктуют решение независимо от моды:
|
||||
|
||||
1. **Редактор практически невозможно переписать нативно.** ProseMirror + весь
|
||||
набор TipTap-расширений + Yjs-CRDT — это не «поле ввода». Нативного
|
||||
production-порта Yjs под Swift/Kotlin нет (есть Rust `yrs` с биндингами, но
|
||||
это отдельный тяжёлый проект). Переписывание ядра нативно = годы и вечное
|
||||
расхождение с веб-версией. **Вывод: редактор остаётся в WebView.**
|
||||
2. **API уже умеет нативного клиента** (Bearer, collab-token).
|
||||
3. **Оффлайн-фундамент уже заложен** на веб-уровне (Yjs + `y-indexeddb`),
|
||||
и он работает внутри WebView.
|
||||
|
||||
---
|
||||
|
||||
## 4. Три возможных пути
|
||||
|
||||
| Путь | Суть | Плюсы | Минусы | Вердикт |
|
||||
|---|---|---|---|---|
|
||||
| **A. Полностью нативно** (Swift/Kotlin) | Переписать всё, включая редактор и CRDT-синк | Максимально нативный UX | Воспроизвести ProseMirror + расширения + Yjs; несоразмерные трудозатраты; вечное отставание от веба | ❌ Не наш случай |
|
||||
| **B. WebView-обёртка SPA (Capacitor)** | Обернуть существующий React-клиент в нативную оболочку, native-возможности — плагинами | Реюз ~100% кода (редактор, коллаборация, оффлайн); один кодовый бэйз → iOS+Android; быстро | Менее «нативно»; риск отказа App Store за «просто сайт» (4.2) — лечится нативной ценностью | ✅ Рекомендуется |
|
||||
| **C. Гибрид: нативная оболочка + WebView-редактор** | Навигация/списки/поиск/логин — нативно (React Native/Swift), экран редактирования — web в WebView | Лучший UX; путь Notion/Linear | Заметно больше работы; нужен мост JS↔native | ⚖️ Цель эволюции из B |
|
||||
|
||||
---
|
||||
|
||||
## 5. Рекомендуемый путь
|
||||
|
||||
**B (Capacitor) как первый релиз, с заложенной эволюцией в C.**
|
||||
|
||||
Почему:
|
||||
- Capacitor создан под сценарий «есть веб-приложение → хочу его в App Store с
|
||||
нативными возможностями». Переиспользуется весь React-клиент и, главное,
|
||||
редактор — то, что нативно не сделать.
|
||||
- Один кодовый бэйз закрывает «iOS обязательно» и «Android как пойдёт»
|
||||
одновременно, без второй команды.
|
||||
- Адаптивная вёрстка уже есть (см. §2.3) — переверстывать под телефон с нуля
|
||||
не нужно; работа смещается в нативную обвязку.
|
||||
- Оффлайн-будущее подготовлено (Yjs + `y-indexeddb`); см.
|
||||
[offline-sync-plan.md](offline-sync-plan.md).
|
||||
- Когда упрётесь в UX отдельных экранов — их по одному выносят в нативную
|
||||
оболочку, оставив редактор в WebView. То есть B → C делается инкрементально.
|
||||
|
||||
Почему **не** чистый React Native сразу: редактор всё равно придётся держать в
|
||||
WebView (ядро web-only), но при этом теряется прямой реюз остального React-кода
|
||||
и появляется мост как обязательная сложность с первого дня — для iOS-first
|
||||
старта это лишний оверхед.
|
||||
|
||||
> Альтернатива: если критичен максимально нативный UX с первого релиза и есть
|
||||
> ресурс — сразу путь C на React Native (Expo) с WebView только под редактор.
|
||||
> Это сознательный размен «больше работы сейчас» за «более нативное ощущение».
|
||||
|
||||
---
|
||||
|
||||
## 6. Что доработать на бэкенде
|
||||
|
||||
Немного, но конкретно:
|
||||
|
||||
1. **Выдача токена в теле ответа для нативного хранения.** Сейчас логин кладёт
|
||||
JWT только в `httpOnly`-cookie и не возвращает его в body. На мобиле
|
||||
`httpOnly`-cookie между разными origin (`capacitor://localhost` ↔ API) — боль
|
||||
с SameSite/CORS. Чище: мобильный логин-флоу, возвращающий JWT в ответе, чтобы
|
||||
хранить его в Keychain/Keystore и слать как `Authorization: Bearer`. Сервер
|
||||
уже принимает Bearer — менять надо только **выдачу**.
|
||||
Файлы: [auth.controller.ts](../apps/server/src/core/auth/auth.controller.ts).
|
||||
2. **CORS.** Сейчас [`app.enableCors()`](../apps/server/src/main.ts) (L144) без
|
||||
конфигурации. Под мобильные origin'ы и для безопасности задать явный whitelist.
|
||||
3. **Push-уведомления.** Модуль `notification` уже есть — добавить регистрацию
|
||||
device-token и интеграцию **APNs** (iOS) / **FCM** (Android).
|
||||
4. **Опционально — OpenAPI/Swagger.** Сейчас спецификации нет; добавить
|
||||
`@nestjs/swagger` дёшево и сильно ускорит мобильную разработку
|
||||
(типизированный клиент).
|
||||
|
||||
---
|
||||
|
||||
## 7. Android-специфика
|
||||
|
||||
На пути Capacitor Android едет почти бесплатно (`npx cap add android` из того же
|
||||
веб-билда), но есть нюансы:
|
||||
|
||||
- **Движок в плюс.** Android System WebView (Chromium) обновляется через Play
|
||||
Store независимо от ОС и обычно свежее iOS WKWebView. Более рискованный движок
|
||||
по совместимости — это iOS, а не Android.
|
||||
- **Фрагментация.** Дешёвые/старые устройства с малой памятью и устаревшим
|
||||
WebView; стек тяжёлый (ProseMirror + Yjs + mermaid + katex + excalidraw) —
|
||||
тестировать на бюджетных аппаратах.
|
||||
- **Обвязка под Android:** аппаратная/жестовая кнопка «Назад» (навигация внутри
|
||||
приложения, а не выход), **FCM** для push, Android App Links (вместо iOS
|
||||
Universal Links), подписание и Play Console.
|
||||
- **Главный риск именно для Android — ввод текста в ProseMirror на Gboard/IME.**
|
||||
Историческая боль `contenteditable` на Android (прыжки курсора, дубли символов
|
||||
при композиции). Стало лучше, но **проверять в первую очередь и рано**.
|
||||
- **Магазин.** Google Play лояльнее к webview-обёрткам, чем App Store; риск
|
||||
«отклонят как просто сайт» для Play практически неактуален.
|
||||
|
||||
---
|
||||
|
||||
## 8. iOS-специфика
|
||||
|
||||
- **WKWebView** на движке WebKit жёстко привязан к версии ОС — это более
|
||||
рискованный по совместимости движок (тестировать прежде всего его).
|
||||
- **App Store guideline 4.2 (minimum functionality).** Чистая webview-обёртка
|
||||
рискует отклонением «это просто сайт». Лечится реальной нативной ценностью:
|
||||
push, share-extension, биометрический разблок, оффлайн-кэш — всё это Capacitor
|
||||
даёт плагинами.
|
||||
- **safe-area** под «чёлку»/системные панели, поведение экранной клавиатуры в
|
||||
редакторе.
|
||||
|
||||
---
|
||||
|
||||
## 9. Оффлайн в будущем
|
||||
|
||||
Оффлайн сейчас не требуется, но позиция хорошая:
|
||||
|
||||
- Тело документа уже редактируется через Yjs (CRDT) + `y-indexeddb` — локальная
|
||||
копия и автослияние правок работают, в том числе в WebView.
|
||||
- «Полностью онлайн» — это всё вокруг тела (навигация, заголовки, комментарии,
|
||||
CRUD, вложения, авторизация). Их оффлайн-синхронизация описана отдельным
|
||||
планом с этапами M0…M4 — см. [offline-sync-plan.md](offline-sync-plan.md).
|
||||
- Мобильное приложение **переиспользует** этот план, а не строит оффлайн заново.
|
||||
Нюанс Android: System WebView под нехваткой места может чистить хранилище →
|
||||
для оффлайна, возможно, понадобится дублировать критичные данные в нативное
|
||||
хранилище, чтобы локальные копии не вычищались.
|
||||
|
||||
---
|
||||
|
||||
## 10. Открытые вопросы (зафиксировать до старта)
|
||||
|
||||
- **Q1.** Путь: Capacitor (B) с эволюцией в гибрид, или сразу React Native (C)?
|
||||
Рекомендация — B.
|
||||
- **Q2.** Мобильная авторизация: отдельный логин-флоу с токеном в body + Keychain/
|
||||
Keystore + Bearer (рекомендуется) или попытка работать через cookie в WebView?
|
||||
- **Q3.** Push: APNs + FCM сразу или iOS-first?
|
||||
- **Q4.** Подключать ли OpenAPI/Swagger для генерации мобильного клиента?
|
||||
- **Q5.** Когда включать оффлайн (M0…M4 из offline-sync-plan.md) относительно
|
||||
первого мобильного релиза?
|
||||
|
||||
---
|
||||
|
||||
## 11. Чеклист первого шага (бутстрап Capacitor, iOS-first)
|
||||
|
||||
- [ ] Прогнать существующий адаптивный UI как PWA/в WebView, отловить отличия
|
||||
(жесты, IME в редакторе, safe-area).
|
||||
- [ ] Добавить Capacitor в монорепо, нацелить на веб-билд `apps/client`.
|
||||
- [ ] `npx cap add ios` (Android — `npx cap add android`, когда будет готова обвязка).
|
||||
- [ ] Бэкенд: мобильный логин-флоу с токеном в body; хранить токен в Keychain/
|
||||
Keystore; слать `Authorization: Bearer`.
|
||||
- [ ] Бэкенд: явный CORS-whitelist под мобильные origin'ы.
|
||||
- [ ] Native-плагины под App Store 4.2: push, биометрия, share, файлы.
|
||||
- [ ] Push: APNs (iOS); FCM добавить вместе с Android.
|
||||
- [ ] Проверить вебсокет коллаборации из WebView (`/auth/collab-token` + Hocuspocus).
|
||||
- [ ] (Опционально) Подключить `@nestjs/swagger`.
|
||||
Reference in New Issue
Block a user