Skip to main content

Shared packages

Reusable infrastructure lives under packages/. Apps depend on these; apps never depend on each other.

PackagePurpose
@constellation-platform/auth-coreJWT types, clearance levels, permission checking
@constellation-platform/auth-nextNext.js auth middleware (withAuth, withTenantAuth, locale primitives)
@constellation-platform/dbTenant-scoped Prisma client, RLS helpers, auditAction() for routine audit, correlation-id propagation
@constellation-platform/design-tokensThe Constellation design language (warm-neutral OKLCH foundation, single amber accent, functional status colours, Geist type) + a Path A bridge aliasing every shadcn variable onto a Constellation token, Tailwind v4 @theme mapping, light/dark palettes, and a11y baseline (prefers-reduced-motion, :focus-visible). The bridge covers elevation as well as colour: Tailwind's --shadow-* scale maps onto the warm --e-* ramp, so stock shadow-sm / shadow-md / shadow-lg render warm rather than neutral-black — see Elevation below. It also carries the shared typography baseline (PLT-455): font-semibold resolves to the kit's 550 heading weight and the base body rule applies the kit's Geist feature settings ('ss01', 'cv11'), in all four consuming product apps. CSS-only — import styles.css from each app's globals.css.
@constellation-platform/errorsStandard error hierarchy and API envelope (AppError, toErrorResponse)
@constellation-platform/auditTransactional audit via outbox (auditCritical() — writes audit row + publishes audit.entry.created event in the same txn)
@constellation-platform/eventsOutbox-based event publishing (publish), polling dispatcher, subscribe
@constellation-platform/jobsJob queue abstraction (createJobQueue({ type: 'postgres' | 'memory', ... })). Two implementations today: PostgresJobQueue (durable) and InMemoryJobQueue (tests). runInWorkerBypassTransaction is the audited cross-tenant drain transaction every worker shares (PLT-1342)
@constellation-platform/storageObject-storage adapter. S3-compatible (MinIO in dev, S3 in cloud, MinIO on-prem) plus a Supabase Storage backend for Supabase-hosted tenants
@constellation-platform/telemetryWeb-vitals telemetry behind a provider seam (WebVitals). Takes no props: the collector path is wrong by default behind the multi-zone rewrites and fails silently, so the package owns both transport paths
@constellation-platform/error-reportingError reporting behind a provider seam (Sentry today): reportError, reportMessage, flushErrorReports, and reportRequestError for Next's onRequestError. Never throws. The /scrub subpath (scrubErrorEvent, reportableError) and the /init-options subpath (errorReportingInitOptions) hold the SDK-free, shared outgoing allowlist and init options that the wiki and the agents zone pass to their init files (ADR-049 § D4). Apps must not import @sentry/* outside the framework init files — the constellation/no-error-reporting-sdk lint rule enforces it
@constellation-platform/i18nBCP-47 Locale type, resolveLocale(), Intl format helpers
@constellation-platform/translationAI-backed translation; wraps ai-core
@constellation-platform/ai-coreProvider-agnostic LLM abstraction (Claude / mock); audit + budget. Sampling params (temperature/top_p) are model-capability-gated: sent only to issued ids of models that still accept them (Opus 4.6, Sonnet 4.6 and earlier), omitted for Opus 4.7 and later and for unrecognised ids, and never sent together (PLT-831)
@constellation-platform/ai-embeddingsEmbedding-API provider abstraction (OpenAI / mock); pgvector-friendly output
@constellation-platform/ai-ragRetrieval-augmented generation primitives — combines ai-core and ai-embeddings for grounded responses
@constellation-platform/coordinatorHosted reasoning service — stateless consultCoordinator() over initiative-scoped Project Tracker state; every consult it persists lands as one coordinator.consults row plus an audit entry via auditCritical (a consult rejected before persistence writes neither). Reachable via the consult_coordinator MCP tool, npx pt consult, and the PT REST route. synthesizeCycleRetrospective() is the system-fired sibling (SYSTEM-attributed unless the caller names another actor type) the PT-816 cycle-close hook will call.
@constellation-platform/testingTest tenant/user factories, test transaction wrapper
@constellation/contractsShared Zod schemas and derived types (cross-module event payloads, common shapes)
@constellation-platform/uiShared React components. Notable primitives: LocaleSwitcher; PersonAvatar + avatarTint (palette-tinted initials avatar, hashing a name onto the eight --avatar-* token pairs, with an optional docked agent marker and an optional profile image, avatarUrl, drawn over the initials once it loads); AgentMarker (the platform's single agent-owned signal, badge or icon); ListFilterBar (the simple list filter bar — status + sort + debounced search + clear-all; a surface needing several independent facets composes Selects alongside it rather than widening its props); Breadcrumbs + the composable Breadcrumb* parts (the trail; pass linkComponent — typically next/link — for client-side navigation, and note it is not optional under a zone basePath: the default plain anchor is not basePath-prefixed, so every collapsed crumb resolves against the root zone and 404s in production while looking correct in dev and CI, where the basePath is unset); LoadingSpinner (the spinner. Announcing by default — it renders an <output>, an implicit status live region named "Loading", which is what you want when the spinner IS the announcement: a page or panel loading with nothing else saying so. Pass decorative inside a control, or the live region nests in that control and the control's accessible name absorbs the spinner's — a submit button reads "Loading Save" rather than "Save". decorative renders the same element with aria-hidden, so the exclusion is inherited rather than wrapped. Note that hiding it is only free where the button's label already changes while pending ("Retry" → "Retrying…"): a button with a STATIC label needs a pending label of its own, or it goes disabled under an unchanged name and says nothing at all); RichTextEditor (the shared TipTap editor, HTML or mode="markdown". It is a controlled component and behaves like one (PLT-1295): onChange fires ONLY for a mutation originating inside the editor — a keystroke, a toolbar command, an upload insertion — and never for the initial seeding, an external content push, or an editable change, so a caller's content survives mount byte-for-byte. The corollary is that the CALLER owns canonicalisation: a value the editor would serialise differently (_under_ → *under*, a reference link → an inline link, <b> → **) stays exactly as you hold it until a real edit happens, and a required-field check must use a rich-text-aware predicate rather than a trim(), because an empty editor's content is whatever you passed — possibly <p></p> — rather than ''); SkipLink (the "Skip to main content" bypass link, WCAG 2.2 SC 2.4.1 — mount it as the first focusable element in the document, since one placed after the chrome cannot skip that chrome, and give its targetId element tabIndex={-1} or activation moves the URL fragment while focus stays behind). See the package's README.md and AGENTS.md for the component guide, and src/index.ts for the authoritative export list
@constellation-platform/coordinator-chatShared coordinator UI rendered by Project Tracker as both the full-page workspace (CoordinatorWorkspace) and the task drawer (PLT-224, PLT-237). Zone-agnostic transport via an injected resolveApiUrl. Consumed as source.
@constellation/configShared ESLint, TypeScript, Vitest configs

i18n entry points​

Internationalisation primitives are split across three packages so apps can compose only what they need:

  • @constellation-platform/i18n — runtime-agnostic. Locale union, SUPPORTED_LOCALES, resolveLocale(), negotiateAcceptLanguage(), formatDate/DateTime/Number/Currency, message-loader with regional → base-language → default fallback, and a localized() JSONB overlay reader.
  • @constellation-platform/auth-next/locale — Next.js wiring. Cookie helpers (readLocaleCookie, serializeLocaleCookie, clearLocaleCookieValue), edge-middleware integration via the localeResolver option on createConstellationAuthMiddleware, withVaryCookie() response decorator, and AuthContext.locale populated by withAuth.
  • @constellation-platform/translation — AI translation. TranslationProvider interface and createTranslationProvider({ ai, model }) that wraps ai-core so tenant budget + audit are inherited.
  • @constellation-platform/ui — shared LocaleSwitcher client component (server-action driven; renders null when fewer than two locales are enabled).

See Multilanguage (i18n) for the architecture overview.

Elevation​

Shadows come from the design language's warm elevation ramp, not Tailwind's stock scale. @constellation-platform/design-tokens maps Tailwind's --shadow-* theme steps onto the four --e-* rungs, so the stock utilities already render warm — reach for shadow-sm / shadow-md / shadow-lg as usual and do not write shadow-[var(--e-2)].

UtilityRungTypical surface
shadow / shadow-2xs / shadow-xs / shadow-sm--e-1inputs, switches, cards, active chips
shadow-md--e-2popover, dropdown menu, select content
shadow-lg--e-3dialog, sheet, submenus, drag lifts
shadow-xl / shadow-2xl--e-popdrawers, command palette, floating panels

Three things worth knowing:

  • Elevation is deliberately four levels, not Tailwind's seven. The design language is near-flat, so steps collapse onto shared rungs. To retune elevation, change the four --e-* values in the token package once — never add a role-specific shadow.
  • Dark mode uses neutral-black shadows, by design. A warm tint needs the higher alpha dark grounds demand to register at all, and at that alpha it reads as haze rather than depth. The warmth in dark mode comes from the neutral ramp underneath.
  • A shadow-[…] holding a literal value (e.g. shadow-[0_4px_10px_rgba(0,0,0,.1)]) opts that element out of both the ramp and the dark theme. An arbitrary value that still references a token — shadow-[var(--e-2)] — keeps resolving per theme; it is merely redundant, since shadow-md already gives you --e-2. shadow-<color> modifiers (e.g. shadow-black/20) no longer tint these steps, because Tailwind cannot parse a colour out of a var(). The separate drop-shadow-* filter utility is not remapped and stays neutral — it is for glyph legibility over imagery, not surface elevation.

When to use which​

  • AI features. Start with @constellation-platform/ai-core for provider-agnostic completion calls (with budget + audit). Add @constellation-platform/ai-embeddings when you need vector embeddings (semantic search, RAG retrieval). Add @constellation-platform/ai-rag when you want grounded answers — it composes ai-core + ai-embeddings and handles the retrieval / context-injection wiring. For initiative-scoped reasoning over live PT + GitHub state (prioritisation, duplicate-effort detection, "what next"), call @constellation-platform/coordinator's consultCoordinator() — it wraps ai-core and writes an append-only coordinator.consults row plus a coordinator.consult.created audit entry for every call it gets far enough to persist (input rejected up front, and a persist that itself fails, write neither). The same package's synthesizeCycleRetrospective() is the system-fired entry point for cycle-close retrospectives, attributed to SYSTEM unless the caller names another actor type — the PT-816 cycle-close hook is what will call it. Its host-supplied CycleRetrospectiveContext optionally carries overCapacityAdmissions (PLT-722): the work whose admission took a cycle past its points target — including the one that first crosses it, not only those admitted when it was already over — which the synthesised retro reports as interruption pressure alongside the delivery figures. Omitting the field is valid and produces the retrospective it produced before the field existed. The figures it carries are never summed — each is the cycle's excess after that admission, not the points it added — and the rendered list is bounded both by entry count and by a character budget, with an omission line naming the true total. It persists identically, is idempotent on a successful retro per cycle (a failed attempt stays retryable), emits the knowledge-base file-back event even with zero citations (the wiki's own space and dedup gates still apply), and is best-effort — every failure resolves as an outcome rather than throwing, so a retro can never fail the cycle close.
  • Persistence. @constellation-platform/db for any tenant-scoped DB access. Use withTenantContext() to open a transaction with app.tenant_id set. Use auditAction() for routine audit; reach for @constellation-platform/audit's auditCritical() only when the mutation must publish an audit.entry.created outbox event for downstream SIEM.
  • Async work. @constellation-platform/jobs for one-off background work. @constellation-platform/events for cross-module domain events (always wrap in the originating transaction).
  • Files. @constellation-platform/storage (S3 / MinIO).
  • Errors you did not map to a response. @constellation-platform/error-reporting. On a serverless path, await flushErrorReports() before responding: the function is frozen once it answers, and an unflushed report is lost.
  • Auth. Apps wire @constellation-platform/auth-next and call into @constellation-platform/auth-core for permission helpers.
  • UI. Always import from @constellation-platform/ui, never from app-local components/ui/. Overlays (Dialog, Sheet, Popover, DropdownMenu, Select, Tooltip, AlertDialog) all portal into document.body, so they stack by z-index alone — never set a z-* class on one; pass layer and let nested overlays derive from it. See overlay-layering.