The README gets a chapter on storage, accounts and live collaboration that describes what is built (local vs server documents, flows, roles, architecture, Yjs layer, channel protocol, data model, API, .env configuration, security, open points). The planning document with its outdated parts (guest accounts, y-rb, edit lock, phases) is removed. CLAUDE.md collects commands, layout and the rules that are easy to break. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
4.3 KiB
Markdown
85 lines
4.3 KiB
Markdown
# CLAUDE.md
|
|
|
|
numolus: spreadsheet à la Apple Numbers (free canvas with table/chart/text/button
|
|
objects) with a hand-written formula engine. Branch `with-server` = Rails 8 app
|
|
with the Vue 3 frontend in `app/frontend/`; accounts, sharing, live collaboration.
|
|
Details for users and architecture: README.md, chapter "Speichern, Konten &
|
|
Zusammenarbeit".
|
|
|
|
## Branches
|
|
|
|
- `main` (tag `client-only`): browser-only app, documents in IndexedDB. Keep it
|
|
free of server code; fixes that apply to both go in their own commit so they
|
|
can be cherry-picked.
|
|
- `with-server`: everything below.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
bin/setup # gems, npm packages, database
|
|
bin/dev # Rails :3000 + Vite dev server :3036 (open localhost:3000)
|
|
npm test # Vitest (engine, store, io, collab)
|
|
bin/rails test # Minitest (models, API, auth, sharing, DocumentChannel)
|
|
npx vue-tsc --noEmit -p . # typecheck
|
|
npm run lint # ESLint
|
|
npm run build # typecheck + production build into public/vite
|
|
bin/brakeman -q --no-pager
|
|
```
|
|
|
|
Run all of them before committing. Mails in development: localhost:3000/letter_opener.
|
|
|
|
## Layout
|
|
|
|
- `app/frontend/engine/` formula engine (framework-agnostic, own README)
|
|
- `app/frontend/store/document.ts` the document store (~3300 lines, one open document)
|
|
- `app/frontend/store/library.ts` which document is open, local (IndexedDB) vs server, URLs
|
|
- `app/frontend/store/session.ts` signed-in user; `io/api.ts` REST client; `io/library.ts` IndexedDB
|
|
- `app/frontend/collab/` live collaboration: `ydoc.ts` (store ↔ Yjs), `session.ts` (ActionCable, undo, awareness)
|
|
- `app/channels/document_channel.rb` stores/relays Yjs updates, checks access
|
|
- `app/models/document.rb` `role_for(user:, token:)` is the one access check
|
|
|
|
## Rules that are easy to break
|
|
|
|
- **Cell addressing stays positional**: `TableObject.cells["col,row"]` in store,
|
|
engine and file format (`.num`/`.json`). Stable row/column ids exist only in
|
|
the Yjs layer (`collab/ydoc.ts`); don't change the file format for them.
|
|
- **Every content-changing store function calls `markDirty()`** (selection-only
|
|
changes call `scheduleSave()`), and must be listed in `READ_ONLY_GUARDED` in
|
|
`document.ts`, else viewers of a shared document can change it locally.
|
|
- **Mid-table row/column insert/delete must call `collabHooks?.onStructure(...)`**
|
|
(as `insertRow`/`deleteRow`/`insertColumn`/`deleteColumn` do). Edge growth/shrink
|
|
needs nothing. Without it, concurrent edits land in the wrong row.
|
|
- View state is per person: `selection`, `activeSheetId`, `activeObjectId`, sheet
|
|
`zoom` are not synced. `applyExternalState` keeps them when others' changes arrive.
|
|
- Remote changes: flush pending local changes into Yjs first, then apply, then
|
|
rebuild the store (see `CollabSession.process`). Keep that order.
|
|
- Local documents persist via `setPersistHandler` (IndexedDB); server documents
|
|
only via the collab session. Undo: snapshots locally, `Y.UndoManager` for server docs.
|
|
- Solid Queue/Cache/Cable live in the same PostgreSQL database as the app data
|
|
(plain migrations in `db/migrate`), no separate databases (user decision).
|
|
- Settings and secrets come from `.env` (gem `dotenv`, template `.env.example`),
|
|
never committed.
|
|
- Share link tokens are stored in plain text on purpose (owner can copy the link
|
|
again); magic link tokens/codes only as SHA-256.
|
|
|
|
## Conventions
|
|
|
|
- UI text and user-facing messages in German; code, comments, commit messages in
|
|
English. Comments explain *why*, in the surrounding (fairly thorough) style.
|
|
- Commit messages: short imperative subject, body with the reasons.
|
|
- The user sometimes commits on their own: check `git status`/`git log` fresh
|
|
before committing; don't push unless asked.
|
|
|
|
## Environment gotchas
|
|
|
|
- `vite-plugin-ruby` makes `app/frontend` the Vite root: Vitest `include`
|
|
patterns in `vite.config.ts` are relative to it (`**/*.spec.ts`).
|
|
- Vite is pinned to v5 (Vitest 2 and `@vitejs/plugin-vue` 5 don't support newer);
|
|
`vite install`/`vite upgrade` may bump it again.
|
|
- Gems install into `vendor/gems` (global `BUNDLE_PATH`), ignored by git.
|
|
- Node comes from mise; outside the project directory scripts may need
|
|
`MISE_NODE_VERSION=22.21.1`.
|
|
- Browser checks: there is no Playwright in the repo; drive Chrome
|
|
(`/Applications/Google Chrome.app`) with `playwright-core` from a scratch
|
|
directory against `bin/dev`.
|