···23235. **Plaintext metadata is opt-in transparency.** Names and tags unencrypted by default for AppView indexing. Full opacity via dummy values + encrypted metadata payload.
24246. **Public keys as PDS records.** atproto DID docs only have signing keys. Opake publishes X25519 encryption public keys as `app.opake.cloud.publicKey/self` singleton records.
25257. **Multi-device: seed phrase** (future). MVP uses plaintext keypair at `~/.config/opake/accounts/<did>/identity.json`.
2626+8. **Storage trait in opake-core.** Config, Identity, Session types and the `Storage` trait live in core so both CLI (`FileStorage`, filesystem) and web (`IndexedDbStorage`, IndexedDB) share the same contract. Platform-specific I/O is injected, never imported.
26272728## Documentation
2829
+14-4
CONTRIBUTING.md
···882. Install Rust 1.75+ via [rustup](https://rustup.rs)
993. Run the test suite: `cargo test`
10104. Run the linter: `cargo clippy -- -D warnings`
1111+5. For web frontend work: install [Bun](https://bun.sh), then `cd web && bun install`
11121213## Code style
1314···2627 - XRPC client with automatic token refresh
2728 - document operations (upload, download, list, delete, resolve)
2829 - AT Protocol record types and lexicon constants
3030+ - Storage trait + config/identity/session types (storage.rs)
2931 - shared config path resolution (paths.rs)
30323131-opake-cli thin CLI wrapper
3333+opake-cli CLI binary wrapping opake-core
3234 - clap command definitions
3333- - config/session/identity persistence
3535+ - FileStorage (impl Storage over filesystem, TOML + JSON)
3436 - user interaction (prompts, formatting)
35373638opake-appview indexer + REST API for grant/keyring discovery
···4345 - #[derive(RedactedDebug)] with #[redact] field attribute
4446 - generates Debug impls showing byte length instead of content
4547 - used by opake-core (ContentKey, Session) and opake-cli (Identity)
4848+4949+web/ React SPA (Vite + TanStack Router + Tailwind/daisyUI)
5050+ - opake-core via WASM (wasm-pack build)
5151+ - IndexedDbStorage (impl Storage over Dexie.js/IndexedDB)
5252+ - Zustand stores, Web Worker for off-main-thread crypto
5353+ - cabinet file browser UI with panel navigation
4654```
47554848-`opake-core` must never depend on filesystem, stdin, or any platform-specific API. All I/O happens in the binary crates.
5656+`opake-core` must never depend on filesystem, stdin, or any platform-specific API. All I/O goes through the `Storage` trait — `FileStorage` (CLI) and `IndexedDbStorage` (web) are the platform-specific implementations.
49575058See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the detailed crate structure and encryption model, and [docs/FLOWS.md](docs/FLOWS.md) for sequence diagrams of every operation.
5159···5967- The `test-utils` feature flag gates test infrastructure in `opake-core`
60686169```sh
6262-cargo test # all tests
7070+cargo test # all Rust tests
6371cargo test -p opake-core # core only
6472cargo test -p opake-cli # CLI only
6573cargo test -p opake-appview # appview only
6674cargo test -- --test-output # show println output
7575+7676+cd web && bun run test # web frontend tests (Vitest + fake-indexeddb)
6777```
68786979## Commit messages
+29-7
README.md
···9999# revoke a share grant
100100opake revoke at://did:plc:abc/app.opake.cloud.grant/tid123
101101102102+# check incoming grants (via AppView)
103103+opake inbox --appview https://appview.example.com
104104+opake inbox --long
105105+106106+# keyring-based group sharing
107107+opake keyring create family-photos
108108+opake keyring ls
109109+opake keyring add-member family-photos alice.example.com
110110+opake upload photo.jpg --keyring family-photos
111111+opake download --keyring-member at://did:plc:abc/app.opake.cloud.document/tid456
112112+opake keyring remove-member family-photos alice.example.com
113113+102114# remove an account
103115opake logout bob.other.com
104116```
···115127116128## Architecture
117129118118-Four crates:
130130+Four crates + a web frontend:
119131120120-- **`opake-core`** — platform-agnostic library (compiles to WASM). Encryption, records, XRPC client, document operations.
121121-- **`opake-cli`** — thin CLI wrapper. Config, session, identity persistence.
132132+- **`opake-core`** — platform-agnostic library (compiles to WASM). Encryption, records, XRPC client, document operations, `Storage` trait.
133133+- **`opake-cli`** — CLI binary. `FileStorage` (filesystem-backed), command dispatch.
122134- **`opake-appview`** — Axum-based indexer and REST API. Jetstream firehose consumer, SQLite storage, DID-scoped Ed25519 auth.
123135- **`opake-derive`** — Proc-macro crate. `RedactedDebug` derive macro for secret-safe Debug output.
136136+- **`web/`** — React SPA (Vite + TanStack Router + Tailwind/daisyUI). Uses `opake-core` via WASM. `IndexedDbStorage` (IndexedDB-backed) implements the same `Storage` trait as the CLI.
124137125138See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the encryption model, crate structure, and design decisions. See [docs/FLOWS.md](docs/FLOWS.md) for sequence diagrams of every operation.
126139···139152- [x] AppView indexer (grants + keyrings from firehose)
140153- [x] AppView REST API with DID-scoped Ed25519 auth
141154- [x] Folder hierarchy (mkdir, tree, path-aware rm/mv/cat/upload)
142142-- [ ] Grant discovery (inbox command — queries AppView)
143143-- [ ] Keyring-based group sharing
144144-- [ ] Web UI (SPA frontend)
155155+- [x] Grant discovery (inbox command — queries AppView)
156156+- [x] Keyring-based group sharing
157157+- [ ] Web UI — cabinet file browser (in progress, auth stubbed)
158158+- [ ] AT Protocol OAuth (DPoP) for browser authentication
159159+- [ ] Seed phrase key derivation for multi-device
145160146161## Development
147162148163```sh
149149-cargo test # run all tests
164164+cargo test # run all Rust tests
150165cargo clippy # lint
151166cargo fmt # format
167167+168168+# web frontend
169169+cd web
170170+bun install # install deps
171171+bun run wasm:build # build opake-core WASM module
172172+bun run dev # start Vite dev server
173173+bun run test # run Vitest suite
152174```
153175154176CI runs on [Tangled](https://tangled.org) via `.tangled/workflows/test.yml`.
+55-6
docs/ARCHITECTURE.md
···4455```mermaid
66graph TB
77- subgraph Client ["Client (your machine)"]
77+ subgraph Client ["Client (your machine / browser)"]
88 CLI["opake CLI"]
99+ Web["Web SPA"]
910 Core["opake-core library"]
1011 Crypto["Client-side crypto<br/>(AES-256-GCM, X25519)"]
1112 end
···2324 end
24252526 CLI --> Core
2727+ Web -->|WASM| Core
2628 Core --> Crypto
2729 Core -->|XRPC / HTTPS| OwnPDS
2830 Core -->|unauthenticated| OtherPDS
2931 Core -->|DID resolution| PLC
3032 CLI -->|inbox query| AppView
3333+ Web -->|inbox query| AppView
31343235 AppView -->|subscribe| Jetstream
3336 AppView --> SQLite
···4144 style Network fill:#16213e,color:#eee
4245```
43464444-The CLI talks directly to PDS instances over XRPC. No PDS modifications needed. All encryption and decryption happens on your machine. The AppView is an optional component that indexes grants and keyrings from the firehose for discovery.
4747+Both the CLI and the web frontend talk directly to PDS instances over XRPC. No PDS modifications needed. All encryption and decryption happens client-side — on your machine (CLI) or in the browser (Web via WASM). The AppView is an optional component that indexes grants and keyrings from the firehose for discovery.
45484649## Crate Structure
4750···5154 src/
5255 atproto.rs AT-URI parsing, shared AT Protocol primitives
5356 resolve.rs Handle/DID → PDS → public key resolution pipeline
5757+ storage.rs Config, Identity types + Storage trait (cross-platform contract)
5458 error.rs Typed error hierarchy (thiserror)
5559 test_utils.rs MockTransport + response queue (behind test-utils feature)
5660 crypto/
···108112 opake-cli/ CLI binary wrapping opake-core
109113 src/
110114 main.rs Clap app, command dispatch
111111- config.rs Multi-account config (default DID, account map)
112112- session.rs Per-account session persistence (JWT tokens)
113113- identity.rs Per-account X25519 + Ed25519 keypair persistence
115115+ config.rs FileStorage (impl Storage for filesystem), anyhow wrappers
116116+ session.rs CommandContext resolution, session persistence
117117+ identity.rs Keypair generation, migration, permission checks
114118 keyring_store.rs Local group key persistence (per-keyring)
115119 transport.rs reqwest-based Transport implementation
116120 utils.rs Test harness, env helpers
···165169 opake-derive/ Proc-macro crate (RedactedDebug derive)
166170 src/
167171 lib.rs #[derive(RedactedDebug)] + #[redact] attribute
172172+173173+web/ React SPA (Vite + TanStack Router + Tailwind + daisyUI)
174174+ src/
175175+ lib/
176176+ storage.ts Storage interface (mirrors opake-core Storage trait)
177177+ storage-types.ts Config, Identity, Session types (mirrors opake-core)
178178+ indexeddb-storage.ts IndexedDbStorage (impl Storage over Dexie.js/IndexedDB)
179179+ api.ts API client helpers
180180+ crypto-types.ts Crypto type definitions
181181+ stores/
182182+ auth.ts Auth state (Zustand)
183183+ routes/
184184+ __root.tsx Root layout with auth guard
185185+ index.tsx Landing page
186186+ login.tsx Login form
187187+ cabinet.tsx File cabinet (main UI)
188188+ components/cabinet/
189189+ PanelStack.tsx Stacked panel navigation
190190+ PanelContent.tsx File grid/list view
191191+ Sidebar.tsx Navigation sidebar
192192+ TopBar.tsx Header with account switcher
193193+ FileGridCard.tsx Grid card with file icon + metadata
194194+ FileListRow.tsx List row variant
195195+ types.ts Discriminated union types for cabinet state
196196+ wasm/opake-wasm/ WASM build of opake-core (via wasm-pack)
197197+ workers/
198198+ crypto.worker.ts Web Worker for off-main-thread crypto (Comlink)
199199+ tests/
200200+ lib/
201201+ indexeddb-storage.test.ts Storage contract tests (fake-indexeddb)
168202```
169203170170-The boundary is strict: `opake-core` never touches the filesystem, stdin, or any platform-specific API. All I/O happens in the binary crates. This keeps `opake-core` compilable to WASM for the future web UI.
204204+The boundary is strict: `opake-core` never touches the filesystem, stdin, or any platform-specific API. All I/O happens through the `Storage` trait — `FileStorage` (CLI, filesystem) and `IndexedDbStorage` (web, IndexedDB) implement the same contract with platform-specific backends. This keeps `opake-core` compilable to WASM, which the web frontend uses via `wasm-pack`.
171205172206## Encryption Model
173207···2612953. Blob (encrypted file content)
262296263297All three are unauthenticated reads — AT Protocol records and blobs are public by design. The encryption is the access control, not the transport.
298298+299299+## Storage Abstraction
300300+301301+Config, identity, and session types live in `opake-core/src/storage.rs` alongside the `Storage` trait. This lets both platforms share the same data model and mutation logic (e.g. `Config::add_account`, `Config::remove_account`, `Config::set_default`).
302302+303303+| Method | Contract |
304304+|--------|----------|
305305+| `load_config` / `save_config` | Read/write the global config (accounts map, default DID) |
306306+| `load_identity` / `save_identity` | Read/write per-account encryption keypairs |
307307+| `load_session` / `save_session` | Read/write per-account JWT tokens |
308308+| `remove_account` | Full cleanup: mutate config + delete identity/session data + persist |
309309+310310+**CLI (`FileStorage`)** — TOML config at `~/.config/opake/config.toml`, JSON files in per-account directories, unix permissions (0600/0700).
311311+312312+**Web (`IndexedDbStorage`)** — Dexie.js over IndexedDB with three object stores (`configs`, `identities`, `sessions`). `removeAccount` runs config mutation + data deletion in a single transaction for atomicity.
264313265314## Multi-Account Support
266315