Skip to main content

SDK ADR 004: Public Application Feedback System

  • Status: Accepted
  • Date: 2026-08-27
  • Owners: Command Center SDK maintainers

Context​

Complete applications frequently wait for prerequisites before their routes are usable: a backend runtime may be starting, schemas may be attaching, a delegated transport may be reconnecting, or an application-level cache may be preparing. A production analytics consumer demonstrated a useful pattern: block incomplete application content, show the producer's real ordered stages, surface elapsed time and active details, explain retries, and end in an actionable failure state.

The SDK already owns several narrower async presentations. ResourceListPage owns collection loading and errors, ResourceDetailShell uses ResourceTransitionShell for resource handoffs, and pickers and action dialogs own their local pending states. ResourceTransitionShell, however, contains only an indeterminate spinner, title, and description. It cannot represent application startup stages, a retry notice, elapsed stage metadata, or a terminal action.

Leaving this presentation entirely application-owned causes otherwise compatible applications to invent different full-screen spacing, status colors, icons, responsive behavior, motion policy, and accessibility semantics. The problem is reusable presentation, not shared backend authority. Readiness endpoints, response shapes, retry rules, and what “ready” means vary by application and must not move into the public package.

Decision​

Publish a new React entrypoint:

@dev-mainsequence/command-center-sdk/feedback

The entrypoint contains three controlled primitives:

PrimitiveSDK-owned behaviorConsumer-owned input
ActivityIndicatorTheme-aware indeterminate icon, standard sizes, reduced motion, optional standalone accessible labelWhether activity is occurring and its accessible label
ProgressStageListOrdered stage layout, state icons and labels, elapsed formatting, active/error detail disclosure, responsive collapseStable stage IDs, true producer state, labels, descriptions, elapsed seconds, and details
ApplicationStatusScreenViewport/contained surface, loading/retrying/error tone, one atomic announcement, progress composition, primary-action placementCurrent state, all copy, stages, retry callback, and whether the screen replaces application content

These components are presentation-only. ApplicationStatusScreen does not accept a promise and does not start, poll, retry, time out, cache, or cancel work. The consumer conditionally renders it from application-owned state:

return ready ? <Application /> : <ApplicationStatusScreen {...feedback} />;

The package therefore standardizes observable feedback without turning a consumer backend or runtime lifecycle into SDK policy.

Public state vocabulary​

ApplicationStatusScreen accepts:

type ApplicationStatusScreenState = "loading" | "retrying" | "error";

ProgressStageList accepts:

type ProgressStageStatus = "pending" | "active" | "complete" | "error";

Consumers explicitly map domain status values into this vocabulary. The SDK does not infer that starting, warming, attaching, running, or an HTTP status means active; such mappings are part of the application's backend adapter. Consumers may override visible status labels without changing semantic status.

The progress list has no percentage API in this decision. A discrete stage producer does not provide enough information for a truthful determinate percentage. Applications must not derive a percentage from stage count when stage duration or work is nonuniform.

Details and elapsed time​

Each stage may include stable detail items and nonnegative elapsed seconds. By default, details render only for active and error stages. This keeps completed work compact while preserving the information useful during a long wait or failure. Consumers can select always or never when their product requires another disclosure policy.

The SDK formats elapsed seconds for display. The producer remains authoritative for the numeric measurement and may omit it. Invalid or negative elapsed values are not displayed.

Details are React presentation input, not a serialized SDK payload. A consumer can map schema names, deployment steps, files, or another bounded list without the SDK knowing those domains.

Accessibility​

The status screen exposes one visually hidden, atomic live region. Loading and retrying use a polite status; error uses an assertive alert. The visible progress tree is not itself a live region, avoiding repeated announcement of every stage and detail whenever one value changes.

The screen identifies its visible heading through aria-labelledby and reports aria-busy while loading or retrying. Icons are decorative inside an already labelled status. A standalone ActivityIndicator becomes a labelled status only when its label prop is supplied.

The consumer owns focus policy because only it knows whether the screen was present at initial navigation, replaced already interactive content, or appeared inside a dialog. A retry action is an ordinary accessible button delivered through a controlled callback.

Reduced-motion media preferences disable indicator rotation. Status remains distinguishable by text and icon shape rather than animation or color alone.

Responsive and theme behavior​

The viewport variant fills the dynamic viewport with a 100vh fallback and responsive SDK page gutters. The contained variant provides a bounded minimum block size for a status surface inside an application-owned region. Both variants use the active SDK theme's semantic colors, typography, radii, shadows, and spacing metrics.

On narrow viewports, stage state metadata moves below stage copy instead of shrinking labels or causing horizontal overflow. Detail chips wrap and allow long identifiers to break safely.

Ownership boundary​

The SDK owns:

  • controlled status and progress React components;
  • public TypeScript types and package exports;
  • DOM hooks and browser-ready component styles;
  • semantic icons, default labels, responsive composition, and reduced motion;
  • accessible announcements and action placement;
  • unit, packed-consumer, theme-audit, and real-browser coverage; and
  • human and agent guidance for selecting and composing the primitives.

The consumer owns:

  • readiness endpoints and authentication;
  • backend response parsing and domain-to-public-state mapping;
  • polling, long-polling, streams, retry, backoff, timeout, and stopping conditions;
  • cancellation and shared-request lifetimes;
  • reconnection and transport-interruption events;
  • product copy and detail vocabulary;
  • whether incomplete or disconnected state blocks the whole application; and
  • whether mounted application content is discarded, hidden, or preserved.

The backend owns its runtime state and the truth of every reported stage. It does not need to implement an SDK feedback schema.

Relationship to existing views​

ResourceTransitionShell remains public from /views for a resource activation that temporarily replaces one resource surface with another. It reuses ActivityIndicator internally without changing its existing props or purpose.

ResourceListPage, ResourceDetailShell, ResourcePicker, and action dialogs retain their current local loading contracts. Consumers should not replace those owned states with an application status screen.

Use application feedback when a prerequisite blocks a complete application or another large consumer-owned region and meaningful progress or recovery information exists.

Serialized contracts and backend impact​

This decision adds no serialized, persisted, iframe, or backend protocol. Public props contain React nodes and callbacks and cannot be treated as wire data. Existing contract IDs, JSON Schemas, fixtures, manifest entries, application state, and iframe versions remain unchanged.

If multiple independent backends later need to exchange one canonical progress payload, that is a separate versioned-contract decision requiring TypeScript/runtime normalization, JSON Schema, fixtures, manifest indexing, rollout, and backend conformance. This UI decision must not be used as an undocumented wire format.

Verification​

The public surface is verified through:

  • server-rendered component tests for state, labels, details, elapsed time, and roles;
  • client tests for controlled recovery actions;
  • TypeScript compilation from the public subpath;
  • the closed SDK theme audit;
  • packed-consumer import and tarball assertions;
  • real Chromium checks at 375×812, 768×900, and 1280×800 in dark and light themes;
  • contained and viewport variants, loading/retrying/error states, long copy and detail values; and
  • computed reduced-motion behavior.

Compatibility and rollout​

The change is additive. Existing applications and existing /views imports continue to work. Applications migrate by adapting their current runtime state into ProgressStageDefinition[] and replacing only application-owned loading markup and CSS. Their polling, cancellation, retry, and backend code remain unchanged.

The release must include the /feedback export, declarations, component CSS, nearest README, human guide, copyable packed-consumer example, browser tests, changelog entry, and the build-application-loading-flow packaged skill. The general application-building skill routes application startup and reconnection feedback to that focused workflow.

Rejected alternatives​

Extend ResourceTransitionShell with every application state​

Rejected because a resource handoff and an application prerequisite have different ownership and composition. Expanding the resource view would make /views the accidental home for non-resource startup behavior and complicate a deliberately small component.

Publish a runtime polling hook​

Rejected because endpoint shapes, retryability, timeouts, authentication, shared work, and reconnection policy are product-specific. One hook would either encode one backend or expose so many callbacks that it no longer owns meaningful behavior.

Publish the analytics runtime response as a contract​

Rejected because that response describes one backend. The reusable boundary is controlled feedback presentation, not those bytes.

Continue with application-owned CSS​

Rejected because responsive layout, semantic status styling, motion policy, and accessibility are repeated presentation concerns already within the SDK's public UI responsibility.