From b51703fb76492bc1cb11b95fc0be4ef1bf1987fe Mon Sep 17 00:00:00 2001 From: tianma-if Date: Tue, 15 Sep 2026 12:10:21 +0800 Subject: [PATCH] docs: drop stale internal notes that drifted from the code Keep visual-diagram design docs. Remove unused rewrite, research, and promotion files so agents follow current Android/iOS paths instead of outdated plans. --- apps/ios/README.md | 2 - docs/demo-sample-note.md | 192 --- docs/demo-sample-note.zh-CN.md | 192 --- docs/desktop-performance-baseline.md | 65 - docs/ios-swift-rewrite.md | 1381 -------------------- docs/mobile-build.md | 27 +- docs/mobile-native-parity.md | 49 - docs/personality-memory-agent.md | 90 -- docs/personality-memory-agent.zh-CN.md | 90 -- docs/project-structure.md | 63 - docs/promotion/edgeever-hero-product.png | Bin 1255564 -> 0 bytes docs/promotion/juejin_cover.jpg | Bin 572832 -> 0 bytes docs/promotion/promotion_article_juejin.md | 143 -- docs/promotion/slogan.md | 54 - 14 files changed, 6 insertions(+), 2342 deletions(-) delete mode 100644 docs/demo-sample-note.md delete mode 100644 docs/demo-sample-note.zh-CN.md delete mode 100644 docs/desktop-performance-baseline.md delete mode 100644 docs/ios-swift-rewrite.md delete mode 100644 docs/mobile-native-parity.md delete mode 100644 docs/personality-memory-agent.md delete mode 100644 docs/personality-memory-agent.zh-CN.md delete mode 100644 docs/project-structure.md delete mode 100644 docs/promotion/edgeever-hero-product.png delete mode 100644 docs/promotion/juejin_cover.jpg delete mode 100644 docs/promotion/promotion_article_juejin.md delete mode 100644 docs/promotion/slogan.md diff --git a/apps/ios/README.md b/apps/ios/README.md index f2617245..b68cca69 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -2,8 +2,6 @@ Native SwiftUI client for EdgeEver. This replaces the React Native / Expo iOS target. -**Design document:** [`docs/ios-swift-rewrite.md`](../../docs/ios-swift-rewrite.md) - **App Store binaries on macOS beta:** use **Xcode Cloud (manual)** — [`docs/ios-xcode-cloud.md`](../../docs/ios-xcode-cloud.md). ## Status diff --git a/docs/demo-sample-note.md b/docs/demo-sample-note.md deleted file mode 100644 index 4d09c3fa..00000000 --- a/docs/demo-sample-note.md +++ /dev/null @@ -1,192 +0,0 @@ -> **EdgeEver** is an open-source, AI-native, and portable serverless/containerized personal knowledge base that revives the beloved **Evernote-style three-pane layout**. Designed for geeks and content creators, it combines 100% free serverless hosting, absolute data ownership, native cross-platform clients, and seamless AI Agent (MCP) synergy to redefine your digital second brain. - ---- - -## ⚡ 1. Why Choose EdgeEver? - -*Tip: In editor mode, click any table cell to edit text directly, or right-click to insert/delete rows and columns.* - -| Dimension | Traditional Cloud Notes (e.g. Evernote) | Local Offline Notes (e.g. Obsidian) | EdgeEver Geek Knowledge Base | -| :--- | :--- | :--- | :--- | -| **Source Code** | Closed source | Closed-source core, open files | **Full-stack open source (AGPL-3.0)** | -| **Hosting Cost** | Costly commercial subscriptions ($10+/mo) | Official cloud sync subscription ($5+/mo) | **100% Free Forever (Cloudflare Free Tier / Docker Self-Hosted)** | -| **Data Ownership** | Proprietary lock-in, hard to export | Local Markdown, cumbersome mobile sync | **Full Ownership (D1 SQLite, R2 Storage, Lossless ZIP Archives)** | -| **Editing Experience** | Rich text only, fragile formatting | Plain Markdown, lacks WYSIWYG flow | **Seamless Dual-View (WYSIWYG Rich Text ⇄ Markdown Source)** | -| **Publishing Ready** | No typography styling, loses format | Requires 3rd-party plugins or scripts | **One-Click Rich Copy for WeChat, Substack, Medium & WordPress** | -| **Social Poster Cards** | Basic screenshots without styling | Requires external tools or plugins | **8 Built-in Exquisite Poster Themes with Typography Options & PNG/JPEG Export** | -| **Native AI Ecosystem** | Paywalled, limited features | Heavy plugin setup required | **Native MCP Server (`/mcp`) & Inline AI Agent Synergy** | -| **Cross-Platform Matrix** | Restricts active free devices | Complex setup across platforms | **Web / PWA / Android / iOS / macOS / Windows (Coming Soon)** | - ---- - -## 🏗️ 2. Architectural Overview & Ecosystem - -The Mermaid diagram below demonstrates how EdgeEver connects cross-platform clients, zero-cost cloud infrastructure, and AI Agents into a cohesive workflow: - -```mermaid -flowchart TD - subgraph MultiClients["Client Matrix"] - C1["Web / PWA (Offline Drafts & Sync Queue)"] - C2["macOS Desktop (Electron + Rust Sidecar)"] - C3["Android Native App (Expo + SQLite)"] - C4["iOS Native App (SwiftUI + GRDB)"] - C5["Web Clipper (Chrome / Edge / Firefox)"] - end - - subgraph CoreEngine["Service & Storage Core"] - API["Hono API Engine (Cloudflare Workers / Docker)"] - MCP["Native MCP Endpoint (/mcp 2026-07-28 & 2025)"] - D1[("D1 / SQLite Database (Sync Cursor & Revisions)")] - R2[("R2 / S3 Storage (Images & Media Attachments)")] - end - - subgraph AIAgents["AI Agents & Integrations"] - A1["Claude Code / Cursor / Antigravity"] - A2["Inline AI Assistant (OpenAI / Gemini / DeepSeek)"] - A3["Notion DB / Feishu Bitable Sync"] - end - - MultiClients <==>|REST API & Sync Cursor| API - API <==> D1 & R2 - MCP <==> D1 & R2 - A1 <==>|MCP Tools / SSE| MCP - A2 -.->|BYOK Direct Key| MultiClients - A1 -.->|Bi-directional Sync| A3 -``` - ---- - -## 🎨 3. Immersive Writing & Typography Aesthetics - -EdgeEver is engineered to provide a distraction-free, elegant writing experience: - -### 🖥️ Dual-View Editor & Focus Modes -- **Seamless Dual-View**: Click the `` button in the top-right corner (or use shortcuts) to instantly toggle between **WYSIWYG Rich Text** and **Markdown Source Code** with 100% fidelity. -- **Collapsible Outline Navigation**: Automatically indexes `H1-H3` heading hierarchies with smooth jump scrolling, keeping lengthy documents organized. -- **Zen Focus Mode**: Press `Cmd/Ctrl + Shift + F` to hide sidebars and distractions, immersing yourself in pure writing flow. -- **Reading Protection Mode**: When reading or reviewing notes, press `Cmd/Ctrl + E` to toggle read-only protection, locking the editor to prevent accidental edits while browsing; press it again to seamlessly resume editing. - -### 🎭 Curated Typography Themes & Publishing Export -- **Preset Editor Themes**: Switch effortlessly between `WeChat Green`, `Fresh Mint`, `Minimal Jade`, `Lined Jade`, and styles such as Warm Page, Lesson Page, and Navy Draft. -- **One-Click Publishing Export**: Built for publishers and bloggers. Click the **WeChat Icon** in the top bar to format your note with inline CSS. Paste directly into WeChat Official Account editor, Substack, Medium, or WordPress while preserving layout and syntax highlighting. - -### 🖼️ 8 Exquisite Social Poster Themes -Click **"Share as Card"** in the top-right menu to turn any note into an eye-catching poster card: -- **8 Themes**: `Slate`, `Aurora`, `Sunset`, `Midnight`, `Mint`, `Notepad` (skeuomorphic paper), `Xuan` (rice paper & Chinese ink), `Lavender`. -- **Customizable Typography**: Switch between Sans, Serif (Songti/Ming), and Monospace fonts, select compact, standard, or wide card widths, and export as high-resolution PNG or JPEG. - ---- - -## ⌨️ 4. Power Productivity Toolbox: Slash Commands, Backlinks & Math - -### 🪄 Slash Commands -Type `/` or press `Space` on an empty line to invoke the command menu: -- Insert `H1/H2/H3` headings, quotes, horizontal rules, code blocks, and rich tables; -- Use `/date`, `/time`, or `/now` to insert current timestamps instantly; -- Attach files, insert images, or summon the inline AI assistant. - -### 🔗 Bi-directional Note Links & Backlinks -Type `@` or insert `#memo=` links to establish bi-directional references across your knowledge base, building an interconnected web of thoughts. - -### 📐 KaTeX Professional LaTeX Mathematics -Native support for KaTeX renders complex mathematical equations in milliseconds: - -- **Inline Math**: For instance, Einstein's mass-energy equivalence $E = mc^2$ or the Gaussian probability density function $f(x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$. -- **Display Block Math**: -$$ -\oint_{\partial \Omega} \mathbf{E} \cdot d\mathbf{S} = \frac{1}{\varepsilon_0} \iiint_{\Omega} \rho \, dV, \quad \oint_{\partial \Omega} \mathbf{B} \cdot d\mathbf{S} = 0 -$$ - -### ✅ Interactive Task Lists -- [x] Explore EdgeEver's dual-view editing and outline navigation -- [x] Experiment with 8 editor themes and 8 social poster card styles -- [ ] Try typing `/` on an empty line to insert current timestamp and tables -- [ ] Connect your AI API Key to experience inline summarization and continuation -- [ ] Connect AI Agents via MCP to automate note categorization - ---- - -## 🤖 5. Native AI Agent Synergy & MCP Protocol - -EdgeEver is architected for the agentic AI era, weaving LLMs directly into the knowledge management lifecycle: - -```mermaid -sequenceDiagram - autonumber - actor User as Creator / Knowledge Worker - participant Client as EdgeEver Clients - participant MCP as EdgeEver MCP Endpoint - participant Agent as AI Agent (Claude / Cursor / Antigravity) - participant Database as Notion / Feishu Bitable - - User->>Client: Capture quick spark or meeting notes - Agent->>MCP: tools/call memo_search (Query unprocessed memos) - MCP-->>Agent: Returns structured Markdown & tags - Agent->>Agent: Analyzes content, extracts key takeaways & tags - Agent->>MCP: tools/call memo_update (Writes back summary & tags) - Agent->>Database: Syncs structured records to master database - Client-->>User: Sub-second live updates in client UI -``` - -### 1️⃣ Inline AI Assistant -Press the **`Space` bar** in an empty block, type **`/ai`**, use the **`/`** slash command menu, or select text and click the **AI button** on the floating toolbar to instantly summon the AI writing assistant: -- **Summarization & Action Items**: Condense documents into key takeaways, conclusions, and actionable todos; -- **Format-Preserving Translation**: Translate accurately into multiple languages while strictly preserving Markdown, math equations, links, and code blocks; -- **Rewriting & Continuation**: Polish phrasing, make writing concise, or continue writing seamlessly; -- **BYOK Direct Key (Bring Your Own Key)**: Direct client-side connection with OpenAI, Anthropic Claude, Google Gemini, DeepSeek, and OpenAI-compatible relays with zero third-party data transit. - -### 2️⃣ Model Context Protocol (MCP) Integration -Generate an API token in **Settings → MCP Settings** to connect EdgeEver directly with Claude Code, Cursor, Antigravity, OpenClaw, and other agent platforms: -- **Direct Reading & Writing**: Connect via the `/mcp` endpoint (supports stateless `2026-07-28` protocol and handshake-based 2025 specs); -- **Automated Workflows**: Let AI Agents read, organize, summarize, tag, and synchronize notes with Notion databases and Feishu Bitable. - ---- - -## 📱 6. Multi-Platform Ecosystem, Offline Sync & Smart Clipping - -- **Cross-Platform Native Apps**: - - **Web / PWA**: Full-featured in modern browsers with offline installation support; - - **Android Native App**: Built with Expo & SQLite, available on Google Play and GitHub Releases; - - **iOS Native App**: Native Swift / SwiftUI with GRDB local mirror, available on the App Store (requires a non-mainland China Apple ID); - - **macOS Desktop App**: Electron + Rust Sidecar for Apple Silicon & Intel Mac with silent background updates; - - **Windows Desktop App**: Development is complete, coming soon. -- **Browser Web Clipper**: Available on Chrome, Edge, and Firefox extension stores to extract clean, ad-free Markdown articles in one click. -- **Mobile WeChat Article Clipper**: Share any WeChat article to EdgeEver on your phone to automatically extract and format it as an editable note. -- **Offline Drafts & Sync Queue**: Keep writing seamlessly without network connectivity; changes are queued locally and automatically synced once reconnected. - ---- - -## 📦 7. Rich Media, Lossless Portability & Zero-Cost Hosting - -### 🖼️ Smart Client-Side Image Compression -When pasting or dragging images into notes, EdgeEver compresses them to WebP locally in your browser before upload, **reducing file size by 50% - 90%** while preserving visual fidelity. - -![EdgeEver Official Logo](/api/v1/resources/res_demo_logo/blob) - -### 📎 Universal File Attachments -Embed PDFs, spreadsheets, archives, and multimedia files directly in notes for preview or download: -- [📄 Product brief PDF: edgeever-product-brief.pdf](/api/v1/resources/res_demo_product_brief_pdf/blob) -- [📊 Feature matrix CSV: feature-matrix.csv](/api/v1/resources/res_demo_feature_matrix_csv/blob) -- [📦 Sample attachment archive: edgeever-attachment-demo.zip](/api/v1/resources/res_demo_attachment_bundle_zip/blob) - -### 🚀 Zero-Cost Serverless & Docker Self-Hosting -1. **Cloudflare Serverless (Recommended, 100% Free Forever)**: Runs entirely within Cloudflare's free tier (Workers + D1 SQLite + R2 Storage). No server bills, no VPS maintenance. -2. **Docker One-Command Deployment (VPS / NAS / Home Server)**: - ```sh - # Official GHCR image - curl -fsSL https://edgeever.org/install.sh | bash - ``` - Configures Docker Compose and automated daily background updates with one command. Some network environments in mainland China may require an available network proxy or a trusted registry mirror to access GHCR. - -### 💾 Complete Data Freedom: Lossless ZIP Portability -Export your entire library at any time from **Profile → Import and export**. The archive contains pure Markdown files with standard YAML Front Matter, relative media paths, and full revision histories—compatible with Obsidian, VS Code, and any plain text editor. - ---- - -> 💡 **Quick Exploration Tips**: -> 1. Click **``** in the top right to try seamless dual-view toggling; -> 2. Press **`Cmd/Ctrl + E`** to test reading protection mode against accidental edits; -> 3. Press **`Cmd/Ctrl + O`** to summon the Quick Switcher; -> 4. Click the **WeChat Icon** to copy formatted rich text ready for publishing; -> 5. Click **"Share as Card"** to export a stunning social poster card; -> 6. Click **"Reset Demo Data"** in settings or the sidebar anytime to restore the demo workspace. diff --git a/docs/demo-sample-note.zh-CN.md b/docs/demo-sample-note.zh-CN.md deleted file mode 100644 index d435c2b6..00000000 --- a/docs/demo-sample-note.zh-CN.md +++ /dev/null @@ -1,192 +0,0 @@ -> **EdgeEver** 是一款兼具**经典三栏美学**与 **AI 原生动力**的开源 Serverless / 容器化个人知识库。它既找回了极客们钟爱的经典印象笔记式双视图与树状目录,又以零服务器成本、完全数据自主、全端原生覆盖与深度 MCP 智能体协同,重新定义了下一代个人第二大脑。 - ---- - -## ⚡ 1. 为什么选择 EdgeEver? - -*提示:在编辑器模式下,你可以直接点击任意表格单元格进行行内编辑,或右键快捷插入/删除行列。* - -| 核心维度 | 传统商业云笔记 (如 Evernote) | 本地离线知识库 (如 Obsidian) | EdgeEver 极客知识库 | -| :--- | :--- | :--- | :--- | -| **源代码** | 闭源 | 核心闭源,文件开放 | **整栈开源 (AGPL-3.0)** | -| **云端托管成本** | 商业订阅高昂($10+/月) | 官方云同步收费($5+/月) | **100% 永久免费 (Cloudflare 免费额度 / Docker 自建)** | -| **数据资产所有权** | 专有格式封闭,导出困难 | 本地 Markdown,移动端同步繁琐 | **完全自主掌控 (D1 SQLite 数据库 / R2 / 无损 ZIP 导出)** | -| **写作与编辑体验** | 仅富文本,排版易错乱 | 纯 Markdown,缺乏沉浸所见即所得 | **双视图自由切换 (所见即所得富文本 ⇄ Markdown 源码)** | -| **自媒体排版分发** | 无样式优化,格式丢失 | 需借助外部排版扩展或工具 | **一键复制到微信公众号 / Substack / Medium / WordPress** | -| **社交卡片分享** | 截图粗糙或无排版设计 | 需第三方插件实现 | **内置 8 款精美海报主题,支持自定义字体与 PNG/JPEG 导出** | -| **AI 原生集成生态** | 封闭收费,仅限特定功能 | 需配置复杂的第三方插件 | **原生内置 MCP 服务端点 (`/mcp`) 与行内 AI 智能体协同** | -| **全平台多端覆盖** | 限制免费设备登录数量 | 多端同步与移动端体验门槛高 | **Web / PWA / Android / iOS / macOS / Windows (即将推出)** | - ---- - -## 🏗️ 2. 全景架构与生态联动 - -通过下面的 Mermaid 架构图,你可以清晰了解 EdgeEver 如何将多端客户端、零成本云基础设施与 AI Agent 紧密串联: - -```mermaid -flowchart TD - subgraph MultiClients["全端客户端矩阵"] - C1["Web / PWA (离线草稿 & 增量同步)"] - C2["macOS 桌面端 (Electron + Rust Sidecar)"] - C3["Android 原生 App (Expo + SQLite)"] - C4["iOS 原生 App (SwiftUI + GRDB)"] - C5["浏览器剪藏插件 (Chrome / Edge / Firefox)"] - end - - subgraph CoreEngine["业务与数据服务层"] - API["Hono API 引擎 (Cloudflare Workers / Docker)"] - MCP["原生 MCP Endpoint (/mcp 2026-07-28 & 2025)"] - D1[("D1 / SQLite 数据库 (双向同步 & 修订版本)")] - R2[("R2 / S3 资源存储 (图片与多媒体附件)")] - end - - subgraph AIAgents["AI 智能体与外部生态"] - A1["Claude Code / Cursor / Antigravity"] - A2["行内 AI 助手 (OpenAI / Gemini / DeepSeek)"] - A3["Notion 数据库 / 飞书多维表格联动"] - end - - MultiClients <==>|REST API & 同步游标| API - API <==> D1 & R2 - MCP <==> D1 & R2 - A1 <==>|MCP Tools / SSE| MCP - A2 -.->|BYOK 密钥直连| MultiClients - A1 -.->|双向数据同步| A3 -``` - ---- - -## 🎨 3. 极致创作体验与排版美学 - -EdgeEver 将高效与优雅融入每一处交互细节,助你专注于思考与表达: - -### 🖥️ 双视图编辑器与沉浸模式 -- **自由切换视图**:点击右上角 `` 按钮或使用快捷键,可在**所见即所得富文本**与 **Markdown 源码**间无缝切换,实时双向保真。 -- **左侧可折叠大纲**:自动解析文档 `H1-H3` 标题层级,支持点击平滑滚动跳转,助你轻松把控万字长文。 -- **Zen 专注模式**:按下 `Cmd/Ctrl + Shift + F`,隐藏所有侧边栏与干扰元素,进入纯粹写作心流。 -- **阅读保护模式 (Reading Protection)**:日常翻阅或查阅笔记时,按下 `Cmd/Ctrl + E` 即可一键开启只读保护,锁定当前编辑状态,避免沉浸阅读时误触键盘或意外改动笔记内容;再次按下即可随手切回编辑。 - -### 🎭 精选排版主题与一键自媒体发布 -- **内置排版主题**:支持一键切换 `微信新绿`、`薄荷清新`、`极简青翠`、`线描翠绿`,以及日暖素笺、青页讲义、夜蓝制图等排版风格。 -- **一键排版复制**:专为内容创作者设计。点击顶部工具栏的**微信公众号图标**,系统自动将当前笔记转为内联 CSS 样式的优雅富文本,直接粘贴至微信公众号后台、Substack 或 WordPress,排版与代码高亮完美保真。 - -### 🖼️ 8 套精美社交分享海报 (Poster Cards) -点击右上角“分享为卡片”,即可将任意笔记或片段渲染为高清分享海报,支持: -- **8 大主题风格**:`Slate (岩灰)`、`Aurora (极光青)`、`Sunset (落日暖橙)`、`Midnight (暗夜极客)`、`Mint (清爽薄荷)`、`Notepad (复古便签)`、`Xuan (宣纸水墨)`、`Lavender (薰衣草紫)`。 -- **排版定制**:支持无衬线 (Sans)、宋体/明朝 (Serif)、等宽代码 (Mono) 字体切换,自由选择紧凑、标准或宽幅卡片,一键导出为高清晰度 PNG 或 JPEG 图片。 - ---- - -## ⌨️ 4. 效率工具箱:斜杠指令、双链与数学公式 - -### 🪄 斜杠指令 (Slash Commands) -在正文空白行输入 `/` 或直接按下 `空格键`,即可呼出快捷指令菜单: -- 快速插入 `H1/H2/H3` 标题、引用、分割线、代码块与富文本表格; -- 输入 `/date`、`/time` 或 `/now` 快速插入当前标准时间戳; -- 插入附件、图片或调起行内 AI 智能助手。 - -### 🔗 知识双链与笔记引用 -在编辑器中输入 `@` 或插入 `#memo=<笔记ID>` 链接,即可建立笔记间的双向关联,点击即可在工作区内快速打开关联笔记,构建结构化网状知识库。 - -### 📐 KaTeX 专业数学公式渲染 -EdgeEver 原生支持 LaTeX 数学表达式,无论是行内微积分还是多行物理方程,均可毫秒级高保真渲染: - -- **行内公式**:例如质能方程 $E = mc^2$,以及正态分布概率密度函数 $f(x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$。 -- **多行独立公式块**: -$$ -\oint_{\partial \Omega} \mathbf{E} \cdot d\mathbf{S} = \frac{1}{\varepsilon_0} \iiint_{\Omega} \rho \, dV, \quad \oint_{\partial \Omega} \mathbf{B} \cdot d\mathbf{S} = 0 -$$ - -### ✅ 交互式待办清单 (Task Lists) -- [x] 体验 EdgeEver 双视图与大纲导航 -- [x] 探索 8 款编辑器主题与 8 套社交海报卡片 -- [ ] 尝试使用斜杠指令 `/` 插入当前日期与结构化表格 -- [ ] 配置个人 AI API Key,体验行内智能总结与续写 -- [ ] 开启 MCP 协议,让 AI Agent 协助整理工作区 - ---- - -## 🤖 5. AI 原生协同与 MCP 智能体生态 - -EdgeEver 走在 AI 时代前沿,将大语言模型与智能体深度融入知识管理生命周期: - -```mermaid -sequenceDiagram - autonumber - actor User as 创作者 / 知识工作者 - participant Client as EdgeEver 多端应用 - participant MCP as EdgeEver MCP 服务端点 - participant Agent as AI Agent (Claude / Cursor / Antigravity) - participant Database as Notion / 飞书多维表格 - - User->>Client: 快速记录碎片想法与会议灵感 - Agent->>MCP: tools/call memo_search (检索近期未整理笔记) - MCP-->>Agent: 返回结构化 Markdown 笔记内容与标签 - Agent->>Agent: 理解内容、自动提炼核心摘要并归类标签 - Agent->>MCP: tools/call memo_update (写回智能总结与增强标签) - Agent->>Database: 同步结构化字段至个人知识总库 - Client-->>User: 客户端毫秒级感知更新,笔记井井有条 -``` - -### 1️⃣ 行内 AI 助手 (Inline AI Assistant) -在正文空白行直接按下 **`空格键 (Space)`**、输入 **`/ai`**、通过 **`/`** 斜杠指令菜单,或选中文本点击浮动工具栏中的 **AI 按钮**,即可即时呼出 AI 写作助手面板: -- **精炼总结与要点提取**:一键压缩提炼全文核心结论、提取待办事项与关键行动项; -- **格式保真智能翻译**:在严格保留原有 Markdown、数学公式、链接与代码块的前提下,精准翻译多国语言; -- **内容重塑与续写**:支持润色与精炼表达,或按上下文顺滑续写; -- **BYOK 隐私直连 (Bring Your Own Key)**:支持直连 OpenAI、Anthropic Claude、Google Gemini、DeepSeek 及各类 OpenAI 兼容的中继 API,数据完全由端侧直发,不经过任何第三方中转。 - -### 2️⃣ 开放 MCP 协议 (Model Context Protocol) -在**设置 → MCP 设置**中生成专属令牌,即可将 EdgeEver 接入 Claude Code、Cursor、Antigravity、OpenClaw 等主流 AI 编码助手与智能体平台: -- **无缝读写**:支持标准 MCP 端点 `/mcp`(兼容最新的无状态 `2026-07-28` 协议与经典握手协议); -- **自动化流转**:AI Agent 可自动读取笔记、智能归档、批量打标,甚至与 Notion、飞书多维表格建立跨平台自动化同步。 - ---- - -## 📱 6. 全平台原生覆盖、离线同步与智能剪藏 - -- **多端原生支持**: - - **Web / PWA**:支持现代浏览器全功能运行与离线安装; - - **Android 原生客户端**:基于 Expo 与 SQLite 构建,已上架 Google Play,并提供签名 APK 下载; - - **iOS 原生客户端**:基于 Swift / SwiftUI 与 GRDB 原生实现,流畅细腻,已上架 App Store(可使用非大陆区 Apple ID 下载); - - **macOS 桌面端**:Electron + Rust 高性能 Sidecar,支持 Apple Silicon 和 Intel Mac,内置静默后台更新; - - **Windows 桌面端**:已开发完毕,即将正式推出。 -- **浏览器网页剪藏 (Web Clipper)**:已在 Chrome、Edge 和 Firefox 官方扩展商店发布,一键剔除网页广告,将正文纯净沉淀为 Markdown 笔记。 -- **微信公众号一键剪藏**:在移动端系统分享菜单中,直接将微信文章分享到 EdgeEver,客户端将自动提取图文排版并转换为可编辑笔记。 -- **离线草稿与弹性同步**:地铁、飞行模式等无网络环境下自由编辑,重新联网后自动入队完成增量同步与冲突协调。 - ---- - -## 📦 7. 多媒体管理、无损备份与零成本自建 - -### 🖼️ 智能本地图片压缩 -在笔记中粘贴或拖入高分辨率图片时,前端会在本地自动将其转码压缩为 WebP 格式,在保证视觉无损的前提下**减少 50% - 90% 的体积**,大幅降低云端存储占用并提升跨端加载速度。 - -![EdgeEver 官方 Logo](/api/v1/resources/res_demo_logo/blob) - -### 📎 多类型附件自由挂载 -支持在笔记中嵌入 PDF 文档、CSV 表格、压缩包及多媒体资源,点击即可在线预览或下载: -- [📄 产品白皮书 PDF:edgeever-product-brief.pdf](/api/v1/resources/res_demo_product_brief_pdf/blob) -- [📊 功能矩阵 CSV:feature-matrix.csv](/api/v1/resources/res_demo_feature_matrix_csv/blob) -- [📦 示例附件压缩包:edgeever-attachment-demo.zip](/api/v1/resources/res_demo_attachment_bundle_zip/blob) - -### 🚀 两种自建部署方案:Serverless 与 Docker -1. **Cloudflare Serverless(推荐,永久 100% 免费)**:基于 Workers + D1 数据库 + R2 对象存储构建,完全处于 Cloudflare 免费额度内,无需采购服务器,免运维、免证书续期。 -2. **Docker 一键自建(VPS / NAS / 家用服务器)**: - ```sh - # 官方 GHCR 镜像 - curl -fsSL https://edgeever.org/install.sh | bash - ``` - 单命令自动配置 Docker Compose 环境,并内置每日定时自动更新。部分中国大陆网络环境可能需要自行配置可用的网络代理或可信的镜像加速服务,才能正常访问 GHCR。 - -### 💾 绝对的数据自由:无损 ZIP 导入与导出 -在**个人中心 → 导入与导出**中,可随时将完整笔记库打包导出为结构清晰的 ZIP 压缩包。解压后即为包含标准 YAML Front Matter、相对路径附件图片与完整修订版本的纯 Markdown 文件树,随时可在 Obsidian、VS Code 等任意工具中无缝打开,永不担心平台绑定! - ---- - -> 💡 **快速体验小贴士**: -> 1. 点击右上角 **``** 体验丝滑的双视图切换; -> 2. 按下 **`Cmd/Ctrl + E`** 试一试阅读保护模式,锁定只读避免误改内容; -> 3. 按下 **`Cmd/Ctrl + O`** 呼出快速切换器 (Quick Switcher); -> 4. 点击顶部 **微信图标**,体验一键带格式排版复制; -> 5. 点击 **“分享为卡片”**,挑选一款你心仪的社交海报风格并导出; -> 6. 随时在侧边栏或设置中点击 **“恢复 Demo 数据”**,一键重置演示工作区。 diff --git a/docs/desktop-performance-baseline.md b/docs/desktop-performance-baseline.md deleted file mode 100644 index 809b6290..00000000 --- a/docs/desktop-performance-baseline.md +++ /dev/null @@ -1,65 +0,0 @@ -# Desktop performance baseline - -The desktop app must remain a local-first application. Remote network latency -must not be part of the critical path for opening or editing a note. - -## Acceptance targets - -| Operation | Target | -| --- | ---: | -| Sidecar ready after process spawn | `< 500 ms` | -| Local SQLite health request | `< 20 ms` | -| Existing local memo list query | `< 50 ms` | -| Existing local memo detail query | `< 20 ms` | -| Create/edit local memo acknowledgement | `< 50 ms` | -| Cloud sync | Background only | - -The Web/PWA build keeps the install-time service-worker precache limited to the -web manifest. Mermaid, -diagram renderer, code-highlighting, and editor-only Tiptap/ProseMirror chunks -stay out of the initial HTML `modulepreload` list and fill runtime caches on -first use, so ordinary note-list startup does not pay for optional -editor/diagram support. The initial modulepreload list is also capped at 750 KiB -uncompressed; the build verifier reports the measured bytes and fails if this -critical-path budget regresses. The web app remains installable through the -manifest. - -Remote resource blobs use a separate 90-day, 500-entry cache-first runtime -cache. When the browser is offline, newly attached files are written to the -same Cache Storage namespace under a local resource URL, added to the local -resource mirror, and queued for upload. The sync worker uploads the bytes, -rewrites memo JSON/Markdown references, and only then removes the local blob. - -## Repeatable checks - -Build and benchmark the sidecar: - -```sh -cargo build --manifest-path crates/desktop-sidecar/Cargo.toml --release -EDGE_EVER_SIDECAR_PATH="$PWD/crates/desktop-sidecar/target/release/edgeever-sidecar" \ - bun run benchmark:desktop:sidecar -``` - -Build an unsigned local desktop bundle and inspect that the bundle contains -the Web renderer, migrations, and sidecar: - -```sh -bun run build:web -cargo build --manifest-path crates/desktop-sidecar/Cargo.toml --release -CSC_IDENTITY_AUTO_DISCOVERY=false \ - bun --cwd apps/desktop run dist -- --dir -``` - -The benchmark exercises the same local domain RPCs used by the desktop -renderer: notebook discovery, memo creation, memo list, and memo detail. Its -`thresholds` object is suitable for CI gating; remote sync is deliberately not -included in the interactive critical path. - -Verify that the install-time PWA precache contains only the web manifest and -that optional editor and diagram chunks stay out of the initial HTML -modulepreload list: - -```sh -bun run build:web -bun run verify:web-performance -``` diff --git a/docs/ios-swift-rewrite.md b/docs/ios-swift-rewrite.md deleted file mode 100644 index 9a125c93..00000000 --- a/docs/ios-swift-rewrite.md +++ /dev/null @@ -1,1381 +0,0 @@ -# EdgeEver iOS Swift Rewrite - -| Field | Value | -| --- | --- | -| **Document** | EdgeEver iOS native rewrite (drop React Native on iOS) | -| **Author** | TBD | -| **Date** | 2026-08-06 | -| **Status** | Approved (rev 4 — design review consensus; scaffold PR 1 landed) | -| **Audience** | Senior engineers implementing `apps/ios` | -| **Related** | `docs/mobile-native-parity.md`, `apps/mobile`, `packages/client`, `packages/shared` | - ---- - -## Overview - -EdgeEver’s mobile client today is a single Expo / React Native app in `apps/mobile` targeting both Android and iOS. Android is on Google Play and is healthy. iOS has been under App Store review but fails in production with hard crashes inside React Native’s JS runtime and Fabric attributed-string paths (`EXC_BREAKPOINT` / `SIGTRAP` on `com.facebook.react.runtime.JavaScript`, malloc aborts near Hermes / AttributedString). These are not ordinary app-logic bugs; further RN crash debugging on iOS is an open-ended cost with low confidence of a durable fix. - -**Product decision (final):** abandon the React Native iOS path entirely and ship a pure Swift / SwiftUI native app under `apps/ios`, keeping the same App Store listing and bundle identifier `org.edgeever.mobile`. Android remains Expo / RN in `apps/mobile`. The first App Store submission of the Swift binary **must include full editing** (login, list, sync, create/edit/autosave, offline queue, TipTap body)—not a read-only MVP. - -This document specifies architecture, API surface, SQLite schema, sync state machine, TipTap WKWebView integration, save lifecycle, directory layout, acceptance criteria, migration from RN iOS, risks, key decisions, and an ordered PR / work-package plan suitable to scaffold `apps/ios` immediately after approval. - ---- - -## Background & Motivation - -### Current mobile architecture (RN) - -| Layer | Location | Role | -| --- | --- | --- | -| Shell UI | `apps/mobile/src/screens/*` | Login, workspace, notes list, detail chrome, settings | -| Local mirror | `apps/mobile/src/lib/local-mirror.ts` | SQLite: notebooks, memos, sync meta, id mappings | -| Sync protocol helpers | `apps/mobile/src/lib/mobile-sync-protocol.ts` | Cursor rewind / identity change / bootstrap batching | -| Offline outbox | `apps/mobile/src/lib/sync-queue.ts` | `memo.create` / `memo.update` queue (AsyncStorage) | -| Session | `apps/mobile/src/lib/session.tsx` | SecureStore session + deviceId; Bearer token | -| API client | `packages/client/src/index.ts` | Full REST surface against `/api/v1/*` | -| Editor | `apps/mobile/src/components/LocalTiptapEditor.tsx` | TipTap in Expo DOM / WebView; shared toolbar copy in `@edgeever/shared/mobile-editor` | -| Share extension | `apps/mobile/ios/expo-sharing-extension/ShareIntoViewController.swift` | Already native Swift; App Group + open host app | -| Parity rules | `docs/mobile-native-parity.md` | Native shell for lists/nav/settings; TipTap WebView for note body; exclude ZIP import/export and trash browse from app core | - -### Pain points - -1. **iOS RN instability under review:** crash logs under `.tmp-app-review-crashes/` point at RN Fabric / Hermes, not EdgeEver business logic. -2. **Dual-platform RN cost on a broken half:** energy spent on iOS RN does not improve Android and blocks App Store continuity. -3. **Editor already hybrid:** TipTap is hosted in a WebView on mobile today. A native Swift shell with the same WebView document model is a smaller leap than rewriting the editor in native text views. -4. **Share extension is already Swift:** host-app rewrite can absorb and simplify the extension without Expo plugin constraints. - -### Product constraints (confirmed) - -- Minimum deployment target: **iOS 17** (SwiftUI-first). -- **Editing required** before first App Store submission of the Swift binary. -- Bundle ID continuity: **`org.edgeever.mobile`** (see `apps/mobile/app.json`). -- Android path: **unchanged** Expo/RN in `apps/mobile`. -- Repo policy (`AGENTS.md`): work on **`main` only** (no new git branches); bilingual docs when changing Chinese docs. -- Soft delete only on mobile (no trash browse UI); recovery remains desktop/PWA. - ---- - -## Goals & Non-Goals - -### Goals - -1. Ship a store-ready native iOS app with: - - Login to self-hosted or cloud instance (`baseUrl` + username/password + `deviceId`). - - Notebook hierarchy browse/select + memo list (filter, sort, pin, search, soft delete); memo move-to-notebook via content outbox. - - Local SQLite mirror + incremental sync (`/api/v1/sync/bootstrap`, `/api/v1/sync/changes`). - - Create / rich-edit / autosave with TipTap body (same `TiptapDoc` schema as web/RN). - - Offline create/update queue with revision conflict handling and `local:` id remap. - - Protected resource loading (images/attachments) with Bearer token. - - Image upload with optional compression and placeholder UX. - - Share Extension (text / URL → open app and create draft). - - Settings subset: locale, theme, image compression, account security (password), API tokens, device sessions, sign out. -2. Keep API contracts identical to `packages/client` (no backend fork for iOS). -3. Preserve App Store listing continuity (same bundle id, ASC app id `6792625631`). -4. Stop EAS iOS builds and RN iOS maintenance; document Android-only evolution of `apps/mobile`. -5. Structure `apps/ios` so an engineer can scaffold and implement module-by-module from this doc. - -### Non-Goals (v1 submission) - -- ZIP import/export, trash browse / empty trash UI. -- Multi-user **admin** UI (`listUsers` / create/disable users). RN mobile only shows the current user's role label in settings—it does **not** ship a user-management console. Do not invent one for iOS. -- Full desktop feature parity (templates library management, merge UI polish, object-storage admin). -- Replacing Android RN with Kotlin/Compose (out of scope). -- OTA JS updates on iOS (EAS Update ends for iOS; native releases only). -- Shared Swift package for Android (impossible); do **not** invent a third shared runtime. -- Offline-first for **every** mutation: content create/update always use the outbox (K6/K14); pin and soft-delete of already-synced notes remain **online-first** (match RN). Notebook **create/rename/delete** is **Deferred** (RN mobile has no CRUD UI). -- Rewriting TipTap in native Swift text views. -- Share Extension media (image/video/audio/file) in v1 (text + web URL only; intentional reduction vs current RN Share extension). - -### v1 vs current RN parity matrix - -Signed-off scope relative to `docs/mobile-native-parity.md` and today's `apps/mobile` implementation. **Required** = must pass store acceptance. **Deferred** = post-v1 / may land if schedule allows without blocking review. **Out of scope** = not in mobile app core. - -| Capability | RN today | iOS Swift v1 | -| --- | --- | --- | -| Login + self-hosted instance | Implemented | **Required** | -| Notebook hierarchy browse + select/filter | Implemented (select/hierarchy only; **no** create/rename/delete UI—`createNotebook`/`updateNotebook`/`deleteNotebook` unused in `apps/mobile/src`) | **Required** (select/filter/tree from mirror; empty set → “请先创建一个笔记本” / create elsewhere) | -| Notebook create / rename / delete | Not in RN app UI | **Deferred** (desktop/PWA; not Gate A/B) | -| Move memo to notebook | Implemented (content path / picker) | **Required** (via memo edit notebook field + outbox `memo.update` / create payload) | -| Memo list, filter, sort, search | Implemented | **Required** | -| Pin / unpin | Implemented (online `updateMemo`) | **Required** (online-first) | -| Batch move / multi-select | Implemented | **Deferred** (single-note move OK if easy) | -| Soft delete | Implemented | **Required** | -| Local TipTap create/edit + autosave | Implemented | **Required** (gate) | -| Offline drafts + mirror + outbox | Implemented | **Required** (gate) | -| Note body viewer (TipTap) | Implemented | **Required** | -| In-note image load (Bearer) | Implemented | **Required** | -| Image upload + compression | Implemented | **Required** for App Review; **not** required for internal text-only edit gate | -| In-note attachment open / rename / delete | Implemented (`handleRenameResource` / `handleDeleteResource`) | **Required** for Gate B (body actions—not the full resource library screen) | -| Resource library screen | Implemented | **Deferred** | -| Tag management (rename/delete global tags) | Implemented | **Deferred** (tags on memo edit Required) | -| Revision history + restore | Implemented | **Deferred** (prefer include if time; not edit-gate) | -| Memo share links | Implemented | **Deferred** | -| MCP/API token management | Implemented | **Required** | -| Password change | Implemented | **Required** | -| Device sessions revoke | Implemented | **Required** | -| Multi-user admin console | Parity doc “Implemented” overstates; settings show role only | **Out of scope** | -| Share Extension text/URL | Implemented | **Required** | -| Share Extension image/video/file | Implemented in Swift extension | **Deferred** (call out in release notes) | -| ZIP import/export, trash browse | Excluded from core | **Out of scope** | -| EAS Update OTA | iOS+Android | Android only; iOS **Out of scope** | - ---- - -## Key Decisions - -| # | Decision | Rationale | -| --- | --- | --- | -| K1 | **Pure Swift / SwiftUI app in `apps/ios`; abandon RN iOS entirely** | RN Fabric/Hermes crashes are not fixable with app logic. Native shell is the product requirement for store reliability. `app.json` has `newArchEnabled: true`; crash stacks align with Fabric/Hermes, reinforcing abandon over patching. | -| K2 | **iOS 17+ only** | Unlocks modern SwiftUI navigation, Observation, and simpler concurrency without UIKit-heavy polyfills. Acceptable given crash recovery priority. | -| K3 | **Same bundle id `org.edgeever.mobile` and ASC app** | Continuity of reviews, ratings, and existing App Store Connect configuration (`eas.json` `ascAppId: 6792625631`). | -| K4 | **Editing + offline queue required before first Swift submission** | Product decision: no read-only MVP. Match RN mobile core capture loop. | -| K5 | **GRDB + SQLite local mirror with same semantic tables as RN** | Proven protocol in `local-mirror.ts`; GRDB is the mature Swift SQLite layer with migrations and observation. | -| K6 | **Committed content create/update always go through optimistic mirror + SQLite outbox; never bypass the queue for body/title/tags/notebook on the content path** | Matches RN: edits of existing memos use `localUpdateMemoMutation` (queue + mirror); first commit of a new note uses `local:` + `memo.create` or, after materialize, `memo.update`. **Uncommitted new-note composer** matches RN create-sheet: draft-only (`mobile_drafts` / `new`) until Done **or** materialize-for-image—no parallel bare `createMemo` + pending `memo.create`. Online-first is **rejected** for content saves of committed memos. | -| K24 | **At most one server create per new-note session; materialize never races outbox create** | RN `materializeMemoForImage` only runs when create-sheet has no pending `memo.create` (drafts only). iOS uses the same draft-until-commit/materialize model for new notes, plus an explicit lock algorithm if a `local:` create is already queued (see Materialize algorithm). | -| K7 | **URLSession-based API client port of `packages/client` subset** | No dependency on JS packages at runtime. Hand-port Codable models from `packages/shared/src/types.ts` + OpenAPI. | -| K8 | **Keychain for session token + deviceId; App Group `group.org.edgeever.mobile` for Share Extension** | Matches SecureStore semantics; group id already in `EdgeEver.entitlements` and share extension (confirm Apple Developer portal still matches). | -| K9 | **WKWebView hosts TipTap for viewer + editor; native chrome around it** | Same document schema (`TiptapDoc` JSON), Mermaid, tables, images as web/RN. Avoid dual editors. | -| K10 | **Editor assets: versioned static `EditorBundle` built from monorepo `EditorSource` (Vite)** | Load via `loadFileURL`. Incremental milestones (viewer → editor → resources → mermaid). Smoke-test with Vitest where feasible. No remote editor at runtime. | -| K11 | **Message bridge: typed JSON over `WKScriptMessageHandler` + `evaluateJavaScript`** | Replaces Expo DOM imperative handle (`LocalTiptapEditorRef`). | -| K12 | **Resource images: native fetch with Bearer → data URL or temp file injected into WebView** | Mirrors RN `getResourceBlob` → `blobToDataUrl` / `onLoadResource`. Do not rely on cookie sessions. | -| K13 | **Offline ids: `local:` prefix; survive bootstrap rebuild; remap via `mobile_id_mappings`** | Exact RN semantics in `local-mirror.ts` / `sync-queue.ts`. | -| K14 | **Outbox kinds v1: `memo.create`, `memo.update` only for committed content; materialize is a separate single online create (K24)** | Matches RN queue for Done/offline create and all updates. Soft delete of remote notes is online; local-only pending creates cancel queue + delete row. Pin online-first. Notebook create/rename/delete **Deferred** (not RN mobile). Materialize may call `createMemo` **only** under K24 (never while a live `memo.create` remains queued). | -| K15 | **Conflict model: revision + contentHash + edit sessions** | Flush: `createMemoEditSession` then compare `baseRevision`/`baseContentHash` to expected; mismatch → `revision_conflict` **before** PATCH. On 409 / precheck conflict, mark queue `conflict` and offer discard-local / keep draft. | -| K16 | **Android stays Expo/RN in `apps/mobile`; stop iOS EAS builds only after Swift TestFlight validates edit AC** | Ordering hazard: do not remove EAS iOS job until a Swift binary is shippable. Then CI/release scripts become Android RN + native iOS pipelines. | -| K17 | **Share Extension rewritten under `apps/ios` control** | Drop Expo sharing plugin; keep App Group payload pattern. v1 = text + web URL only. | -| K18 | **No new git branches** | `AGENTS.md`: ordered **work packages / commit series on `main`**. “PR” in this doc means logical reviewable package, **not** a mandate to open long-lived GitHub branches. | -| K19 | **Minimum TipTap feature set for store = RN mobile toolbar** | Bold, bullet list, indent, blockquote, HR, image; tables/Mermaid render; not full desktop toolbar. | -| K20 | **Markdown conversion runs inside EditorBundle only; native Swift does not reimplement markdown** | Wire format for create/update remains `contentMarkdown` (RN). Editor works in `TiptapDoc` JSON; `docToMarkdown` / `markdownToDoc` use the same TipTap Markdown extension path as `@edgeever/shared` content, validated by shared golden fixtures. Autosave payloads are markdown strings produced by the bundle (or via bridge `getMarkdown()`), not a second Swift converter. | -| K21 | **Cross-client sync contract fixtures are shared and required** | Android RN and iOS Swift both consume golden fixtures under `tests/mobile-sync-fixtures/` (or `packages/shared`); Android mirror/queue changes update fixtures in the same commit series. | -| K22 | **HTTPS default; optional user-confirmed `http://` for LAN self-host** | Match Android cleartext practicality with explicit warning; limited ATS exception + App Review notes. | -| K23 | **Freeze/remove `apps/mobile/ios` generation once Android-only prebuild is configured** | Prevent accidental EAS iOS builds from the RN tree after cutover. | - ---- - -## Proposed Design - -### High-level architecture - -```mermaid -flowchart TB - subgraph iosApp ["apps/ios EdgeEver.app"] - UI[SwiftUI Shell
Login / List / Detail / Settings] - Session[SessionStore
Keychain] - API[APIClient
URLSession] - Mirror[LocalMirror
GRDB SQLite] - Outbox[SyncOutbox
SQLite] - SyncEngine[SyncEngine
bootstrap + changes + flush] - Editor[TipTapWebView
WKWebView + bridge] - ShareHost[ShareHandoff
App Group] - UI --> Session - UI --> Mirror - UI --> SyncEngine - UI --> Editor - SyncEngine --> API - SyncEngine --> Mirror - SyncEngine --> Outbox - Editor --> API - Session --> API - ShareHost --> UI - end - - subgraph server ["EdgeEver API"] - Auth["/api/v1/auth/*"] - Sync["/api/v1/sync/*"] - Memos["/api/v1/memos/*"] - Res["/api/v1/resources/*"] - end - - API --> Auth - API --> Sync - API --> Memos - API --> Res - - subgraph shareExt ["Share Extension"] - SE[ShareViewController] - SE --> ShareHost - end -``` - -### Layering - -| Layer | Responsibility | Tech | -| --- | --- | --- | -| **Presentation** | Navigation, lists, forms, sheets, accessibility | SwiftUI, iOS 17+ | -| **Application** | Use cases: sign-in, open memo, save draft, run sync | Actors / `@Observable` stores | -| **Domain models** | `Notebook`, `MemoDetail`, `SyncChange`, queue items | Codable structs (parity with `packages/shared`) | -| **Data** | Mirror queries, outbox mutations, preferences | GRDB, FileManager cache | -| **Network** | REST, multipart upload, blob download | URLSession | -| **Editor host** | Load TipTap bundle, bridge messages, inject resources | WKWebView | -| **Security** | Tokens, App Group, ATS | Keychain, entitlements | - -### Navigation / screens (v1) - -```mermaid -flowchart LR - Launch --> SessionCheck - SessionCheck -->|no session| Login - SessionCheck -->|session| Workspace - Login --> Workspace - Workspace --> NotesList - Workspace --> Settings - NotesList --> MemoDetail - MemoDetail --> MemoEditor - NotesList --> CreateMemo - CreateMemo --> MemoEditor - Settings --> AccountSecurity - Settings --> ApiTokens - Settings --> DeviceSessions -``` - -| Screen | RN source of truth | Notes | -| --- | --- | --- | -| Login | `LoginScreen.tsx` | `baseUrl`, username, password; normalize URL like `session.tsx` | -| Workspace shell | `WorkspaceScreen.tsx` | Notebook sidebar/sheet + notes stack | -| Notes list | `WorkspaceNotesView.tsx` | Search debounce 250ms; sort/filter; density | -| Memo detail (viewer) | `WorkspaceMemoDetail.tsx` | Native chrome + TipTap `mode: viewer` | -| Create / edit | create sheet + rich edit session in `WorkspaceScreen.tsx` | TipTap `mode: editor` + autosave | -| Settings | `WorkspaceSettingsView.tsx` | Locale, theme, compression, sync status, sign out | -| Account security | `AccountSecurityModal.tsx` | Change password | -| Sync conflicts | inline in workspace | Discard local / copy draft (port `discardMobileMemoConflict`) | - -### Data scope - -Mirror and outbox are partitioned by: - -```text -scope = lowercased(trimmed baseUrl) + "|" + (userId ?? "anonymous") -``` - -Same as `createMobileDataScope` in `local-mirror.ts`. Signing out or switching account must not leak rows across scopes. Identity change / full rebuild clears remote rows for the scope but **preserves `id LIKE 'local:%'`** memos. - -### Sync engine state machine - -```mermaid -stateDiagram-v2 - [*] --> Idle - Idle --> CheckingMeta: appActive / pullRequested - CheckingMeta --> Bootstrap: cursor missing OR identity missing - CheckingMeta --> Incremental: meta OK - Bootstrap --> WritingSnapshot: pages from /sync/bootstrap - WritingSnapshot --> Bootstrap: has nextAfterId - WritingSnapshot --> Incremental: snapshot complete, set cursor+identity - Incremental --> ApplyingChanges: /sync/changes - ApplyingChanges --> Rebuild: cursorRewound OR identityChanged - Rebuild --> Bootstrap: clear cursor+identity meta - ApplyingChanges --> Incremental: hasMore - ApplyingChanges --> FlushOutbox: !hasMore - FlushOutbox --> Idle: done - FlushOutbox --> FlushOutbox: next pending item - FlushOutbox --> ConflictItem: 409 revision_conflict - ConflictItem --> Idle: user resolves or leaves -``` - -**Rules (match RN):** - -1. Single-flight per scope (RN uses `syncPromises` map). -2. Bootstrap page size **200** (RN passes 200; `packages/client` default is 100—do not use the client default); write batches of 50. -3. Changes page size 200. -4. On rewind (`serverCursor < localCursor`) or identity mismatch: delete `cursor`+`identity` meta keys and re-bootstrap; delete non-`local:` memos; re-upsert notebooks from snapshot. -5. **Outbox flush triggers** (any of these; not only “after pull”): - - Queue becomes non-empty after enqueue (create/update/autosave). - - App becomes active / foreground (`scenePhase == .active`). - - After a successful mirror pull completes (opportunistic). - - Retry timer fires for `error` items (`nextAttemptAt`). - - Match RN `useMobileAutomaticSync`: if already running, coalesce a follow-up run (`requestedRef` pattern). -6. Flush order: oldest `createdAt` among `pending` / `error` / `syncing` with `nextAttemptAt == null || nextAttemptAt <= now`. -7. Retry backoff: `min(5min, 2^min(attempt,6) * 1s)` — port `getSyncRetryDelayMs` from `packages/shared/src/sync.ts`. -8. Create absorbs subsequent updates for the same memo id (RN `queueMobileMemoUpdate` folds title/markdown/notebook/tags into pending `memo.create`). -9. On successful create: `replaceLocalMemoId(temporaryId, remoteMemo)` + insert id mapping; if queue version moved during flight, promote remaining create payload to `memo.update` against new id (`promoteQueuedMemoCreate`). -10. On successful update while a newer version was enqueued: rebase `expectedRevision`/`expectedContentHash` from server memo (`rebaseQueuedMemoUpdate`). -11. On successful create flush when the queue item was **removed mid-flight** (user cancelled/deleted local note): soft-delete the remote orphan (`deleteMemo(id, permanent: false)`) — RN best-effort cleanup in `syncMobileQueuedChanges`. -12. Edit-session precheck on flush of `memo.update`: after `createMemoEditSession`, if `editSession.baseRevision != expectedRevision` **or** `editSession.baseContentHash != expectedContentHash`, treat as `revision_conflict` **before** calling `updateMemo` (RN `syncMobileQueueItem`). - -#### Soft delete algorithm (port `mobile-memo-delete.ts`) - -For each memo id in a delete request: - -1. Classify **local-only** vs **remote**: - - Local-only if `id` starts with `local:` **or** there is a pending `memo.create` for that id. - - Exception: if `local:` maps via `mobile_id_mappings` / `resolveLocalMemo` to a non-local remote id without pending create, treat as remote. -2. **Local-only path:** `cancelMobileMemoQueueItems` (all kinds for that memoId) → hard `DELETE` mirror row → clear draft. **No API call.** -3. **Remote path:** require online client; `deleteMemo` (single) or `deleteMemos` (batch) with `permanent: false` for ordinary mobile delete → `softDeleteLocalMemo` (set `isDeleted`/`deletedAt` on mirror) → clear draft. Permanent delete only if explicitly requested (not default UI). -4. In-flight create race: if create flush already succeeded and user deleted local-only id that was remapped, resolve mapping and soft-delete remote; if create flush completes after cancel and finds item removed, soft-delete remote orphan (rule 11). - -### Editing save lifecycle - -**Hard decision (K6/K14/K24):** - -1. **Committed content** create/update always optimistic mirror + outbox. Online-first content save for committed memos is rejected. -2. **New-note composer** matches RN create-sheet (`WorkspaceScreen` create path): **draft-until-Done-or-materialize**—no `memo.create` and no mirror list row until the user commits (Done) **or** materializes for image upload. This prevents double server creates. -3. Direct online `updateMemo` remains reserved for **non-content** paths such as pin. - -RN reference: - -| Situation | RN behavior | iOS | -| --- | --- | --- | -| New note typing before Done | `writeMobileNewMemoDraft` only (~350 ms); **no** outbox | Same → `mobile_drafts` key `new` | -| Done offline / no prior materialize | `local:` + `upsertLocalMemo` + `queueMobileMemoCreate` | Same | -| Done after materialize | optimistic update + `queueMobileMemoUpdate` on **server** id | Same | -| Image on create sheet | `materializeMemoForImage` → online `createMemo` once (no pending create); then upload | Same algorithm below | -| Edit existing memo | `localUpdateMemoMutation` always outbox | Same | - -```mermaid -sequenceDiagram - participant User - participant Editor as TipTapWebView - participant Store as MemoEditStore - participant Drafts as mobile_drafts - participant Mirror as LocalMirror - participant Outbox as SyncOutbox - participant Sync as SyncEngine - participant API as APIClient - - alt New note before commit - User->>Editor: type - Editor->>Store: change - Store->>Drafts: write draft key new - Note over Drafts: No memo.create yet (RN create-sheet) - else Done without materialize - Store->>Mirror: upsert local: MemoDetail - Store->>Outbox: memo.create - else Edit committed / after materialize - Store->>Mirror: upsert optimistic - Store->>Outbox: memo.update or absorb into pending create - end - Sync->>Outbox: flush - Outbox->>API: createMemo OR editSession + updateMemo - API-->>Outbox: MemoDetail - Outbox->>Mirror: replaceLocalMemoId / upsert -``` - -**Mutation classes:** - -| Class | Examples | Path | -| --- | --- | --- | -| **Uncommitted new draft** | Create-sheet typing before Done/materialize | `mobile_drafts` only—**not** outbox, **not** list row | -| **Content (outbox)** | Done on new note (`memo.create`); edits after materialize or of existing memo (`memo.update`); create-absorbs-update while `local:` pending | Optimistic mirror + outbox → flush | -| **Materialize (online create, once)** | First image on uncommitted new note | Online `createMemo` under K24 rules; then content path is `memo.update` only | -| **Online-first** | pin/unpin; soft-delete remote note | API immediately; on failure alert + keep prior UI; mirror on success | -| **Deferred online-first** | notebook create/rename/delete | Not in RN mobile UI; post-v1 if needed | -| **Local-only** | discard unsynced `local:` create or new draft | Cancel outbox if any + delete mirror/draft (no API) | - -#### New-note create path (RN-aligned) - -1. Require a target `notebookId` (default notebook from mirror; block if none with “请先创建一个笔记本”). -2. While composing: autosave **only** to `mobile_drafts` (`draft_key = new` for scope) at ~350 ms—**do not** allocate `local:`, **do not** enqueue `memo.create`, **do not** call `createMemo`. -3. **Done without prior materialize:** - - Allocate `temporaryId = "local:" + base36(time) + ":" + random`. - - Build `MemoDetail` (`revision: 0`, `contentHash: "local:" + temporaryId`, full content fields). - - `upsertLocalMemo` + `queue memo.create` (title, contentMarkdown, notebookId, tags, createdAt). - - Clear draft `new`. List shows the note; flush creates server row later. -4. **Done after materialize:** treat as update path on server memo id (`memo.update` outbox + mirror upsert); clear memo draft; **never** enqueue `memo.create` for that session. -5. Subsequent autosaves while `memo.create` still pending for a `local:` id **fold into** the create payload (create-absorbs-update)—never parallel `memo.update` for the same `local:` id. -6. Images: run **Materialize algorithm** (below) before upload; offline → disable image control with clear copy. - -#### Materialize algorithm (at most one server create) — K24 - -Invariant: for a given new-note composer session / `local:` id, **exactly one** of: (a) pending or completed outbox `memo.create`, or (b) a successful online materialize `createMemo`—never both, and never two online creates. - -Implement as a single actor method `materializeForImage(session)` with exclusive lock on `(scope, sessionId)`: - -```text -materializeForImage(session): - // session holds: optional localId, optional serverMemo, current editor payload - - if session.serverMemo != null: - return session.serverMemo // already materialized (RN materializedMemoRef) - - if session.localId != null: - resolved = resolveLocalMemo(scope, session.localId) - if resolved != null AND not resolved.id.hasPrefix("local:"): - session.serverMemo = resolved - return resolved // outbox create already remapped - - require online client else throw needNetworkForImages - - with exclusiveTransaction(scope, memoKey: session.localId ?? session.draftKey): - createItem = outbox.find(kind: memo.create, memoId: session.localId) if localId else nil - - if createItem == null AND session.localId == null: - // RN common case: draft-only create-sheet - response = API.createMemo(currentPayload) - session.serverMemo = response.memo - upsertLocalMemo(response.memo) - clearDraft(new) - // NO memo.create enqueued - return response.memo - - if createItem == null AND session.localId != null: - // Mirror row exists without outbox create (unexpected); still only one create - response = API.createMemo(currentPayload) - replaceLocalMemoId(session.localId, response.memo) // or upsert + delete local + mapping - session.serverMemo = response.memo - session.localId = response.memo.id - return response.memo - - if createItem.status == "syncing": - // Create already in flight — MUST NOT call createMemo again - await flushWaitFor(createItem) // or wait for SyncEngine completion for this id - resolved = resolveLocalMemo(scope, session.localId) - if resolved == null OR resolved.id.hasPrefix("local:"): - throw materializeFailed - session.serverMemo = resolved - session.localId = resolved.id - return resolved - - if createItem.status == "pending" OR createItem.status == "error": - // Outbox would also create — cancel first, then single online create (or wait-for-flush) - // Preferred for image latency: cancel + online create with latest editor payload - cancelOutboxItem(createItem) // version-checked remove; no API - response = API.createMemo(currentPayloadFromEditor) // includes latest title/body/tags - replaceLocalMemoId(session.localId, response.memo) - session.serverMemo = response.memo - session.localId = response.memo.id - // Do NOT re-queue memo.create; further edits → memo.update only - return response.memo - - if createItem.status == "conflict": - // memo.create should not 409; surface error, do not create second memo - throw materializeFailed -``` - -**Forbidden:** - -- Calling `API.createMemo` while a `memo.create` for the same local id remains in outbox (`pending`/`error`/`syncing`). -- Enqueueing `memo.create` after a successful materialize for that session. -- Leaving both a remapped server row and a live `memo.create` for the old `local:` id. - -**After materialize:** image upload uses `session.serverMemo.id`; editor autosave uses `memo.update` with that id’s revision/hash; Done uses update path (RN createMutation materialized branch). - -**Unit tests (required for Gate B / PR 7):** draft-only materialize → one `createMemo`; materialize after Done with pending create → cancel then one `createMemo` OR wait-flush zero extra creates; materialize while `syncing` → zero extra `createMemo`; concurrent double-tap image → single create (lock). - -**Update path (content) — existing or post-materialize memo:** - -1. Base revision/hash from current mirror row (`resolveLocalMemo`), not stale UI-only state. -2. Optimistic upsert of title, tags, notebookId, contentJson/markdown/text/excerpt + `queue memo.update` with `expectedRevision` / `expectedContentHash` and full content fields. -3. Title, tags, and notebook changes participate in the **same** autosave/outbox path as body (not body-only). -4. Flush: `POST /memos/:id/edit-sessions` → precheck base revision/hash → `PATCH` with `editSessionId` + expectations + contentMarkdown/title/tags/notebookId. -5. Autosave debounce: **500 ms** idle after editor body change (`CHANGE_IDLE_MS`); title/tags ~**350 ms**. Force flush on `scenePhase` background / resign active (for **committed** mirror rows / outbox items; uncommitted new drafts only force-write `mobile_drafts`). - -**Drafts (process death):** - -- New uncommitted: `mobile_drafts` key `new` (RN `edgeever.mobile.newMemoDraft:`). -- Committed / editing existing: `mobile_drafts` key `memo:` (RN `edgeever.mobile.memoDraft:`). -- On editor open, rehydrate draft if newer than mirror; clear after successful commit enqueue or discard. - -### TipTap / WKWebView integration - -#### Hosting model and extraction reality - -`LocalTiptapEditor.tsx` is a large DOM surface (viewer + editor modes, Mermaid via `beautiful-mermaid` + mermaid, resource press/load, image upload placeholders, search/replace, toolbar active flags, `MEMO_CONTENT_STYLE`). Treat extraction as a **multi-milestone** project, not a “small Vite page.” - -Ship a static package produced at monorepo build time: - -```text -apps/ios/EditorSource/ # TypeScript source (Vite) - package.json - vite.config.ts - src/main.ts - src/editor.ts - src/bridge.ts - src/markdown.ts # TipTap Markdown; sole doc↔md converter (K20) - src/mermaid.ts -apps/ios/EditorBundle/ # build output committed or CI-produced - index.html - assets/*.js - assets/*.css -``` - -Build: `Scripts/build-editor-bundle.sh` → Vite. Prefer smoke tests with Vitest on bridge message shapes and markdown round-trips using shared fixtures. - -**Milestones (map to PR 6a–6d):** - -| Milestone | Deliverable | Unlocks | -| --- | --- | --- | -| **6a Viewer** | Offline `setDocument` + render paragraphs/lists/quotes/tables; no network | Detail body without images | -| **6b Editor** | Toolbar, `change` events, `getMarkdown()` / doc JSON, focus/flush | **Text edit-ready gate** (with outbox PR) | -| **6c Resources** | `loadResource` bridge, placeholders, image upload hooks, resource press | Images + attachments | -| **6d Mermaid** | Mermaid render in viewer/editor | Full store body AC | - -Load: - -```swift -webView.loadFileURL(indexURL, allowingReadAccessTo: bundleDirectory) -``` - -#### Bridge protocol (typed JSON) - -**Native → Web** (`evaluateJavaScript` calling `window.EdgeEverEditor.*`): - -| Method | Purpose | -| --- | --- | -| `configure({ mode, locale, theme, placeholder })` | viewer vs editor | -| `setDocument(docJson)` | replace content | -| `getDocument()` | return current doc (promise via callback id) | -| `focusEnd()` | caret | -| `flush()` | force change emit | -| `exec(actionId)` | toolbar: bold, lists, etc. | -| `beginImageUpload(uploadId, previewDataUrl)` | placeholder image | -| `completeImageUpload(uploadId, imageUrl, alt)` | swap placeholder | -| `cancelImageUpload(uploadId)` | remove placeholder | -| `appendAttachment(url, filename)` | attachment link node | -| `removeResource(targetJson)` / `renameResource(...)` | resource ops | -| `search(query, index)` / `replaceAll(query, replacement)` | find | - -**Web → Native** (`webkit.messageHandlers.edgeever.postMessage`): - -| Event | Payload | -| --- | --- | -| `ready` | `{ startupMs }` | -| `change` | `{ contentJson, contentMarkdown }` (editor only; markdown from EditorBundle converter—K20) | -| `loadResource` | `{ requestId, source }` → native replies with data URL | -| `resourcePress` | `{ targetJson }` → native sheet: open / rename / delete (Required for store) | -| `imagePreview` | `{ alt, source }` (viewer) | -| `activeFlags` | bitfield matching `MOBILE_EDITOR_ACTIVE_FLAGS` | -| `searchResult` | `{ count, index }` | -| `log` / `error` | diagnostics | - -Resource reply path: native completes `loadResource` by evaluating `EdgeEverEditor.resolveResource(requestId, dataUrlOrNull)`. - -Native must **not** re-derive markdown from JSON; trust `contentMarkdown` from the bundle on `change` (or call `getMarkdown()` on flush). - -#### Protected resources - -1. Detect `/api/v1/resources/:id` and `/blob` variants (port `getResourceIdFromUrl` / `normalizeMobileResourceHref`). -2. `APIClient.getResourceData(path)` with `Authorization: Bearer `. -3. Convert to data URL (images) or temp file URL (attachments open-in-place / share sheet). -4. Cache on disk under Application Support `resource-cache/` with size cap (e.g. 200 MB LRU) — port spirit of `cacheMobileResource` in `mobile-attachments.ts`. - -#### Mermaid - -Keep rendering **inside** the WebView (same as RN editor) to avoid dual implementations. Optional: separate lightweight mermaid renderer HTML for list previews is **not** required for v1 if list uses excerpt text only (RN list uses excerpt). - -#### Image upload - -1. `PHPicker` / `PhotosUI` → optional compression (max edge 2560, quality ~0.82 WebP) matching `mobile-image-upload.ts`. -2. Resolve target memo id via **Materialize algorithm (K24)**—never bare `createMemo` without outbox coordination. -3. `multipart/form-data` `POST /api/v1/memos/:id/resources` using the **single** server memo id returned by materialize. -4. Placeholder → complete with returned `resource.url`. -5. Offline or materialize failure: cancel placeholder; no second create attempts without user retry. - -### Session & security - -| Item | Storage | Notes | -| --- | --- | --- | -| Session JSON `{ baseUrl, token, user }` | Keychain (kSecClassGenericPassword) | Key: `org.edgeever.mobile.session` | -| `deviceId` | Keychain | `mobile-