Folder intent
Project Structure
| Path |
Role |
Why it exists |
app/ |
React Native application |
All user-facing state, gestures, dialogs, preferences, and local UI features. |
app/hooks/_useWorklet.ts |
Runtime bridge |
Starts the Bare bundle once and translates backend RPC messages into React state. |
backend/ |
Embedded backend source |
Owns canonical list mutations, Autobase setup, Corestore, P2P join, and cleanup. |
rpc-commands.mjs |
Shared command ABI |
Keeps frontend and backend in sync without string command names. |
assets/data/ |
Grocery taxonomy source |
CSV inputs for category and translation generation. |
scripts/ |
Build and generation helpers |
Bundles the Bare backend and generates multilingual grocery lookup files. |
website/ |
Public marketing/static site |
Listam landing page, privacy page, and join link handling. |
/wiki/ |
This study site (multi-app wiki) |
Human-readable architecture docs for all Listam apps; a sibling folder of listam-mobile. |
React Native layer
Frontend Functionality
The app is no longer a single text-keyed shopping list. It now manages typed
lists organized into groups through a synced list registry, with grocery,
board, and todo surfaces. UI state lives in Redux Toolkit slices fed by RPC replies, not
in React state colocated in app/index.tsx.
App composition
app/index.tsx is the composition root. It wires useWorklet,
useSubscription, join handling, the multi-list navigation chrome, and
paywall gating, then hands UI state to Redux Toolkit slices.
app/index.tsx
Redux Toolkit state
listsSlice, boardConfigSlice, and labelsSlice
(plus registrySelectors and syncSlice) hold list contents,
board configuration, synced labels, and the active surface. Slices are fed by RPC
replies decoded in _useWorklet.ts.
app/store/listsSlice.ts
app/store/registrySelectors.ts
Typed lists & groups
A synced list registry layers named lists, organized into groups, on top of the
project base. Each surface is keyed by (listId, type) — grocery, board,
and todo — using the shared list-registry and list-nav
domain modules.
app/store/registrySelectors.ts
Multi-list navigation
ListsMenu picks the active list, ListContextBar shows and
renames the current surface, and ListSwipePager swipes between lists.
Board and todo surfaces have their own editors layered on the grocery list view.
app/components/ListsMenu.tsx
app/components/ListSwipePager.tsx
View modes
The standard grocery list uses an animated inertial list with center-item scaling. The
grid view groups grocery items into categories and renders icon cards (via
MaterialCommunityIcons) with haptic feedback.
app/components/intertial_scroll.tsx
app/components/VisualGridList.tsx
Categories and icons
Category lookup uses generated multilingual maps, keyword fallback, substring matching,
and Levenshtein typo tolerance. Category glyphs now render via
MaterialCommunityIcons from @expo/vector-icons (a
category-default plus curated overrides, since no icon kit covers every grocery item).
app/components/categoryLookup.ts
app/components/categoryConstants.ts
Connection-status header dot
A header dot reflects real P2P state — green/online, blinking grey/connecting, or
grey/no-connection — derived by deriveConnectionStatus from the DHT
online flag and live connection count (not flushed(), which resolves
offline too).
app/components/connectionStatus.ts
Loyalty cards
The scanner captures QR/barcode data with Expo Camera. The viewer draws QR codes and
selected barcode formats with react-native-svg.
app/components/LoyaltyCardScanner.tsx
app/components/LoyaltyCardViewer.tsx
Day-plan surface
Overview / Day-plan
A cross-list day-plan surface that pulls flagged items into a single “today”
focus, shared across devices over a dedicated meta-item channel with no new RPC opcode.
OverviewScreen & PlanSheet
OverviewScreen renders the day plan and per-list cards;
PlanSheet is the planner sheet. Swiping right on an item flags it into
today’s focus. The surface is typecheck-complete and still needs a device build.
app/components/OverviewScreen.tsx
app/components/PlanSheet.tsx
Shared plan channel
Plan state syncs through @listam/domain/plan over a reserved
__plan__ (PLAN_LIST_ID) meta-item channel — no new RPC, just
an RPC_UPDATE (3) upsert. isPlanItem guards keep plan
meta-items out of every list projection.
listam-packages/packages/domain/plan.mjs
Lead:
The day plan is a projection, not a new list type. Flagging writes a pointer meta-item to
the shared base, so the same focus list appears on desktop and mobile without any extra
wire format.
Bare worklet layer
Backend Internals
Shared engine
The real P2P engine now lives in @listam/backend — the mobile
backend/backend.mjs only calls startBackend with the
bare-kit platform adapter. The same engine ships on desktop and headless
via different RPC/filesystem adapters.
backend/backend.mjs
Storage lease
The backend takes a storage lease so only one instance touches storage at a time; a
second instance backs off rather than corrupting keys. Storage is namespaced
(lista on mobile) to keep concurrent platforms isolated.
listam-packages/packages/backend/lib/storage-lease.mjs
Autobase initialization
initAutobase tears down prior resources, opens Corestore, clears
stale boot/encryption metadata when joining a different base, enables encrypted values,
rebuilds the list view, starts Hyperswarm, sets up blind pairing, and sends an invite to
the frontend.
listam-packages/packages/backend/lib/network.mjs
Apply function
The Autobase apply function is the reducer. It validates item payloads,
writes a materialized JSON view, and emits backend-to-frontend RPC updates. All item
mutations flow through @listam/domain’s id-keyed, last-write-wins
reduction — identical code on every platform.
backend/backend.mjs
Recent additions
Shipped mobile features
Move items between lists
MoveItemSheet moves items across lists and types via
RPC_MOVE (28). When a target board needs rigor fields the move replies
move-rigor-missing over RPC_MESSAGE so nothing is deleted.
app/components/MoveItemSheet.tsx
Reorder items
A long-press “Move to top/bottom” overlay reorders items through the
shared last-write-wins order field in
@listam/domain/ordering (sortByOrder /
computeReorder).
listam-packages/packages/domain/ordering.mjs
Encrypted backup & import
Settings export/import all data and export the instance seed, password-encrypted with
Argon2id + XChaCha20-Poly1305, via RPC_EXPORT_DATA (24),
RPC_EXPORT_SEED (25), and RPC_IMPORT (26). Envelopes move
through the OS share sheet and expo-document-picker.
app/components/BackupSettings.tsx
WYSIWYG markdown editor
RichMarkdownEditor edits ticket descriptions and markdown/callout blocks
as live WYSIWYG (TipTap in a webview via @10play/tentap-editor) over the
DOM-free @listam/domain/markdown; the stored value stays markdown.
app/components/board/RichMarkdownEditor.tsx
Synced peer/device names
A device-name input in Settings self-asserts a legible name over
@listam/domain/labels; those names show on the members screen
(MembersDialog). Labels sync through a registry-style meta-item channel.
app/components/MembersDialog.tsx
Pair a leaf (BLE)
useLeafProvisioning + LeafPairingDialog provision the ESP32
leaf over Bluetooth (react-native-ble-plx), reading the hub control key
and address from owner-control status. Needs a dev-client build.
app/hooks/useLeafProvisioning.ts
app/components/LeafPairingDialog.tsx
Persistence
Storage and Local State
| Data |
Storage |
Owner |
Notes |
| Typed-list log and materialized view |
{document}/lista-local |
Bare backend |
Corestore + Autobase, encrypted when Autobase has an encryption key. One base holds
multiple typed lists (grocery / board / todo). |
| List registry, labels & plan |
Autobase meta-items |
Bare backend |
Named lists/groups, synced peer/device names, and the day plan all replicate as
reserved-bucket meta-items in the shared base. |
| Autobase base & encryption keys |
@listam/secrets secure storage |
Bare backend |
No longer plain-hex files. Keys live in @listam/secrets secure
storage (Expo SecureStore); legacy lista-*-key.txt files are migrated and
deleted (LEGACY_SECRET_FILES). |
| Invite |
Runtime / secure storage |
Bare backend |
BlindPairing invites are single-use and ~10-minute expiry; minted at runtime. |
| UI preferences |
AsyncStorage keys like @lista_grid_view |
React Native app |
View mode, category switches, icon/list sizing, and icon variant. |
| Loyalty cards |
@lista_loyalty_cards |
React Native app |
JSON array containing card name, barcode/QR data, and type. |
| Trial start |
@lista_trial_start |
React Native app |
Local timestamp used for the 30-day trial calculation. |
Dependency roles
Libraries
React Native / ExpoMobile shell, app lifecycle, camera, linking, haptics, file paths.
react-native-bare-kitEmbeds and starts the packed Bare backend inside the app process.
bare-rpcTyped-by-number IPC contract between frontend and worklet.
CorestoreLocal Hypercore storage used by Autobase.
AutobaseMulti-writer append log and deterministic materialized view.
HyperswarmPeer discovery and replication connections.
BlindPairingInvite flow that exchanges base and encryption credentials.
z32Shareable encoding for blind-pairing invites.
react-native-iapSubscription paywall, trial expiry, purchase and restore.
Redux ToolkitUI state slices fed by RPC replies (lists, board config, labels, sync).
@expo/vector-iconsCategory glyphs render via MaterialCommunityIcons.
@10play/tentap-editorTipTap-in-a-webview WYSIWYG markdown editor for tickets and blocks.
react-native-ble-plxBLE transport for “Pair a leaf” ESP32 provisioning.
expo-document-pickerPicks encrypted backup envelopes for import.
@listam/secretsSecure storage for base/encryption keys and loyalty payloads.
package.json
Lead:
App version 1.0.1. UI locales are System / EN / ES / DE / FR / IT / PT,
derived from the shared @listam/i18n LOCALE_CHOICES.
Maintainability
Conventions and Quality Notes
Conventions observed
- Single quotes and no semicolon-heavy style in application files.
- UI state lives in Redux Toolkit slices fed by RPC replies, not colocated React state.
- Backend mutable singletons are centralized in
backend/lib/state.mjs.
- Generated lookup files are marked as auto-generated and sourced from CSV.
- RPC command constants are numeric and re-exported from
@listam/protocol.
Quality gaps to track
package-lock.json handling: install requires --legacy-peer-deps.
- Committed generated bundles (base64 backend) make review noisy.
- Several new surfaces (Overview, leaf pairing, markdown editor) are typecheck-clean
but still need an on-device dev-client build.
- Board tickets are still written with the legacy wire type
'kanban' for
old-peer interop (isBoardType() dual-reads).
Verification
Testing
How an implementation agent tests the mobile app in isolation and proves it interacts
with other instances. Pair this with the
implementation plans and the cross-instance
matrix.
Unit
Store slices and selectors (test:store); category lookup and grocery
intelligence (test:grocery); backend secret-storage migration and crypto
(test:security).
Integration
Worklet RPC round-trips (add/update/delete → backend → SYNC_LIST /
*_FROM_BACKEND); join success/timeout/rollback; secure-storage migration of
keys and loyalty cards; deep-link join requires confirmation.
Manual / E2E
Parity checklist on iOS and Android; offline edit then reconnect-sync; restart persistence.
How to run
npm test runs test:security + test:store +
test:grocery with node:test (TypeScript transpiled on the fly;
there is no Jest). Expo dev client for manual runs; two simulators for device-to-device.
app/store/listsSlice.test.mjs
Interaction:
Use the shared harness (separate storage roots + a private
--bootstrap) to test
mobile ↔ mobile, mobile ↔ desktop, and mobile ↔ headless. Mobile rows of the
cross-instance matrix: generated, edited,
completed, and deleted content converge; stays-synced-after-reopen; link-join confirmation
(H2); and id-based duplicate handling (M1).