Skip to main content

SDK architecture

The Command Center SDK is a set of composable contracts and UI primitives, not an application framework that takes over the host. It standardizes the parts that should behave consistently across applications while leaving product policy and infrastructure in the consuming application.

The most useful mental model is a one-way pipeline:

backend bytes
↓ application-owned client
normalized adapter result
↓ SDK definition and lifecycle
controlled React view
↓ semantic intent or callback
application-owned router, mutation, or notification

Each boundary has a different job. Keeping those jobs separate makes an application easier to test and prevents routing, credentials, or backend-specific payloads from leaking into reusable SDK code.

The five layers​

1. Definitions describe stable meaning​

Definitions name resources, columns, navigation destinations, actions, and detail tabs. They are plain typed data plus narrow callbacks. IDs in definitions are compatibility identifiers: they are used to reconcile controlled state and should remain stable after release.

Examples include ResourceApplicationDefinition, NavigationApplicationDefinition, and ProgressStageDefinition.

Definitions do not fetch data or decide routes. A resource definition may say that a row resolves to { resource: "services", uid: "…" }; the host decides which URL that intent opens.

2. Adapters normalize external systems​

Adapters isolate backend shape and transport policy. They accept SDK requests and return SDK models. The application supplies authentication, base URLs, headers, retries, error translation, and any product-specific endpoint behavior.

For a conventional HTTP service, createHttpResourceAdapter removes repetitive endpoint and normalization plumbing. For GraphQL, RPC, local-first storage, or an existing client library, implement ResourceAdapter directly.

The normalized result is the important boundary. A server may return { count, results }, but a resource list consumes { items, pageInfo } with authoritative pagination.

3. Lifecycle code owns reusable interaction state​

High-level SDK views own state that must behave the same in every application: debounced search, stale-request cancellation, loading and empty states, selection, bulk-action preflight, confirmation, refresh, and accessible feedback.

The host still owns durable or navigable state such as route parameters, query cache policy, selected detail tabs encoded in the URL, authentication sessions, and notification systems.

This division explains why the SDK favors controlled props and semantic callbacks. The SDK can coordinate an interaction without becoming the owner of application policy.

4. Views render normalized models​

Use the highest-level view that owns the workflow you need:

WorkflowPreferred surface
Searchable, pageable collectionResourceListPage
One entity with summary and tabsResourceDetailShell
Single, multiple, or action selectionResourcePicker
Many items chosen side by side from a long listResourceTransferList
Complete responsive page composition/layout primitives
Actions, badges, and labelled fields/controls primitives
Application startup or reconnect statusApplicationStatusScreen
Application hierarchy and destinations/navigation components

Drop to smaller exported components only when the high-level composition does not own your workflow. Rebuilding a list from DataTable, ResourceSearch, and ResourcePagination also means you take responsibility for coordinating their state correctly.

5. The host owns policy and effects​

The consuming application supplies the decisions that cannot be safely generalized:

  • who the current user is and which resources they may access;
  • which API client and credentials to use;
  • how URLs map to screens and how navigation is committed;
  • which errors produce notifications, retries, or support guidance;
  • what is persisted and how it is migrated; and
  • which deployment, CSP, CORS, and iframe policies apply.

The SDK may validate or present the result of those decisions, but it does not replace them.

Compose the embedded root before its routes​

A production application is embedded in the main Command Center. The host owns top/global chrome; the child never adds a top navigation bar. From its first frame the child gates host context, delegated transport, and critical API readiness. Only then does it mount exactly one selected navigation depth and route content:

import { ApplicationStatusScreen } from "@dev-mainsequence/command-center-sdk/feedback";
import { ApplicationNavigationPanelShell } from "@dev-mainsequence/command-center-sdk/navigation";
import { ApplicationPage, ApplicationPageStack } from "@dev-mainsequence/command-center-sdk/layout";
import { ResourceListPage } from "@dev-mainsequence/command-center-sdk/views";

export function ApplicationRoot() {
if (!readiness.ready) {
return (
<ApplicationStatusScreen
action={readiness.failure ? { label: "Retry startup", onSelect: retry } : undefined}
message={readiness.message}
stages={readiness.stages}
state={readiness.failure ? "error" : "loading"}
title={readiness.failure ? "Application could not start" : "Preparing application"}
variant="viewport"
/>
);
}

return (
<ApplicationNavigationPanelShell
application={operationsNavigation}
activeDestinationId="services"
menuOpen={menuOpen}
onMenuOpenChange={setMenuOpen}
onNavigate={openNavigationIntent}
presentation="auto"
>
<ApplicationPage>
<ApplicationPageStack>
<ResourceListPage
definition={serviceResource}
searchable
refreshable
navigation={{ open: openResourceIntent }}
/>
</ApplicationPageStack>
</ApplicationPage>
</ApplicationNavigationPanelShell>
);
}

This example selected navigation depth one: one cohesive operations area with several durable destinations. Use no shell for one destination. Use the rail + panel ApplicationNavigationShell only when multiple independent work areas each contain several destinations; set presentation="auto" and overlayTrigger="floating". Page subdivisions use tabs, never a third sidebar level.

In this composition:

  • the shell and routes do not exist until real application readiness succeeds;
  • navigation definitions describe the hierarchy;
  • the host converts navigation intents to routes;
  • layout primitives own page geometry;
  • the resource definition and adapter normalize domain data;
  • ResourceListPage owns the collection interaction lifecycle; and
  • the host converts a resource-open intent to a route.

No SDK object needs access to a global router, credential store, or product registry.

Choose the right extension seam​

Before adding a wrapper or forking a component, identify what varies:

What variesExtension seam
Backend envelope or endpointAdapter and normalizer
Column value or rich cellResourceColumnDefinition
Route opened by an interactionNavigation adapter or callback
Allowed navigation entriesFilter definitions before rendering
Product actionPublic action definition or contribution prop
Detail contentControlled tab body or child content
Visual tokensPublished theme variables and helpers
Reusable SDK behavior missing everywhereSDK source change and public contract review

Avoid broad render overrides when a narrow definition, adapter, callback, or child slot represents the actual variation. Broad overrides make lifecycle and accessibility behavior impossible for the SDK to guarantee.

Framework and runtime boundaries​

The /resource and /contracts JavaScript entrypoints are framework-neutral. React components and hooks live in /views, /navigation, /layout, /feedback, /controls, /resource/react, and /embed/react. Browser-only work should remain behind browser entrypoints or be executed only after a DOM exists.

The package root currently re-exports the framework-neutral resource surface for compatibility. Prefer the explicit subpath for new code so dependencies and intent stay clear:

import { defineResourceApplication } from "@dev-mainsequence/command-center-sdk/resource";

CSS is opt-in and must be imported once by the browser application. JavaScript imports do not silently install global styles.

AI capabilities are a separate package​

A chat with a Main Sequence Agent, agent sessions, and model provider settings are not an SDK layer. They live in @dev-mainsequence/command-center-ai, which takes this SDK as a peer and draws with its controls and theme; the SDK does not depend on it. An application that needs them installs that package next to the SDK, as Getting started describes.