Repository: dxos/dxos
Pull request #8831: chore(schema): Move schema-tool to space-plugin
Author: login: zaymonoid
Created: 2025-03-19T20:12:06Z | Merged: 2025-03-21T19:56:33Z

Acting on @wittjosiah's suggestion this PR moves the schema-tool to the `space-plugin`.


Diff:
diff --git a/packages/plugins/plugin-space/package.json b/packages/plugins/plugin-space/package.json
index ea77034cab6..7a6ab1f4618 100644
--- a/packages/plugins/plugin-space/package.json
+++ b/packages/plugins/plugin-space/package.json
@@ -34,6 +34,7 @@
   ],
   "dependencies": {
     "@dxos/app-framework": "workspace:*",
+    "@dxos/artifact": "workspace:*",
     "@dxos/async": "workspace:*",
     "@dxos/automerge": "workspace:*",
     "@dxos/client": "workspace:*",
@@ -63,6 +64,8 @@
     "@dxos/react-ui-stack": "workspace:*",
     "@dxos/react-ui-syntax-highlighter": "workspace:*",
     "@dxos/react-ui-tabs": "workspace:*",
+    "@dxos/react-ui-theme": "workspace:*",
+    "@dxos/schema": "workspace:*",
     "@dxos/shell": "workspace:*",
     "@dxos/util": "workspace:*",
     "@preact/signals-core": "^1.6.0",
diff --git a/packages/plugins/plugin-space/src/SpacePlugin.tsx b/packages/plugins/plugin-space/src/SpacePlugin.tsx
index 840ded03f40..52951041ecf 100644
--- a/packages/plugins/plugin-space/src/SpacePlugin.tsx
+++ b/packages/plugins/plugin-space/src/SpacePlugin.tsx
@@ -28,6 +28,7 @@ import {
   ReactRoot,
   ReactSurface,
   Schema,
+  Tools,
   SpaceCapabilities,
   SpaceSettings,
   SpacesReady,
@@ -178,5 +179,10 @@ export const SpacePlugin = ({
       ),
       activate: SpacesReady,
     }),
+    defineModule({
+      id: `${meta.id}/module/tools`,
+      activatesOn: Events.SetupArtifactDefinition,
+      activate: Tools,
+    }),
   ]);
 };
diff --git a/packages/plugins/plugin-space/src/capabilities/index.ts b/packages/plugins/plugin-space/src/capabilities/index.ts
index 9fbb1e292d3..0d8e6d16e80 100644
--- a/packages/plugins/plugin-space/src/capabilities/index.ts
+++ b/packages/plugins/plugin-space/src/capabilities/index.ts
@@ -11,6 +11,7 @@ export const IntentResolver = lazy(() => import('./intent-resolver'));
 export const ReactRoot = lazy(() => import('./react-root'));
 export const ReactSurface = lazy(() => import('./react-surface'));
 export const Schema = lazy(() => import('./schema'));
+export const Tools = lazy(() => import('./schema-tools'));
 export const SpaceSettings = lazy(() => import('./settings'));
 export const SpaceState = lazy(() => import('./state'));
 export const SpacesReady = lazy(() => import('./spaces-ready'));
diff --git a/packages/plugins/plugin-table/src/capabilities/schema-tool.test.ts b/packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts
similarity index 95%
rename from packages/plugins/plugin-table/src/capabilities/schema-tool.test.ts
rename to packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts
index db97f1a0149..7e23b915b4a 100644
--- a/packages/plugins/plugin-table/src/capabilities/schema-tool.test.ts
+++ b/packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts
@@ -6,7 +6,7 @@ import { describe, it, expect } from 'vitest';
 
 import { S } from '@dxos/echo-schema';
 
-import { TypeNameSchema } from './schema-tool';
+import { TypeNameSchema } from './schema-tools';
 
 describe('TypeNameSchema format', () => {
   const valid = [
diff --git a/packages/plugins/plugin-space/src/capabilities/schema-tools.ts b/packages/plugins/plugin-space/src/capabilities/schema-tools.ts
new file mode 100644
index 00000000000..cc0946ee7c6
--- /dev/null
+++ b/packages/plugins/plugin-space/src/capabilities/schema-tools.ts
@@ -0,0 +1,124 @@
+//
+// Copyright 2025 DXOS.org
+//
+
+import { DescriptionAnnotationId, ExamplesAnnotationId, TitleAnnotationId } from 'effect/SchemaAST';
+
+import { Capabilities, contributes, type PromiseIntentDispatcher } from '@dxos/app-framework';
+import { defineTool, ToolResult } from '@dxos/artifact';
+import { type Space } from '@dxos/client/echo';
+import { FormatEnum, FormatEnums, S, SelectOptionSchema, GeoPoint, toJsonSchema } from '@dxos/echo-schema';
+import { invariant } from '@dxos/invariant';
+import { hues } from '@dxos/react-ui-theme';
+import { echoSchemaFromPropertyDefinitions } from '@dxos/schema';
+
+// TODO(burdon): Factor out.
+declare global {
+  interface ToolContextExtensions {
+    space?: Space;
+    dispatch?: PromiseIntentDispatcher;
+  }
+}
+
+// TODO(ZaymonFC): Move this somewhere common.
+export const TypeNameSchema = S.String.pipe(
+  S.pattern(/^\w+\.\w{2,}\/[\w/]+$/i),
+  S.annotations({
+    [TitleAnnotationId]: 'TypeName',
+    [DescriptionAnnotationId]:
+      'Domain-style type name path. Dashes are not allowed. Use camel case for the final component of the type name.',
+    [ExamplesAnnotationId]: ['example.com/type/Document', 'example.com/type/FlightList'],
+  }),
+);
+
+const formatDescription = `The format of the property. Additional information:
+  ${FormatEnum.GeoPoint}: ${JSON.stringify(toJsonSchema(GeoPoint))}
+  This tuple is GeoJSON. You must specify \`${FormatEnum.GeoPoint}\` as [Longitude, Latitude]`;
+
+// TODO(ZaymonFC): All properties are default optional, but maybe we should allow for required properties.
+const PropertyDefinitionSchema = S.Struct({
+  name: S.String.annotations({ [DescriptionAnnotationId]: 'The name of the property.' }),
+  format: S.Union(...FormatEnums.map((format) => S.Literal(format))).annotations({
+    [DescriptionAnnotationId]: formatDescription,
+  }),
+  config: S.optional(
+    S.Struct({
+      options: S.optional(
+        S.Array(SelectOptionSchema)
+          .annotations({
+            description: `Options for SingleSelect/MultiSelect formats. Available colors: ${hues.join(', ')}`,
+          })
+          .pipe(S.mutable),
+      ),
+    }),
+  ),
+}).pipe(S.mutable);
+
+const SYSTEM_NAMESPACE = 'dxos.org/echo/schema';
+
+export default () =>
+  contributes(Capabilities.Tools, [
+    defineTool(SYSTEM_NAMESPACE, {
+      name: 'list',
+      description: 'List registered schemas in the space.',
+      caption: 'Listing registered schemas...',
+      schema: S.Struct({}),
+      execute: async (_input, { extensions }) => {
+        invariant(extensions?.space, 'No space.');
+        const space = extensions.space;
+
+        const schemas = await space.db.schemaRegistry.query({}).run();
+        return ToolResult.Success(
+          schemas.map((schema) => ({
+            typename: schema.typename,
+            version: schema.version,
+          })),
+        );
+      },
+    }),
+    defineTool(SYSTEM_NAMESPACE, {
+      name: 'get',
+      description: 'Get a specific schema by its typename.',
+      caption: 'Getting schema...',
+      schema: S.Struct({
+        typename: S.String.annotations({
+          description: 'The fully qualified typename of the schema.',
+        }),
+      }),
+      execute: async ({ typename }, { extensions }) => {
+        invariant(extensions?.space, 'No space.');
+        const space = extensions.space;
+
+        const schema = await space.db.schemaRegistry.query({ typename }).firstOrUndefined();
+        if (!schema) {
+          return ToolResult.Error(`Schema not found: ${typename}`);
+        }
+
+        return ToolResult.Success(schema);
+      },
+    }),
+    defineTool(SYSTEM_NAMESPACE, {
+      name: 'create',
+      description: 'Create a new schema with the provided definition.',
+      caption: 'Creating schema...',
+      schema: S.Struct({
+        typename: TypeNameSchema.annotations({
+          description:
+            'The fully qualified schema typename. Must start with a domain, and then one or more path components (e.g., "example.com/type/TypeName").',
+        }),
+        properties: S.Array(PropertyDefinitionSchema).pipe(
+          S.annotations({ description: 'Array of property definitions for the schema.' }),
+          S.mutable,
+        ),
+      }),
+      execute: async ({ typename, properties }, { extensions }) => {
+        invariant(extensions?.space, 'No space.');
+        const space = extensions.space;
+
+        const schema = echoSchemaFromPropertyDefinitions(typename, properties);
+        const [registeredSchema] = await space.db.schemaRegistry.register([schema]);
+
+        return ToolResult.Success(registeredSchema);
+      },
+    }),
+  ]);
diff --git a/packages/plugins/plugin-space/tsconfig.json b/packages/plugins/plugin-space/tsconfig.json
index 0cd9fb599f5..467605fc527 100644
--- a/packages/plugins/plugin-space/tsconfig.json
+++ b/packages/plugins/plugin-space/tsconfig.json
@@ -62,6 +62,9 @@
     {
       "path": "../../core/protocols"
     },
+    {
+      "path": "../../experimental/artifact"
+    },
     {
       "path": "../../sdk/app-framework"
     },
@@ -74,6 +77,9 @@
     {
       "path": "../../sdk/react-client"
     },
+    {
+      "path": "../../sdk/schema"
+    },
     {
       "path": "../../sdk/shell"
     },
diff --git a/packages/plugins/plugin-table/src/capabilities/artifact-definition.ts b/packages/plugins/plugin-table/src/capabilities/artifact-definition.ts
index 0de05562c2c..12020a0649c 100644
--- a/packages/plugins/plugin-table/src/capabilities/artifact-definition.ts
+++ b/packages/plugins/plugin-table/src/capabilities/artifact-definition.ts
@@ -13,7 +13,6 @@ import { SpaceAction } from '@dxos/plugin-space/types';
 import { create, fullyQualifiedId, Filter, type Space } from '@dxos/react-client/echo';
 import { TableType } from '@dxos/react-ui-table';
 
-import { schemaTools } from './schema-tool';
 import { meta } from '../meta';
 import { TableAction } from '../types';
 
@@ -44,7 +43,6 @@ export default () => {
     `,
     schema: TableType,
     tools: [
-      ...schemaTools,
       defineTool(meta.id, {
         name: 'create',
         description: `
diff --git a/packages/plugins/plugin-table/src/capabilities/schema-tool.ts b/packages/plugins/plugin-table/src/capabilities/schema-tool.ts
deleted file mode 100644
index 248e59c5489..00000000000
--- a/packages/plugins/plugin-table/src/capabilities/schema-tool.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-//
-// Copyright 2025 DXOS.org
-//
-
-import { DescriptionAnnotationId, ExamplesAnnotationId, TitleAnnotationId } from '@effect/schema/AST';
-
-import { defineTool, ToolResult } from '@dxos/artifact';
-import {
-  FormatEnum,
-  FormatEnums,
-  formatToType,
-  S,
-  TypedObject,
-  TypeEnum,
-  SelectOptionSchema,
-  GeoPoint,
-  toJsonSchema,
-} from '@dxos/echo-schema';
-import { invariant } from '@dxos/invariant';
-import { hues } from '@dxos/react-ui-theme';
-import { makeSingleSelectAnnotations } from '@dxos/schema';
-
-// TODO(ZaymonFC): Move this somewhere common.
-export const TypeNameSchema = S.String.pipe(
-  S.pattern(/^\w+\.\w{2,}\/[\w/]+$/i),
-  S.annotations({
-    [TitleAnnotationId]: 'TypeName',
-    [DescriptionAnnotationId]:
-      'Domain-style type name path. Dashes are not allowed. Use camel case for the final component of the type name.',
-    [ExamplesAnnotationId]: ['example.com/type/Document', 'example.com/type/FlightList'],
-  }),
-);
-
-const formatDescription = `The format of the property. Additional information:
-  ${FormatEnum.GeoPoint}: ${JSON.stringify(toJsonSchema(GeoPoint))}
-  This tuple is GeoJSON. You must specify \`${FormatEnum.GeoPoint}\` as [Longitude, Latitude]`;
-
-// TODO(ZaymonFC): All properties are default optional, but maybe we should allow for required properties.
-const PropertyDefinitionSchema = S.Struct({
-  name: S.String.annotations({ [DescriptionAnnotationId]: 'The name of the property.' }),
-  format: S.Union(...FormatEnums.map((format) => S.Literal(format))).annotations({
-    [DescriptionAnnotationId]: formatDescription,
-  }),
-  config: S.optional(
-    S.Struct({
-      options: S.optional(
-        S.Array(SelectOptionSchema).annotations({
-          description: `Options for SingleSelect/MultiSelect formats. Available colors: ${hues.join(', ')}`,
-        }),
-      ),
-    }),
-  ),
-}).pipe(S.mutable);
-
-const SYSTEM_NAMESPACE = 'dxos.org/echo/schema';
-
-// TODO(ZaymonFC): If this works well, move this to global tools.
-export const schemaTools = [
-  defineTool(SYSTEM_NAMESPACE, {
-    name: 'list',
-    description: 'List registered schemas in the space.',
-    caption: 'Listing registered schemas...',
-    schema: S.Struct({}),
-    execute: async (_input, { extensions }) => {
-      invariant(extensions?.space, 'No space.');
-      const space = extensions.space;
-
-      const schemas = await space.db.schemaRegistry.query({}).run();
-      return ToolResult.Success(
-        schemas.map((schema) => ({
-          typename: schema.typename,
-          version: schema.version,
-        })),
-      );
-    },
-  }),
-  defineTool(SYSTEM_NAMESPACE, {
-    name: 'get',
-    description: 'Get a specific schema by its typename.',
-    caption: 'Getting schema...',
-    schema: S.Struct({
-      typename: S.String.annotations({
-        description: 'The fully qualified typename of the schema.',
-      }),
-    }),
-    execute: async ({ typename }, { extensions }) => {
-      invariant(extensions?.space, 'No space.');
-      const space = extensions.space;
-
-      const schema = await space.db.schemaRegistry.query({ typename }).firstOrUndefined();
-      if (!schema) {
-        return ToolResult.Error(`Schema not found: ${typename}`);
-      }
-
-      return ToolResult.Success(schema);
-    },
-  }),
-  defineTool(SYSTEM_NAMESPACE, {
-    name: 'create',
-    description: 'Create a new schema with the provided definition.',
-    caption: 'Creating schema...',
-    schema: S.Struct({
-      typename: TypeNameSchema.annotations({
-        description:
-          'The fully qualified schema typename. Must start with a domain, and then one or more path components (e.g., "example.com/type/TypeName").',
-      }),
-      properties: S.Array(PropertyDefinitionSchema).pipe(
-        S.annotations({ description: 'Array of property definitions for the schema.' }),
-        S.mutable,
-      ),
-    }),
-    execute: async ({ typename, properties }, { extensions }) => {
-      invariant(extensions?.space, 'No space.');
-      const space = extensions.space;
-
-      const typeToSchema: Record<TypeEnum, S.Any> = {
-        [TypeEnum.String]: S.String.pipe(S.optional),
-        [TypeEnum.Number]: S.Number.pipe(S.optional),
-        [TypeEnum.Boolean]: S.Boolean.pipe(S.optional),
-        [TypeEnum.Object]: S.Object.pipe(S.optional),
-        [TypeEnum.Ref]: S.String.pipe(S.optional), // TODO(burdon): Is this correct for refs?
-      };
-
-      // TODO(ZaymonFC): It would be better to construct the full JSON schema here, formats and all.
-      //   This way we are falling back to the primitive type only.
-      const fields: any = Object.fromEntries(
-        properties.map((prop) => [prop.name, typeToSchema[formatToType[prop.format]]]),
-      );
-
-      const schema = TypedObject({ typename, version: '0.1.0' })(fields);
-      const [registeredSchema] = await space.db.schemaRegistry.register([schema]);
-
-      // TODO(ZaymonFC): This is a temporary workaround.
-      //   We should consolidate schema manipulation logic between here and the ViewProjection.
-      //   Currently this just implements the SingleSelect format, but we need to extend this
-      //   to all formats.
-      for (const prop of properties) {
-        if (prop.format === FormatEnum.SingleSelect && prop.config?.options) {
-          makeSingleSelectAnnotations(registeredSchema.jsonSchema.properties![prop.name], [...prop.config.options]);
-        }
-
-        if (prop.format === FormatEnum.GeoPoint) {
-          registeredSchema.jsonSchema.properties![prop.name].format = FormatEnum.GeoPoint;
-          registeredSchema.jsonSchema.properties![prop.name].type = TypeEnum.Object;
-        }
-      }
-
-      return ToolResult.Success(registeredSchema);
-    },
-  }),
-];
diff --git a/packages/sdk/schema/src/construction.ts b/packages/sdk/schema/src/construction.ts
new file mode 100644
index 00000000000..4c4e0a7a528
--- /dev/null
+++ b/packages/sdk/schema/src/construction.ts
@@ -0,0 +1,51 @@
+//
+// Copyright 2025 DXOS.org
+//
+
+import { FormatEnum, formatToType, S, TypedObject, TypeEnum, type SelectOptionSchema } from '@dxos/echo-schema';
+import { createEchoSchema } from '@dxos/live-object/testing';
+
+import { makeSingleSelectAnnotations } from './util';
+
+// TODO(ZaymonFC): Keep this in sync with the schema in `schema-tools.ts`.
+type PropertyDefinition = {
+  name: string;
+  format: FormatEnum;
+  config?: {
+    options?: Array<S.Schema.Type<typeof SelectOptionSchema>>;
+  };
+};
+
+export const echoSchemaFromPropertyDefinitions = (typename: string, properties: PropertyDefinition[]) => {
+  const typeToSchema: Record<TypeEnum, S.Any> = {
+    [TypeEnum.String]: S.String.pipe(S.optional),
+    [TypeEnum.Number]: S.Number.pipe(S.optional),
+    [TypeEnum.Boolean]: S.Boolean.pipe(S.optional),
+    [TypeEnum.Object]: S.Object.pipe(S.optional),
+    [TypeEnum.Ref]: S.String.pipe(S.optional), // TODO(burdon): Is this correct for refs?
+  };
+
+  // TODO(ZaymonFC): It would be better to construct the full JSON schema here, formats and all.
+  //   This way we are falling back to the primitive type only.
+  const fields: any = Object.fromEntries(
+    properties.map((prop) => [prop.name, typeToSchema[formatToType[prop.format]]]),
+  );
+
+  const schema = createEchoSchema(TypedObject({ typename, version: '0.1.0' })(fields));
+
+  // TODO(ZaymonFC): This is a temporary workaround.
+  //   We should consolidate schema manipulation logic between here and the ViewProjection.
+  //   We need to extend this to support all formats!!
+  for (const prop of properties) {
+    if (prop.format === FormatEnum.SingleSelect && prop.config?.options) {
+      makeSingleSelectAnnotations(schema.jsonSchema.properties![prop.name], [...prop.config.options]);
+    }
+
+    if (prop.format === FormatEnum.GeoPoint) {
+      schema.jsonSchema.properties![prop.name].format = FormatEnum.GeoPoint;
+      schema.jsonSchema.properties![prop.name].type = TypeEnum.Object;
+    }
+  }
+
+  return schema;
+};
diff --git a/packages/sdk/schema/src/index.ts b/packages/sdk/schema/src/index.ts
index 45e28361228..dd34c8c59ca 100644
--- a/packages/sdk/schema/src/index.ts
+++ b/packages/sdk/schema/src/index.ts
@@ -3,6 +3,7 @@
 //
 
 export * from './common';
+export * from './construction';
 export * from './format';
 export * from './projection';
 export * from './properties';
diff --git a/packages/ui/react-ui-table/src/components/testing.ts b/packages/ui/react-ui-table/src/components/testing.ts
index 669107f6fef..eb49bb2e4a3 100644
--- a/packages/ui/react-ui-table/src/components/testing.ts
+++ b/packages/ui/react-ui-table/src/components/testing.ts
@@ -75,6 +75,7 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
       const rowIdx = Math.floor(Math.random() * items.length);
       const fields = table.view?.target?.fields ?? [];
       const columnIdx = Math.floor(Math.random() * fields.length);
+      // TODO(ZaymonFC): ... This is borked.
       const projection: ViewProjection = (table as any)._projection;
       const field = fields[columnIdx];
       const item = items[rowIdx];
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 45ec5a34131..0d07223ebb2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -52,7 +52,7 @@ importers:
     devDependencies:
       '@cloudflare/vitest-pool-workers':
         specifier: ^0.5.17
-        version: 0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8)
       '@cloudflare/workers-types':
         specifier: ^4.20241018.0
         version: 4.20241022.0
@@ -106,7 +106,7 @@ importers:
         version: link:packages/common/typings
       '@effect/vitest':
         specifier: ~0.17.3
-        version: 0.17.8(effect@3.13.3)(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 0.17.8(effect@3.13.3)(vitest@3.0.8)
       '@mdx-js/react':
         specifier: ^2.1.3
         version: 2.1.5(react@18.2.0)
@@ -127,13 +127,13 @@ importers:
         version: 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))
       '@nx/playwright':
         specifier: 19.7.2
-        version: 19.7.2(@babel/traverse@7.25.9)(@playwright/test@1.46.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(esbuild@0.23.1)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 19.7.2(@babel/traverse@7.25.9)(@playwright/test@1.46.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(esbuild@0.23.1)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)
       '@nx/storybook':
         specifier: 19.7.2
         version: 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)
       '@nx/vite':
         specifier: 19.7.2
-        version: 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)
       '@nx/web':
         specifier: 19.7.2
         version: 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)
@@ -229,7 +229,7 @@ importers:
         version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.10.2)(playwright@1.46.1)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)(webdriverio@8.33.1(encoding@0.1.13)(typescript@5.7.3))
       '@vitest/coverage-v8':
         specifier: ^3.0.8
-        version: 3.0.8(@vitest/browser@3.0.8(@testing-library/dom@10.4.0)(@types/node@22.10.2)(playwright@1.46.1)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)(webdriverio@8.33.1(encoding@0.1.13)(typescript@5.7.3)))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 3.0.8(@vitest/browser@3.0.8)(vitest@3.0.8)
       '@vitest/expect':
         specifier: ^3.0.8
         version: 3.0.8
@@ -4798,7 +4798,7 @@ importers:
     devDependencies:
       '@cloudflare/vitest-pool-workers':
         specifier: ^0.5.17
-        version: 0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+        version: 0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8)
       '@cloudflare/workers-types':
         specifier: ^4.20241018.0
         version: 4.20241022.0
@@ -8820,6 +8820,9 @@ importers:
       '@dxos/app-framework':
         specifier: workspace:*
         version: link:../../sdk/app-framework
+      '@dxos/artifact':
+        specifier: workspace:*
+        version: link:../../experimental/artifact
       '@dxos/async':
         specifier: workspace:*
         version: link:../../common/async
@@ -8907,6 +8910,12 @@ importers:
       '@dxos/react-ui-tabs':
         specifier: workspace:*
         version: link:../../ui/react-ui-tabs
+      '@dxos/react-ui-theme':
+        specifier: workspace:*
+        version: link:../../ui/react-ui-theme
+      '@dxos/schema':
+        specifier: workspace:*
+        version: link:../../sdk/schema
       '@dxos/shell':
         specifier: workspace:*
         version: link:../../sdk/shell
@@ -8944,9 +8953,6 @@ importers:
       '@dxos/react-ui':
         specifier: workspace:*
         version: link:../../ui/react-ui
-      '@dxos/react-ui-theme':
-        specifier: workspace:*
-        version: link:../../ui/react-ui-theme
       '@dxos/storybook-utils':
         specifier: workspace:*
         version: link:../../common/storybook-utils
@@ -37467,7 +37473,7 @@ snapshots:
     dependencies:
       mime: 3.0.0
 
-  '@cloudflare/vitest-pool-workers@0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@cloudflare/vitest-pool-workers@0.5.17(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@3.0.8)(@vitest/snapshot@3.0.8)(vitest@3.0.8)':
     dependencies:
       '@vitest/runner': 3.0.8
       '@vitest/snapshot': 3.0.8
@@ -37949,7 +37955,7 @@ snapshots:
       effect: 3.13.3
       fast-check: 3.23.1
 
-  '@effect/vitest@0.17.8(effect@3.13.3)(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@effect/vitest@0.17.8(effect@3.13.3)(vitest@3.0.8)':
     dependencies:
       effect: 3.13.3
       vitest: 3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2)
@@ -39688,9 +39694,9 @@ snapshots:
       - '@swc/core'
       - debug
 
-  '@nrwl/vite@19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@nrwl/vite@19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)':
     dependencies:
-      '@nx/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+      '@nx/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)
     transitivePeerDependencies:
       - '@babel/traverse'
       - '@swc-node/register'
@@ -39998,12 +40004,12 @@ snapshots:
   '@nx/nx-win32-x64-msvc@19.7.2':
     optional: true
 
-  '@nx/playwright@19.7.2(@babel/traverse@7.25.9)(@playwright/test@1.46.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(esbuild@0.23.1)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@nx/playwright@19.7.2(@babel/traverse@7.25.9)(@playwright/test@1.46.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(esbuild@0.23.1)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)':
     dependencies:
       '@nx/devkit': 19.7.2(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))
       '@nx/eslint': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))
       '@nx/js': 19.7.2(patch_hash=mavp33mumclr54vvfyyf557nvq)(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)
-      '@nx/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+      '@nx/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)
       '@nx/webpack': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(esbuild@0.23.1)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.7.3)
       '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3)
       minimatch: 9.0.3
@@ -40070,9 +40076,9 @@ snapshots:
       - typescript
       - verdaccio
 
-  '@nx/vite@19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@nx/vite@19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)':
     dependencies:
-      '@nrwl/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))
+      '@nrwl/vite': 19.7.2(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)
       '@nx/devkit': 19.7.2(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))
       '@nx/js': 19.7.2(patch_hash=mavp33mumclr54vvfyyf557nvq)(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1))(@types/node@22.10.2)(nx@19.7.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.1))(@swc/types@0.1.5)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.1)))(typescript@5.7.3)
       '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3)
@@ -45500,7 +45506,7 @@ snapshots:
       - utf-8-validate
       - vite
 
-  '@vitest/coverage-v8@3.0.8(@vitest/browser@3.0.8(@testing-library/dom@10.4.0)(@types/node@22.10.2)(playwright@1.46.1)(typescript@5.7.3)(vite@5.4.7(patch_hash=i2cyw7vpoo7fbe7qgy5pwsznyq)(@types/node@22.10.2)(less@4.1.3)(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))(vitest@3.0.8)(webdriverio@8.33.1(encoding@0.1.13)(typescript@5.7.3)))(vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.10.2)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(happy-dom@13.3.4)(jsdom@24.0.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(msw@2.7.3(@types/node@22.10.2)(typescript@5.7.3))(sass@1.70.0)(stylus@0.59.0)(terser@5.19.2))':
+  '@vitest/coverage-v8@3.0.8(@vitest/browser@3.0.8)(vitest@3.0.8)':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 1.0.2


Reviews:
user: login: wittjosiah
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T11:32:02Z
user: login: zaymonoid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:21:06Z

Review comments:
diff_hunk: @@ -178,5 +179,10 @@ export const SpacePlugin = ({
       ),
       activate: SpacesReady,
     }),
+    defineModule({
+      id: `${meta.id}/module/schema-tools`,
+      activatesOn: Events.SetupArtifactDefinition,
+      activate: SchemaTools,
+    }),
path: packages/plugins/plugin-space/src/SpacePlugin.tsx
user: login: wittjosiah
body: probably just Tools? there's likely other echo tools which will be added here
created_at: 2025-03-21T11:30:11Z
author_association: MEMBER
diff_hunk: @@ -63,8 +64,11 @@
     "@dxos/react-ui-stack": "workspace:*",
     "@dxos/react-ui-syntax-highlighter": "workspace:*",
     "@dxos/react-ui-tabs": "workspace:*",
+    "@dxos/react-ui-theme": "workspace:*",
+    "@dxos/schema": "workspace:*",
     "@dxos/shell": "workspace:*",
     "@dxos/util": "workspace:*",
+    "@effect/schema": "^0.75.5",
path: packages/plugins/plugin-space/package.json
user: login: wittjosiah
body: this package is deprecated, import schema from `effect`
created_at: 2025-03-21T11:30:31Z
author_association: MEMBER
diff_hunk: @@ -75,6 +75,7 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
       const rowIdx = Math.floor(Math.random() * items.length);
       const fields = table.view?.target?.fields ?? [];
       const columnIdx = Math.floor(Math.random() * fields.length);
+      // TODO(ZaymonFC): ... This is borked.
path: packages/ui/react-ui-table/src/components/testing.ts
user: login: wittjosiah
body: What's not working? Due to this pr or already broken?
created_at: 2025-03-21T11:31:56Z
author_association: MEMBER
diff_hunk: @@ -75,6 +75,7 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
       const rowIdx = Math.floor(Math.random() * items.length);
       const fields = table.view?.target?.fields ?? [];
       const columnIdx = Math.floor(Math.random() * fields.length);
+      // TODO(ZaymonFC): ... This is borked.
path: packages/ui/react-ui-table/src/components/testing.ts
user: login: zaymonoid
body: Already broken.
created_at: 2025-03-21T18:21:06Z
author_association: CONTRIBUTOR

PR comments:
user: login: semanticdiff-com[bot]
created_at: 2025-03-19T20:12:09Z
body: Review changes with &nbsp;<a href="https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes"><picture><source media="(prefers-color-scheme: dark)" srcset="https://app.semanticdiff.com/images/github_button.svg?theme=dark&smaller=1"><source media="(prefers-color-scheme: light)" srcset="https://app.semanticdiff.com/images/github_button.svg?theme=light&smaller=1"><img src="https://app.semanticdiff.com/images/github_button.svg?theme=light&smaller=1" align="center" alt="SemanticDiff"></picture></a>
<details>
<summary>Changed Files</summary>
<br/>

| File | Status |
| :--- | :---   |
| [<img src="https://app.semanticdiff.com/static/file-icons/pnpm.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#pnpm-lock.yaml)&nbsp;&nbsp;[pnpm\-lock\.yaml](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#pnpm-lock.yaml) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=12">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#pnpm-lock.yaml) &nbsp;12% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/json.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/package.json)&nbsp;&nbsp;[packages/plugins/plugin\-space/package\.json](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/package.json) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/package.json) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/react_ts.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/SpacePlugin.tsx)&nbsp;&nbsp;[packages/plugins/plugin\-space/src/SpacePlugin\.tsx](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/SpacePlugin.tsx) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/SpacePlugin.tsx) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/index.ts)&nbsp;&nbsp;[packages/plugins/plugin\-space/src/capabilities/index\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/index.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/index.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/test-ts.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts)&nbsp;&nbsp;[packages/plugins/plugin\-space/src/capabilities/schema\-tool\.test\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tool.test.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tools.ts)&nbsp;&nbsp;[packages/plugins/plugin\-space/src/capabilities/schema\-tools\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tools.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/src/capabilities/schema-tools.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/json.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/tsconfig.json)&nbsp;&nbsp;[packages/plugins/plugin\-space/tsconfig\.json](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/tsconfig.json) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-space/tsconfig.json) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/artifact-definition.ts)&nbsp;&nbsp;[packages/plugins/plugin\-table/src/capabilities/artifact\-definition\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/artifact-definition.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/artifact-definition.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/schema-tool.ts)&nbsp;&nbsp;[packages/plugins/plugin\-table/src/capabilities/schema\-tool\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/schema-tool.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/plugins/plugin-table/src/capabilities/schema-tool.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/construction.ts)&nbsp;&nbsp;[packages/sdk/schema/src/construction\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/construction.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/construction.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/index.ts)&nbsp;&nbsp;[packages/sdk/schema/src/index\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/index.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/sdk/schema/src/index.ts) &nbsp;0% smaller |
| [<img src="https://app.semanticdiff.com/static/file-icons/typescript.svg" width="20" height="20" align="center">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/ui/react-ui-table/src/components/testing.ts)&nbsp;&nbsp;[packages/ui/react\-ui\-table/src/components/testing\.ts](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/ui/react-ui-table/src/components/testing.ts) | [<img src="https://app.semanticdiff.com/images/progressbar.svg?progress=0">](https://app.semanticdiff.com/gh/dxos/dxos/pull/8831/changes#packages/ui/react-ui-table/src/components/testing.ts) &nbsp;0% smaller |

</details>
author_association: NONE
user: login: nx-cloud[bot]
created_at: 2025-03-19T20:14:10Z
body: 
View your [CI Pipeline Execution ↗](https://cloud.nx.app/cipes/67ddbfc5dceabc6b990384db?utm_source=pull-request&utm_medium=comment) for commit 837544cb39ec0cd0cabda536b35cf52c3dac01eb.

| Command | Status | Duration | Result |
|---------|--------|----------:|--------|
| `nx affected --base=ba2dec9c6b8f7c38f4571bd3fbf5...` | ✅ Succeeded | 3s | [View ↗](https://cloud.nx.app/runs/cUuxO6sf29?utm_source=pull-request&utm_medium=comment) |
| `nx affected --base=ba2dec9c6b8f7c38f4571bd3fbf5...` | ✅ Succeeded | 2m 21s | [View ↗](https://cloud.nx.app/runs/taE6khn2GA?utm_source=pull-request&utm_medium=comment) |
| `nx affected --base=ba2dec9c6b8f7c38f4571bd3fbf5...` | ✅ Succeeded | 11s | [View ↗](https://cloud.nx.app/runs/G0pOxnK6pQ?utm_source=pull-request&utm_medium=comment) |

---

☁️ [Nx Cloud](https://cloud.nx.app?utm_source=pull-request&utm_medium=comment) last updated this comment at `2025-03-21 19:41:34` UTC

<!-- NX_CLOUD_APP_COMMENT_END -->
author_association: NONE