Getting started

TinyCld is two things working together: an app shell that bundles @tinycld/core for shared runtime + UI, plus a set of feature packages (@tinycld/mail, @tinycld/contacts, @tinycld/calendar, @tinycld/drive, and more) that the app opts into at build time. A clean checkout of the app shell runs with zero feature packages - you add exactly the features you want by linking siblings.

Ecosystem layout

Every repository lives under a single parent directory. Most developers use ~/code/tinycld/:

~/code/tinycld/
    tinycld/                 # the app shell (bundles @tinycld/core)
    contacts/                # @tinycld/contacts
    mail/                    # @tinycld/mail
    calendar/                # @tinycld/calendar
    drive/                   # @tinycld/drive
    google-takeout-import/   # @tinycld/google-takeout-import

The tinycld repo is the only required one. Each feature sibling is its own git repo with its own history, issues, and CI. There is no monorepo and no workspaces - just independent feature packages that the app links in.

How the app shell, core, and siblings relate

@tinycld/core (bundled inside the app shell at tinycld/packages/@tinycld/core/) holds the runtime: React Native, Expo Router, the PocketBase client, pbtsdb, shared UI components, theming, auth. Every feature sibling peer-depends on those; none of them ship their own copies. Siblings depend on core’s file layout - a sibling imports from @tinycld/core/lib/* and @tinycld/core/ui/*, never the other way around.

Siblings do not depend on each other. If the takeout importer wants to know whether mail is installed, it reads the runtime package registry (usePackages()) rather than taking a compile-time import on @tinycld/mail. This keeps each sibling independently releasable and keeps the app shell’s lean-shell guarantee intact - a fresh clone with no feature packages still typechecks.

Fresh-clone default

Clone the app shell and run it:

git clone https://github.com/tinycld/tinycld.git ~/code/tinycld/tinycld
cd ~/code/tinycld/tinycld
npm install
npm run dev

tinycld.packages.ts starts as [] as const. With no feature packages linked, the app shell boots as an empty app - you can sign in, land in an org, and that’s about it. Everything beyond authentication is a package contribution.

The dev loop

cd ~/code/tinycld/tinycld
npm run dev           # runs packages:generate, then starts Expo + PocketBase
npm run checks        # biome lint + format + typecheck
npm run test:unit     # vitest (includes sibling tests)
npm run test:e2e      # playwright (includes sibling tests)

npm run dev (or its alias npm run start) runs the package generator first, then starts the normal Expo dev server, a local PocketBase instance, and a single-port HTTP proxy that fronts both — open http://localhost:7100 (or https:// if certs are present) and the app talks to PB same-origin via /api. The generator produces re-exports under app/a/[orgSlug]/<slug>/..., registers sibling collections with pbtsdb, and symlinks package migrations into server/pb_migrations/. You don’t run it manually - the dev launcher and build:web both invoke it automatically.

Sibling tests run alongside core tests. If your package ships unit or e2e tests in a tests/ directory, they’re picked up by core’s vitest and Playwright configs as soon as the package is linked. Unlinking a package removes its tests from the run with no config change.

Where to go next