oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

Docs native Apple clients

Paul's decision, 2026-09-13: build macOS and iOS simultaneously, using entirely native editing controls, with parity against the implemented web app. Reuse the pinned RastrilloApple native sign-in infrastructure from Calendar and Memoria.

Actual parity inventory

Inventory source is this native/apple-clients worktree's inherited web implementation: fixtures/editor-entry.js, toolbar.mjs, menu.mjs, autoformat.mjs, pages.mjs, wordcount.mjs; internal/docs/menu.go, handlers.go, share.go, publish.go, app.go.

  • Document listing, recent groups, title/content search, opening owned/shared documents, creating documents for members; guests retain their current restricted access.
  • Native rich text editing: paragraphs, headings 1–3, bold, italic, inline code, sans/serif/mono fonts, the eleven explicit size values and three persisted legacy size marks, left/center/right paragraph alignment, indent/outdent, bullet/ordered lists including nesting, block quotes, hard breaks, selection, clipboard editing, undo/redo, clear formatting, existing keyboard shortcuts and Markdown-like input rules.
  • Preserve the entire existing ProseMirror Y.XmlFragment model, including schema nodes/attributes/marks that can already arrive through paste or existing documents (links, image nodes, horizontal rules, code blocks and headings 4–6). These known schema nodes must render and remain editable to the extent the web permits; merely retaining opaque data does not meet parity. Truly unknown future schema content must additionally be retained losslessly; it must never be flattened or rewritten.
  • Automatic collaborative updates with current permissions, bounded retries, conflict/rebase behavior, save failures clearly visible, no discard on closing an unsaved editor. Own undo must not undo another collaborator's work.
  • Paged/infinite personal presentation preference, document/selection word counts, keyboard shortcut reference, PDF/Markdown export and native printing.
  • Native owner sharing management: grant/revoke existing roles and public link enable/disable, using the same admission and ownership rules as web.
  • No claim for the web's disabled placeholders: find/replace, ruler/tab stops, insert link/image/table/page break/rule UI. Comments and version-history editors have no implemented routes and are outside current parity.

Implementation and invariants

Add native/ Swift package/Xcode project and reuse shared auth/build packaging at revision 33ffe994173438bbb45e88d16061eba2ac4f6d4c. SwiftUI owns navigation, commands, settings and sharing. Platform rich-text views (NSTextView/UITextView) are allowed where necessary for selection-aware editing, paragraph layout and native clipboard; no WKWebView, JavaScript editor engine or HTML editing surface.

Keep the existing pinned yrs engine inside a WebAssembly sandbox, interpreted on Apple through a narrow native C ABI wrapper using the already-pinned wasmi runtime. The web editor is never embedded; WebAssembly contains only the CRDT engine. Directly decoding untrusted updates in statically linked yrs is prohibited: core/src/abi.rs records unchecked UTF-8 decode operations that current server isolation deliberately contains. Bound sandbox memory and fuel; malformed data or traps produce an actionable error and no partial accepted mutation. A native replica owns client identity, fragment and UndoManager. Native commands mutate identifiable XML nodes/ranges transactionally and emit Yjs v1 deltas. Never rebuild the full fragment from an attributed-string snapshot. Projection includes stable CRDT node IDs and UTF-16 ranges so selected edits target existing content and remote updates preserve unaffected identities. Stable selection anchors and IME composition buffering are required. Native random identity comes from the OS, not a deterministic server identifier. The host supplies GUID and client ID to sandbox creation; undo tracking uses an explicit local transaction origin, excluding imported peer updates. Rust panic/error boundaries must never unwind through Swift. Each document owns an isolated interpreter instance running off the main actor. Fuel and memory limits apply to every call including allocation, projection, snapshots and mutations. Validate host ABI lengths, UTF-8 and decoded projection shape before use. On a trap discard the instance and restore the last-good replica plus retained unacknowledged updates; create a fresh OS-random client ID before issuing new mutations so rolled-back clocks cannot be reused. If undo history cannot be recovered, clear it with a localized visible explanation. Set yrs Options.offset_kind to UTF-16 explicitly; Apple text storage offsets are UTF-16, and surrogate pairs must never be split.

Keep shared server auth separate from existing browser cookies, using the proof-bound native approval/exchange implementation with current admission checks. Native JSON routes expose metadata and sharing; CRDT transport reuses the existing store validation/snapshot/updates code paths, owner/role checks, byte limits and encrypted storage. Responses are no-store and unauthorized access is nondisclosing. Native tokens must be revocable and origin-bound; no transport response is trusted just because it has JSON.

Before export/sharing or closing, await acknowledged pending updates or present a visible actionable failure. Retry queues are bounded and retain unacknowledged edits; reconnection fetches server state and merges CRDT deltas rather than replacing content. A revoked reader/editor must not retain mutation capability through an established stream. Recheck token expiry/revocation, session validity and current admission before each native stream delivery, as well as the existing document permission/lease check.

Document replicas and pending queues remain in memory only, with ephemeral URLSession configuration and no URLCache disk storage; neither content nor tokens enter logs or error strings. Any future background persistence requires authenticated encryption and a separate reviewed design. User-initiated clipboard and export destinations are explicit exceptions, not permission for background caches or autosave.

All user-visible strings resolve through a native String Catalog generated from the existing twelve base locale catalogs where matching translations exist; new keys receive all twelve translations. Native controls carry localized accessible names, Dynamic Type support, keyboard navigation, visible non-color state, minimum targets and small-screen reflow. Verify VoiceOver and keyboard behavior, not merely label presence.

Acceptance

Native edit fixtures round-trip through Yjs and yrs in both directions with formatting, Unicode, nested lists and unknown/schema-preserved content. Concurrent native/browser edits and own-only undo preserve peer edits. Non-BMP insertion/deletion, grapheme selection and malformed UTF-8 cases must cover the explicit UTF-16 boundary. Trap/fuel recovery tests prove restored unacknowledged edits and non-reused clocks. Tests cover reconnect, save failure, reader/guest/owner separation, revoked grants, malformed/oversized data, IME selection and clipboard behavior. Each new test must be observed failing for the intended cause as required by AGENTS.md.

Build and test Mac plus iOS simulator/device architectures. Exercise native app creation, formatting, nesting, export and sharing with isolated fixtures. Run existing make ci and native tests. Record visual/accessibility verification and any missing parity candidly; successful compilation alone is not parity. No deployment or publication is part of this task.

Reviewed client-ID protocol correction (2026-09-13)

The native authority and each independent displayed generation use fresh, nonzero 32-bit client IDs, checked against all imported state-vector IDs and every ID reserved in this document session. IDs are never recycled after retirement or rollback. The pinned yrs 0.23.5 V1 decoder reads client IDs as u32 even though its public ClientID type and encoder accept u64; actual two-interpreter tests with 53-bit IDs reproduced truncated parent IDs and missing updates. The native open boundary therefore rejects IDs above UInt32.max. The independent gpt-6-astra (high) spec reviewer approved this protocol correction without relaxing the single-writer lineage or collision checks. Client IDs are not authentication IDs.