contracts

module
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: Apache-2.0

README

Mosaic SDUI

The published Server-Driven-UI contract for Mosaic. It is to the interface what sdk is to content: the single, language-neutral surface a producer (the Platform, or a Module) emits and a client (the Shell, a future Flutter client) renders — so nobody re-writes it per language.

Single source of truth

schema/sdui.schema.json is the source of truth. The language bindings are generated from it — never hand-written:

schema/sdui.schema.json        ← the contract (JSON Schema 2020-12). Edit this.
        │  scripts/generate.sh (quicktype)
        ├──────────────► sdui/contract/contract.gen.go   (Go types)   — generated
        ├──────────────► ts/contract.gen.ts              (TS types)   — generated
        └──────────────► (dart, swift, … when a client needs them)

Two guards keep it honest:

  • Drift guardscripts/check-generated.sh regenerates and fails if the committed bindings are stale (run it in CI). Change the schema → regenerate → commit.
  • Conformance tests (sdui/conformance_test.go) — validate what the hand-written builders produce, and every file in definitions/, against the schema. So even the ergonomic layer cannot drift from the contract.

JSON Schema, not protobuf, for the authoring layer — the node tree is open (props are an untyped bag by design) and the definitions and tokens are JSON data. See ADR 0025. The wire is protobuf end to end: UINode is generated as a message too (ADR 0044), and since ADR 0061 protobuf/Connect is the only client transport — there is no GraphQL surface left to ride.

Layout

schema/         the single source of truth (JSON Schema)
sdui/
  contract/     GENERATED Go types (contract.gen.go) — do not edit
  sdui.go       aliases + constants over the generated types
  actions.go    Action constructors
  components.go standard-component builders (Screen, Section, PosterCard, …)
ts/
  contract.gen.ts  GENERATED TypeScript types — do not edit
definitions/    the standard component library, as data (a client registers these)
tokens/         design tokens (W3C DTCG) — compiled to CSS vars / Flutter theme
scripts/        generate.sh, check-generated.sh

Only the ergonomic builders are hand-written; they sit on top of the generated types and are held to the schema by the conformance tests. Generation is also wired to go generate ./....

Using it — a Go producer

Screens are authored with the declarative ui layer — a widget tree where children, props and slots intermix. Build() compiles to the protobuf UINode the transport carries.

import "github.com/mosaic-media/contracts/ui"

home := ui.Screen(
    ui.Hero("Spirited Away",
        ui.Meta("2001", "Anime Film", "PG"),
        ui.Actions(
            ui.Button("Play", "primary", ui.OnTap(ui.Play("part-1"))),
        ),
    ),
    ui.Section("Continue watching",
        ui.Carousel(
            ui.PosterCard("Cowboy Bebop", "Anime Series",
                ui.Progress(0.6),
                ui.OnTap(ui.Navigate("detail", map[string]any{"title": "Cowboy Bebop"})),
            ),
        ),
    ),
).Build() // → exactly the UINode the Shell renders.

The ui constructors are generated from ui.spec.json by tools/genui, which also emits the TypeScript twin and lints the spec against the standard definitions (see Regenerating). The github.com/mosaic-media/contracts/sdui package keeps the shared types (Node, Action, the action constructors, tone/type constants).

Add it like any Go module:

go get github.com/mosaic-media/contracts/ui@latest

For local work across the sibling repos, use a replace github.com/mosaic-media/contracts => ../contracts in the consumer's go.mod instead.

Using it — a TypeScript client

Published to npm as @mosaic-media/sdui:

npm install @mosaic-media/sdui
import type { UINode, Action, ComponentDefinition } from "@mosaic-media/sdui";
import heroBanner from "@mosaic-media/sdui/definitions/hero-banner.json";
import tokens from "@mosaic-media/sdui/tokens.json";

// The generated protobuf client transport — the service descriptors + message
// schemas a Connect-Web client speaks, and the wire UINode message. Pair with
// @connectrpc/connect + @connectrpc/connect-web.
//
//   /auth    — AuthService: sign in, get a session (ADR 0061).
//   /session — SessionService: the two-lane live session (ADR 0041).
//
// Together they are the whole client surface; there is no second transport.
import { AuthService } from "@mosaic-media/sdui/auth";
import { SessionService, RegionUpdate_Op } from "@mosaic-media/sdui/session";
import { UINodeSchema } from "@mosaic-media/sdui/sdui-pb";

The package is types + JSON data (no runtime code); it's meant for a bundler (the Shell uses Vite).

Widget-style authoring (mocks & fixtures)

The @mosaic-media/sdui/ui subpath is a declarative authoring layer — the TypeScript twin of the Go sdui/ui package — for hand-writing screens (the Shell's mock payloads, storybook stories) as a widget tree instead of raw UINode JSON. build() returns exactly that JSON. The API mirrors the Go one name-for-name, so a screen transliterates between the two:

import {
  Screen, Section, Carousel, Hero, PosterCard, Button,
  Actions, Meta, Progress, OnTap, Navigate, Play,
} from "@mosaic-media/sdui/ui";

const home = Screen(
  Hero("Spirited Away",
    Meta("2001", "Anime Film", "PG"),
    Actions(Button("Play", "primary", OnTap(Play("part-1")))),
  ),
  Section("Continue watching",
    Carousel(
      PosterCard("Cowboy Bebop", "Anime Series",
        Progress(0.6), OnTap(Navigate("detail", { title: "Cowboy Bebop" }))),
    ),
  ),
).build(); // → the same UINode payload the Shell renders

The standard definitions

The reusable components — PosterCard, HeroBanner, Section, Badge, … — live here as ComponentDefinition data, not per-client code. A client registers them; a producer emits { "type": "HeroBanner", … } and it renders identically on every client, with the Module shipping zero UI code. A Module can ship its own definitions the same way. Only the irreducible primitives are per-client native code; definitions compose only those (ADR 0024).

Regenerating

Two generators, both driven by a single source of truth:

  • The contract (schema/sdui.schema.json) → sdui/contract/contract.gen.go, ts/contract.gen.ts via quicktype.
  • The ui authoring layer (ui.spec.json) → ui/components.gen.go, ts/ui.ts via tools/genui. The same tool lints the spec against definitions/*.json: every definition must have a component, and every prop a definition's template binds must be exposed by some helper — so a new component that nothing authors fails the build, and Go/TS can never drift.

Everything runs in a container; nothing is generated or tested on the host. The full gate — version check, drift guard, Go tests, TypeScript typecheck — is one command:

docker compose -f docker-compose.test.yml run --rm test

Regenerating, or running one step, is the same command with the step named:

docker compose -f docker-compose.test.yml run --rm test bash scripts/generate.sh        # regenerate (schema + ui spec), then lint
docker compose -f docker-compose.test.yml run --rm test bash scripts/check-generated.sh  # fail if any generated file is stale or lint fails
docker compose -f docker-compose.test.yml run --rm test go run ./tools/genui -lint       # just lint the ui spec against the definitions
docker compose -f docker-compose.test.yml run --rm test go test ./...                    # unit + schema-conformance tests

Editing the ui layer means editing ui.spec.json (and adding a component there when you add a definitions/*.json), never the generated files. This is the one repository that needs two toolchains at once — the drift guard regenerates Go and TypeScript and needs go, gofmt, node and npx together — which is why it has a Dockerfile.test of its own that pins all of them. A host with three of the four produces a check that passes by not running.

Releasing

One tag publishes every language:

# bump package.json + tag must match
git tag v0.1.0 && git push origin v0.1.0
  • Go needs nothing more — the module proxy serves the tag; consumers go get …@v0.1.0.
  • npm is published by .github/workflows/release.yml on the tag. It requires an NPM_TOKEN repository secret (an npm automation token for the @mosaic-media scope). Without it, publish manually: npm publish --access public.

.github/workflows/verify.yml runs on every push: it fails if the generated bindings are stale, runs the Go + conformance tests, and typechecks the TypeScript.

Next

  • Wire the Shell to import @mosaic-media/sdui, load definitions/*.json, and generate its CSS variables from the tokens — retiring its local copies.
  • Migrate the remaining standard definitions from the Shell into definitions/.
  • A tokens generator (DTCG → CSS + Dart) and the light theme.
  • Add the Dart target to generate.sh when the Flutter client lands.

Licence

Apache-2.0 (see LICENSE and NOTICE). A contract surface must be permissive so a Module may build its UI against it under any licence, as the SDK is (ADR 0022, ADR 0025).

Directories

Path Synopsis
Package conformance publishes the vocabulary a conforming client must implement, as data.
Package conformance publishes the vocabulary a conforming client must implement, as data.
Package definitions is the standard SDUI component library as data — one JSON file per component, each a name, its default params and a template of primitives (ADR 0024).
Package definitions is the standard SDUI component library as data — one JSON file per component, each a name, its default params and a template of primitives (ADR 0024).
gen
Package sdui is the Go binding of the Mosaic Server-Driven-UI contract — the producer side.
Package sdui is the Go binding of the Mosaic Server-Driven-UI contract — the producer side.
contract
Code generated from schema/sdui.schema.json by quicktype.
Code generated from schema/sdui.schema.json by quicktype.
Package tokens is the design token set as data — every literal value the interface is built from, in one place, for every client.
Package tokens is the design token set as data — every literal value the interface is built from, in one place, for every client.
tools
genui command
Command genui generates the SDUI vocabulary from the single spec ui.spec.json: the Go (ui/components.gen.go) and TypeScript (ts/ui.ts) ergonomic constructors, the machine-readable vocabulary registry (sdui/vocabulary.gen.go, ts/vocabulary.gen.ts) and the client conformance fixture (conformance/vocabulary.json).
Command genui generates the SDUI vocabulary from the single spec ui.spec.json: the Go (ui/components.gen.go) and TypeScript (ts/ui.ts) ergonomic constructors, the machine-readable vocabulary registry (sdui/vocabulary.gen.go, ts/vocabulary.gen.ts) and the client conformance fixture (conformance/vocabulary.json).
Package ui is a declarative authoring layer for the Mosaic SDUI — a "reads like a widget tree" ergonomics (Flutter/Compose/Vaadin, the gomponents pattern in Go).
Package ui is a declarative authoring layer for the Mosaic SDUI — a "reads like a widget tree" ergonomics (Flutter/Compose/Vaadin, the gomponents pattern in Go).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL