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`.