Packages
Shared workspace packages for UI components, linting, TypeScript config, and feature management
The packages/ directory contains shared workspace packages. Some are private workspace-only packages (@workspace/*), others are published to npm under the @celestia-project org.
@celestia-project/ui
A shadcn-style React component library built on Base UI and Tailwind CSS v4, with 150+ components.
Exports:
| Import path | Resolves to |
|---|---|
@celestia-project/ui | Barrel export of every component (tree-shaken) |
@celestia-project/ui/globals.css | Global styles and CSS variables |
@celestia-project/ui/primitive/* | Primitive components (button, card, input, label, etc.) |
@celestia-project/ui/composite/* | Composite components (field, sidebar, data-table, form, etc.) |
@celestia-project/ui/ai | Barrel export of all AI chat & agent components |
@celestia-project/ui/components/ai/* | AI chat & agent components |
@celestia-project/ui/hooks/* | Shared React hooks |
@celestia-project/ui/lib/* | Utilities (cn helper) |
Usage:
import { Button } from "@celestia-project/ui"import { Button } from "@celestia-project/ui/primitive/button"import { cn } from "@celestia-project/ui/lib/utils"Key dependencies: @base-ui/react, class-variance-authority, tailwind-merge, next-themes, @phosphor-icons/react
Using it outside this monorepo
The package is published to npm and can be installed in any React 19 + Tailwind CSS v4 project:
pnpm add @celestia-project/uiIntegration Option A: Quickstart (All-in-one)
Import the global stylesheet in your app entry point (app/layout.tsx or globals.css):
import "@celestia-project/ui/globals.css";Integration Option B: Existing Tailwind v4 App (@source directive)
If your app already has an existing Tailwind v4 setup and custom theme variables, add the @source directive in your main CSS file so Tailwind scans the package without double-importing or overriding styles:
@import "tailwindcss";@source "../../node_modules/@celestia-project/ui";@source "../**";
@custom-variant dark (&:is(.dark *));See the Components docs and the npm package for details.
@celestia-project/mobile
Native iOS and Android UI components built on @expo/ui — real SwiftUI on iOS and Jetpack Compose on Android, with no web-shim layer in between. The package reuses the same primitive / composite / layout taxonomy and the same design tokens as @celestia-project/ui, but it is not a port: mobile-only affordances (haptics, safe areas) have no web counterpart. For slide-up panels, use MobileBottomSheet on native and BottomSheet on web.
@celestia-project/mobile is part of the npm publish set: scripts/publish.sh supports publishing @celestia-project/ui, @celestia-project/create, and @celestia-project/mobile. Inside the workspace, it is consumed from source through the workspace protocol ("@celestia-project/mobile": "workspace:*"). Publish it standalone with pnpm publish:mobile, or all packages together with pnpm publish:packages.
Exports:
| Import path | Resolves to |
|---|---|
@celestia-project/mobile | Barrel export of every component, hook and token (tree-shaken) |
@celestia-project/mobile/tokens | useMobileTheme(), colour ramps, spacing scale, metrics |
@celestia-project/mobile/host | MobileHost — theme + safe-area root |
@celestia-project/mobile/primitive | All 20 generic components (barrel) |
@celestia-project/mobile/primitive/* | One module — e.g. primitive/button |
@celestia-project/mobile/composite | All 13 opinionated assemblies (barrel) |
@celestia-project/mobile/composite/* | One module — e.g. composite/tab-bar |
@celestia-project/mobile/layout | All 10 shells and screens (barrel) |
@celestia-project/mobile/layout/* | One module — e.g. layout/sign-in-screen |
Usage:
import { MobileButton, MobileScreen } from "@celestia-project/mobile"import { MobileButton } from "@celestia-project/mobile/primitive/button"import { useMobileTheme } from "@celestia-project/mobile/tokens"Key dependencies: @expo/ui, expo-haptics, react-native-safe-area-context
The package is consumed from source — its exports map points straight at .ts / .tsx, so there is no build step and no dist/. The consuming app's bundler (Metro, via Expo) compiles it. Every colour comes from useMobileTheme(); nothing is hardcoded, which is what lets MobileHost switch themes at runtime.
See the Mobile (Expo) docs for setup, the full component inventory, and the design rules.
@celestia-project/create
The interactive CLI that scaffolds a new Celestia Starter project. Lives in packages/cli.
Usage:
npx @celestia-project/create my-appnpx @celestia-project/create my-app --template web --features auth,dashboardnpx @celestia-project/create my-desktop --template desktopBin registered: create-celestia
Publishing:
# From the monorepo root — publishes both @celestia-project/ui and @celestia-project/createpnpm publish:packages
# Dry run first (previews tarball contents without uploading)pnpm publish:dry-runSee the CLI docs for the full feature flag reference and local testing guide.
@workspace/eslint-config
Shared ESLint flat-config presets for consistent linting across the monorepo.
Exports:
| Import path | Purpose |
|---|---|
@workspace/eslint-config/base | Base JS/TS rules (strict, turbo, prettier compat) |
@workspace/eslint-config/next-js | Next.js-specific rules (adds @next/eslint-plugin-next) |
@workspace/eslint-config/react-internal | React library rules (hooks, jsx) |
Usage in a consumer eslint.config.js:
import { config } from "@workspace/eslint-config/next-js"
export default config@workspace/typescript-config
Reusable tsconfig profiles that all workspace projects extend.
| File | Use case |
|---|---|
base.json | Default — strict, ES2022, NodeNext resolution |
nextjs.json | Next.js apps — adds JSX, bundler resolution, plugins |
react-library.json | React component libraries — JSX + DOM libs |
Usage in a consumer tsconfig.json:
{ "extends": "@workspace/typescript-config/nextjs.json"}@workspace/feature-manager
A CLI and runtime registry that discovers, installs, and tracks features defined by feature.json manifests under the project's packages/feature-manager/features/ directory.
CLI commands (via root scripts):
pnpm add-feature <name> # Install (or --force reinstall/upgrade) a featurepnpm remove-feature <name> # Uninstall a feature, restoring shared filespnpm list-features # List available and installed featurespnpm verify-features [name] # Check installed features for drift or breakagepnpm prompt-feature <name> # Generate the AI verification promptCommon flags: --dry-run (plan only, writes nothing), --force (reinstall/upgrade,
or override removal safety checks), --strict (treat warnings as errors),
--json (machine-readable report), --quiet.
How add works:
- Reads
packages/feature-manager/features/<name>/feature.json(legacy v1 manifests usingfilesare normalized) - Validates it — malformed fields and paths that escape the repository are rejected before anything is written
- Verifies
requiresagainst the tracker (rootfeatures.json) - Plans every action, then snapshots each path it will touch
- Copies
copiesentries into the workspace —apps/api,apps/web,packages/*, root files - Inserts
insertionssnippets into managed marker regions - Applies
jsonAppendsto JSON arrays (e.g. the docs sidebarmeta.json) - Merges
dependencies/devDependenciesinto each target package'spackage.json, reporting any version conflict instead of silently replacing it - Records the feature, its files (with content hashes), backups and dependency
changes in the tracker, then prints next steps (
pnpm install, env vars,postInstallcommands)
If any step fails, every touched path is rolled back — a failed install can never leave a half-applied repository behind.
Shared files: when two features provide the same path (for example the CMS
media dialog, provided by cms, media-r2 and unsplash), the newest install
wins and the previous content is backed up under .feature-manager/backups/.
Removing a feature restores the previous owner's version rather than deleting the
file. Files you edited after installing are kept unless --force is given.
Feature manifest schema (feature.json):
{ "name": "blog", "version": "1.0.0", "description": "Full CRUD blog over the typed RPC client", "requires": ["auth", "dashboard"], "copies": [ { "from": "api/src/features/blog/routes.ts", "to": "apps/api/src/features/blog/routes.ts" } ], "insertions": [ { "file": "apps/api/src/app.ts", "marker": "routes", "snippet": "snippets/api-routes.ts" } ], "jsonAppends": [ { "file": "apps/web/content/docs/meta.json", "path": "pages", "value": "blog" } ], "dependencies": { "apps/api": { "drizzle-orm": "^0.45.2" } }, "env": { "apps/api": ["DATABASE_URL=postgresql://postgres:postgres@localhost:5432/celestia"] }, "postInstall": ["pnpm --filter @workspace/db db:push"], "notes": "Optional post-install instructions"}Managed marker regions
Insertions target empty sentinel regions that already exist in shared files. Installing wraps the snippet in a per-feature block so it can later be replaced (idempotent reinstall) or removed cleanly:
// feature-manager:<marker>:begin// feature-manager:<marker>:<feature>:begin...snippet...// feature-manager:<marker>:<feature>:end// feature-manager:<marker>:endAvailable regions:
| Marker | File | What features insert |
|---|---|---|
imports | apps/api/src/app.ts | Route module imports |
routes | apps/api/src/app.ts | .route(...) mounts |
auth-imports | apps/api/src/auth.ts | Better Auth server-plugin imports |
auth-plugins | apps/api/src/auth.ts | Plugin entries in the plugins array |
tables | packages/db/src/schema.ts | Drizzle tables + relations |
user-fields | packages/db/src/schema.ts | Additional columns on the user table |
imports | apps/web/components/dashboard/sidebar.tsx | Icon imports |
nav | apps/web/components/dashboard/sidebar.tsx | Sidebar navigation items |
nav-visibility | apps/web/components/dashboard/sidebar.tsx | Per-item visibility filter inside the nav map |
client-imports | apps/web/lib/auth-client.ts | Better Auth client-plugin imports |
client-plugins | apps/web/lib/auth-client.ts | Client-plugin entries in the plugins array |
cards | apps/web/content/docs/index.mdx | Landing page <Card> links |
features | README.md | "Installed Features" bullets |
Marker ids are scoped per file, so imports in app.ts and imports in sidebar.tsx don't collide. Comment syntax follows the file type (// for TS/TSX, <!-- --> for .md, {/* */} for .mdx). Files that cannot carry comments (e.g. meta.json) use jsonAppends instead.
Authoring snippets:
- Snippets are inserted verbatim — include their leading indentation in the snippet file itself (only the sentinel comments are auto-indented)
- A single trailing newline is stripped before insertion
Programmatic exports:
| Import path | Purpose |
|---|---|
@workspace/feature-manager | Everything below, re-exported from one entry point |
@workspace/feature-manager/markers | Marker engine: insertIntoRegion, removeFromRegion, hasRegion, listRegionFeatures, jsonAppend, jsonRemove |
@workspace/feature-manager/manifest | loadManifest, normalizeManifest, validateManifest, discoverFeatures |
@workspace/feature-manager/installer | planInstall, executeInstall, installFeature |
@workspace/feature-manager/remover | planRemove, executeRemove, removeFeature |
@workspace/feature-manager/verifier | verify — integrity report for installed features |
@workspace/feature-manager/types | Feature, FeatureManifest, FeatureCopy, FeatureInsertion, FeatureTracker types |
@workspace/feature-manager/registry | Runtime feature registry |
All of these take an explicit repo root, so they can be driven from scripts and
tests rather than only from the CLI.