Screens

Packages contribute two kinds of routes: org-scoped screens under /a/[orgSlug]/<slug>/... and (optionally) public top-level routes under /<path>. Both are plain Expo Router files. The generator re-exports them into the app shell’s tinycld/app/ tree so Expo Router’s file-based router picks them up.

Org-scoped screens

Point routes.directory at the folder containing your screens. The convention is 'screens':

routes: { directory: 'screens' },

Mirror the URL structure you want inside the folder. Package TypeScript lives under a tinycld/<slug>/ prefix, so for a package with slug: 'mail':

mail/tinycld/mail/screens/
    _layout.tsx          → tinycld/app/a/[orgSlug]/mail/_layout.tsx
    index.tsx            → tinycld/app/a/[orgSlug]/mail/index.tsx
    [id].tsx             → tinycld/app/a/[orgSlug]/mail/[id].tsx

The generated re-exports are thin - each one simply forwards export { default } from '@tinycld/mail/screens/<path>'. The actual components live in the member repo; the app shell only holds the glue.

_layout.tsx is the package’s route group root. Put layout concerns (navigation, providers, shared UI) here. If you don’t declare one, Expo Router falls back to its default stack layout.

Screens import core utilities via the @tinycld/core/... package paths:

import { useStore } from '@tinycld/core/lib/pocketbase'
import { useOrgLiveQuery } from '@tinycld/core/lib/use-org-live-query'
import { useAuth } from '@tinycld/core/lib/auth'

Do not attempt to install @tinycld/core as a dependency - resolution works because the package’s tsconfig.json extends @tinycld/core/tsconfig.package-base.json by package name, and @tinycld/core/* resolves by package name through the node_modules/@tinycld/* symlink.

Public top-level routes

Some packages need pre-auth entry points that don’t sit under /a/[orgSlug]/. Declare publicRoutes and place files in the named directory:

publicRoutes: { directory: 'public-screens' },

The generator emits re-exports at tinycld/app/p/<slug>/<relpath>. Drive’s tinycld/drive/public-screens/share/[token].tsx becomes tinycld/app/p/drive/share/[token].tsx, served at /p/drive/share/<token>.

package.json exports

For the generator and Metro to resolve these files, your package.json needs wildcard exports that cover each directory:

{
    "exports": {
        "./screens/*": "./tinycld/mail/screens/*.tsx",
        "./public-screens/*": "./tinycld/mail/public-screens/*.tsx"
    }
}

What the generator produces

Each file under routes.directory or publicRoutes.directory with a .tsx, .ts, .jsx, or .js extension becomes a re-export in tinycld/app/. Nested folders and layout files are preserved. Non-route files (helpers, styles) are ignored - put those in components/, hooks/, or alongside the screens but give them a different extension (e.g. .helpers.ts).

The re-exports under tinycld/app/a/[orgSlug]/<slug>/ and tinycld/app/p/<slug>/ are gitignored and regenerated on every pnpm run packages:generate. Don’t edit them by hand. The wrapping tinycld/app/p/_layout.tsx is hand-written — a bare <Slot /> with no auth gate — and stays in git.