Skip to content

Surface

import * as Surface from '@dxos/app-framework/Surface';
import * as AppSurface from '@dxos/app-toolkit/AppSurface';

Surface creates surface contributions and renders surfaces. AppSurface holds the App Toolkit’s roles and the filters that match them. Surfaces explains how matching works.

Surface.create takes an id, a filter, a component and a props mapper. AppSurface.object(role, Type) matches a role when the data’s subject is an object of the type.

import * as
import Surface
Surface
from '@dxos/app-framework/Surface';
import * as
import AppSurface
AppSurface
from '@dxos/app-toolkit/AppSurface';
export const
const NoteArticleSurface: ReactDefinition<T>
NoteArticleSurface
=
import Surface
Surface
.
const create: <Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}, {
role: string;
subject: Note & OfKind<EntityKind.Object>;
attendableId: string;
}, "article">(definition: Readonly<{
id: "article";
filter: Filter<Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>;
position?: Position;
component: React.ComponentType<...>;
props?: ((props: ComponentProps<...>) => {
...;
}) | undefined;
}>) => ReactDefinition<Omit<...> & {
subject: Note & OfKind<EntityKind.Object>;
}>

Creates a React surface definition from a typed filter.

create
({
id: "article"
id
: 'article',
// Matches the article role when the data's subject is a Note.
filter: Filter<Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>
filter
:
import AppSurface
AppSurface
.
const object: <Role<AppSurface.ArticleData<any, {}, unknown>>, typeof Note>(token: Role<AppSurface.ArticleData<any, {}, unknown>>, schema: typeof Note, predicate?: ((data: NonNullable<AppSurface.ArticleData<any, {}, unknown>>) => boolean) | undefined) => Surface.Filter<Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}> (+1 overload)
object
(
import AppSurface
AppSurface
.
const Article: Role<AppSurface.ArticleData<any, {}, unknown>>

Role token for the article role. Subject defaults to any until narrowed via

object

,

literal

, or

subject

. Extra passthrough props are permitted.

Article
,
class Note
Note
),
component: React.ComponentType<{
role: string;
subject: Note & OfKind<EntityKind.Object>;
attendableId: string;
}>

Accepts any component type (not just a function) so a container re-exported through a lazy() barrel as ComponentType<any> can be registered without a cast.

component
:
const NoteArticle: ({ role, subject, }: AppSurface.ObjectArticleProps<Note>) => React.JSX.Element
NoteArticle
,
// Maps the surface's data onto the component's props; checked against the
// filter's data type.
props?: ((props: ComponentProps<Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>) => {
role: string;
subject: Note & OfKind<EntityKind.Object>;
attendableId: string;
}) | undefined

Maps the surface props onto the component's own props, so a plain container can be registered directly instead of being wrapped in an adapter that unpacks data.

Prefer this over an inline component: ({ data }) => <Container … />: the mapper's input type derives from the same filter that defines the surface's data shape, so the unpacking is checked against the filter rather than restated by hand.

@example Surface.create({ id: 'defaultPluginSettings', filter: AppSurface.settings(AppSurface.Article), component: DefaultSettings, props: ({ data: { subject } }) => ({ subject }), });

props
: ({
role: string

The resolved role NSID string (e.g. org.dxos.role.article). Present on matched components only — multi-role containers can branch on this value by comparing against a token's .role (e.g. role === AppSurface.Section.role).

role
,
data: Omit<NonNullable<AppSurface.ArticleData<any, {}, unknown>>, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}
data
: {
subject: Note & OfKind<EntityKind.Object>
subject
,
attendableId: string
attendableId
} }) => ({
role: string
role
,
subject: Note & OfKind<EntityKind.Object>
subject
,
attendableId: string
attendableId
,
}),
});

Contribute surfaces from a module through Capabilities.ReactSurface. In a manifest, AppCapability.surface makes that module and loads it when one of its roles is first requested.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capabilities
Capabilities
from '@dxos/app-framework/Capabilities';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
export default
import Capability
Capability
.
const makeModule: <void, Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>, never, never>(fn: (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>, never, Scope>) => (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>, never, Scope>

Helper to define a capability module with explicit typing. Wraps the default export function to provide better type inference and make the pattern explicit.

This helper provides explicit typing for the module activation function, making it clear that the function should:

  • Access declared requires via yield* (or the ambient Capability.Service/Plugin.Service)
  • Return an array of typed

Contribution

s (see

contribute

/

contributeAll

)

@example

// Module without options - single capability
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const client = yield* ClientCapabilities.Client;
return Capability.contribute(Capabilities.SettingsStore, store);
})
);
// Module with multiple capabilities
export default Capability.makeModule(
Effect.fnUntraced(function* () {
return [
Capability.contribute(Capabilities.SettingsStore, store),
Capability.contribute(Capabilities.Translations, translations),
];
})
);
// Module with required options (context accessed via layer)
export default Capability.makeModule(
Effect.fnUntraced(function* (props: { observability: boolean }) {
const invoker = yield* Capabilities.OperationInvoker;
return Capability.contribute(Capabilities.OperationHandler, ...);
})
);
// Module with scoped resources (closed automatically on deactivation)
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const scope = yield* Scope.Scope;
yield* Scope.addFinalizer(scope, Effect.sync(() => cleanup()));
return Capability.contribute(Capabilities.MyCapability, implementation);
})
);

makeModule
(() =>
import Effect
Effect
.
const succeed: <Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>>(value: Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">>, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
(
import Capability
Capability
.
const contribute: <Capability.MultiTag<any, "org.dxos.app-framework.capability.reactSurface">>(capability: Capability.MultiTag<any, "org.dxos.app-framework.capability.reactSurface">, implementation: any) => Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.app-framework.capability.reactSurface", "multi">> (+1 overload)
contribute
(
import Capabilities
Capabilities
.
const ReactSurface: Capability.MultiTag<any, "org.dxos.app-framework.capability.reactSurface">

Surface definitions that can be either React components or Web Components.

@category ― Capability

ReactSurface
, [
const NoteArticleSurface: ReactDefinition<T>
NoteArticleSurface
,
const NoteCardSurface: ReactDefinition<T>
NoteCardSurface
,
]),
),
);

AppSurface.oneOf matches any of several filters, and AppSurface.allOf requires all of them. Other filters match a literal value (AppSurface.literal), the object a companion panel is attached to (AppSurface.companion), or any subject (AppSurface.subject).

import * as
import Surface
Surface
from '@dxos/app-framework/Surface';
import * as
import AppSurface
AppSurface
from '@dxos/app-toolkit/AppSurface';
export const
const NoteCardSurface: ReactDefinition<T>
NoteCardSurface
=
import Surface
Surface
.
const create: <(Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}) | (Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}), {
subject: Note & OfKind<EntityKind.Object>;
}, "card">(definition: Readonly<{
id: "card";
filter: Filter<(Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
...;
}) | (Omit<...> & {
...;
})>;
position?: Position;
component: React.ComponentType<...>;
props?: ((props: ComponentProps<...>) => {
...;
}) | undefined;
}>) => ReactDefinition<(Omit<...> & {
subject: Note & OfKind<EntityKind.Object>;
}) | (Omit<...> & {
subject: Note & OfKind<EntityKind.Object>;
})>

Creates a React surface definition from a typed filter.

create
({
id: "card"
id
: 'card',
filter: Filter<(Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}) | (Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
})>
filter
:
import AppSurface
AppSurface
.
const oneOf: <[Surface.Filter<Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>, Surface.Filter<Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>]>(filters_0: Surface.Filter<Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}>, filters_1: Surface.Filter<...>) => Surface.Filter<...>

Filter disjunction across roles. Bindings from each filter are concatenated; the resulting data type is the union.

Use this when a single component should render for multiple roles — replaces the legacy role: ['article', 'section'] array.

oneOf
(
import AppSurface
AppSurface
.
const object: <Role<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}>, typeof Note>(token: Role<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}>, schema: typeof Note, predicate?: ((data: {
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}) => boolean) | undefined) => Surface.Filter<...> (+1 overload)
object
(
import AppSurface
AppSurface
.
const CardContent: Role<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}>

Role token for the card slot.

CardContent
,
class Note
Note
),
import AppSurface
AppSurface
.
const object: <Role<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}>, typeof Note>(token: Role<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}>, schema: typeof Note, predicate?: ((data: {
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}) => boolean) | undefined) => Surface.Filter<Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}> (+1 overload)
object
(
import AppSurface
AppSurface
.
const Section: Role<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}>

Role token for the section role.

Section
,
class Note
Note
),
),
component: React.ComponentType<{
subject: Note & OfKind<EntityKind.Object>;
}>

Accepts any component type (not just a function) so a container re-exported through a lazy() barrel as ComponentType<any> can be registered without a cast.

component
:
const NoteCard: ({ subject }: {
subject: Note;
}) => React.JSX.Element
NoteCard
,
props?: ((props: ComponentProps<(Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}) | (Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
})>) => {
subject: Note & OfKind<EntityKind.Object>;
}) | undefined

Maps the surface props onto the component's own props, so a plain container can be registered directly instead of being wrapped in an adapter that unpacks data.

Prefer this over an inline component: ({ data }) => <Container … />: the mapper's input type derives from the same filter that defines the surface's data shape, so the unpacking is checked against the filter rather than restated by hand.

@example Surface.create({ id: 'defaultPluginSettings', filter: AppSurface.settings(AppSurface.Article), component: DefaultSettings, props: ({ data: { subject } }) => ({ subject }), });

props
: ({
data: (Omit<{
subject: any;
projection?: ProjectionModel;
ignorePaths?: ReadonlyArray<string>;
editable?: boolean;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
}) | (Omit<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}, "subject"> & {
subject: Note & OfKind<EntityKind.Object>;
})
data
: {
subject: Note & OfKind<EntityKind.Object>
subject
} }) => ({
subject: Note & OfKind<EntityKind.Object>
subject
}),
});

When several surfaces match, position on the contribution orders them. Pass Position.first or Position.last from @dxos/util to come before or after the rest.

Surface.Surface renders whatever surface matches a role and data. Pass limit to cap how many render.

import * as
import Surface
Surface
from '@dxos/app-framework/Surface';
import * as
import AppSurface
AppSurface
from '@dxos/app-toolkit/AppSurface';
// Renders whatever surface another plugin contributed for this object in the
// section role.
export const
const Embedded: ({ subject }: {
subject: Note;
}) => React.JSX.Element
Embedded
= ({
subject: Note
subject
}: {
subject: Note
subject
:
class Note
Note
}) => (
<
import Surface
Surface
.
const Surface: (<TToken extends Role<any>>(props: TypedProps<TToken>) => React.ReactNode) & React.NamedExoticComponent<TypedProps<Role<any>>>
Surface
type: Role<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}>
type
={
import AppSurface
AppSurface
.
const Section: Role<{
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
}>

Role token for the section role.

Section
}
data?: {
attendableId: string;
nodeId?: string;
subject: any;
extrinsic?: boolean;
} | undefined
data
={{
subject: any
subject
,
attendableId: string
attendableId
:
subject: Note
subject
.
OfKind<EntityKind.Object>.id: string
id
}}
limit?: number | undefined
limit
={1}
/>
);
Role Where it renders Data
Article The main content area, one object per plank. { subject, attendableId }
Section An object inside a stack or another document. { subject, attendableId }
CardContent, CardIcon, CardMenu Parts of an object’s card. { subject }
ObjectProperties The properties panel of an object. { subject }
Dialog, Popover A dialog or popover opened by an operation. Set by the caller.
StatusIndicator The status bar. None.
deckCompanion(variant) A workspace-wide panel beside the deck. { id?, subject? }
Page What it covers
Add a surface A surface end to end.
React Hooks a surface component uses.