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 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. For a package with slug: 'mail':
mail/screens/
_layout.tsx → app/a/[orgSlug]/mail/_layout.tsx
index.tsx → app/a/[orgSlug]/mail/index.tsx
[id].tsx → 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 sibling 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 run inside the app shell’s bundle context, so you can import core utilities via the @tinycld/core/... package paths:
import { useOrgLiveQuery, useStore } from '@tinycld/core/lib/pocketbase'
import { useAuth } from '@tinycld/core/lib/auth'
Do not attempt to install @tinycld/core as a dependency - resolution already works because the sibling compiles inside the app shell’s tsconfig context.
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 app/<relpath> - no org prefix. Drive’s public share route lives at drive/public-screens/share/[token].tsx and ends up as app/share/[token].tsx.
If two packages declare the same public path, the generator throws with both package names. Collisions are loud by design.
package.json exports
For the generator and Metro to resolve these files, your package.json needs wildcard exports that cover each directory:
{
"exports": {
"./screens/*": "./screens/*.tsx",
"./public-screens/*": "./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 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 app/a/[orgSlug]/<slug>/ and app/<publicRoute>/ are gitignored and regenerated on every npm run packages:generate. Don’t edit them by hand.