Skip to content

Your spaces

By the end of this page you can make named spaces, such as Book club and Climbing, and switch between them.

A space is a group of identities that replicate the same data. ECHO keeps a database in each one; the contact book uses spaces only for who is in them. In Keyhive terms a space is a group, and the identity that creates it is its first admin. Admins invite people, change what members may do and remove them.

Space.create makes a space, and Space.spaces lists every space your identity belongs to. Your identity also has a personal space, which only your own devices join; Space.spaces leaves it out.

  1. Create a space. Space.create returns the new space’s id, name and state at once. The space opens a moment later, so wait for Space.waitReady before you use it, then select it. New space calls this.

    src/app/Spaces.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    React
    , {
    function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    } from 'react';
    import {
    import Space
    Space
    } from '@dxos/halo';
    18 collapsed lines
    import {
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    import { type
    type SpaceId = string & {
    __SpaceId: true;
    }
    const SpaceId: Codec<SpaceId, string, never, never> & {
    byteLength: number;
    encode: (value: Uint8Array) => SpaceId;
    decode: (value: SpaceId) => Uint8Array;
    isValid: (value: unknown) => value is SpaceId;
    make: (value: string) => SpaceId;
    random: () => SpaceId;
    }

    A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    } from '@dxos/keys';
    import {
    const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {
    label: string;
    action: string;
    initial?: string;
    multiline?: boolean;
    compact?: boolean;
    autoFocus?: boolean;
    onSubmit: (value: string) => Promise<unknown>;
    }) => React.JSX.Element

    A field and a button, which shows the error if onSubmit fails.

    Prompt
    } from './ui.tsx';
    /** Picks the current space, and makes new ones. */
    export const
    const SpacePicker: ({ spaces, current, onSelect, }: {
    spaces: readonly Space.Info[];
    current?: SpaceId;
    onSelect: (id: SpaceId) => void;
    }) => React.JSX.Element

    Picks the current space, and makes new ones.

    SpacePicker
    = ({
    spaces: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]
    spaces
    ,
    current: SpaceId | undefined
    current
    ,
    onSelect: (id: SpaceId) => void
    onSelect
    ,
    }: {
    spaces: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]
    spaces
    : readonly
    import Space
    Space
    .
    type Info = {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }

    Public view of a space. A plain snapshot — database access stays on the ECHO Database service keyed by

    SpaceId

    , not on this value.

    Info
    [];
    current?: SpaceId | undefined
    current
    ?:
    type SpaceId = string & {
    __SpaceId: true;
    }

    A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    ;
    onSelect: (id: SpaceId) => void
    onSelect
    : (
    id: SpaceId
    id
    :
    type SpaceId = string & {
    __SpaceId: true;
    }

    A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    ) => void;
    }) => {
    const
    const services: Context<HaloServices>
    services
    =
    function useHaloServices(): Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ();
    const [
    const creating: boolean
    creating
    ,
    const setCreating: React.Dispatch<React.SetStateAction<boolean>>
    setCreating
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    (false);
    /** Creates a space called `name`, waits until it opens, and selects it. */
    const
    const handleCreate: (name: string) => Promise<void>

    Creates a space called name, waits until it opens, and selects it.

    handleCreate
    = (
    name: string
    name
    : string) =>
    import Effect
    Effect
    .
    const gen: <Effect.Effect<void, SpaceError, Space.Service>, {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }>(f: () => Generator<Effect.Effect<void, SpaceError, Space.Service>, {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, never>) => Effect.Effect<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, SpaceError, Space.Service> (+1 overload)

    Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

    When to use

    Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

    Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

    Example (Sequencing effects with generators)

    import { Data, Effect } from "effect"
    class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
    const addServiceCharge = (amount: number) => amount + 1
    const applyDiscount = (
    total: number,
    discountRate: number
    ): Effect.Effect<number, DiscountRateError> =>
    discountRate === 0
    ? Effect.fail(new DiscountRateError())
    : Effect.succeed(total - (total * discountRate) / 100)
    const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
    const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
    export const program = Effect.gen(function*() {
    const transactionAmount = yield* fetchTransactionAmount
    const discountRate = yield* fetchDiscountRate
    const discountedAmount = yield* applyDiscount(
    transactionAmount,
    discountRate
    )
    const finalAmount = addServiceCharge(discountedAmount)
    return `Final amount to charge: ${finalAmount}`
    })
    await Effect.runPromise(program) // => "Final amount to charge: 96"

    @stability ― stable

    @category ― constructors

    @since ― 2.0.0

    gen
    (function* () {
    const
    const space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    = yield*
    import Space
    Space
    .
    const create: (options?: Space.CreateOptions) => Effect.Effect<Space.Info, SpaceError, Space.Service>

    Create a new space (requires

    Service

    ).

    create
    ({
    name?: string | undefined
    name
    });
    yield*
    import Space
    Space
    .
    const waitReady: (id: SpaceId) => Effect.Effect<void, SpaceError, Space.Service>

    Resolve once the space is ready (requires

    Service

    ).

    waitReady
    (
    const space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    .
    id: SpaceId
    id
    );
    return
    const space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    ;
    })
    .
    Pipeable.pipe<Effect.Effect<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, SpaceError, Space.Service>, Effect.Effect<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, SpaceError, never>, Promise<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, SpaceError, Space.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+21 overloads)
    pipe
    (
    import Effect
    Effect
    .
    const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)

    Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

    Example (Providing dependencies with a layer)

    import { Context, Effect, Layer } from "effect"
    interface Database {
    readonly query: (sql: string) => Effect.Effect<string>
    }
    const Database = Context.Service<Database>("Database")
    const DatabaseLayer = Layer.succeed(Database)({
    query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
    })
    const program = Effect.gen(function*() {
    const db = yield* Database
    return yield* db.query("SELECT * FROM users")
    })
    const provided = Effect.provide(program, DatabaseLayer)
    await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

    @stability ― stable

    @category ― providing services

    @since ― 2.0.0

    provide
    (
    const services: Context<HaloServices>
    services
    ),
    import Effect
    Effect
    .
    const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>

    Executes an effect and returns the result as a Promise.

    When to use

    Use when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

    If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

    Example (Running a successful effect as a Promise)

    import { Effect } from "effect"
    await Effect.runPromise(Effect.succeed(1)) // => 1

    Example (Running effects as promises)

    //Example: Handling a Failing Effect as a Rejected Promise
    import { Effect } from "effect"
    const output: Array<unknown> = []
    await Effect.runPromise(Effect.fail("my error")).catch(() => {
    void output.push("rejected")
    })
    output // => ["rejected"]

    @see ― runPromiseExit for a version that returns an Exit type instead of rejecting.

    @stability ― stable

    @category ― running

    @since ― 2.0.0

    runPromise
    )
    .
    Promise<{ readonly id: SpaceId; readonly state: "inactive" | "closed" | "ready"; readonly name?: string | undefined; }>.then<void, never>(onfulfilled?: ((value: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

    Attaches callbacks for the resolution and/or rejection of the Promise.

    @param ― onfulfilled The callback to execute when the Promise is resolved.

    @param ― onrejected The callback to execute when the Promise is rejected.

    @returns ― A Promise for the completion of which ever callback is executed.

    then
    ((
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    ) =>
    onSelect: (id: SpaceId) => void
    onSelect
    (
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    .
    id: SpaceId
    id
    ));
  2. List your spaces. useSpaces returns the spaces that are ready, and re-renders when one is created, joined or left. It follows Space.spaces, which also lists spaces that are not open. The dropdown shows the named ones, by name.

    src/app/ContactBook.tsx
    import
    (alias) namespace React
    import React
    React
    , {
    function useMemo<T>(factory: () => T, deps: React.DependencyList): T

    useMemo will only recompute the memoized value when one of the deps has changed.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useMemo

    useMemo
    ,
    function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    } from 'react';
    2 collapsed lines
    import { type
    import Identity
    Identity
    } from '@dxos/halo';
    import {
    const useIdentity: () => Identity.Info | undefined

    Returns the local identity, or undefined if none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces @dxos/react-client's useIdentity.

    useIdentity
    ,
    const useSpaces: ({ all }?: UseSpacesOptions) => readonly Info[]

    Returns the spaces known to the local identity. By default only ready spaces are returned; pass { all: true } for every space. Reactive. Replaces @dxos/react-client's useSpaces.

    useSpaces
    } from '@dxos/halo-react';
    39 collapsed lines
    import { type
    type SpaceId = string & {
    __SpaceId: true;
    }
    const SpaceId: Codec<SpaceId, string, never, never> & {
    byteLength: number;
    encode: (value: Uint8Array) => SpaceId;
    decode: (value: SpaceId) => Uint8Array;
    isValid: (value: unknown) => value is SpaceId;
    make: (value: string) => SpaceId;
    random: () => SpaceId;
    }

    A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    } from '@dxos/keys';
    import { type
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ,
    const reached: (stage: Stage, at: Stage) => boolean

    Whether stage includes what at adds.

    reached
    } from '../stage.ts';
    import {
    const Contacts: () => React.JSX.Element

    Everyone you share a space with, once each, with the spaces you share.

    Contacts
    ,
    const Members: ({ spaceId }: {
    spaceId: SpaceId;
    }) => React.JSX.Element

    The members of one space, before the app lists contacts.

    Members
    } from './Contacts.tsx';
    import {
    const Devices: () => React.JSX.Element

    Your identity's devices, and a way to add another.

    Devices
    } from './Devices.tsx';
    import {
    const InviteBar: ({ spaceId }: {
    spaceId?: SpaceId;
    }) => React.JSX.Element

    Invite someone to the current space, or join a space with a code.

    InviteBar
    } from './Invitations.tsx';
    import {
    const ProfileChip: ({ name }: {
    name?: string;
    }) => React.JSX.Element

    Your name, which you click to change.

    ProfileChip
    } from './Profile.tsx';
    import {
    const Recovery: () => React.JSX.Element

    Your recovery codes: make one, see the ones that work, and revoke one.

    Recovery
    } from './Recovery.tsx';
    import {
    const SpacePicker: ({ spaces, current, onSelect, }: {
    spaces: readonly Info[];
    current?: SpaceId;
    onSelect: (id: SpaceId) => void;
    }) => React.JSX.Element

    Picks the current space, and makes new ones.

    SpacePicker
    } from './Spaces.tsx';
    import {
    class Boundary

    Shows the error in place of children when they fail to render.

    Boundary
    } from './ui.tsx';
    import {
    const Welcome: ({ stage }: {
    stage: Stage;
    }) => React.JSX.Element

    The device before it has an identity: start one, join one from another device, or recover one.

    Welcome
    } from './Welcome.tsx';
    export type
    type ContactBookProps = {
    stage: Stage;
    }
    ContactBookProps
    = {
    stage: Stage
    stage
    :
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ;
    };
    /**
    * The contact book on one device, with what `stage` has built. It runs against
    * the HALO services of the `HaloProvider` above it.
    */
    export const
    const ContactBook: ({ stage }: ContactBookProps) => React.JSX.Element

    The contact book on one device, with what stage has built. It runs against the HALO services of the HaloProvider above it.

    ContactBook
    = ({
    stage: Stage
    stage
    }:
    type ContactBookProps = {
    stage: Stage;
    }
    ContactBookProps
    ) => {
    /** The identity, re-rendering on each change; undefined before it exists. */
    const
    const identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    } | undefined

    The identity, re-rendering on each change; undefined before it exists.

    identity
    =
    function useIdentity(): Identity.Info | undefined

    Returns the local identity, or undefined if none exists. Reactive: re-renders when the identity is created or its profile changes. Replaces @dxos/react-client's useIdentity.

    useIdentity
    ();
    return
    const identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    } | undefined

    The identity, re-rendering on each change; undefined before it exists.

    identity
    ? (
    <
    const Signedin: ({ stage, identity, }: {
    stage: Stage;
    identity: Identity.Info;
    }) => React.JSX.Element
    Signedin
    stage: Stage
    stage
    ={
    stage: Stage
    stage
    }
    identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }
    identity
    ={
    const identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }

    The identity, re-rendering on each change; undefined before it exists.

    identity
    } />
    ) : (
    <
    const Welcome: ({ stage }: {
    stage: Stage;
    }) => React.JSX.Element

    The device before it has an identity: start one, join one from another device, or recover one.

    Welcome
    stage: Stage
    stage
    ={
    stage: Stage
    stage
    } />
    );
    };
    type
    type Tab = "devices" | "recovery" | "people"
    Tab
    = 'people' | 'devices' | 'recovery';
    const
    const Signedin: ({ stage, identity, }: {
    stage: Stage;
    identity: Identity.Info;
    }) => React.JSX.Element
    Signedin
    = ({
    stage: Stage
    stage
    ,
    identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }
    identity
    ,
    }: {
    stage: Stage
    stage
    :
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ;
    identity: {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }
    identity
    :
    import Identity
    Identity
    .
    type Info = {
    readonly did: IdentityDid;
    readonly identityKey?: string | undefined;
    readonly displayName?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }

    Public view of the local identity. Keyed by the DID; identityKey (hex) is retained for consumers that seed deterministic UI (avatar/hue) or address the legacy credential key. data carries arbitrary profile metadata (e.g. avatar emoji, hue).

    Info
    ;
    }) => {
    /** The spaces that are open, re-rendering on each change. */
    const
    const spaces: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    The spaces that are open, re-rendering on each change.

    spaces
    =
    function useSpaces({ all }?: UseSpacesOptions): readonly Info[]

    Returns the spaces known to the local identity. By default only ready spaces are returned; pass { all: true } for every space. Reactive. Replaces @dxos/react-client's useSpaces.

    useSpaces
    ();
    /** The named ones, by name. */
    const
    const named: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    The named ones, by name.

    named
    =
    useMemo<{
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]>(factory: () => {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[], deps: React.DependencyList): {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    useMemo will only recompute the memoized value when one of the deps has changed.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useMemo

    useMemo
    (
    () =>
    const spaces: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    The spaces that are open, re-rendering on each change.

    spaces
    .
    ReadonlyArray<{ readonly id: SpaceId; readonly state: "inactive" | "closed" | "ready"; readonly name?: string | undefined; }>.filter(predicate: (value: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, index: number, array: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]) => unknown, thisArg?: any): {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[] (+1 overload)

    Returns the elements of an array that meet the condition specified in a callback function.

    @param ― predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.

    @param ― thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.

    filter
    ((
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    ) =>
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    .
    name?: string | undefined
    name
    )
    .
    Array<{ readonly id: SpaceId; readonly state: "inactive" | "closed" | "ready"; readonly name?: string | undefined; }>.sort(compareFn?: ((a: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }, b: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }) => number) | undefined): {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    Sorts an array in place. This method mutates the array and returns a reference to the same array.

    @param ―

    compareFn Function used to determine the order of the elements. It is expected to return a negative value if the first argument is less than the second argument, zero if they're equal, and a positive value otherwise. If omitted, the elements are sorted in ascending, UTF-16 code unit order.

    [11,2,22,1].sort((a, b) => a - b)

    sort
    ((
    a: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    a
    ,
    b: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    b
    ) => (
    a: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    a
    .
    name?: string | undefined
    name
    ?? '').
    String.localeCompare(that: string, locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions): number (+2 overloads)

    Determines whether two strings are equivalent in the current or specified locale.

    @param ― that String to compare to target string

    @param ― locales A locale string or array of locale strings that contain one or more language or locale tags. If you include more than one locale string, list them in descending order of priority so that the first entry is the preferred locale. If you omit this parameter, the default locale of the JavaScript runtime is used. This parameter must conform to BCP 47 standards; see the Intl.Collator object for details.

    @param ― options An object that contains one or more properties that specify comparison options. see the Intl.Collator object for details.

    localeCompare
    (
    b: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    b
    .
    name?: string | undefined
    name
    ?? '')),
    [
    const spaces: readonly {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }[]

    The spaces that are open, re-rendering on each change.

    spaces
    ],
    );

Alice has a name and no spaces. Press New space, name it, and make another; the dropdown switches between them.

You can create spaces and list them in a component. Next, Invite people lets someone else in.