Developing B3Note
Repository layout
Section titled “Repository layout”backend/ Rust canister (ic-cdk 0.20, ic-stable-structures 0.7, vetKD, ic-llm) backend.did Candid interface (checked by a unit test, see below)mock_llm/ Test double of the LLM canister's v1_chat endpointintegration_tests/ PocketIC tests with real vetKD (keys verified with the ic-vetkeys crate)frontend/ Vite + React 19 + Tailwind v4 app on ic-reactor 4 (beta) and @icp-sdk/* src/canisters/ Generated from backend.did by @ic-reactor/vite-plugin (candid-core) src/reactor.ts The ic-reactor client, its sign-in, and the backend handle src/lib/ Crypto (vault, share links, key pinning), note format, search, sign-in tests/ Test-client tests and the PicJS suite against the real canistericp.yaml icp-cli project: backend, frontend, local network with Internet Identityscripts/ build-canisters.sh, test-backend.sh, migrate-mainnet.shdocs/ How it works, development, operations, v2Getting started
Section titled “Getting started”Prerequisites: Rust with the wasm32-unknown-unknown target, Node.js 22 with pnpm, and
icp-cli with ic-wasm
(npm i -g @icp-sdk/icp-cli @icp-sdk/ic-wasm).
rustup target add wasm32-unknown-unknownicp network start -d # a local network with Internet Identity and the vetKD test keysicp deploy # builds and deploys backend + frontendOpen the frontend URL that icp deploy prints. For hot reload, run pnpm dev in frontend/. The
ic-reactor Vite plugin reads canister ids and the root key from the local network.
Without icp-cli (UI work only): ./scripts/build-canisters.sh, then pnpm dev:pocketic (starts
PocketIC with the backend and a mock LLM, POCKET_IC_BIN must point to a PocketIC 16 server binary named
pocket-ic, Node 22.18 or later), then pnpm dev in another terminal. Sign in as a guest; Internet Identity is not
installed there, so share links need an icp-cli network.
The frontend on ic-reactor 4
Section titled “The frontend on ic-reactor 4”src/canisters/backend.tsis generated frombackend/backend.did(the Vite plugin runscandid-core-cli genon start and on every.didchange). It exports theactorschema,type Actorand a type per Candid declaration:opt TisT | null, variants are{ tag, value }, blobs areUint8Array. CI fails if the committed file does not match the.did.src/reactor.tscreates the tab’s client (createClient({ network: "env", auth })), whichReactorProviderborrows. Components calluseQuery(client.queryOptions(backend, "list_shares"))anduseMutation(client.mutationOptions(backend, "create_note")); other code calls the backend directly (await backend.get_config()). AResultis unwrapped: anErrrejects with acanister_errReactorErrorwhoseerris the typedErrorvariant.src/lib/sessionAuth.tsis the app’s sign-in: oneAuthLikeover Internet Identity and the guest key, souseAuth(), query keys and the agent all follow whichever is active.- Share links are opened with a separate anonymous client (
createReaderClient), so a signed-in reader’s principal is not tied to the share, and the signed-out app client, which refuses updates, is not needed.
AI locally
Section titled “AI locally”The assistant is off by default. A local network also has no LLM canister: deploy DFINITY’s LLM canister
with Ollama (see the ic-llm examples), or any canister that implements v1_chat,
then switch it on:
icp canister call backend update_config '(record { ai_enabled = opt true; llm_canister = opt principal "<id>" })'(pnpm dev:pocketic installs a mock LLM and switches the assistant on.)
When a canister named llm is part of the project, icp deploy injects PUBLIC_CANISTER_ID:llm and the
backend uses it automatically.
Testing
Section titled “Testing”./scripts/test-backend.sh # Rust unit tests + PocketIC integration testscd frontend && pnpm test # unit, component and fake-replica tests (vitest)cd frontend && pnpm test:pic # the frontend's crypto + data layer vs. the real canister- Backend unit tests cover quotas, rate limits, share burning and replay, expiry, config
validation, the Internet Identity key check, AI prompt building, and a check that
backend.didmatches the code (UPDATE_CANDID=1 cargo test -p backendregenerates it). - PocketIC tests (
integration_tests/) run the real wasm with real vetKD. They decrypt and verify keys with theic-vetkeyscrate and check the canister’s public keys against PocketIC’s master keys. They also cover access control (guests get no vetKey or share links), optimistic concurrency, self-destruct timers, burn-after-reading, replay protection, rate limits, upgrades, install args and the AI endpoint with a mock LLM canister. - Frontend tests run the real crypto against a fake vetKD with a known master key, the data layer
and sign-in on ic-reactor’s test client (a real client over a fake replica, with mocked canisters),
and (
test:pic) the browser code and ic-reactor clients against the real canister in PocketIC, for guests and for Internet Identity users.
The pocket-ic crate downloads its server automatically; set POCKET_IC_BIN to use your own (16.x). CI
runs all of the above (.github/workflows/ci.yml).