This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Client monorepo for Concrnt v2, a decentralized SNS. One repo produces both the mobile app (app + src-tauri, Tauri 2) and the web app (web). pnpm workspace, Node 22 (Volta), package manager pnpm.
pnpm install
pnpm build # builds all packages; required once before Tauri dev (libs must exist in dist/)
pnpm dev # vite dev server for the mobile-app frontend (world-app)
pnpm --filter web dev # vite dev server for the web app
pnpm tauri android dev # / ios dev — mobile development (run `pnpm build` first)
pnpm tauri android build # / ios build — production builds (need signing config, see README)
npx eslint <files> # lint (flat config at root eslint.config.js); husky+lint-staged runs eslint --fix + prettier on commit- During development a
pnpm devprocess is usually already running externally — don't start another one. - There are no tests anywhere in the repo. CI (
build-check.yaml) only runspnpm build— that's the check to pass. Typechecking happens viatscinside each package's build. - After editing
client,worldlib, orui, runpnpm build(or the package's ownpnpm --filter <pkg> build) so dependents pick up the compileddist/output. uihas Storybook:pnpm --filter @concrnt/ui storybook(port 6006).
Layering (each layer only uses the one below):
app / web → @concrnt/ui → @concrnt/worldlib → @concrnt/client → Concrnt servers
app → src-tauri (Rust, via invoke) → plugins/ (custom Tauri plugins)
client/(@concrnt/client) — low-level protocol library.api.ts(classApi: JWT signing, cached fetch layers,getEntity/getDocument/commit/query),core.ts(CCURI—cckv:///ccfs://addressing),model.ts(Document,Entity, IDs: CCIDcon1…user master key, CKIDcck1…subkey, CSIDccs1…server),socket.ts(WebSocket subscriptions with auto-reconnect),timelineReader.ts/chunkline/(live chunked timelines),crypto.ts(secp256k1/Cosmos HD wallet, mnemonic EN+JA),auth/(AuthProviderinterface — master key signs or delegates to subkeys),cache/(KVSinterface: IndexedDB or in-memory). Builds dual CJS+ESM.worldlib/(@concrnt/worldlib) — SNS domain layer over client.client.ts(Clientfacade — the object apps instantiate; caches viaCachedPromise),message.ts/timeline.ts/user.ts/association.ts/list.ts.schemas.tsmaps schema names →https://schema.concrnt.world/...URLs;schemas/*.tsare generated bycollectSchemas.ts(Deno + json-schema-to-typescript) — regenerate, never hand-edit (they're eslint-ignored).signal/login.tsimplements QR cross-device login (another device signs a subkey).ui/(@concrnt/ui) — custom component primitives (Button, Dialog, TextField, Text, …) and theming (contexts/Theme.tsx, themes indata/Themes.ts). No external UI framework.app/vsweb/— deliberately mirrored trees (components/,views/,contexts/,hooks/, …). Keep the code in the two as identical as possible; only platform-specific parts diverge:- Routing:
webuses react-router-dom v7 (all routes inweb/src/main.tsx,LoginGuard.tsxgates auth).apphas no router — native-style navigation viaTabLayout/SidebarLayout/StackLayout(app/src/layouts/Stack.tsx, push/pop stacks) composed inapp/src/views/Main.tsx. - Auth:
webkeeps keys in localStorage withInMemoryAuthProvider.appnever lets keys leave the OS keychain —app/src/lib/authProvider.ts(TauriAuthProvider) delegates signing to Rust viainvoke(get_session,sign_subkey,initialize_master,backup_masterkey, …), implemented insrc-tauri/src/{commands,auth,session,backup}.rs. - When changing shared behavior in one of
app/web, apply the same change to the other.
- Routing:
- State management is React Context only (no Redux/Zustand): each concern is a provider in
*/src/contexts/(Client, Theme, Composer, Drawer, Modal, …).ClientProviderbuilds the worldlibClientand is the data layer; live updates come fromTimelineReader/Socketsubscriptions. plugins/— custom Tauri plugins:tauri-plugin-keychain(OS keychain key storage),tauri-plugin-file-saver,tauri-plugin-safari-scroll-killer(iOS WKWebView bounce fix).
The README documents the cckv://<owner>/concrnt.world/... KV layout used for settings, profiles, and per-profile timelines/posts/lists.
- Do NOT create utility functions to deduplicate code — duplication is accepted. Only consider a utility once the same code is used in 10+ places. Same for shared CSS style objects: make a component instead, or don't share at all.
- Use UI-library components raw. Only wrap a component to add functionality — never create a style-only wrapper; embed the CSS at each usage site instead.
- Never use hand-written
classNamestrings. Use thestyleprop with plain objects. - For styles inline
stylecannot express (pseudo-classes,@keyframes), use a CSS Modules file paired 1:1 with the component (Foo.module.cssnext toFoo.tsx). No shared CSS files. Reference keyframe names via the CSS Modules export (styles.spin). - Never embed
<style>tags in JSX or inject style elements intodocument.head.