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>
4.3 KiB
4.3 KiB
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(tagclient-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
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.tsthe document store (~3300 lines, one open document)app/frontend/store/library.tswhich document is open, local (IndexedDB) vs server, URLsapp/frontend/store/session.tssigned-in user;io/api.tsREST client;io/library.tsIndexedDBapp/frontend/collab/live collaboration:ydoc.ts(store ↔ Yjs),session.ts(ActionCable, undo, awareness)app/channels/document_channel.rbstores/relays Yjs updates, checks accessapp/models/document.rbrole_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 callscheduleSave()), and must be listed inREAD_ONLY_GUARDEDindocument.ts, else viewers of a shared document can change it locally. - Mid-table row/column insert/delete must call
collabHooks?.onStructure(...)(asinsertRow/deleteRow/insertColumn/deleteColumndo). Edge growth/shrink needs nothing. Without it, concurrent edits land in the wrong row. - View state is per person:
selection,activeSheetId,activeObjectId, sheetzoomare not synced.applyExternalStatekeeps 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.UndoManagerfor 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(gemdotenv, 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 logfresh before committing; don't push unless asked.
Environment gotchas
vite-plugin-rubymakesapp/frontendthe Vite root: Vitestincludepatterns invite.config.tsare relative to it (**/*.spec.ts).- Vite is pinned to v5 (Vitest 2 and
@vitejs/plugin-vue5 don't support newer);vite install/vite upgrademay bump it again. - Gems install into
vendor/gems(globalBUNDLE_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) withplaywright-corefrom a scratch directory againstbin/dev.