diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5f47a6b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,42 @@ +# CLAUDE.md + +Context file for Claude Code. Read this before making changes. See `docs/ARCHITECTURE.md` for deep technical rationale. + +## Project + +Native Apple ecosystem client for a self-hosted [Outline](https://github.com/outline/outline) instance (wiki/knowledge base). Targets **iOS, iPadOS, macOS**, single SwiftUI multiplatform codebase. Goal is **full editing parity** with Outline's web app — not a read-only viewer. + +Working name: `OutlineClient` (rename freely — placeholder only, not a branding decision). + +## License context (do not re-litigate) + +Outline server is BSL 1.1 licensed. This restricts *hosting/reselling Outline itself as a service*. It does **not** restrict building an API client. This app only ever talks to Outline's public REST/WebSocket API — it never vendors, forks, or redistributes Outline's server source. No license concerns for this codebase. + +## Architecture summary + +- **REST layer** — Outline's RPC-style API (`/api/*`), Bearer token auth (`Authorization: Bearer `). Handles collections, documents (non-realtime CRUD), search, users. +- **Realtime collab layer** — Outline runs a **Hocuspocus** server (Yjs CRDT over WebSocket) at `wss:///collaboration/document.`. This is the actual editing transport when a document is open — not the REST API. Full parity requires speaking this protocol, including the Yjs awareness protocol for remote cursors/presence. +- **CRDT engine** — [YSwift](https://github.com/y-crdt/yswift) (Swift bindings over `yrs`, the Rust Yjs port, via UniFFI). This is the only realistic native path to binary-compatible Yjs sync. It is pre-1.0 / WIP — expect to patch or contribute upstream, don't assume full feature coverage going in. +- **Editor UI** — native TextKit 2 (`NSTextLayoutManager` / `AttributedString`) rendering mapped from Outline's ProseMirror document schema (headings, lists, checklists, tables, code blocks, embeds, comments). This is the largest single scope item in the project — treat it as its own subsystem, not a UI detail. + +## Phased build order + +1. **Phase 1 — Read/browse + REST-only editing.** Auth, collections tree, document list, search, markdown-rendered read view, basic edit-and-PUT (last-write-wins, no live collab). Ship this before touching Yjs at all. +2. **Phase 2 — Realtime collaboration.** Integrate YSwift + Hocuspocus WebSocket, map Yjs XML fragment ↔ native editor state, add awareness/presence. +3. **Phase 3 — Polish.** Offline cache, conflict UI, embeds/attachments, tables, comments, macOS-specific affordances (menu bar, multiple windows). + +Do not start Phase 2 work until Phase 1 is stable and shipped to the device. The CRDT/editor layer is high-risk; de-risk everything else first. + +## Conventions + +- Swift, SwiftUI-first. No UIKit/AppKit escape hatches unless SwiftUI genuinely can't do it (flag these explicitly in PR/commit messages). +- Networking: `URLSession` + `async/await`, no third-party HTTP client. +- API tokens stored in Keychain only — never UserDefaults, never logged. +- Prefer protocol-oriented boundaries between the REST layer, the CRDT/sync layer, and the editor UI layer so any one of them (especially YSwift) can be swapped without touching the others. +- Target iOS 17+ / macOS 14+ minimum (needed for mature `AttributedString`/TextKit 2 APIs). + +## Do not + +- Do not attempt to reimplement Yjs's CRDT algorithm from scratch — use YSwift/yrs. This has been tried by others and is a multi-year effort (see Yrs project history). +- Do not fall back to polling the REST API for "realtime" — it will visibly diverge from true collaborative editing and defeats the point of this project. +- Do not vendor or copy Outline's server source into this repo. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23d6e91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 Puranjay Savar Mattas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd0491c --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Outpost + +A native Apple ecosystem client for [Outline](https://github.com/outline/outline) — built for iOS, iPadOS, and macOS from a single SwiftUI codebase, aiming for full editing parity with Outline's web app, including realtime collaborative editing. + +## Why + +Outline's web app is great, but there's no native Apple client with full editing parity. This project connects to a self-hosted Outline instance over its REST API and realtime collaboration socket to provide a proper native experience across the Apple ecosystem. + +## Status + +Early development. See `CLAUDE.md` for the current architecture and phased build plan. + +- [ ] Phase 1 — Auth, browse, search, REST-only editing +- [ ] Phase 2 — Realtime collaborative editing (Yjs/Hocuspocus) +- [ ] Phase 3 — Offline cache, tables, embeds, comments, macOS polish + +## Requirements + +- Xcode 16+ +- iOS 17+ / iPadOS 17+ / macOS 14+ +- A self-hosted (or hosted) Outline instance with API access + +> They will be updated. These are old requirements. + +## Setup + +1. Clone the repo and open the `.xcodeproj` in Xcode. +2. Generate a scoped API key on your Outline instance (Settings → API Keys). +3. On first launch, enter your instance URL and API key — these are stored in Keychain, never in app config or source. + +## Architecture + +See [`CLAUDE.md`](./CLAUDE.md) and [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full technical breakdown: REST layer, Yjs/Hocuspocus realtime sync via [YSwift](https://github.com/y-crdt/yswift), and the ProseMirror-schema-to-native-editor mapping. + +## Disclaimer + +This project is being built to solve a personal problem — I wanted a genuinely good native knowledge-base client for my own self-hosted Outline instance and none of the existing options fit. It's a personal-use tool first, not a polished product with support guarantees. + +Parts of this codebase are AI-assisted (built with the help of AI coding tools). Treat that however you like — review, use, fork, or ignore the code accordingly. No warranty of quality, security, or fitness for any particular use is implied beyond what's stated in the license below. + +## License + +This project is a client only — it does not include, vendor, or redistribute any of Outline's (BSL 1.1 licensed) server source. See [`LICENSE`](./LICENSE) for this repository's own license. + +## Not affiliated with Outline + +This is an independent, unofficial client. Not affiliated with or endorsed by General Outline, Inc. diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..60d8d05 --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,38 @@ +# Architecture Notes + +Deeper technical reference backing `CLAUDE.md`. Update this as decisions are made — treat it as a living design doc, not a one-time spec. + +## 1. Outline API surface + +- **Base**: `https:///api/*`, RPC-style (POST for nearly everything, not strict REST verbs). +- **Auth**: `Authorization: Bearer ` header. Keys are created per-user under Settings → API Keys on the Outline instance. Store in Keychain, scope a dedicated key to this app rather than reusing a personal one. +- **Core endpoints to wrap first**: `documents.info`, `documents.list`, `documents.search`, `documents.create`, `documents.update`, `collections.list`, `collections.info`, `users.info`. +- Full reference: your instance's `/developers` page (same docs as getoutline.com/developers, versioned per Outline release — check against your self-hosted version, not just the public docs). + +## 2. Realtime collaboration transport + +- Endpoint: `wss:///collaboration/document.`. +- Protocol: Hocuspocus (a Yjs-CRDT-over-WebSocket server implementation). This is **not** the same socket as any notification/presence channel — it's the actual document edit transport. +- Payload: binary Yjs update messages (Yjs update format v1/v2) plus the Yjs awareness protocol for cursor/selection presence. +- Your reverse proxy (Caddy, in your homelab) must forward `Upgrade`/`Connection` headers correctly or collaboration silently falls back to "no live sync" while REST edits still work — worth a smoke test against your own Caddy config early. + +## 3. CRDT engine — YSwift + +- Repo: `y-crdt/yswift`. Swift bindings over `yrs` (Rust) via UniFFI, distributed with a prebuilt XCFramework. +- Status as of mid-2026: WIP, pre-1.0, small but active contributor base. Not all Yrs/Yjs features are exposed yet. +- Integration point: YSwift gives you a local `Y.Doc` you mutate; a Hocuspocus-compatible WebSocket provider (you'll likely need to write this provider yourself in Swift — no off-the-shelf Swift Hocuspocus client exists yet) syncs that doc's binary updates with the server. +- Fallback if YSwift blocks progress: vendor the C FFI (`yffi`) directly and write a thinner Swift wrapper for just the operations you need (XML fragment read/write, update encode/decode, state vector diffing). + +## 4. Document schema mapping + +Outline's editor is ProseMirror-based; the Yjs doc stores document structure as a `Y.XmlFragment` mirroring the ProseMirror schema (paragraphs, headings, bullet/ordered/checklist lists, tables, code blocks, blockquotes, embeds, comments-as-marks). Native editor work breaks into: + +- **Read path**: Y.XmlFragment → your own document model → `AttributedString`/TextKit 2 layout. +- **Write path**: local edit → diff against your document model → Yjs transaction on the XmlFragment → binary update sent over the Hocuspocus socket. +- Build the node-type mapping table (ProseMirror node/mark name → native representation) as an explicit, tested module — this is the piece most likely to silently drift from Outline's actual schema as Outline ships updates. + +## 5. Known risk areas (revisit as the project progresses) + +- YSwift feature gaps vs. full Yjs (undo/redo manager, relative positions) — check current state before Phase 2 starts, this library moves fast. +- Table and embed support are the least standardized part of ProseMirror schemas generally — budget them last. +- Outline server version drift: self-hosted instance upgrades could change API shapes or the collaboration payload; pin against your actual running version, not assumptions from docs.