From 1207ef1143ade66e5f56d8e347c0eb62e976253c Mon Sep 17 00:00:00 2001 From: vvzvlad Date: Thu, 18 Jun 2026 01:55:42 +0300 Subject: [PATCH] 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). --- README.md | 2 +- README.ru.md | 2 +- docs/mobile-app-plan.md | 236 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 docs/mobile-app-plan.md diff --git a/README.md b/README.md index cd8dbc1b..ac81cf8f 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ community feature, with no enterprise license. Open it from the page header; the - 🔭 **Viewer comments** — let read-only viewers leave comments. - 🔭 **Password-protected pages** — protect individual pages / shares with a password. - 🔭 **Windows / Linux app** — native desktop app for Windows and Linux. -- 🔭 **Mobile app** — native mobile application. +- 🔭 **Mobile app** — mobile apps (iOS first, Android to follow), reusing the existing responsive web UI and editor via a Capacitor wrapper, with offline planned for later. See [docs/mobile-app-plan.md](docs/mobile-app-plan.md). - 🔭 **Offline mode** — offline sync & PWA support. - 🔭 **Voice dictation** — microphone button in the AI agent chat and the page editor; audio is transcribed server-side (Whisper / OpenAI-compatible STT) via the workspace AI provider, with an admin toggle to show/hide it. See [docs/voice-dictation-plan.md](docs/voice-dictation-plan.md). - 🔭 **Editor & UX improvements** — blocks inside tables (lists, to-do items), column layout, additional heading levels, highlight blocks, custom emoji in callouts, floating images, anchor links for page mentions, toggles (shared-page width, aside/sidebar, spellcheck, ligatures), sanitized space-tree export, and mentions in breadcrumbs. diff --git a/README.ru.md b/README.ru.md index 36faa4bd..8d188395 100644 --- a/README.ru.md +++ b/README.ru.md @@ -111,7 +111,7 @@ real-time-коллаборации Docmost, поэтому запись нико - 🔭 **Комментарии зрителей** — возможность комментировать для пользователей с доступом только на чтение. - 🔭 **Защищённые паролем страницы** — защита отдельных страниц / шар паролем. - 🔭 **Приложение для Windows / Linux** — нативное десктоп-приложение для Windows и Linux. -- 🔭 **Мобильное приложение** — нативное мобильное приложение. +- 🔭 **Мобильное приложение** — мобильные приложения (iOS обязательно, Android как пойдёт) на базе существующей адаптивной веб-версии и редактора через обёртку Capacitor; оффлайн запланирован на будущее. См. [docs/mobile-app-plan.md](docs/mobile-app-plan.md). - 🔭 **Офлайн-режим** — офлайн-синхронизация и поддержка PWA. - 🔭 **Голосовая диктовка** — кнопка-микрофон в чате AI-агента и в редакторе страниц; аудио распознаётся на сервере (Whisper / OpenAI-совместимый STT) через AI-провайдер воркспейса, с тумблером админа для показа/скрытия. См. [docs/voice-dictation-plan.md](docs/voice-dictation-plan.md). - 🔭 **Улучшения редактора и UX** — блоки внутри таблиц (списки, чек-листы), колоночная вёрстка, дополнительные уровни заголовков, highlight-блоки, кастомные эмодзи в callout-ах, плавающие изображения, anchor-ссылки на упоминания страниц, тоглы (ширина шары, aside/сайдбар, spellcheck, лигатуры), санитизация экспорта дерева спейса и mentions в хлебных крошках. diff --git a/docs/mobile-app-plan.md b/docs/mobile-app-plan.md new file mode 100644 index 00000000..f4660454 --- /dev/null +++ b/docs/mobile-app-plan.md @@ -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`.