diff --git a/.changeset/olive-crabs-repeat.md b/.changeset/olive-crabs-repeat.md new file mode 100644 index 000000000..79f7beeae --- /dev/null +++ b/.changeset/olive-crabs-repeat.md @@ -0,0 +1,5 @@ +--- +"executor": patch +--- + +Add an Artifacts tab. Interactive components an agent generates with `render-ui` are saved and listed in the console, and each one has its own page that renders it live — the page an MCP client without MCP Apps support deep-links to. Artifacts can be renamed and deleted from the console, and agents find them again by title. diff --git a/.oxfmtrc.json b/.oxfmtrc.json index d92df54ec..73f725581 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -13,6 +13,7 @@ "*.tsbuildinfo", "executor-*.tgz", "**/routeTree.gen.ts", + "**/smoke-harness-bundle.gen.ts", "apps/cloud/src/services/executor-schema.ts" ] } diff --git a/apps/cloud/drizzle/0013_sturdy_living_mummy.sql b/apps/cloud/drizzle/0013_sturdy_living_mummy.sql new file mode 100644 index 000000000..606cbfd19 --- /dev/null +++ b/apps/cloud/drizzle/0013_sturdy_living_mummy.sql @@ -0,0 +1 @@ +ALTER TABLE "artifact" ADD COLUMN "bindings" json; \ No newline at end of file diff --git a/apps/cloud/drizzle/meta/0013_snapshot.json b/apps/cloud/drizzle/meta/0013_snapshot.json new file mode 100644 index 000000000..d4e2e7af9 --- /dev/null +++ b/apps/cloud/drizzle/meta/0013_snapshot.json @@ -0,0 +1,1409 @@ +{ + "id": "469b6311-cd77-46f8-80d8-2589e77727fd", + "prevId": "c0757240-8619-468a-adf6-39c86a38d641", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memberships": { + "name": "memberships", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memberships_account_id_accounts_id_fk": { + "name": "memberships_account_id_accounts_id_fk", + "tableFrom": "memberships", + "tableTo": "accounts", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memberships_organization_id_organizations_id_fk": { + "name": "memberships_organization_id_organizations_id_fk", + "tableFrom": "memberships", + "tableTo": "organizations", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "memberships_account_id_organization_id_pk": { + "name": "memberships_account_id_organization_id_pk", + "columns": ["account_id", "organization_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.artifact": { + "name": "artifact", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bindings": { + "name": "bindings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "artifact_uidx": { + "name": "artifact_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.blob": { + "name": "blob", + "schema": "", + "columns": { + "namespace": { + "name": "namespace", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blob_id_uidx": { + "name": "blob_id_uidx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection": { + "name": "connection", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_ids": { + "name": "item_ids", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health": { + "name": "last_health", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "tools_synced_at": { + "name": "tools_synced_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "oauth_client": { + "name": "oauth_client", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_owner": { + "name": "oauth_client_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_item_id": { + "name": "refresh_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "oauth_scope": { + "name": "oauth_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_token_url": { + "name": "oauth_token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_state": { + "name": "provider_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "connection_uidx": { + "name": "connection_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.definition": { + "name": "definition", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "definition_uidx": { + "name": "definition_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration": { + "name": "integration", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "health_check": { + "name": "health_check", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "config_revised_at": { + "name": "config_revised_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "can_remove": { + "name": "can_remove", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "can_refresh": { + "name": "can_refresh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "integration_uidx": { + "name": "integration_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant": { + "name": "grant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_item_id": { + "name": "client_secret_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_integration": { + "name": "origin_integration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_issuer": { + "name": "origin_issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_redirect_uri": { + "name": "origin_redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_client_uidx": { + "name": "oauth_client_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_session": { + "name": "oauth_session", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "client_slug": { + "name": "client_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "integration": { + "name": "integration", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_url": { + "name": "redirect_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pkce_verifier": { + "name": "pkce_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_session_uidx": { + "name": "oauth_session_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_storage": { + "name": "plugin_storage", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "collection": { + "name": "collection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "plugin_storage_uidx": { + "name": "plugin_storage_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "collection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.private_executor_cloud_settings": { + "name": "private_executor_cloud_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool": { + "name": "tool", + "schema": "", + "columns": { + "integration": { + "name": "integration", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_schema": { + "name": "input_schema", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "output_schema": { + "name": "output_schema", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tool_uidx": { + "name": "tool_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policy": { + "name": "tool_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "tenant": { + "name": "tenant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tool_policy_uidx": { + "name": "tool_policy_uidx", + "columns": [ + { + "expression": "tenant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/cloud/drizzle/meta/_journal.json b/apps/cloud/drizzle/meta/_journal.json index b67e2ced0..8b2b14f8d 100644 --- a/apps/cloud/drizzle/meta/_journal.json +++ b/apps/cloud/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1785215550990, "tag": "0012_talented_surge", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1785291252295, + "tag": "0013_sturdy_living_mummy", + "breakpoints": true } ] } diff --git a/apps/cloud/package.json b/apps/cloud/package.json index 258b12ab7..05f21b7bb 100644 --- a/apps/cloud/package.json +++ b/apps/cloud/package.json @@ -30,7 +30,8 @@ "typecheck:slow": "tsc --noEmit", "db:backfill-org-slugs:prod": "op run --env-file=.env.production -- bun run scripts/backfill-org-slugs.ts", "db:backfill-org-slugs:dev": "op run --env-file=.env.op -- bun run scripts/backfill-org-slugs.ts", - "routes:gen": "bun scripts/gen-routes.ts" + "routes:gen": "bun scripts/gen-routes.ts", + "vendor-wasm": "bun run scripts/vendor-quickjs-wasm.ts" }, "dependencies": { "@cloudflare/vite-plugin": "^1.31.1", @@ -53,6 +54,7 @@ "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", "@executor-js/vite-plugin": "workspace:*", + "@jitl/quickjs-wasmfile-release-sync": "catalog:", "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "~1.9.0", "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", @@ -74,6 +76,7 @@ "jose": "^5.6.3", "postgres": "^3.4.9", "posthog-js": "^1.372.5", + "quickjs-emscripten-core": "0.31.0", "react": "catalog:", "react-dom": "catalog:", "sonner": "^2.0.7" diff --git a/apps/cloud/scripts/vendor-quickjs-wasm.ts b/apps/cloud/scripts/vendor-quickjs-wasm.ts new file mode 100644 index 000000000..1518babf4 --- /dev/null +++ b/apps/cloud/scripts/vendor-quickjs-wasm.ts @@ -0,0 +1,18 @@ +// Vendors the QuickJS engine WASM into src/ so wrangler's CompiledWasm module +// rule (rooted at the app dir) can statically compile it at build time. Workers +// forbid runtime WASM compilation, and the rule's glob won't reach the +// monorepo-root node_modules, so the bytes must live inside this app. +// +// Re-run after bumping @jitl/quickjs-wasmfile-release-sync: +// bun run scripts/vendor-quickjs-wasm.ts +import { copyFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); +const source = require.resolve("@jitl/quickjs-wasmfile-release-sync/wasm"); +const dest = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "quickjs-engine.wasm"); + +copyFileSync(source, dest); +console.log(`vendored ${source} -> ${dest}`); diff --git a/apps/cloud/src/db/executor-schema.ts b/apps/cloud/src/db/executor-schema.ts index f74f0d738..ca000c215 100644 --- a/apps/cloud/src/db/executor-schema.ts +++ b/apps/cloud/src/db/executor-schema.ts @@ -217,6 +217,7 @@ export const artifact = pgTable( title: text("title").notNull(), description: text("description"), code: text("code").notNull(), + bindings: json("bindings"), created_at: timestamp("created_at").notNull(), updated_at: timestamp("updated_at").notNull(), row_id: varchar("row_id", { length: 255 }) diff --git a/apps/cloud/src/engine/execution-gate.ts b/apps/cloud/src/engine/execution-gate.ts index 6c12d53ef..82d5f4c2a 100644 --- a/apps/cloud/src/engine/execution-gate.ts +++ b/apps/cloud/src/engine/execution-gate.ts @@ -100,6 +100,10 @@ export const withPreExecutionGate = ( ), // resume is never gated: paused executions must be able to complete. resume: (executionId, response) => engine.resume(executionId, response), + // Optional member, so it must be forwarded explicitly — a decorator that + // rebuilds the object literal drops it, and the host then reads every settled + // execution as "never existed". + isExecutionSettled: engine.isExecutionSettled, getPausedExecution: (executionId) => engine.getPausedExecution(executionId), pausedExecutionCount: () => engine.pausedExecutionCount(), hasPausedExecutions: () => engine.hasPausedExecutions(), diff --git a/apps/cloud/src/engine/execution-usage.ts b/apps/cloud/src/engine/execution-usage.ts index 50ba3fc34..94b85cb39 100644 --- a/apps/cloud/src/engine/execution-usage.ts +++ b/apps/cloud/src/engine/execution-usage.ts @@ -12,12 +12,15 @@ export const withExecutionUsageTracking = ( engine .execute(code, options) .pipe(Effect.tap(() => Effect.sync(() => trackUsage(organizationId)))), - executeWithPause: (code) => + // `options` MUST be forwarded: it carries `autoApprove`, and dropping it + // silently turned every auto-approved execution back into a paused one. + executeWithPause: (code, options) => engine - .executeWithPause(code) + .executeWithPause(code, options) .pipe(Effect.tap(() => Effect.sync(() => trackUsage(organizationId)))), // resume doesn't count as usage resume: (executionId, response) => engine.resume(executionId, response), + isExecutionSettled: engine.isExecutionSettled, getPausedExecution: (executionId) => engine.getPausedExecution(executionId), pausedExecutionCount: () => engine.pausedExecutionCount(), hasPausedExecutions: () => engine.hasPausedExecutions(), diff --git a/apps/cloud/src/mcp/session-durable-object.ts b/apps/cloud/src/mcp/session-durable-object.ts index b11ca8e76..ae243b2b7 100644 --- a/apps/cloud/src/mcp/session-durable-object.ts +++ b/apps/cloud/src/mcp/session-durable-object.ts @@ -25,8 +25,9 @@ import { createExecutorMcpServer, } from "@executor-js/host-mcp/tool-server"; import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval"; -import { artifactUrlFor } from "@executor-js/host-mcp/render-ui"; +import { artifactUrlFor } from "@executor-js/host-mcp/create-artifact"; import { loadMcpAppsShellHtml } from "@executor-js/mcp-apps-shell"; +import { smokeRenderArtifact } from "@executor-js/mcp-apps-shell/smoke-render"; import { McpAgentSessionDOBase, type BuiltMcpServer, @@ -66,6 +67,7 @@ import { type DbServiceShape, } from "../db/db"; import { makeExecutionStack } from "../engine/execution-stack"; +import { preloadQuickJs } from "../quickjs"; import { CloudMeteredExecutionStackLayer } from "../engine/execution-stack-metered"; import { AutumnService } from "../extensions/billing/service"; import { DoTelemetryLive, flushTracerProvider } from "../observability/telemetry"; @@ -227,6 +229,13 @@ export class McpSessionDOSqlite extends McpAgentSessionDOBase { const self = this; return Effect.gen(function* () { + // QuickJS-WASM must be loaded before anything asks for a sandbox: the + // default variant cannot fetch its own `.wasm` on Workers. Cloud runs + // user `execute` code on the dynamic-worker runtime, but the artifact + // smoke render is a QuickJS sandbox on every host — without this it fails + // open on each create and the check silently does nothing. + // Idempotent per isolate. + yield* Effect.promise(() => preloadQuickJs()); const { executor, engine } = yield* makeExecutionStack( sessionMeta.userId, sessionMeta.organizationId, @@ -253,8 +262,13 @@ export class McpSessionDOSqlite extends McpAgentSessionDOBase self.currentParentSpan(), debug: env.EXECUTOR_MCP_DEBUG === "true", browserApprovalStore: self.browserApprovalStore, diff --git a/apps/cloud/src/quickjs-engine.wasm b/apps/cloud/src/quickjs-engine.wasm new file mode 100644 index 000000000..ee1a98f5a Binary files /dev/null and b/apps/cloud/src/quickjs-engine.wasm differ diff --git a/apps/cloud/src/quickjs.ts b/apps/cloud/src/quickjs.ts new file mode 100644 index 000000000..9d84484ed --- /dev/null +++ b/apps/cloud/src/quickjs.ts @@ -0,0 +1,35 @@ +import { newQuickJSWASMModuleFromVariant, newVariant } from "quickjs-emscripten-core"; +import baseVariant from "@jitl/quickjs-wasmfile-release-sync"; +// Static .wasm import: wrangler/workerd compiles this to a WebAssembly.Module at +// BUILD time. Workers forbid runtime WASM compilation (both fetching the .wasm +// and `WebAssembly.instantiate()` of bytes are blocked), so the engine bytes +// MUST be a pre-compiled module imported like this. The file is vendored into +// src/ (copied from @jitl/quickjs-wasmfile-release-sync) because wrangler's +// CompiledWasm module rule is rooted at the app dir and won't match the +// monorepo-root node_modules path — see scripts/vendor-quickjs-wasm.ts. +import wasmModule from "./quickjs-engine.wasm"; + +import { setQuickJSModule } from "@executor-js/runtime-quickjs"; + +// --------------------------------------------------------------------------- +// QuickJS-on-Workers WASM loading. +// +// The base variant's module loader resolves to the variant package's `workerd` +// build (its `./emscripten-module` export has a `workerd` condition wrangler +// selects) — that build expects the WASM module to be supplied rather than +// fetched/compiled at runtime. `newVariant(base, { wasmModule })` hands it the +// statically-imported, pre-compiled module, and `setQuickJSModule` makes every +// `makeQuickJsExecutor()` reuse it. Preloaded once per isolate. +// --------------------------------------------------------------------------- + +let preloaded: Promise | null = null; + +export const preloadQuickJs = (): Promise => { + if (!preloaded) { + const variant = newVariant(baseVariant, { wasmModule }); + preloaded = newQuickJSWASMModuleFromVariant(variant).then((mod) => { + setQuickJSModule(mod); + }); + } + return preloaded; +}; diff --git a/apps/cloud/src/routeTree.gen.ts b/apps/cloud/src/routeTree.gen.ts index e689e72b7..0711b978b 100644 --- a/apps/cloud/src/routeTree.gen.ts +++ b/apps/cloud/src/routeTree.gen.ts @@ -19,11 +19,13 @@ import { Route as SecretsRouteImport } from './routes/app/secrets' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' import { Route as OrgRouteImport } from './routes/app/org' import { Route as BillingRouteImport } from './routes/app/billing' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' import { Route as ApiKeysRouteImport } from './routes/app/api-keys' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' import { Route as ResumeDotexecutionIdRouteImport } from './routes/app/resume.$executionId' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' import { Route as Billing_DotplansRouteImport } from './routes/app/billing_.plans' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' const SetupMcpRoute = SetupMcpRouteImport.update({ @@ -80,6 +82,12 @@ const BillingRoute = BillingRouteImport.update({ path: '/{-$orgSlug}/billing', getParentRoute: () => rootRouteImport, } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport.update({ + id: '/{-$orgSlug}/artifacts', + path: '/{-$orgSlug}/artifacts', + getParentRoute: () => rootRouteImport, + } as any) const ApiKeysRoute = ApiKeysRouteImport.update({ id: '/{-$orgSlug}/api-keys', path: '/{-$orgSlug}/api-keys', @@ -112,6 +120,15 @@ const Billing_DotplansRoute = Billing_DotplansRouteImport.update({ path: '/{-$orgSlug}/billing/plans', getParentRoute: () => rootRouteImport, } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport.update( + { + id: '/$artifactId', + path: '/$artifactId', + getParentRoute: () => + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, + } as any, + ) const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRoute = DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport.update( { @@ -126,6 +143,7 @@ export interface FileRoutesByFullPath { '/login': typeof LoginRoute '/setup-mcp': typeof SetupMcpRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/billing': typeof BillingRoute '/{-$orgSlug}/org': typeof OrgRoute '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute @@ -133,6 +151,7 @@ export interface FileRoutesByFullPath { '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute @@ -144,6 +163,7 @@ export interface FileRoutesByTo { '/login': typeof LoginRoute '/setup-mcp': typeof SetupMcpRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/billing': typeof BillingRoute '/{-$orgSlug}/org': typeof OrgRoute '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute @@ -151,6 +171,7 @@ export interface FileRoutesByTo { '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/billing/plans': typeof Billing_DotplansRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute @@ -163,6 +184,7 @@ export interface FileRoutesById { '/login': typeof LoginRoute '/setup-mcp': typeof SetupMcpRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/billing': typeof BillingRoute '/{-$orgSlug}/org': typeof OrgRoute '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute @@ -170,6 +192,7 @@ export interface FileRoutesById { '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/billing_/plans': typeof Billing_DotplansRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof ResumeDotexecutionIdRoute @@ -183,6 +206,7 @@ export interface FileRouteTypes { | '/login' | '/setup-mcp' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/billing' | '/{-$orgSlug}/org' | '/{-$orgSlug}/policies' @@ -190,6 +214,7 @@ export interface FileRouteTypes { | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/billing/plans' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' @@ -201,6 +226,7 @@ export interface FileRouteTypes { | '/login' | '/setup-mcp' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/billing' | '/{-$orgSlug}/org' | '/{-$orgSlug}/policies' @@ -208,6 +234,7 @@ export interface FileRouteTypes { | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/billing/plans' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' @@ -219,6 +246,7 @@ export interface FileRouteTypes { | '/login' | '/setup-mcp' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/billing' | '/{-$orgSlug}/org' | '/{-$orgSlug}/policies' @@ -226,6 +254,7 @@ export interface FileRouteTypes { | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/billing_/plans' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' @@ -238,6 +267,7 @@ export interface RootRouteChildren { LoginRoute: typeof LoginRoute SetupMcpRoute: typeof SetupMcpRoute ApiKeysRoute: typeof ApiKeysRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren BillingRoute: typeof BillingRoute OrgRoute: typeof OrgRoute DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute @@ -323,6 +353,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BillingRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts': { + id: '/{-$orgSlug}/artifacts' + path: '/{-$orgSlug}/artifacts' + fullPath: '/{-$orgSlug}/artifacts' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport + parentRoute: typeof rootRouteImport + } '/{-$orgSlug}/api-keys': { id: '/{-$orgSlug}/api-keys' path: '/{-$orgSlug}/api-keys' @@ -358,6 +395,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Billing_DotplansRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts/$artifactId': { + id: '/{-$orgSlug}/artifacts/$artifactId' + path: '/$artifactId' + fullPath: '/{-$orgSlug}/artifacts/$artifactId' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport + parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute + } '/{-$orgSlug}/integrations/add/$pluginKey': { id: '/{-$orgSlug}/integrations/add/$pluginKey' path: '/{-$orgSlug}/integrations/add/$pluginKey' @@ -368,6 +412,21 @@ declare module '@tanstack/react-router' { } } +interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute +} + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren = + { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute, + } + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute._addFileChildren( + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, + ) + interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute } @@ -388,6 +447,8 @@ const rootRouteChildren: RootRouteChildren = { LoginRoute: LoginRoute, SetupMcpRoute: SetupMcpRoute, ApiKeysRoute: ApiKeysRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren, BillingRoute: BillingRoute, OrgRoute: OrgRoute, DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: @@ -411,11 +472,15 @@ export const routeTree = rootRouteImport ._addFileTypes() import type { getRouter } from './router.tsx' + import type { startInstance } from './start.ts' + declare module '@tanstack/react-start' { interface Register { ssr: true + router: Awaited> + config: Awaited> } } diff --git a/apps/cloud/src/routes/__root.tsx b/apps/cloud/src/routes/__root.tsx index f508a316c..3614bb14d 100644 --- a/apps/cloud/src/routes/__root.tsx +++ b/apps/cloud/src/routes/__root.tsx @@ -20,6 +20,7 @@ import { EXECUTOR_ORG_HEADER } from "@executor-js/react/api/server-connection"; import { OrgSlugGate } from "@executor-js/react/multiplayer/org-slug-gate"; import { Toaster } from "@executor-js/react/components/sonner"; import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; +import { ArtifactRendererProvider } from "@executor-js/react/api/artifact-renderer"; import { plugins as clientPlugins } from "virtual:executor/plugins-client"; import type { AuthHint } from "@executor-js/react/multiplayer/auth-hint"; import { AuthProvider, useAuth } from "../web/auth"; @@ -60,6 +61,13 @@ if (typeof window !== "undefined" && import.meta.env.VITE_PUBLIC_POSTHOG_KEY) { }); } +// The MCP-Apps shell is browser-only — it imports `@tailwindcss/browser`, which +// touches `document` at import scope. A static import here would put it in this +// SSR app's server graph and 500 every document request, so it is registered as +// a dynamic import the artifact page resolves in the browser. Module scope keeps +// the loader identity stable, so the lazy component behind it never remounts. +const artifactRendererLoader = () => import("@executor-js/mcp-apps-shell/shell/artifact-renderer"); + const analyticsClient: AnalyticsClient | undefined = typeof window !== "undefined" && import.meta.env.VITE_PUBLIC_POSTHOG_KEY ? (name, properties) => posthog.capture(name, properties) @@ -302,7 +310,9 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { (a foreign slug already 404'd above). The gate only keeps the URL canonical — bare → /. */} - + + + diff --git a/apps/cloud/src/wasm.d.ts b/apps/cloud/src/wasm.d.ts new file mode 100644 index 000000000..1bfd61f61 --- /dev/null +++ b/apps/cloud/src/wasm.d.ts @@ -0,0 +1,6 @@ +// On Cloudflare Workers, a `.wasm` import resolves to a pre-compiled +// `WebAssembly.Module` (wrangler's built-in CompiledWasm module rule). +declare module "*.wasm" { + const wasmModule: WebAssembly.Module; + export default wasmModule; +} diff --git a/apps/cloud/vite.config.ts b/apps/cloud/vite.config.ts index 83be48137..166fa4595 100644 --- a/apps/cloud/vite.config.ts +++ b/apps/cloud/vite.config.ts @@ -5,6 +5,7 @@ import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import executorVitePlugin from "@executor-js/vite-plugin"; +import { innerRendererPlugin, mcpAppsShellAsset } from "@executor-js/mcp-apps-shell/vite"; import { workerBundlerArtifact } from "@executor-js/plugin-apps/vite"; import { unstable_readConfig } from "wrangler"; @@ -128,6 +129,13 @@ export default defineConfig(({ command, mode }) => { devCrashGuard(), workerBundlerArtifact(), tailwindcss(), + // The artifact page hosts the MCP-Apps shell as a sandboxed iframe over + // the MCP-Apps protocol: `mcpAppsShellAsset` emits the built shell + // document and hands the page its URL, and `innerRendererPlugin` inlines + // the shell's own sandboxed inner frame from + // `virtual:executor-inner-renderer`. + mcpAppsShellAsset() as Plugin, + innerRendererPlugin(), executorVitePlugin(), cloudflare({ viteEnvironment: { name: "ssr" }, inspectorPort: false }), tanstackStart({ diff --git a/apps/host-cloudflare/src/mcp/session-durable-object.ts b/apps/host-cloudflare/src/mcp/session-durable-object.ts index 194bfbbd2..130128ee3 100644 --- a/apps/host-cloudflare/src/mcp/session-durable-object.ts +++ b/apps/host-cloudflare/src/mcp/session-durable-object.ts @@ -5,8 +5,9 @@ import { createExecutorMcpServer, } from "@executor-js/host-mcp/tool-server"; import { buildResumeApprovalUrl } from "@executor-js/host-mcp/browser-approval"; -import { artifactUrlFor } from "@executor-js/host-mcp/render-ui"; +import { artifactUrlFor } from "@executor-js/host-mcp/create-artifact"; import { loadMcpAppsShellHtml } from "@executor-js/mcp-apps-shell"; +import { smokeRenderArtifact } from "@executor-js/mcp-apps-shell/smoke-render"; import type { ExecutorDbHandle } from "@executor-js/api/server"; import { McpAgentSessionDOBase, @@ -133,8 +134,12 @@ export class McpSessionDO extends McpAgentSessionDOBase rootRouteImport, } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport.update({ + id: '/{-$orgSlug}/artifacts', + path: '/{-$orgSlug}/artifacts', + getParentRoute: () => rootRouteImport, + } as any) const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute = DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport.update( { @@ -75,6 +83,15 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRout getParentRoute: () => rootRouteImport, } as any, ) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport.update( + { + id: '/$artifactId', + path: '/$artifactId', + getParentRoute: () => + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, + } as any, + ) const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( { @@ -93,11 +110,13 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginK ) export interface FileRoutesByFullPath { + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -105,11 +124,13 @@ export interface FileRoutesByFullPath { '/{-$orgSlug}/plugins/$pluginId/$': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute } export interface FileRoutesByTo { + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -118,11 +139,13 @@ export interface FileRoutesByTo { } export interface FileRoutesById { __root__: typeof rootRouteImport + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -132,11 +155,13 @@ export interface FileRoutesById { export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -144,11 +169,13 @@ export interface FileRouteTypes { | '/{-$orgSlug}/plugins/$pluginId/$' fileRoutesByTo: FileRoutesByTo to: + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -156,11 +183,13 @@ export interface FileRouteTypes { | '/{-$orgSlug}/plugins/$pluginId/$' id: | '__root__' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -169,6 +198,7 @@ export interface FileRouteTypes { fileRoutesById: FileRoutesById } export interface RootRouteChildren { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren @@ -217,6 +247,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts': { + id: '/{-$orgSlug}/artifacts' + path: '/{-$orgSlug}/artifacts' + fullPath: '/{-$orgSlug}/artifacts' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport + parentRoute: typeof rootRouteImport + } '/{-$orgSlug}/toolkits/$toolkitSlug': { id: '/{-$orgSlug}/toolkits/$toolkitSlug' path: '/$toolkitSlug' @@ -238,6 +275,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts/$artifactId': { + id: '/{-$orgSlug}/artifacts/$artifactId' + path: '/$artifactId' + fullPath: '/{-$orgSlug}/artifacts/$artifactId' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport + parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute + } '/{-$orgSlug}/plugins/$pluginId/$': { id: '/{-$orgSlug}/plugins/$pluginId/$' path: '/{-$orgSlug}/plugins/$pluginId/$' @@ -255,6 +299,21 @@ declare module '@tanstack/react-router' { } } +interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute +} + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren = + { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute, + } + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute._addFileChildren( + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, + ) + interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute } @@ -271,6 +330,8 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren = ) const rootRouteChildren: RootRouteChildren = { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren, DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: diff --git a/apps/host-cloudflare/web/routes/__root.tsx b/apps/host-cloudflare/web/routes/__root.tsx index eaf4e356a..24f8485c1 100644 --- a/apps/host-cloudflare/web/routes/__root.tsx +++ b/apps/host-cloudflare/web/routes/__root.tsx @@ -3,6 +3,7 @@ import { useEffect, type ReactNode } from "react"; import { ExecutorProvider } from "@executor-js/react/api/provider"; import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; +import { ArtifactRendererProvider } from "@executor-js/react/api/artifact-renderer"; import { OrganizationProvider } from "@executor-js/react/api/organization-context"; import { OrgSlugGate } from "@executor-js/react/multiplayer/org-slug-gate"; import { Toaster } from "@executor-js/react/components/sonner"; @@ -24,6 +25,13 @@ import { plugins as clientPlugins } from "virtual:executor/plugins-client"; // omits the API-keys nav item and just uses the default set. // --------------------------------------------------------------------------- +// The MCP-Apps shell is browser-only — it imports `@tailwindcss/browser`, which +// touches `document` at import scope. It is registered as a dynamic import the +// artifact page resolves in the browser, never a static one, so it stays out of +// any server graph. Module scope keeps the loader identity stable, so the lazy +// component behind it never remounts. +const artifactRendererLoader = () => import("@executor-js/mcp-apps-shell/shell/artifact-renderer"); + export const Route = createRootRoute({ component: RootComponent, }); @@ -78,7 +86,13 @@ function AuthenticatedApp() { wrangler.jsonc run_worker_first), so a slug-pinned URL would fall through to the SPA. */} - {organization ? {gated} : gated} + + {organization ? ( + {gated} + ) : ( + gated + )} + diff --git a/apps/host-selfhost/src/mcp/session-store.ts b/apps/host-selfhost/src/mcp/session-store.ts index 17def563f..0f2233be9 100644 --- a/apps/host-selfhost/src/mcp/session-store.ts +++ b/apps/host-selfhost/src/mcp/session-store.ts @@ -23,6 +23,7 @@ import { SelfHostExecutionStackLayer } from "../execution"; // --------------------------------------------------------------------------- import { loadMcpAppsShellHtml } from "@executor-js/mcp-apps-shell"; +import { smokeRenderArtifact } from "@executor-js/mcp-apps-shell/smoke-render"; export { McpEngineBuildError } from "@executor-js/host-mcp/in-memory-session-store"; @@ -38,7 +39,7 @@ export const makeSelfHostMcpSessionStore = ( makeInMemoryMcpSessionStore( makeMcpBuildServer( SelfHostExecutionStackLayer.pipe(Layer.provide(Layer.succeed(SelfHostDb)(db))), - { loadAppShellHtml: loadMcpAppsShellHtml }, + { loadAppShellHtml: loadMcpAppsShellHtml, smokeRenderArtifact }, ), { webBaseUrl }, ); diff --git a/apps/host-selfhost/tsconfig.json b/apps/host-selfhost/tsconfig.json index e214693ed..18cfacc0a 100644 --- a/apps/host-selfhost/tsconfig.json +++ b/apps/host-selfhost/tsconfig.json @@ -3,6 +3,10 @@ "target": "ESNext", "module": "ESNext", "moduleResolution": "bundler", + // This host opts into the create-time artifact smoke render, whose entry + // dynamically imports a `.tsx` component graph. Nothing here renders + // anything; the compiler still resolves that graph. + "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true, diff --git a/apps/host-selfhost/vite.config.ts b/apps/host-selfhost/vite.config.ts index 9cf280edd..93a15cce5 100644 --- a/apps/host-selfhost/vite.config.ts +++ b/apps/host-selfhost/vite.config.ts @@ -7,6 +7,7 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; import executorVitePlugin from "@executor-js/vite-plugin"; +import { innerRendererPlugin, mcpAppsShellAsset } from "@executor-js/mcp-apps-shell/vite"; import { routes } from "./tsr.routes"; import { MCP_ORIGINAL_PATH_HEADER, stripMcpOrgSegment } from "./src/mcp/org-path"; @@ -230,6 +231,12 @@ export default defineConfig({ plugins: [ executorApiPlugin(), tailwindcss(), + // The artifact page hosts the MCP-Apps shell as a sandboxed iframe over the + // MCP-Apps protocol: `mcpAppsShellAsset` emits the built shell document and + // hands the page its URL, and `innerRendererPlugin` inlines the shell's own + // sandboxed inner frame from `virtual:executor-inner-renderer`. + mcpAppsShellAsset() as Plugin, + innerRendererPlugin(), executorVitePlugin({ configPath: fileURLToPath(new URL("./executor.config.ts", import.meta.url)), }), diff --git a/apps/host-selfhost/web/routeTree.gen.ts b/apps/host-selfhost/web/routeTree.gen.ts index e7c9fbb94..3f601e7f8 100644 --- a/apps/host-selfhost/web/routeTree.gen.ts +++ b/apps/host-selfhost/web/routeTree.gen.ts @@ -14,12 +14,14 @@ import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRouteImport import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteImport } from './../../../packages/react/src/routes/toolkits' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRouteImport } from './../../../packages/react/src/routes/secrets' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport } from './../../../packages/react/src/routes/policies' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport } from './../../../packages/react/src/routes/artifacts' import { Route as ApiKeysRouteImport } from './routes/app/api-keys' import { Route as AdminRouteImport } from './routes/app/admin' import { Route as JoinDotcodeRouteImport } from './routes/public/join.$code' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRouteImport } from './../../../packages/react/src/routes/toolkits.$toolkitSlug' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRouteImport } from './../../../packages/react/src/routes/resume.$executionId' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport } from './../../../packages/react/src/routes/integrations.$namespace' +import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport } from './../../../packages/react/src/routes/artifacts.$artifactId' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport } from './../../../packages/react/src/routes/plugins.$pluginId.$' import { Route as DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotaddDotpluginKeyRouteImport } from './../../../packages/react/src/routes/integrations.add.$pluginKey' @@ -53,6 +55,12 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute = path: '/{-$orgSlug}/policies', getParentRoute: () => rootRouteImport, } as any) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport.update({ + id: '/{-$orgSlug}/artifacts', + path: '/{-$orgSlug}/artifacts', + getParentRoute: () => rootRouteImport, + } as any) const ApiKeysRoute = ApiKeysRouteImport.update({ id: '/{-$orgSlug}/api-keys', path: '/{-$orgSlug}/api-keys', @@ -93,6 +101,15 @@ const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRout getParentRoute: () => rootRouteImport, } as any, ) +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport.update( + { + id: '/$artifactId', + path: '/$artifactId', + getParentRoute: () => + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute, + } as any, + ) const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRoute = DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPluginsDotpluginIdDotsplatRouteImport.update( { @@ -114,11 +131,13 @@ export interface FileRoutesByFullPath { '/join/$code': typeof JoinDotcodeRoute '/{-$orgSlug}/admin': typeof AdminRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -129,11 +148,13 @@ export interface FileRoutesByTo { '/join/$code': typeof JoinDotcodeRoute '/{-$orgSlug}/admin': typeof AdminRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -145,11 +166,13 @@ export interface FileRoutesById { '/join/$code': typeof JoinDotcodeRoute '/{-$orgSlug}/admin': typeof AdminRoute '/{-$orgSlug}/api-keys': typeof ApiKeysRoute + '/{-$orgSlug}/artifacts': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren '/{-$orgSlug}/policies': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute '/{-$orgSlug}/secrets': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute '/{-$orgSlug}/toolkits': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren '/{-$orgSlug}/tools': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolsRoute '/{-$orgSlug}/': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIndexRoute + '/{-$orgSlug}/artifacts/$artifactId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute '/{-$orgSlug}/integrations/$namespace': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRoute '/{-$orgSlug}/resume/$executionId': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesResumeDotexecutionIdRoute '/{-$orgSlug}/toolkits/$toolkitSlug': typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute @@ -162,11 +185,13 @@ export interface FileRouteTypes { | '/join/$code' | '/{-$orgSlug}/admin' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -177,11 +202,13 @@ export interface FileRouteTypes { | '/join/$code' | '/{-$orgSlug}/admin' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -192,11 +219,13 @@ export interface FileRouteTypes { | '/join/$code' | '/{-$orgSlug}/admin' | '/{-$orgSlug}/api-keys' + | '/{-$orgSlug}/artifacts' | '/{-$orgSlug}/policies' | '/{-$orgSlug}/secrets' | '/{-$orgSlug}/toolkits' | '/{-$orgSlug}/tools' | '/{-$orgSlug}/' + | '/{-$orgSlug}/artifacts/$artifactId' | '/{-$orgSlug}/integrations/$namespace' | '/{-$orgSlug}/resume/$executionId' | '/{-$orgSlug}/toolkits/$toolkitSlug' @@ -208,6 +237,7 @@ export interface RootRouteChildren { JoinDotcodeRoute: typeof JoinDotcodeRoute AdminRoute: typeof AdminRoute ApiKeysRoute: typeof ApiKeysRoute + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteWithChildren @@ -256,6 +286,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts': { + id: '/{-$orgSlug}/artifacts' + path: '/{-$orgSlug}/artifacts' + fullPath: '/{-$orgSlug}/artifacts' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteImport + parentRoute: typeof rootRouteImport + } '/{-$orgSlug}/api-keys': { id: '/{-$orgSlug}/api-keys' path: '/{-$orgSlug}/api-keys' @@ -298,6 +335,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesIntegrationsDotnamespaceRouteImport parentRoute: typeof rootRouteImport } + '/{-$orgSlug}/artifacts/$artifactId': { + id: '/{-$orgSlug}/artifacts/$artifactId' + path: '/$artifactId' + fullPath: '/{-$orgSlug}/artifacts/$artifactId' + preLoaderRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRouteImport + parentRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute + } '/{-$orgSlug}/plugins/$pluginId/$': { id: '/{-$orgSlug}/plugins/$pluginId/$' path: '/{-$orgSlug}/plugins/$pluginId/$' @@ -315,6 +359,21 @@ declare module '@tanstack/react-router' { } } +interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute +} + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren = + { + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsDotartifactIdRoute, + } + +const DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren = + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute._addFileChildren( + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteChildren, + ) + interface DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsRouteChildren { DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute: typeof DotDotDotDotDotDotDotDotPackagesReactSrcRoutesToolkitsDottoolkitSlugRoute } @@ -334,6 +393,8 @@ const rootRouteChildren: RootRouteChildren = { JoinDotcodeRoute: JoinDotcodeRoute, AdminRoute: AdminRoute, ApiKeysRoute: ApiKeysRoute, + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRoute: + DotDotDotDotDotDotDotDotPackagesReactSrcRoutesArtifactsRouteWithChildren, DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute: DotDotDotDotDotDotDotDotPackagesReactSrcRoutesPoliciesRoute, DotDotDotDotDotDotDotDotPackagesReactSrcRoutesSecretsRoute: diff --git a/apps/host-selfhost/web/routes/__root.tsx b/apps/host-selfhost/web/routes/__root.tsx index f80045010..c3edd7664 100644 --- a/apps/host-selfhost/web/routes/__root.tsx +++ b/apps/host-selfhost/web/routes/__root.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, type ReactNode } from "react"; import { ExecutorProvider } from "@executor-js/react/api/provider"; import { ExecutorPluginsProvider } from "@executor-js/sdk/client"; +import { ArtifactRendererProvider } from "@executor-js/react/api/artifact-renderer"; import { OrganizationProvider } from "@executor-js/react/api/organization-context"; import { OrgSlugGate } from "@executor-js/react/multiplayer/org-slug-gate"; import { Toaster } from "@executor-js/react/components/sonner"; @@ -32,6 +33,13 @@ import { fetchNeedsSetup } from "../setup-status"; // Auth), injected here. No billing, Sentry, or PostHog. // --------------------------------------------------------------------------- +// The MCP-Apps shell is browser-only — it imports `@tailwindcss/browser`, which +// touches `document` at import scope. It is registered as a dynamic import the +// artifact page resolves in the browser, never a static one, so it stays out of +// any server graph. Module scope keeps the loader identity stable, so the lazy +// component behind it never remounts. +const artifactRendererLoader = () => import("@executor-js/mcp-apps-shell/shell/artifact-renderer"); + export const Route = createRootRoute({ notFoundComponent: NotFoundPage, component: RootComponent, @@ -167,7 +175,13 @@ function AuthenticatedApp() { a slug-pinned URL would 404, and a single-org instance has nothing to select anyway. */} - {organization ? {gated} : gated} + + {organization ? ( + {gated} + ) : ( + gated + )} + diff --git a/apps/local/src/main.ts b/apps/local/src/main.ts index 3daff99c5..4395ef337 100644 --- a/apps/local/src/main.ts +++ b/apps/local/src/main.ts @@ -1,8 +1,9 @@ import { Context, Data, Effect, Layer, ManagedRuntime } from "effect"; import { createExecutionEngine } from "@executor-js/execution"; -import { artifactUrlFor } from "@executor-js/host-mcp/render-ui"; +import { artifactUrlFor } from "@executor-js/host-mcp/create-artifact"; import { loadMcpAppsShellHtml } from "@executor-js/mcp-apps-shell"; +import { smokeRenderArtifact } from "@executor-js/mcp-apps-shell/smoke-render"; import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; import { makeLocalApiHandler } from "./app"; import { createExecutorHandle, disposeExecutor, getExecutorBundle } from "./executor"; @@ -91,15 +92,37 @@ export const createServerHandlers = async (token: string): Promise { if (resource.kind === "default") { - return { config: { engine, artifacts: executor.artifacts, ...appsConfig } }; + return { + config: { + engine, + artifacts: executor.artifacts, + connections: executor.connections, + ...appsConfig, + }, + }; } const handle = await createExecutorHandle({ activeToolkitSlug: resource.slug, @@ -112,6 +135,7 @@ export const createServerHandlers = async (token: string): Promise { - const result = await inspector.renderTool("render-ui", { code: APP_SRC }); + const result = await inspector.renderTool("create-artifact", { code: APP_SRC }); const app = result.app().frameLocator("iframe"); // see "extra iframe" below await expect(app.locator('button:has-text("Increment")')).toBeVisible(); }); @@ -39,7 +39,7 @@ test("widget mounts", async ({ inspector }) => { 1. **sunpeak doesn't advertise the MCP-Apps UI _client_ capability.** Its inspector connects with `new Client({ name, version })` and never declares `capabilities.extensions["io.modelcontextprotocol/ui"]`. executor's - `render-ui` (correctly, per the MCP-Apps spec) only mounts the widget inline + `create-artifact` (correctly, per the MCP-Apps spec) only mounts the widget inline when the host advertises it can render `text/html;profile=mcp-app`; otherwise it returns its browser **fallback URL** and nothing renders. `scripts/patch-sunpeak.mjs` (a postinstall) adds that capability to sunpeak's client. This is @@ -52,7 +52,7 @@ test("widget mounts", async ({ inspector }) => { ## Why stdio + from source -`render-ui`'s shell resource (`ui://executor/shell-tanstack-query.html`) is +`create-artifact`'s shell resource (`ui://executor/shell-tanstack-query.html`) is `packages/hosts/mcp-apps-shell/dist/mcp-app.html`. `pretest` builds it. Running `executor mcp` from source serves it directly. (The compiled binary now ships the shell too — see `apps/cli/src/build.ts` — but source is the cheaper path diff --git a/e2e/mcp-apps/tests/render-ui.spec.ts b/e2e/mcp-apps/tests/create-artifact.spec.ts similarity index 85% rename from e2e/mcp-apps/tests/render-ui.spec.ts rename to e2e/mcp-apps/tests/create-artifact.spec.ts index 33c4c75c2..340a7d3c3 100644 --- a/e2e/mcp-apps/tests/render-ui.spec.ts +++ b/e2e/mcp-apps/tests/create-artifact.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "sunpeak/test"; -// executor's render-ui shell mounts the generated component in a *nested* srcdoc +// executor's create-artifact shell mounts the generated component in a *nested* srcdoc // iframe (shell-app -> inner-renderer, the MCP-Apps double-iframe sandbox), one // level below sunpeak's own `result.app()`. Descend that extra level. const appBody = (result: { app: () => ReturnType["app"]> } | any) => @@ -19,8 +19,8 @@ const COUNTER = `function App() { // sunpeak runs every test against both the Claude and ChatGPT host simulations. -test("render-ui mounts an interactive React widget", async ({ inspector }) => { - const result = await inspector.renderTool("render-ui", { +test("create-artifact mounts an interactive React widget", async ({ inspector }) => { + const result = await inspector.renderTool("create-artifact", { code: COUNTER, title: "MCP App counter", }); @@ -35,9 +35,9 @@ test("render-ui mounts an interactive React widget", async ({ inspector }) => { await expect(app.locator("text=1")).toBeVisible(); }); -test("render-ui renders in dark theme", async ({ inspector }) => { +test("create-artifact renders in dark theme", async ({ inspector }) => { const result = await inspector.renderTool( - "render-ui", + "create-artifact", { code: COUNTER, title: "MCP App counter" }, { theme: "dark" }, ); @@ -53,8 +53,8 @@ test("render-ui renders in dark theme", async ({ inspector }) => { const structuredOf = (result: { structuredContent?: unknown }): Record => (result.structuredContent ?? {}) as Record; -test("render-ui returns an inline widget, not the deep-link fallback", async ({ mcp }) => { - const result = await mcp.callTool("render-ui", { +test("create-artifact returns an inline widget, not the deep-link fallback", async ({ mcp }) => { + const result = await mcp.callTool("create-artifact", { code: COUNTER, title: "MCP App counter", description: "A counter used by the MCP Apps e2e harness", @@ -69,7 +69,7 @@ test("render-ui returns an inline widget, not the deep-link fallback", async ({ }); test("a rendered artifact is listed and can be shown again", async ({ inspector, mcp }) => { - const rendered = await mcp.callTool("render-ui", { + const rendered = await mcp.callTool("create-artifact", { code: COUNTER, title: "Retrievable counter", description: "Saved so list-artifacts can find it", diff --git a/e2e/scenarios/artifact-approval.test.ts b/e2e/scenarios/artifact-approval.test.ts new file mode 100644 index 000000000..d2d4ef937 --- /dev/null +++ b/e2e/scenarios/artifact-approval.test.ts @@ -0,0 +1,229 @@ +// Cross-target: approving a destructive action triggered from inside an +// artifact. +// +// This is the console's own path, not MCP. The artifact page has no MCP client, +// so the shell's host adapter posts `POST /executions` (with the artifact id) +// and then `POST /executions/:id/resume` when the human answers the modal. The +// existing approval coverage all runs over MCP, where the engine lives for the +// lifetime of a session Durable Object — so none of it could see that on a host +// building an executor per request the pause and the resume land on DIFFERENT +// engine instances, and the approval failed with `ExecutionNotFoundError`. +// +// Two things are asserted that the older scenarios could not: +// +// 1. The approval is honoured at all on this path, and the side effect lands. +// 2. It is still honoured after a human-scale delay. The reported symptom was +// an approval that took "tens of seconds", so a scenario approving +// instantly would stay green against the broken code. It is worth being +// precise about why: the bug is NOT time-based (a resume 7ms after the +// pause failed identically), so the delay is not what makes this catch the +// regression — the artifact-scoped path is. The delay guards the separate +// promise that an approval window is human-scale. +import { randomUUID } from "node:crypto"; + +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; +import { composePluginApi } from "@executor-js/api/server"; +import type { ArtifactId } from "@executor-js/sdk/shared"; + +import { scenario } from "../src/scenario"; +import { Api, Target } from "../src/services"; + +const coreApi = composePluginApi([] as const); + +/** + * How long the scenario waits before approving. + * + * Long enough to be a real human pause rather than a same-tick round trip, and + * well past the 4-minute paused-execution lease the MCP plane advertises being + * irrelevant here. Kept modest so the scenario stays inside the default timeout. + */ +const APPROVAL_DELAY_MS = 65_000; + +/** + * The one call the shell's proxy is allowed to emit: a single awaited tool call, + * no statements around it. `policies.create` carries a `requiresApproval` + * annotation, so with no matching policy in play the annotation is the only + * thing that can pause it — and the pattern is unique-per-run and matches no + * real tool, so the created rule is inert. + * + * `executor` is a reserved tool root (`RESERVED_TOOL_ROOTS`), so it resolves + * without a connection binding — which lets the scenario exercise the + * artifact-scoped approval path without first connecting an integration. + */ +const artifactActionCode = (pattern: string) => + `return await tools.executor.coreTools.policies.create(${JSON.stringify({ + owner: "user", + pattern, + action: "block", + })})`; + +/** A minimal artifact. Its source is irrelevant to the approval path — what + * matters is that a real artifact id scopes the call, exactly as the rendered + * page supplies it. */ +const artifactSource = ` +function App() { + return
Approval fixture
; +} +`; + +const pausedExecutionId = (structured: unknown): string | undefined => + (structured as { readonly executionId?: string } | null)?.executionId; + +scenario( + "Artifacts · a destructive action approved from an artifact runs, even minutes later", + { timeout: 240_000 }, + Effect.gen(function* () { + const target = yield* Target; + const apiSurface = yield* Api; + const identity = yield* target.newIdentity(); + const client = yield* apiSurface.client(coreApi, identity); + + const pattern = `artifact-approval-${randomUUID().slice(0, 8)}.*`; + const code = artifactActionCode(pattern); + + const artifact = yield* client.artifacts.save({ + payload: { + title: `Approval fixture ${randomUUID().slice(0, 8)}`, + code: artifactSource, + }, + }); + + const cleanup = Effect.all( + [ + client.artifacts + .remove({ params: { artifactId: artifact.id as ArtifactId } }) + .pipe(Effect.ignore), + client.policies.list().pipe( + Effect.flatMap((list) => + Effect.forEach( + list.filter((p) => p.pattern === pattern), + (p) => + client.policies + .remove({ params: { policyId: p.id }, payload: { owner: "user" } }) + .pipe(Effect.ignore), + ), + ), + Effect.ignore, + ), + ], + { discard: true }, + ); + + yield* Effect.gen(function* () { + // The artifact fires its mutation. The tool gates itself, so the shell + // gets a pause back and renders the approval modal. + const paused = yield* client.executions.execute({ + payload: { code, artifactId: artifact.id }, + }); + expect(paused.status, "a destructive artifact action pauses for approval").toBe("paused"); + if (paused.status !== "paused") return; // narrowing only + + const executionId = pausedExecutionId(paused.structured); + expect(executionId, "the pause carries an execution id to approve").toBeTruthy(); + if (!executionId) return; // narrowing only + + // Nothing may happen while the human is still deciding. + const beforeApproval = yield* client.policies.list(); + expect( + beforeApproval.some((p) => p.pattern === pattern), + "the action does not run while it is waiting on approval", + ).toBe(false); + + // The human reads the request and decides. This is the wait that makes the + // approval window a real promise rather than a same-request formality. + yield* Effect.sleep(APPROVAL_DELAY_MS); + + const approved = yield* client.executions.resume({ + params: { executionId }, + payload: { action: "accept", content: {} }, + }); + + expect(approved.status, "approving the action runs it to completion").toBe("completed"); + if (approved.status !== "completed") return; // narrowing only + expect(approved.isError, "the approved action is not an error").toBe(false); + + const afterApproval = yield* client.policies.list(); + expect( + afterApproval.some((p) => p.pattern === pattern), + "the approved action's side effect actually landed", + ).toBe(true); + }).pipe(Effect.ensuring(cleanup)); + }), +); + +scenario( + "Artifacts · declining an artifact action leaves it unrun", + {}, + Effect.gen(function* () { + const target = yield* Target; + const apiSurface = yield* Api; + const identity = yield* target.newIdentity(); + const client = yield* apiSurface.client(coreApi, identity); + + const pattern = `artifact-decline-${randomUUID().slice(0, 8)}.*`; + const code = artifactActionCode(pattern); + + const artifact = yield* client.artifacts.save({ + payload: { + title: `Decline fixture ${randomUUID().slice(0, 8)}`, + code: artifactSource, + }, + }); + + const cleanup = Effect.all( + [ + client.artifacts + .remove({ params: { artifactId: artifact.id as ArtifactId } }) + .pipe(Effect.ignore), + client.policies.list().pipe( + Effect.flatMap((list) => + Effect.forEach( + list.filter((p) => p.pattern === pattern), + (p) => + client.policies + .remove({ params: { policyId: p.id }, payload: { owner: "user" } }) + .pipe(Effect.ignore), + ), + ), + Effect.ignore, + ), + ], + { discard: true }, + ); + + yield* Effect.gen(function* () { + const paused = yield* client.executions.execute({ + payload: { code, artifactId: artifact.id }, + }); + expect(paused.status, "the action pauses for approval").toBe("paused"); + if (paused.status !== "paused") return; // narrowing only + + const executionId = pausedExecutionId(paused.structured); + if (!executionId) return; // narrowing only + + yield* client.executions.resume({ + params: { executionId }, + payload: { action: "decline" }, + }); + + const afterDecline = yield* client.policies.list(); + expect( + afterDecline.some((p) => p.pattern === pattern), + "a declined action never runs", + ).toBe(false); + + // A declined approval is spent: the same id cannot be re-approved into a + // run afterwards. + yield* client.executions + .resume({ params: { executionId }, payload: { action: "accept", content: {} } }) + .pipe(Effect.ignore); + + const afterRetry = yield* client.policies.list(); + expect( + afterRetry.some((p) => p.pattern === pattern), + "approving after a decline does not resurrect the action", + ).toBe(false); + }).pipe(Effect.ensuring(cleanup)); + }), +); diff --git a/e2e/scenarios/artifacts.test.ts b/e2e/scenarios/artifacts.test.ts new file mode 100644 index 000000000..135dbcc95 --- /dev/null +++ b/e2e/scenarios/artifacts.test.ts @@ -0,0 +1,660 @@ +// Cross-target: the artifacts journey, end to end. +// +// An agent on a client that cannot display MCP Apps calls `create-artifact`. The +// product promise under test is vision.md's delivery negotiation: the model +// behaves identically either way, and only DELIVERY changes — a client without +// MCP Apps gets a deep link into the web app instead of an embedded widget. +// Persistence is what makes that possible, so the same artifact must then be +// reachable three ways: the deep link, the Artifacts tab, and back through MCP +// by title. +// +// Two scenarios, split by what they prove: +// 1. create-artifact saves + delivers a working deep link, and the page renders +// the component live (the fallback path a non-Apps client actually walks). +// 2. Rename and delete in the console are what MCP reads back afterwards +// (the console and the agent share one store, not two caches). +import { randomBytes } from "node:crypto"; + +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; +import type { Page } from "playwright"; +import { composePluginApi } from "@executor-js/api/server"; +import { AccountHttpApi } from "@executor-js/api"; +import type { ArtifactId } from "@executor-js/sdk/shared"; + +import { scenario } from "../src/scenario"; +import { Api, Browser, Mcp, Target } from "../src/services"; + +const api = composePluginApi([] as const); + +/** + * The component `create-artifact` persists. + * + * It must declare none of the ~280 globals the shell puts in scope (`Card`, + * `useState`, …) or the server's guard rejects it before it is ever saved, and + * its rendered text must be distinctive enough to assert on inside the shell's + * nested sandbox iframe. + * + * It is purely declarative, which is now the whole contract rather than a + * stylistic choice: there is no `run()` in scope, and code that calls one is + * rejected before it is saved. Rows come from `Array.from`, a display constant — + * an artifact that needed live data would reach for + * `useQuery(tools...queryOptions(...))`. + * + * It is also deliberately TALL — 40 rows, well past the frame's initial height. + * The shell reports its content height over the MCP-Apps resize protocol and the + * artifact page, as host, grows the iframe to match. When the shell was mounted + * inline there was no host to consume those notifications and tall artifacts + * clipped mid-viewport, so the last row is what proves the frame grew. + */ +const ARTIFACT_ROW_COUNT = 40; + +const artifactSource = (marker: string) => ` +function App() { + return ( +
+

Release Readiness

+

${marker}

+ {Array.from({ length: ${ARTIFACT_ROW_COUNT} }, (_, i) => ( +

+ Check {i + 1}: ${marker} +

+ ))} +
+ ); +} +`; + +/** Selfhost shares one workspace across scenarios, so every title is unique to + * this run and assertions look for "mine", never "the only one". */ +const uniqueSuffix = () => randomBytes(4).toString("hex"); + +const structuredOf = (result: { readonly raw: unknown }): Record => + ((result.raw as { structuredContent?: Record }).structuredContent ?? + {}) as Record; + +/** + * The generated component, two frames down. + * + * The artifact page hosts the shell DOCUMENT in a sandboxed iframe and speaks + * the MCP-Apps protocol to it — the same way any MCP-Apps client loads + * `ui://executor/shell.html`. The shell then compiles the stored JSX into its + * own nested sandbox. Both hops are load-bearing, so tests descend through both. + */ +const artifactContent = (page: Page) => + page.frameLocator('[data-testid="artifact-shell-frame"]').frameLocator("iframe"); + +/** + * A fingerprint of the CONSOLE document's styling. + * + * The shell ships its own Tailwind build and its own palette; the console's is + * strictly grayscale. Sampling a token, a real rendered control, and the + * stylesheet count catches the leak whether it arrives as a ` + +
@@ -109,23 +194,102 @@ const buildRendererSrcDoc = (token: string): string => { const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); -const TOOL_PATH_SEGMENT = /^[A-Za-z_$][\w$]*$/; +// --------------------------------------------------------------------------- +// Remembered approvals ("Approve and don't ask again") +// --------------------------------------------------------------------------- -const toolPathToCode = (path: unknown, args: unknown): string => { - if (!Array.isArray(path) || path.length === 0) { - throw new Error("Invalid tool path."); +/** + * A grant is scoped to ONE tool address, on ONE artifact, for ONE viewer. + * + * - Tool address: `interaction.address`, the only stable identity a pause + * carries (the execution id is minted per call). Approving + * `github.issues.create` here says nothing about `github.repos.delete`. + * - Artifact: an artifact's bindings are what turn that address into a real + * connection, so the same address on a different artifact is a different + * action and asks again. + * - Viewer: v1 scoping is the storage itself. The shell frame is same-origin + * with the console (see the outer sandbox in `artifact-renderer.tsx`), so + * these grants live in the console origin's `localStorage`, which is already + * per browser profile and therefore per signed-in person at this machine. + * There is no viewer id inside the shell frame and inventing one would be a + * fiction; when grants need to follow a viewer across devices they belong on + * the server, keyed by the real viewer id. + * + * Nothing here is a security boundary. The SERVER still gates every call; this + * only decides whether the shell shows the human a modal it would otherwise + * answer the same way. + */ +const APPROVAL_GRANTS_STORAGE_KEY = "executor.shell.approval-grants.v1"; + +/** + * `localStorage`, or `null` when it is unusable. + * + * The shell document is same-origin with the console and normally has storage, + * but that is a property of how it is embedded, not a guarantee: an opaque + * origin throws on the property read itself, and a browser with site data + * blocked throws on write while still exposing the object. Both are probed + * here, once per call, so an approval can never be broken by storage — the + * worst case is that a grant is not remembered. + */ +const approvalGrantStorage = (): Storage | null => { + try { + const storage = globalThis.localStorage; + const probe = `${APPROVAL_GRANTS_STORAGE_KEY}.probe`; + storage.setItem(probe, "1"); + storage.removeItem(probe); + return storage; + } catch { + return null; + } +}; + +const readApprovalGrants = (): ReadonlySet => { + const storage = approvalGrantStorage(); + if (!storage) return new Set(); + try { + const raw = storage.getItem(APPROVAL_GRANTS_STORAGE_KEY); + if (!raw) return new Set(); + const parsed: unknown = JSON.parse(raw); + return new Set( + Array.isArray(parsed) + ? parsed.filter((entry): entry is string => typeof entry === "string") + : [], + ); + } catch { + return new Set(); } - const parts = path.map((part) => { - if (typeof part !== "string" || !TOOL_PATH_SEGMENT.test(part)) { - throw new Error("Invalid tool path."); - } - return part; - }); - const argList = Array.isArray(args) ? args : []; - const serializedArgs = JSON.stringify(argList[0] ?? {}); - return `return await tools.${parts.join(".")}(${serializedArgs})`; }; +const rememberApprovalGrant = (key: string): void => { + const storage = approvalGrantStorage(); + if (!storage) return; + const next = new Set(readApprovalGrants()); + next.add(key); + try { + storage.setItem(APPROVAL_GRANTS_STORAGE_KEY, JSON.stringify([...next])); + } catch { + // Quota, or storage revoked between the probe and the write. The approval + // the user just gave still stands; only the memory of it is lost. + } +}; + +/** + * `null` when this pause cannot be scoped — no artifact id, or a pause with no + * address on it. Such an interaction is never auto-approved and never offers to + * be remembered, because a grant we cannot scope is a grant we cannot honor + * precisely. + */ +const approvalGrantKey = (artifactId: string | undefined, address: unknown): string | null => + artifactId !== undefined && + artifactId.length > 0 && + typeof address === "string" && + address.length > 0 + ? // A space separates the two halves: artifact ids are `art_`-prefixed + // base36 and tool addresses are dot-joined identifiers, so neither can + // contain one and no pair can collide. + `${artifactId} ${address}` + : null; + // --------------------------------------------------------------------------- // Shell App — connects to MCP host, receives code, renders components // --------------------------------------------------------------------------- @@ -142,8 +306,15 @@ export function McpAppsShell({ const [error, setError] = useState(null); const [hostContext, setHostContext] = useState(); const [pendingInteraction, setPendingInteraction] = useState(null); - const toolsRef = useRef>({}); - const runRef = useRef<(code: string) => Promise>(() => Promise.resolve(null)); + const callToolRef = useRef< + (path: readonly string[], args: readonly unknown[], role?: string) => Promise + >(() => Promise.resolve(null)); + // Which artifact is rendering. Set from the tool input the host delivers + // alongside the code, so the server can look up this artifact's connection + // bindings when the component calls a tool. Held in a ref rather than state + // because only the tool caller reads it, and rebuilding the caller on every + // delivery would race the first call. + const artifactIdRef = useRef(undefined); const pendingInteractionRef = useRef(null); const rendererFrameRef = useRef(null); const rendererRef = useRef(null); @@ -155,12 +326,27 @@ export function McpAppsShell({ const requestTrustedInteraction = useCallback( (interaction: TrustedInteraction): Promise => new Promise((resolve) => { + // Remembered approvals are answered HERE, before any state is set, so a + // granted tool never mounts the modal — not even for a frame. + const grantKey = approvalGrantKey(artifactIdRef.current, interaction.interaction.address); + if (grantKey !== null && readApprovalGrants().has(grantKey)) { + // A schema-bearing elicitation is only auto-answerable when its own + // defaults satisfy it. A required field with no default has no + // answer the user ever gave, so fall through to the modal rather + // than resuming with fabricated values. + const content = elicitationDefaultContent(interaction.interaction.requestedSchema); + if (content !== null) { + resolve({ action: "accept", content }); + return; + } + } + if (pendingInteractionRef.current) { resolve({ action: "cancel" }); return; } - const pending = { ...interaction, resolve }; + const pending = { ...interaction, resolve, grantKey }; pendingInteractionRef.current = pending; setPendingInteraction(pending); }), @@ -238,40 +424,20 @@ export function McpAppsShell({ return; } - if (data.type === "executor.run") { - if (typeof data.code !== "string") { - respond(data.requestId, false, undefined, "Invalid run payload."); - return; - } - runRef - .current(data.code) - .then((value) => respond(data.requestId, true, value)) - .catch((err: unknown) => - respond( - data.requestId, - false, - undefined, - err instanceof Error ? err.message : String(err), - ), - ); - return; - } - + // The ONLY request the generated iframe may make. There is no code + // channel: the inner renderer sends a tool path plus args, and the outer + // frame is what turns that into the one `execute-action` grammar. if (data.type === "executor.toolCall") { - let code: string; - try { - code = toolPathToCode(data.path, data.args); - } catch (err) { - respond( - data.requestId, - false, - undefined, - err instanceof Error ? err.message : String(err), - ); + if (!Array.isArray(data.path)) { + respond(data.requestId, false, undefined, "Invalid tool path."); return; } - runRef - .current(code) + callToolRef + .current( + data.path as readonly string[], + Array.isArray(data.args) ? data.args : [], + typeof data.role === "string" && data.role.length > 0 ? data.role : undefined, + ) .then((value) => respond(data.requestId, true, value)) .catch((err: unknown) => respond( @@ -319,12 +485,17 @@ export function McpAppsShell({ }, []); useEffect(() => { - toolsRef.current = createToolsProxy(app, requestTrustedInteraction); - runRef.current = createRunFn(app, requestTrustedInteraction); + callToolRef.current = createToolCaller( + app, + requestTrustedInteraction, + () => artifactIdRef.current, + ); // Handle tool input — fires on init (including page reload) with // the tool arguments. For generative UI the arguments contain { code }. app.ontoolinput = (params: { arguments?: Record }) => { + const artifactId = params.arguments?.artifactId; + if (typeof artifactId === "string") artifactIdRef.current = artifactId; const code = params.arguments?.code; if (code && typeof code === "string") { renderCode(code); @@ -334,6 +505,10 @@ export function McpAppsShell({ app.ontoolresult = (result: CallToolResult) => { const structured = result.structuredContent as Record | undefined; const code = structured?.code; + // The result is where `create-artifact` / `show-artifact` report the id + // they saved under; the input only carries one on a reload replay. + const artifactId = structured?.artifactId; + if (typeof artifactId === "string") artifactIdRef.current = artifactId; if (code && typeof code === "string") { renderCode(code); @@ -401,14 +576,8 @@ export function McpAppsShell({ if (error) { return ( -
- - - Error - - {error} - - +
+
); } @@ -426,13 +595,21 @@ export function McpAppsShell({ const Component = component; const config = renderer?.config ?? {}; - const maxHeight = typeof config.maxHeight === "number" ? config.maxHeight : 800; - const rendererHeight = renderer ? Math.min(renderer.height, maxHeight) : undefined; + const maxHeight = resolveMaxHeight(config, hostContext); + const rendererHeight = renderer + ? maxHeight === undefined + ? renderer.height + : Math.min(renderer.height, maxHeight) + : undefined; return (
{ + const approve = (remember: boolean) => { const content = approval.content(); if (content === null) return; + if (remember && pending.grantKey !== null) rememberApprovalGrant(pending.grantKey); onComplete({ action: "accept", content }); }; @@ -537,7 +715,7 @@ function TrustedInteractionModal({
Approve action
- This approval is handled by the Executor shell. + You've triggered a destructive action in the UI, please confirm
+ {/* One way to say no, not two. The approval gate treats "cancel" + and "decline" identically — `buildElicit` and `enforceApproval` + in `@executor-js/sdk` both raise `ElicitationDeclinedError` for + any non-accept action, and the engine forwards the word only so + the message can read "cancelled" instead of "declined". So this + sends "cancel", the action that also means "I closed this + without answering", which is what a Cancel button says. */} onComplete({ action: "cancel" })} > Cancel - onComplete({ action: "decline" })} - > - Decline - - - Approve - +
+ approve(false)} + > + Approve + + {/* Offered only when the grant can be scoped to a tool and an + artifact; an unscopable pause gets the plain button rather + than an option that would remember the wrong thing. */} + {pending.grantKey !== null && ( + // `modal={false}`: the menu opens on top of a plain fixed + // overlay, not a Radix dialog. A modal menu locks + // `pointer-events` on the body for the duration, and selecting + // an item here unmounts the whole overlay in the same tick — + // the unlock would run against a tree that no longer exists. + + + + + + + + approve(true)} + > + Approve and don't ask again + + + + )} +
@@ -606,16 +823,11 @@ class ErrorBoundary extends React.Component<{ children: ReactNode }, { error: Er override render() { if (this.state.error) { return ( - - - Runtime Error - - {this.state.error.message} - {this.state.error.stack && ( -
{this.state.error.stack}
- )} -
-
+ ); } return this.props.children; diff --git a/packages/hosts/mcp-apps-shell/src/shell/shell-html-url.d.ts b/packages/hosts/mcp-apps-shell/src/shell/shell-html-url.d.ts new file mode 100644 index 000000000..7c9b21430 --- /dev/null +++ b/packages/hosts/mcp-apps-shell/src/shell/shell-html-url.d.ts @@ -0,0 +1,13 @@ +/** + * The URL of the built, self-contained shell document, supplied by + * `mcpAppsShellAsset` (`@executor-js/mcp-apps-shell/vite`). + * + * Only a URL crosses this seam, never the document's bytes: the artifact page + * loads it into an iframe, so the 4.5 MB shell must stay out of the console's + * JS chunks. + */ +declare module "virtual:executor-mcp-apps-shell-html" { + const shellHtmlUrl: string; + export { shellHtmlUrl }; + export default shellHtmlUrl; +} diff --git a/packages/hosts/mcp-apps-shell/src/shell/smoke-harness-bundle.gen.ts b/packages/hosts/mcp-apps-shell/src/shell/smoke-harness-bundle.gen.ts new file mode 100644 index 000000000..54b766d65 --- /dev/null +++ b/packages/hosts/mcp-apps-shell/src/shell/smoke-harness-bundle.gen.ts @@ -0,0 +1,17 @@ +/** + * The smoke-render harness bundle, as source text. GENERATED — do not edit. + * + * Written by `bun run gen:smoke-harness` (scripts/gen-smoke-harness.ts) and + * regenerated by this package's `prepare`/`build:shell`, so a fresh checkout + * and every app build produce it before anything imports it. + * + * It is a committed placeholder rather than a build-time file read because its + * consumers include Workers hosts, where there is no filesystem to read from at + * runtime and every byte must be inlined by the bundler. `null` means "not + * generated here" — the smoke render then FAILS OPEN (saves and logs) exactly + * as it does when the sandbox itself is unavailable, so a partial checkout + * never blocks a create. + */ +const bundle: string | null = "\"use strict\";\n(() => {\n var __create = Object.create;\n var __defProp = Object.defineProperty;\n var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __getProtoOf = Object.getPrototypeOf;\n var __hasOwnProp = Object.prototype.hasOwnProperty;\n var __commonJS = (cb, mod) => function __require() {\n return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;\n };\n var __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n };\n var __copyProps = (to2, from2, except, desc) => {\n if (from2 && typeof from2 === \"object\" || typeof from2 === \"function\") {\n for (let key of __getOwnPropNames(from2))\n if (!__hasOwnProp.call(to2, key) && key !== except)\n __defProp(to2, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });\n }\n return to2;\n };\n var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n // If the importer is in node compatibility mode or this is not an ESM\n // file that has been converted to a CommonJS file using a Babel-\n // compatible transform (i.e. \"__esModule\" has not been set), then set\n // \"default\" to the CommonJS \"module.exports\" for node compatibility.\n isNodeMode || !mod || !mod.__esModule ? __defProp(target, \"default\", { value: mod, enumerable: true }) : target,\n mod\n ));\n\n // ../../../node_modules/.bun/react@19.2.5/node_modules/react/cjs/react.production.js\n var require_react_production = __commonJS({\n \"../../../node_modules/.bun/react@19.2.5/node_modules/react/cjs/react.production.js\"(exports) {\n \"use strict\";\n var REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for(\"react.transitional.element\");\n var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for(\"react.portal\");\n var REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for(\"react.fragment\");\n var REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for(\"react.strict_mode\");\n var REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for(\"react.profiler\");\n var REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for(\"react.consumer\");\n var REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for(\"react.context\");\n var REACT_FORWARD_REF_TYPE2 = /* @__PURE__ */ Symbol.for(\"react.forward_ref\");\n var REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for(\"react.suspense\");\n var REACT_MEMO_TYPE2 = /* @__PURE__ */ Symbol.for(\"react.memo\");\n var REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for(\"react.lazy\");\n var REACT_ACTIVITY_TYPE = /* @__PURE__ */ Symbol.for(\"react.activity\");\n var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;\n function getIteratorFn(maybeIterable) {\n if (null === maybeIterable || \"object\" !== typeof maybeIterable) return null;\n maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[\"@@iterator\"];\n return \"function\" === typeof maybeIterable ? maybeIterable : null;\n }\n var ReactNoopUpdateQueue = {\n isMounted: function() {\n return false;\n },\n enqueueForceUpdate: function() {\n },\n enqueueReplaceState: function() {\n },\n enqueueSetState: function() {\n }\n };\n var assign2 = Object.assign;\n var emptyObject = {};\n function Component2(props, context, updater) {\n this.props = props;\n this.context = context;\n this.refs = emptyObject;\n this.updater = updater || ReactNoopUpdateQueue;\n }\n Component2.prototype.isReactComponent = {};\n Component2.prototype.setState = function(partialState, callback) {\n if (\"object\" !== typeof partialState && \"function\" !== typeof partialState && null != partialState)\n throw Error(\n \"takes an object of state variables to update or a function which returns an object of state variables.\"\n );\n this.updater.enqueueSetState(this, partialState, callback, \"setState\");\n };\n Component2.prototype.forceUpdate = function(callback) {\n this.updater.enqueueForceUpdate(this, callback, \"forceUpdate\");\n };\n function ComponentDummy() {\n }\n ComponentDummy.prototype = Component2.prototype;\n function PureComponent6(props, context, updater) {\n this.props = props;\n this.context = context;\n this.refs = emptyObject;\n this.updater = updater || ReactNoopUpdateQueue;\n }\n var pureComponentPrototype = PureComponent6.prototype = new ComponentDummy();\n pureComponentPrototype.constructor = PureComponent6;\n assign2(pureComponentPrototype, Component2.prototype);\n pureComponentPrototype.isPureReactComponent = true;\n var isArrayImpl = Array.isArray;\n function noop7() {\n }\n var ReactSharedInternals = { H: null, A: null, T: null, S: null };\n var hasOwnProperty = Object.prototype.hasOwnProperty;\n function ReactElement(type, key, props) {\n var refProp = props.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type,\n key,\n ref: void 0 !== refProp ? refProp : null,\n props\n };\n }\n function cloneAndReplaceKey(oldElement, newKey) {\n return ReactElement(oldElement.type, newKey, oldElement.props);\n }\n function isValidElement27(object) {\n return \"object\" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;\n }\n function escape(key) {\n var escaperLookup = { \"=\": \"=0\", \":\": \"=2\" };\n return \"$\" + key.replace(/[=:]/g, function(match2) {\n return escaperLookup[match2];\n });\n }\n var userProvidedKeyEscapeRegex = /\\/+/g;\n function getElementKey(element, index2) {\n return \"object\" === typeof element && null !== element && null != element.key ? escape(\"\" + element.key) : index2.toString(36);\n }\n function resolveThenable(thenable) {\n switch (thenable.status) {\n case \"fulfilled\":\n return thenable.value;\n case \"rejected\":\n throw thenable.reason;\n default:\n switch (\"string\" === typeof thenable.status ? thenable.then(noop7, noop7) : (thenable.status = \"pending\", thenable.then(\n function(fulfilledValue) {\n \"pending\" === thenable.status && (thenable.status = \"fulfilled\", thenable.value = fulfilledValue);\n },\n function(error) {\n \"pending\" === thenable.status && (thenable.status = \"rejected\", thenable.reason = error);\n }\n )), thenable.status) {\n case \"fulfilled\":\n return thenable.value;\n case \"rejected\":\n throw thenable.reason;\n }\n }\n throw thenable;\n }\n function mapIntoArray(children, array2, escapedPrefix, nameSoFar, callback) {\n var type = typeof children;\n if (\"undefined\" === type || \"boolean\" === type) children = null;\n var invokeCallback = false;\n if (null === children) invokeCallback = true;\n else\n switch (type) {\n case \"bigint\":\n case \"string\":\n case \"number\":\n invokeCallback = true;\n break;\n case \"object\":\n switch (children.$$typeof) {\n case REACT_ELEMENT_TYPE:\n case REACT_PORTAL_TYPE:\n invokeCallback = true;\n break;\n case REACT_LAZY_TYPE:\n return invokeCallback = children._init, mapIntoArray(\n invokeCallback(children._payload),\n array2,\n escapedPrefix,\n nameSoFar,\n callback\n );\n }\n }\n if (invokeCallback)\n return callback = callback(children), invokeCallback = \"\" === nameSoFar ? \".\" + getElementKey(children, 0) : nameSoFar, isArrayImpl(callback) ? (escapedPrefix = \"\", null != invokeCallback && (escapedPrefix = invokeCallback.replace(userProvidedKeyEscapeRegex, \"$&/\") + \"/\"), mapIntoArray(callback, array2, escapedPrefix, \"\", function(c2) {\n return c2;\n })) : null != callback && (isValidElement27(callback) && (callback = cloneAndReplaceKey(\n callback,\n escapedPrefix + (null == callback.key || children && children.key === callback.key ? \"\" : (\"\" + callback.key).replace(\n userProvidedKeyEscapeRegex,\n \"$&/\"\n ) + \"/\") + invokeCallback\n )), array2.push(callback)), 1;\n invokeCallback = 0;\n var nextNamePrefix = \"\" === nameSoFar ? \".\" : nameSoFar + \":\";\n if (isArrayImpl(children))\n for (var i = 0; i < children.length; i++)\n nameSoFar = children[i], type = nextNamePrefix + getElementKey(nameSoFar, i), invokeCallback += mapIntoArray(\n nameSoFar,\n array2,\n escapedPrefix,\n type,\n callback\n );\n else if (i = getIteratorFn(children), \"function\" === typeof i)\n for (children = i.call(children), i = 0; !(nameSoFar = children.next()).done; )\n nameSoFar = nameSoFar.value, type = nextNamePrefix + getElementKey(nameSoFar, i++), invokeCallback += mapIntoArray(\n nameSoFar,\n array2,\n escapedPrefix,\n type,\n callback\n );\n else if (\"object\" === type) {\n if (\"function\" === typeof children.then)\n return mapIntoArray(\n resolveThenable(children),\n array2,\n escapedPrefix,\n nameSoFar,\n callback\n );\n array2 = String(children);\n throw Error(\n \"Objects are not valid as a React child (found: \" + (\"[object Object]\" === array2 ? \"object with keys {\" + Object.keys(children).join(\", \") + \"}\" : array2) + \"). If you meant to render a collection of children, use an array instead.\"\n );\n }\n return invokeCallback;\n }\n function mapChildren(children, func, context) {\n if (null == children) return children;\n var result = [], count3 = 0;\n mapIntoArray(children, result, \"\", \"\", function(child) {\n return func.call(context, child, count3++);\n });\n return result;\n }\n function lazyInitializer(payload) {\n if (-1 === payload._status) {\n var ctor = payload._result;\n ctor = ctor();\n ctor.then(\n function(moduleObject) {\n if (0 === payload._status || -1 === payload._status)\n payload._status = 1, payload._result = moduleObject;\n },\n function(error) {\n if (0 === payload._status || -1 === payload._status)\n payload._status = 2, payload._result = error;\n }\n );\n -1 === payload._status && (payload._status = 0, payload._result = ctor);\n }\n if (1 === payload._status) return payload._result.default;\n throw payload._result;\n }\n var reportGlobalError = \"function\" === typeof reportError ? reportError : function(error) {\n if (\"object\" === typeof window && \"function\" === typeof window.ErrorEvent) {\n var event = new window.ErrorEvent(\"error\", {\n bubbles: true,\n cancelable: true,\n message: \"object\" === typeof error && null !== error && \"string\" === typeof error.message ? String(error.message) : String(error),\n error\n });\n if (!window.dispatchEvent(event)) return;\n } else if (\"object\" === typeof process && \"function\" === typeof process.emit) {\n process.emit(\"uncaughtException\", error);\n return;\n }\n console.error(error);\n };\n var Children8 = {\n map: mapChildren,\n forEach: function(children, forEachFunc, forEachContext) {\n mapChildren(\n children,\n function() {\n forEachFunc.apply(this, arguments);\n },\n forEachContext\n );\n },\n count: function(children) {\n var n = 0;\n mapChildren(children, function() {\n n++;\n });\n return n;\n },\n toArray: function(children) {\n return mapChildren(children, function(child) {\n return child;\n }) || [];\n },\n only: function(children) {\n if (!isValidElement27(children))\n throw Error(\n \"React.Children.only expected to receive a single React element child.\"\n );\n return children;\n }\n };\n exports.Activity = REACT_ACTIVITY_TYPE;\n exports.Children = Children8;\n exports.Component = Component2;\n exports.Fragment = REACT_FRAGMENT_TYPE;\n exports.Profiler = REACT_PROFILER_TYPE;\n exports.PureComponent = PureComponent6;\n exports.StrictMode = REACT_STRICT_MODE_TYPE;\n exports.Suspense = REACT_SUSPENSE_TYPE;\n exports.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ReactSharedInternals;\n exports.__COMPILER_RUNTIME = {\n __proto__: null,\n c: function(size4) {\n return ReactSharedInternals.H.useMemoCache(size4);\n }\n };\n exports.cache = function(fn) {\n return function() {\n return fn.apply(null, arguments);\n };\n };\n exports.cacheSignal = function() {\n return null;\n };\n exports.cloneElement = function(element, config, children) {\n if (null === element || void 0 === element)\n throw Error(\n \"The argument must be a React element, but you passed \" + element + \".\"\n );\n var props = assign2({}, element.props), key = element.key;\n if (null != config)\n for (propName in void 0 !== config.key && (key = \"\" + config.key), config)\n !hasOwnProperty.call(config, propName) || \"key\" === propName || \"__self\" === propName || \"__source\" === propName || \"ref\" === propName && void 0 === config.ref || (props[propName] = config[propName]);\n var propName = arguments.length - 2;\n if (1 === propName) props.children = children;\n else if (1 < propName) {\n for (var childArray = Array(propName), i = 0; i < propName; i++)\n childArray[i] = arguments[i + 2];\n props.children = childArray;\n }\n return ReactElement(element.type, key, props);\n };\n exports.createContext = function(defaultValue) {\n defaultValue = {\n $$typeof: REACT_CONTEXT_TYPE,\n _currentValue: defaultValue,\n _currentValue2: defaultValue,\n _threadCount: 0,\n Provider: null,\n Consumer: null\n };\n defaultValue.Provider = defaultValue;\n defaultValue.Consumer = {\n $$typeof: REACT_CONSUMER_TYPE,\n _context: defaultValue\n };\n return defaultValue;\n };\n exports.createElement = function(type, config, children) {\n var propName, props = {}, key = null;\n if (null != config)\n for (propName in void 0 !== config.key && (key = \"\" + config.key), config)\n hasOwnProperty.call(config, propName) && \"key\" !== propName && \"__self\" !== propName && \"__source\" !== propName && (props[propName] = config[propName]);\n var childrenLength = arguments.length - 2;\n if (1 === childrenLength) props.children = children;\n else if (1 < childrenLength) {\n for (var childArray = Array(childrenLength), i = 0; i < childrenLength; i++)\n childArray[i] = arguments[i + 2];\n props.children = childArray;\n }\n if (type && type.defaultProps)\n for (propName in childrenLength = type.defaultProps, childrenLength)\n void 0 === props[propName] && (props[propName] = childrenLength[propName]);\n return ReactElement(type, key, props);\n };\n exports.createRef = function() {\n return { current: null };\n };\n exports.forwardRef = function(render2) {\n return { $$typeof: REACT_FORWARD_REF_TYPE2, render: render2 };\n };\n exports.isValidElement = isValidElement27;\n exports.lazy = function(ctor) {\n return {\n $$typeof: REACT_LAZY_TYPE,\n _payload: { _status: -1, _result: ctor },\n _init: lazyInitializer\n };\n };\n exports.memo = function(type, compare) {\n return {\n $$typeof: REACT_MEMO_TYPE2,\n type,\n compare: void 0 === compare ? null : compare\n };\n };\n exports.startTransition = function(scope) {\n var prevTransition = ReactSharedInternals.T, currentTransition = {};\n ReactSharedInternals.T = currentTransition;\n try {\n var returnValue = scope(), onStartTransitionFinish = ReactSharedInternals.S;\n null !== onStartTransitionFinish && onStartTransitionFinish(currentTransition, returnValue);\n \"object\" === typeof returnValue && null !== returnValue && \"function\" === typeof returnValue.then && returnValue.then(noop7, reportGlobalError);\n } catch (error) {\n reportGlobalError(error);\n } finally {\n null !== prevTransition && null !== currentTransition.types && (prevTransition.types = currentTransition.types), ReactSharedInternals.T = prevTransition;\n }\n };\n exports.unstable_useCacheRefresh = function() {\n return ReactSharedInternals.H.useCacheRefresh();\n };\n exports.use = function(usable) {\n return ReactSharedInternals.H.use(usable);\n };\n exports.useActionState = function(action, initialState15, permalink) {\n return ReactSharedInternals.H.useActionState(action, initialState15, permalink);\n };\n exports.useCallback = function(callback, deps) {\n return ReactSharedInternals.H.useCallback(callback, deps);\n };\n exports.useContext = function(Context) {\n return ReactSharedInternals.H.useContext(Context);\n };\n exports.useDebugValue = function() {\n };\n exports.useDeferredValue = function(value, initialValue) {\n return ReactSharedInternals.H.useDeferredValue(value, initialValue);\n };\n exports.useEffect = function(create, deps) {\n return ReactSharedInternals.H.useEffect(create, deps);\n };\n exports.useEffectEvent = function(callback) {\n return ReactSharedInternals.H.useEffectEvent(callback);\n };\n exports.useId = function() {\n return ReactSharedInternals.H.useId();\n };\n exports.useImperativeHandle = function(ref, create, deps) {\n return ReactSharedInternals.H.useImperativeHandle(ref, create, deps);\n };\n exports.useInsertionEffect = function(create, deps) {\n return ReactSharedInternals.H.useInsertionEffect(create, deps);\n };\n exports.useLayoutEffect = function(create, deps) {\n return ReactSharedInternals.H.useLayoutEffect(create, deps);\n };\n exports.useMemo = function(create, deps) {\n return ReactSharedInternals.H.useMemo(create, deps);\n };\n exports.useOptimistic = function(passthrough, reducer) {\n return ReactSharedInternals.H.useOptimistic(passthrough, reducer);\n };\n exports.useReducer = function(reducer, initialArg, init) {\n return ReactSharedInternals.H.useReducer(reducer, initialArg, init);\n };\n exports.useRef = function(initialValue) {\n return ReactSharedInternals.H.useRef(initialValue);\n };\n exports.useState = function(initialState15) {\n return ReactSharedInternals.H.useState(initialState15);\n };\n exports.useSyncExternalStore = function(subscribe2, getSnapshot, getServerSnapshot) {\n return ReactSharedInternals.H.useSyncExternalStore(\n subscribe2,\n getSnapshot,\n getServerSnapshot\n );\n };\n exports.useTransition = function() {\n return ReactSharedInternals.H.useTransition();\n };\n exports.version = \"19.2.5\";\n }\n });\n\n // ../../../node_modules/.bun/react@19.2.5/node_modules/react/index.js\n var require_react = __commonJS({\n \"../../../node_modules/.bun/react@19.2.5/node_modules/react/index.js\"(exports, module) {\n \"use strict\";\n if (true) {\n module.exports = require_react_production();\n } else {\n module.exports = null;\n }\n }\n });\n\n // ../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/cjs/react-dom.production.js\n var require_react_dom_production = __commonJS({\n \"../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/cjs/react-dom.production.js\"(exports) {\n \"use strict\";\n var React134 = require_react();\n function formatProdErrorMessage3(code) {\n var url = \"https://react.dev/errors/\" + code;\n if (1 < arguments.length) {\n url += \"?args[]=\" + encodeURIComponent(arguments[1]);\n for (var i = 2; i < arguments.length; i++)\n url += \"&args[]=\" + encodeURIComponent(arguments[i]);\n }\n return \"Minified React error #\" + code + \"; visit \" + url + \" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\";\n }\n function noop7() {\n }\n var Internals = {\n d: {\n f: noop7,\n r: function() {\n throw Error(formatProdErrorMessage3(522));\n },\n D: noop7,\n C: noop7,\n L: noop7,\n m: noop7,\n X: noop7,\n S: noop7,\n M: noop7\n },\n p: 0,\n findDOMNode: null\n };\n var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for(\"react.portal\");\n function createPortal$1(children, containerInfo, implementation) {\n var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;\n return {\n $$typeof: REACT_PORTAL_TYPE,\n key: null == key ? null : \"\" + key,\n children,\n containerInfo,\n implementation\n };\n }\n var ReactSharedInternals = React134.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;\n function getCrossOriginStringAs(as, input2) {\n if (\"font\" === as) return \"\";\n if (\"string\" === typeof input2)\n return \"use-credentials\" === input2 ? input2 : \"\";\n }\n exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;\n exports.createPortal = function(children, container) {\n var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;\n if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType)\n throw Error(formatProdErrorMessage3(299));\n return createPortal$1(children, container, null, key);\n };\n exports.flushSync = function(fn) {\n var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;\n try {\n if (ReactSharedInternals.T = null, Internals.p = 2, fn) return fn();\n } finally {\n ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f();\n }\n };\n exports.preconnect = function(href, options2) {\n \"string\" === typeof href && (options2 ? (options2 = options2.crossOrigin, options2 = \"string\" === typeof options2 ? \"use-credentials\" === options2 ? options2 : \"\" : void 0) : options2 = null, Internals.d.C(href, options2));\n };\n exports.prefetchDNS = function(href) {\n \"string\" === typeof href && Internals.d.D(href);\n };\n exports.preinit = function(href, options2) {\n if (\"string\" === typeof href && options2 && \"string\" === typeof options2.as) {\n var as = options2.as, crossOrigin = getCrossOriginStringAs(as, options2.crossOrigin), integrity = \"string\" === typeof options2.integrity ? options2.integrity : void 0, fetchPriority = \"string\" === typeof options2.fetchPriority ? options2.fetchPriority : void 0;\n \"style\" === as ? Internals.d.S(\n href,\n \"string\" === typeof options2.precedence ? options2.precedence : void 0,\n {\n crossOrigin,\n integrity,\n fetchPriority\n }\n ) : \"script\" === as && Internals.d.X(href, {\n crossOrigin,\n integrity,\n fetchPriority,\n nonce: \"string\" === typeof options2.nonce ? options2.nonce : void 0\n });\n }\n };\n exports.preinitModule = function(href, options2) {\n if (\"string\" === typeof href)\n if (\"object\" === typeof options2 && null !== options2) {\n if (null == options2.as || \"script\" === options2.as) {\n var crossOrigin = getCrossOriginStringAs(\n options2.as,\n options2.crossOrigin\n );\n Internals.d.M(href, {\n crossOrigin,\n integrity: \"string\" === typeof options2.integrity ? options2.integrity : void 0,\n nonce: \"string\" === typeof options2.nonce ? options2.nonce : void 0\n });\n }\n } else null == options2 && Internals.d.M(href);\n };\n exports.preload = function(href, options2) {\n if (\"string\" === typeof href && \"object\" === typeof options2 && null !== options2 && \"string\" === typeof options2.as) {\n var as = options2.as, crossOrigin = getCrossOriginStringAs(as, options2.crossOrigin);\n Internals.d.L(href, as, {\n crossOrigin,\n integrity: \"string\" === typeof options2.integrity ? options2.integrity : void 0,\n nonce: \"string\" === typeof options2.nonce ? options2.nonce : void 0,\n type: \"string\" === typeof options2.type ? options2.type : void 0,\n fetchPriority: \"string\" === typeof options2.fetchPriority ? options2.fetchPriority : void 0,\n referrerPolicy: \"string\" === typeof options2.referrerPolicy ? options2.referrerPolicy : void 0,\n imageSrcSet: \"string\" === typeof options2.imageSrcSet ? options2.imageSrcSet : void 0,\n imageSizes: \"string\" === typeof options2.imageSizes ? options2.imageSizes : void 0,\n media: \"string\" === typeof options2.media ? options2.media : void 0\n });\n }\n };\n exports.preloadModule = function(href, options2) {\n if (\"string\" === typeof href)\n if (options2) {\n var crossOrigin = getCrossOriginStringAs(options2.as, options2.crossOrigin);\n Internals.d.m(href, {\n as: \"string\" === typeof options2.as && \"script\" !== options2.as ? options2.as : void 0,\n crossOrigin,\n integrity: \"string\" === typeof options2.integrity ? options2.integrity : void 0\n });\n } else Internals.d.m(href);\n };\n exports.requestFormReset = function(form) {\n Internals.d.r(form);\n };\n exports.unstable_batchedUpdates = function(fn, a2) {\n return fn(a2);\n };\n exports.useFormState = function(action, initialState15, permalink) {\n return ReactSharedInternals.H.useFormState(action, initialState15, permalink);\n };\n exports.useFormStatus = function() {\n return ReactSharedInternals.H.useHostTransitionStatus();\n };\n exports.version = \"19.2.5\";\n }\n });\n\n // ../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/index.js\n var require_react_dom = __commonJS({\n \"../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/index.js\"(exports, module) {\n \"use strict\";\n function checkDCE() {\n if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === \"undefined\" || typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== \"function\") {\n return;\n }\n if (false) {\n throw new Error(\"^_^\");\n }\n try {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);\n } catch (err) {\n console.error(err);\n }\n }\n if (true) {\n checkDCE();\n module.exports = require_react_dom_production();\n } else {\n module.exports = null;\n }\n }\n });\n\n // ../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js\n var require_react_dom_server_legacy_browser_production = __commonJS({\n \"../../../node_modules/.bun/react-dom@19.2.5+3f10a4be4e334a9b/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js\"(exports) {\n \"use strict\";\n var React134 = require_react();\n var ReactDOM5 = require_react_dom();\n function formatProdErrorMessage3(code) {\n var url = \"https://react.dev/errors/\" + code;\n if (1 < arguments.length) {\n url += \"?args[]=\" + encodeURIComponent(arguments[1]);\n for (var i = 2; i < arguments.length; i++)\n url += \"&args[]=\" + encodeURIComponent(arguments[i]);\n }\n return \"Minified React error #\" + code + \"; visit \" + url + \" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\";\n }\n var REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for(\"react.transitional.element\");\n var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for(\"react.portal\");\n var REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for(\"react.fragment\");\n var REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for(\"react.strict_mode\");\n var REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for(\"react.profiler\");\n var REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for(\"react.consumer\");\n var REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for(\"react.context\");\n var REACT_FORWARD_REF_TYPE2 = /* @__PURE__ */ Symbol.for(\"react.forward_ref\");\n var REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for(\"react.suspense\");\n var REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for(\"react.suspense_list\");\n var REACT_MEMO_TYPE2 = /* @__PURE__ */ Symbol.for(\"react.memo\");\n var REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for(\"react.lazy\");\n var REACT_SCOPE_TYPE = /* @__PURE__ */ Symbol.for(\"react.scope\");\n var REACT_ACTIVITY_TYPE = /* @__PURE__ */ Symbol.for(\"react.activity\");\n var REACT_LEGACY_HIDDEN_TYPE = /* @__PURE__ */ Symbol.for(\"react.legacy_hidden\");\n var REACT_MEMO_CACHE_SENTINEL = /* @__PURE__ */ Symbol.for(\"react.memo_cache_sentinel\");\n var REACT_VIEW_TRANSITION_TYPE = /* @__PURE__ */ Symbol.for(\"react.view_transition\");\n var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;\n function getIteratorFn(maybeIterable) {\n if (null === maybeIterable || \"object\" !== typeof maybeIterable) return null;\n maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[\"@@iterator\"];\n return \"function\" === typeof maybeIterable ? maybeIterable : null;\n }\n var isArrayImpl = Array.isArray;\n function murmurhash3_32_gc(key, seed2) {\n var remainder = key.length & 3;\n var bytes = key.length - remainder;\n var h1 = seed2;\n for (seed2 = 0; seed2 < bytes; ) {\n var k1 = key.charCodeAt(seed2) & 255 | (key.charCodeAt(++seed2) & 255) << 8 | (key.charCodeAt(++seed2) & 255) << 16 | (key.charCodeAt(++seed2) & 255) << 24;\n ++seed2;\n k1 = 3432918353 * (k1 & 65535) + ((3432918353 * (k1 >>> 16) & 65535) << 16) & 4294967295;\n k1 = k1 << 15 | k1 >>> 17;\n k1 = 461845907 * (k1 & 65535) + ((461845907 * (k1 >>> 16) & 65535) << 16) & 4294967295;\n h1 ^= k1;\n h1 = h1 << 13 | h1 >>> 19;\n h1 = 5 * (h1 & 65535) + ((5 * (h1 >>> 16) & 65535) << 16) & 4294967295;\n h1 = (h1 & 65535) + 27492 + (((h1 >>> 16) + 58964 & 65535) << 16);\n }\n k1 = 0;\n switch (remainder) {\n case 3:\n k1 ^= (key.charCodeAt(seed2 + 2) & 255) << 16;\n case 2:\n k1 ^= (key.charCodeAt(seed2 + 1) & 255) << 8;\n case 1:\n k1 ^= key.charCodeAt(seed2) & 255, k1 = 3432918353 * (k1 & 65535) + ((3432918353 * (k1 >>> 16) & 65535) << 16) & 4294967295, k1 = k1 << 15 | k1 >>> 17, h1 ^= 461845907 * (k1 & 65535) + ((461845907 * (k1 >>> 16) & 65535) << 16) & 4294967295;\n }\n h1 ^= key.length;\n h1 ^= h1 >>> 16;\n h1 = 2246822507 * (h1 & 65535) + ((2246822507 * (h1 >>> 16) & 65535) << 16) & 4294967295;\n h1 ^= h1 >>> 13;\n h1 = 3266489909 * (h1 & 65535) + ((3266489909 * (h1 >>> 16) & 65535) << 16) & 4294967295;\n return (h1 ^ h1 >>> 16) >>> 0;\n }\n var assign2 = Object.assign;\n var hasOwnProperty = Object.prototype.hasOwnProperty;\n var VALID_ATTRIBUTE_NAME_REGEX = RegExp(\n \"^[:A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD][:A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\\\\-.0-9\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040]*$\"\n );\n var illegalAttributeNameCache = {};\n var validatedAttributeNameCache = {};\n function isAttributeNameSafe(attributeName) {\n if (hasOwnProperty.call(validatedAttributeNameCache, attributeName))\n return true;\n if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) return false;\n if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName))\n return validatedAttributeNameCache[attributeName] = true;\n illegalAttributeNameCache[attributeName] = true;\n return false;\n }\n var unitlessNumbers = new Set(\n \"animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp\".split(\n \" \"\n )\n );\n var aliases = /* @__PURE__ */ new Map([\n [\"acceptCharset\", \"accept-charset\"],\n [\"htmlFor\", \"for\"],\n [\"httpEquiv\", \"http-equiv\"],\n [\"crossOrigin\", \"crossorigin\"],\n [\"accentHeight\", \"accent-height\"],\n [\"alignmentBaseline\", \"alignment-baseline\"],\n [\"arabicForm\", \"arabic-form\"],\n [\"baselineShift\", \"baseline-shift\"],\n [\"capHeight\", \"cap-height\"],\n [\"clipPath\", \"clip-path\"],\n [\"clipRule\", \"clip-rule\"],\n [\"colorInterpolation\", \"color-interpolation\"],\n [\"colorInterpolationFilters\", \"color-interpolation-filters\"],\n [\"colorProfile\", \"color-profile\"],\n [\"colorRendering\", \"color-rendering\"],\n [\"dominantBaseline\", \"dominant-baseline\"],\n [\"enableBackground\", \"enable-background\"],\n [\"fillOpacity\", \"fill-opacity\"],\n [\"fillRule\", \"fill-rule\"],\n [\"floodColor\", \"flood-color\"],\n [\"floodOpacity\", \"flood-opacity\"],\n [\"fontFamily\", \"font-family\"],\n [\"fontSize\", \"font-size\"],\n [\"fontSizeAdjust\", \"font-size-adjust\"],\n [\"fontStretch\", \"font-stretch\"],\n [\"fontStyle\", \"font-style\"],\n [\"fontVariant\", \"font-variant\"],\n [\"fontWeight\", \"font-weight\"],\n [\"glyphName\", \"glyph-name\"],\n [\"glyphOrientationHorizontal\", \"glyph-orientation-horizontal\"],\n [\"glyphOrientationVertical\", \"glyph-orientation-vertical\"],\n [\"horizAdvX\", \"horiz-adv-x\"],\n [\"horizOriginX\", \"horiz-origin-x\"],\n [\"imageRendering\", \"image-rendering\"],\n [\"letterSpacing\", \"letter-spacing\"],\n [\"lightingColor\", \"lighting-color\"],\n [\"markerEnd\", \"marker-end\"],\n [\"markerMid\", \"marker-mid\"],\n [\"markerStart\", \"marker-start\"],\n [\"overlinePosition\", \"overline-position\"],\n [\"overlineThickness\", \"overline-thickness\"],\n [\"paintOrder\", \"paint-order\"],\n [\"panose-1\", \"panose-1\"],\n [\"pointerEvents\", \"pointer-events\"],\n [\"renderingIntent\", \"rendering-intent\"],\n [\"shapeRendering\", \"shape-rendering\"],\n [\"stopColor\", \"stop-color\"],\n [\"stopOpacity\", \"stop-opacity\"],\n [\"strikethroughPosition\", \"strikethrough-position\"],\n [\"strikethroughThickness\", \"strikethrough-thickness\"],\n [\"strokeDasharray\", \"stroke-dasharray\"],\n [\"strokeDashoffset\", \"stroke-dashoffset\"],\n [\"strokeLinecap\", \"stroke-linecap\"],\n [\"strokeLinejoin\", \"stroke-linejoin\"],\n [\"strokeMiterlimit\", \"stroke-miterlimit\"],\n [\"strokeOpacity\", \"stroke-opacity\"],\n [\"strokeWidth\", \"stroke-width\"],\n [\"textAnchor\", \"text-anchor\"],\n [\"textDecoration\", \"text-decoration\"],\n [\"textRendering\", \"text-rendering\"],\n [\"transformOrigin\", \"transform-origin\"],\n [\"underlinePosition\", \"underline-position\"],\n [\"underlineThickness\", \"underline-thickness\"],\n [\"unicodeBidi\", \"unicode-bidi\"],\n [\"unicodeRange\", \"unicode-range\"],\n [\"unitsPerEm\", \"units-per-em\"],\n [\"vAlphabetic\", \"v-alphabetic\"],\n [\"vHanging\", \"v-hanging\"],\n [\"vIdeographic\", \"v-ideographic\"],\n [\"vMathematical\", \"v-mathematical\"],\n [\"vectorEffect\", \"vector-effect\"],\n [\"vertAdvY\", \"vert-adv-y\"],\n [\"vertOriginX\", \"vert-origin-x\"],\n [\"vertOriginY\", \"vert-origin-y\"],\n [\"wordSpacing\", \"word-spacing\"],\n [\"writingMode\", \"writing-mode\"],\n [\"xmlnsXlink\", \"xmlns:xlink\"],\n [\"xHeight\", \"x-height\"]\n ]);\n var matchHtmlRegExp = /[\"'&<>]/;\n function escapeTextForBrowser(text) {\n if (\"boolean\" === typeof text || \"number\" === typeof text || \"bigint\" === typeof text)\n return \"\" + text;\n text = \"\" + text;\n var match2 = matchHtmlRegExp.exec(text);\n if (match2) {\n var html = \"\", index2, lastIndex = 0;\n for (index2 = match2.index; index2 < text.length; index2++) {\n switch (text.charCodeAt(index2)) {\n case 34:\n match2 = \""\";\n break;\n case 38:\n match2 = \"&\";\n break;\n case 39:\n match2 = \"'\";\n break;\n case 60:\n match2 = \"<\";\n break;\n case 62:\n match2 = \">\";\n break;\n default:\n continue;\n }\n lastIndex !== index2 && (html += text.slice(lastIndex, index2));\n lastIndex = index2 + 1;\n html += match2;\n }\n text = lastIndex !== index2 ? html + text.slice(lastIndex, index2) : html;\n }\n return text;\n }\n var uppercasePattern = /([A-Z])/g;\n var msPattern = /^ms-/;\n var isJavaScriptProtocol = /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*:/i;\n function sanitizeURL(url) {\n return isJavaScriptProtocol.test(\"\" + url) ? \"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')\" : url;\n }\n var ReactSharedInternals = React134.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;\n var ReactDOMSharedInternals = ReactDOM5.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;\n var sharedNotPendingObject = {\n pending: false,\n data: null,\n method: null,\n action: null\n };\n var previousDispatcher = ReactDOMSharedInternals.d;\n ReactDOMSharedInternals.d = {\n f: previousDispatcher.f,\n r: previousDispatcher.r,\n D: prefetchDNS,\n C: preconnect,\n L: preload,\n m: preloadModule,\n X: preinitScript,\n S: preinitStyle,\n M: preinitModuleScript\n };\n var PRELOAD_NO_CREDS = [];\n var currentlyFlushingRenderState = null;\n var scriptRegex = /(<\\/|<)(s)(cript)/gi;\n function scriptReplacer(match2, prefix3, s2, suffix3) {\n return \"\" + prefix3 + (\"s\" === s2 ? \"\\\\u0073\" : \"\\\\u0053\") + suffix3;\n }\n function createResumableState(identifierPrefix, externalRuntimeConfig, bootstrapScriptContent, bootstrapScripts, bootstrapModules) {\n return {\n idPrefix: void 0 === identifierPrefix ? \"\" : identifierPrefix,\n nextFormID: 0,\n streamingFormat: 0,\n bootstrapScriptContent,\n bootstrapScripts,\n bootstrapModules,\n instructions: 0,\n hasBody: false,\n hasHtml: false,\n unknownResources: {},\n dnsResources: {},\n connectResources: { default: {}, anonymous: {}, credentials: {} },\n imageResources: {},\n styleResources: {},\n scriptResources: {},\n moduleUnknownResources: {},\n moduleScriptResources: {}\n };\n }\n function createFormatContext(insertionMode, selectedValue, tagScope, viewTransition) {\n return {\n insertionMode,\n selectedValue,\n tagScope,\n viewTransition\n };\n }\n function getChildFormatContext(parentContext, type, props) {\n var subtreeScope = parentContext.tagScope & -25;\n switch (type) {\n case \"noscript\":\n return createFormatContext(2, null, subtreeScope | 1, null);\n case \"select\":\n return createFormatContext(\n 2,\n null != props.value ? props.value : props.defaultValue,\n subtreeScope,\n null\n );\n case \"svg\":\n return createFormatContext(4, null, subtreeScope, null);\n case \"picture\":\n return createFormatContext(2, null, subtreeScope | 2, null);\n case \"math\":\n return createFormatContext(5, null, subtreeScope, null);\n case \"foreignObject\":\n return createFormatContext(2, null, subtreeScope, null);\n case \"table\":\n return createFormatContext(6, null, subtreeScope, null);\n case \"thead\":\n case \"tbody\":\n case \"tfoot\":\n return createFormatContext(7, null, subtreeScope, null);\n case \"colgroup\":\n return createFormatContext(9, null, subtreeScope, null);\n case \"tr\":\n return createFormatContext(8, null, subtreeScope, null);\n case \"head\":\n if (2 > parentContext.insertionMode)\n return createFormatContext(3, null, subtreeScope, null);\n break;\n case \"html\":\n if (0 === parentContext.insertionMode)\n return createFormatContext(1, null, subtreeScope, null);\n }\n return 6 <= parentContext.insertionMode || 2 > parentContext.insertionMode ? createFormatContext(2, null, subtreeScope, null) : parentContext.tagScope !== subtreeScope ? createFormatContext(\n parentContext.insertionMode,\n parentContext.selectedValue,\n subtreeScope,\n null\n ) : parentContext;\n }\n function getSuspenseViewTransition(parentViewTransition) {\n return null === parentViewTransition ? null : {\n update: parentViewTransition.update,\n enter: \"none\",\n exit: \"none\",\n share: parentViewTransition.update,\n name: parentViewTransition.autoName,\n autoName: parentViewTransition.autoName,\n nameIdx: 0\n };\n }\n function getSuspenseFallbackFormatContext(resumableState, parentContext) {\n parentContext.tagScope & 32 && (resumableState.instructions |= 128);\n return createFormatContext(\n parentContext.insertionMode,\n parentContext.selectedValue,\n parentContext.tagScope | 12,\n getSuspenseViewTransition(parentContext.viewTransition)\n );\n }\n function getSuspenseContentFormatContext(resumableState, parentContext) {\n resumableState = getSuspenseViewTransition(parentContext.viewTransition);\n var subtreeScope = parentContext.tagScope | 16;\n null !== resumableState && \"none\" !== resumableState.share && (subtreeScope |= 64);\n return createFormatContext(\n parentContext.insertionMode,\n parentContext.selectedValue,\n subtreeScope,\n resumableState\n );\n }\n var styleNameCache = /* @__PURE__ */ new Map();\n function pushStyleAttribute(target, style) {\n if (\"object\" !== typeof style) throw Error(formatProdErrorMessage3(62));\n var isFirst = true, styleName;\n for (styleName in style)\n if (hasOwnProperty.call(style, styleName)) {\n var styleValue = style[styleName];\n if (null != styleValue && \"boolean\" !== typeof styleValue && \"\" !== styleValue) {\n if (0 === styleName.indexOf(\"--\")) {\n var nameChunk = escapeTextForBrowser(styleName);\n styleValue = escapeTextForBrowser((\"\" + styleValue).trim());\n } else\n nameChunk = styleNameCache.get(styleName), void 0 === nameChunk && (nameChunk = escapeTextForBrowser(\n styleName.replace(uppercasePattern, \"-$1\").toLowerCase().replace(msPattern, \"-ms-\")\n ), styleNameCache.set(styleName, nameChunk)), styleValue = \"number\" === typeof styleValue ? 0 === styleValue || unitlessNumbers.has(styleName) ? \"\" + styleValue : styleValue + \"px\" : escapeTextForBrowser((\"\" + styleValue).trim());\n isFirst ? (isFirst = false, target.push(' style=\"', nameChunk, \":\", styleValue)) : target.push(\";\", nameChunk, \":\", styleValue);\n }\n }\n isFirst || target.push('\"');\n }\n function pushBooleanAttribute(target, name, value) {\n value && \"function\" !== typeof value && \"symbol\" !== typeof value && target.push(\" \", name, '=\"\"');\n }\n function pushStringAttribute(target, name, value) {\n \"function\" !== typeof value && \"symbol\" !== typeof value && \"boolean\" !== typeof value && target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n }\n var actionJavaScriptURL = escapeTextForBrowser(\n \"javascript:throw new Error('React form unexpectedly submitted.')\"\n );\n function pushAdditionalFormField(value, key) {\n this.push('\");\n }\n function validateAdditionalFormField(value) {\n if (\"string\" !== typeof value) throw Error(formatProdErrorMessage3(480));\n }\n function getCustomFormFields(resumableState, formAction) {\n if (\"function\" === typeof formAction.$$FORM_ACTION) {\n var id = resumableState.nextFormID++;\n resumableState = resumableState.idPrefix + id;\n try {\n var customFields = formAction.$$FORM_ACTION(resumableState);\n if (customFields) {\n var formData = customFields.data;\n null != formData && formData.forEach(validateAdditionalFormField);\n }\n return customFields;\n } catch (x2) {\n if (\"object\" === typeof x2 && null !== x2 && \"function\" === typeof x2.then)\n throw x2;\n }\n }\n return null;\n }\n function pushFormActionAttribute(target, resumableState, renderState, formAction, formEncType, formMethod, formTarget, name) {\n var formData = null;\n if (\"function\" === typeof formAction) {\n var customFields = getCustomFormFields(resumableState, formAction);\n null !== customFields ? (name = customFields.name, formAction = customFields.action || \"\", formEncType = customFields.encType, formMethod = customFields.method, formTarget = customFields.target, formData = customFields.data) : (target.push(\" \", \"formAction\", '=\"', actionJavaScriptURL, '\"'), formTarget = formMethod = formEncType = formAction = name = null, injectFormReplayingRuntime(resumableState, renderState));\n }\n null != name && pushAttribute(target, \"name\", name);\n null != formAction && pushAttribute(target, \"formAction\", formAction);\n null != formEncType && pushAttribute(target, \"formEncType\", formEncType);\n null != formMethod && pushAttribute(target, \"formMethod\", formMethod);\n null != formTarget && pushAttribute(target, \"formTarget\", formTarget);\n return formData;\n }\n function pushAttribute(target, name, value) {\n switch (name) {\n case \"className\":\n pushStringAttribute(target, \"class\", value);\n break;\n case \"tabIndex\":\n pushStringAttribute(target, \"tabindex\", value);\n break;\n case \"dir\":\n case \"role\":\n case \"viewBox\":\n case \"width\":\n case \"height\":\n pushStringAttribute(target, name, value);\n break;\n case \"style\":\n pushStyleAttribute(target, value);\n break;\n case \"src\":\n case \"href\":\n if (\"\" === value) break;\n case \"action\":\n case \"formAction\":\n if (null == value || \"function\" === typeof value || \"symbol\" === typeof value || \"boolean\" === typeof value)\n break;\n value = sanitizeURL(\"\" + value);\n target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"defaultValue\":\n case \"defaultChecked\":\n case \"innerHTML\":\n case \"suppressContentEditableWarning\":\n case \"suppressHydrationWarning\":\n case \"ref\":\n break;\n case \"autoFocus\":\n case \"multiple\":\n case \"muted\":\n pushBooleanAttribute(target, name.toLowerCase(), value);\n break;\n case \"xlinkHref\":\n if (\"function\" === typeof value || \"symbol\" === typeof value || \"boolean\" === typeof value)\n break;\n value = sanitizeURL(\"\" + value);\n target.push(\" \", \"xlink:href\", '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"contentEditable\":\n case \"spellCheck\":\n case \"draggable\":\n case \"value\":\n case \"autoReverse\":\n case \"externalResourcesRequired\":\n case \"focusable\":\n case \"preserveAlpha\":\n \"function\" !== typeof value && \"symbol\" !== typeof value && target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"inert\":\n case \"allowFullScreen\":\n case \"async\":\n case \"autoPlay\":\n case \"controls\":\n case \"default\":\n case \"defer\":\n case \"disabled\":\n case \"disablePictureInPicture\":\n case \"disableRemotePlayback\":\n case \"formNoValidate\":\n case \"hidden\":\n case \"loop\":\n case \"noModule\":\n case \"noValidate\":\n case \"open\":\n case \"playsInline\":\n case \"readOnly\":\n case \"required\":\n case \"reversed\":\n case \"scoped\":\n case \"seamless\":\n case \"itemScope\":\n value && \"function\" !== typeof value && \"symbol\" !== typeof value && target.push(\" \", name, '=\"\"');\n break;\n case \"capture\":\n case \"download\":\n true === value ? target.push(\" \", name, '=\"\"') : false !== value && \"function\" !== typeof value && \"symbol\" !== typeof value && target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"cols\":\n case \"rows\":\n case \"size\":\n case \"span\":\n \"function\" !== typeof value && \"symbol\" !== typeof value && !isNaN(value) && 1 <= value && target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"rowSpan\":\n case \"start\":\n \"function\" === typeof value || \"symbol\" === typeof value || isNaN(value) || target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n break;\n case \"xlinkActuate\":\n pushStringAttribute(target, \"xlink:actuate\", value);\n break;\n case \"xlinkArcrole\":\n pushStringAttribute(target, \"xlink:arcrole\", value);\n break;\n case \"xlinkRole\":\n pushStringAttribute(target, \"xlink:role\", value);\n break;\n case \"xlinkShow\":\n pushStringAttribute(target, \"xlink:show\", value);\n break;\n case \"xlinkTitle\":\n pushStringAttribute(target, \"xlink:title\", value);\n break;\n case \"xlinkType\":\n pushStringAttribute(target, \"xlink:type\", value);\n break;\n case \"xmlBase\":\n pushStringAttribute(target, \"xml:base\", value);\n break;\n case \"xmlLang\":\n pushStringAttribute(target, \"xml:lang\", value);\n break;\n case \"xmlSpace\":\n pushStringAttribute(target, \"xml:space\", value);\n break;\n default:\n if (!(2 < name.length) || \"o\" !== name[0] && \"O\" !== name[0] || \"n\" !== name[1] && \"N\" !== name[1]) {\n if (name = aliases.get(name) || name, isAttributeNameSafe(name)) {\n switch (typeof value) {\n case \"function\":\n case \"symbol\":\n return;\n case \"boolean\":\n var prefix$8 = name.toLowerCase().slice(0, 5);\n if (\"data-\" !== prefix$8 && \"aria-\" !== prefix$8) return;\n }\n target.push(\" \", name, '=\"', escapeTextForBrowser(value), '\"');\n }\n }\n }\n }\n function pushInnerHTML(target, innerHTML, children) {\n if (null != innerHTML) {\n if (null != children) throw Error(formatProdErrorMessage3(60));\n if (\"object\" !== typeof innerHTML || !(\"__html\" in innerHTML))\n throw Error(formatProdErrorMessage3(61));\n innerHTML = innerHTML.__html;\n null !== innerHTML && void 0 !== innerHTML && target.push(\"\" + innerHTML);\n }\n }\n function flattenOptionChildren(children) {\n var content = \"\";\n React134.Children.forEach(children, function(child) {\n null != child && (content += child);\n });\n return content;\n }\n function injectFormReplayingRuntime(resumableState, renderState) {\n if (0 === (resumableState.instructions & 16)) {\n resumableState.instructions |= 16;\n var preamble = renderState.preamble, bootstrapChunks = renderState.bootstrapChunks;\n (preamble.htmlChunks || preamble.headChunks) && 0 === bootstrapChunks.length ? (bootstrapChunks.push(renderState.startInlineScript), pushCompletedShellIdAttribute(bootstrapChunks, resumableState), bootstrapChunks.push(\n \">\",\n `addEventListener(\"submit\",function(a){if(!a.defaultPrevented){var c=a.target,d=a.submitter,e=c.action,b=d;if(d){var f=d.getAttribute(\"formAction\");null!=f&&(e=f,b=null)}\"javascript:throw new Error('React form unexpectedly submitted.')\"===e&&(a.preventDefault(),b?(a=document.createElement(\"input\"),a.name=b.name,a.value=b.value,b.parentNode.insertBefore(a,b),b=new FormData(c),a.parentNode.removeChild(a)):b=new FormData(c),a=c.ownerDocument||c,(a.$$reactFormReplay=a.$$reactFormReplay||[]).push(c,d,b))}});`,\n \"<\\/script>\"\n )) : bootstrapChunks.unshift(\n renderState.startInlineScript,\n \">\",\n `addEventListener(\"submit\",function(a){if(!a.defaultPrevented){var c=a.target,d=a.submitter,e=c.action,b=d;if(d){var f=d.getAttribute(\"formAction\");null!=f&&(e=f,b=null)}\"javascript:throw new Error('React form unexpectedly submitted.')\"===e&&(a.preventDefault(),b?(a=document.createElement(\"input\"),a.name=b.name,a.value=b.value,b.parentNode.insertBefore(a,b),b=new FormData(c),a.parentNode.removeChild(a)):b=new FormData(c),a=c.ownerDocument||c,(a.$$reactFormReplay=a.$$reactFormReplay||[]).push(c,d,b))}});`,\n \"<\\/script>\"\n );\n }\n }\n function pushLinkImpl(target, props) {\n target.push(startChunkForTag(\"link\"));\n for (var propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n case \"dangerouslySetInnerHTML\":\n throw Error(formatProdErrorMessage3(399, \"link\"));\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\"/>\");\n return null;\n }\n var styleRegex = /(<\\/|<)(s)(tyle)/gi;\n function styleReplacer(match2, prefix3, s2, suffix3) {\n return \"\" + prefix3 + (\"s\" === s2 ? \"\\\\73 \" : \"\\\\53 \") + suffix3;\n }\n function pushSelfClosing(target, props, tag) {\n target.push(startChunkForTag(tag));\n for (var propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n case \"dangerouslySetInnerHTML\":\n throw Error(formatProdErrorMessage3(399, tag));\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\"/>\");\n return null;\n }\n function pushTitleImpl(target, props) {\n target.push(startChunkForTag(\"title\"));\n var children = null, innerHTML = null, propKey;\n for (propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n children = propValue;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML = propValue;\n break;\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\">\");\n props = Array.isArray(children) ? 2 > children.length ? children[0] : null : children;\n \"function\" !== typeof props && \"symbol\" !== typeof props && null !== props && void 0 !== props && target.push(escapeTextForBrowser(\"\" + props));\n pushInnerHTML(target, innerHTML, children);\n target.push(endChunkForTag(\"title\"));\n return null;\n }\n function pushScriptImpl(target, props) {\n target.push(startChunkForTag(\"script\"));\n var children = null, innerHTML = null, propKey;\n for (propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n children = propValue;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML = propValue;\n break;\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\">\");\n pushInnerHTML(target, innerHTML, children);\n \"string\" === typeof children && target.push((\"\" + children).replace(scriptRegex, scriptReplacer));\n target.push(endChunkForTag(\"script\"));\n return null;\n }\n function pushStartSingletonElement(target, props, tag) {\n target.push(startChunkForTag(tag));\n var innerHTML = tag = null, propKey;\n for (propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n tag = propValue;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML = propValue;\n break;\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\">\");\n pushInnerHTML(target, innerHTML, tag);\n return tag;\n }\n function pushStartGenericElement(target, props, tag) {\n target.push(startChunkForTag(tag));\n var innerHTML = tag = null, propKey;\n for (propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n tag = propValue;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML = propValue;\n break;\n default:\n pushAttribute(target, propKey, propValue);\n }\n }\n target.push(\">\");\n pushInnerHTML(target, innerHTML, tag);\n return \"string\" === typeof tag ? (target.push(escapeTextForBrowser(tag)), null) : tag;\n }\n var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\\.\\-\\d]*$/;\n var validatedTagCache = /* @__PURE__ */ new Map();\n function startChunkForTag(tag) {\n var tagStartChunk = validatedTagCache.get(tag);\n if (void 0 === tagStartChunk) {\n if (!VALID_TAG_REGEX.test(tag))\n throw Error(formatProdErrorMessage3(65, tag));\n tagStartChunk = \"<\" + tag;\n validatedTagCache.set(tag, tagStartChunk);\n }\n return tagStartChunk;\n }\n function pushStartInstance(target$jscomp$0, type, props, resumableState, renderState, preambleState, hoistableState, formatContext, textEmbedded) {\n switch (type) {\n case \"div\":\n case \"span\":\n case \"svg\":\n case \"path\":\n break;\n case \"a\":\n target$jscomp$0.push(startChunkForTag(\"a\"));\n var children = null, innerHTML = null, propKey;\n for (propKey in props)\n if (hasOwnProperty.call(props, propKey)) {\n var propValue = props[propKey];\n if (null != propValue)\n switch (propKey) {\n case \"children\":\n children = propValue;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML = propValue;\n break;\n case \"href\":\n \"\" === propValue ? pushStringAttribute(target$jscomp$0, \"href\", \"\") : pushAttribute(target$jscomp$0, propKey, propValue);\n break;\n default:\n pushAttribute(target$jscomp$0, propKey, propValue);\n }\n }\n target$jscomp$0.push(\">\");\n pushInnerHTML(target$jscomp$0, innerHTML, children);\n if (\"string\" === typeof children) {\n target$jscomp$0.push(escapeTextForBrowser(children));\n var JSCompiler_inline_result = null;\n } else JSCompiler_inline_result = children;\n return JSCompiler_inline_result;\n case \"g\":\n case \"p\":\n case \"li\":\n break;\n case \"select\":\n target$jscomp$0.push(startChunkForTag(\"select\"));\n var children$jscomp$0 = null, innerHTML$jscomp$0 = null, propKey$jscomp$0;\n for (propKey$jscomp$0 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$0)) {\n var propValue$jscomp$0 = props[propKey$jscomp$0];\n if (null != propValue$jscomp$0)\n switch (propKey$jscomp$0) {\n case \"children\":\n children$jscomp$0 = propValue$jscomp$0;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$0 = propValue$jscomp$0;\n break;\n case \"defaultValue\":\n case \"value\":\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$0,\n propValue$jscomp$0\n );\n }\n }\n target$jscomp$0.push(\">\");\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$0, children$jscomp$0);\n return children$jscomp$0;\n case \"option\":\n var selectedValue = formatContext.selectedValue;\n target$jscomp$0.push(startChunkForTag(\"option\"));\n var children$jscomp$1 = null, value = null, selected = null, innerHTML$jscomp$1 = null, propKey$jscomp$1;\n for (propKey$jscomp$1 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$1)) {\n var propValue$jscomp$1 = props[propKey$jscomp$1];\n if (null != propValue$jscomp$1)\n switch (propKey$jscomp$1) {\n case \"children\":\n children$jscomp$1 = propValue$jscomp$1;\n break;\n case \"selected\":\n selected = propValue$jscomp$1;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$1 = propValue$jscomp$1;\n break;\n case \"value\":\n value = propValue$jscomp$1;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$1,\n propValue$jscomp$1\n );\n }\n }\n if (null != selectedValue) {\n var stringValue = null !== value ? \"\" + value : flattenOptionChildren(children$jscomp$1);\n if (isArrayImpl(selectedValue))\n for (var i = 0; i < selectedValue.length; i++) {\n if (\"\" + selectedValue[i] === stringValue) {\n target$jscomp$0.push(' selected=\"\"');\n break;\n }\n }\n else\n \"\" + selectedValue === stringValue && target$jscomp$0.push(' selected=\"\"');\n } else selected && target$jscomp$0.push(' selected=\"\"');\n target$jscomp$0.push(\">\");\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$1, children$jscomp$1);\n return children$jscomp$1;\n case \"textarea\":\n target$jscomp$0.push(startChunkForTag(\"textarea\"));\n var value$jscomp$0 = null, defaultValue = null, children$jscomp$2 = null, propKey$jscomp$2;\n for (propKey$jscomp$2 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$2)) {\n var propValue$jscomp$2 = props[propKey$jscomp$2];\n if (null != propValue$jscomp$2)\n switch (propKey$jscomp$2) {\n case \"children\":\n children$jscomp$2 = propValue$jscomp$2;\n break;\n case \"value\":\n value$jscomp$0 = propValue$jscomp$2;\n break;\n case \"defaultValue\":\n defaultValue = propValue$jscomp$2;\n break;\n case \"dangerouslySetInnerHTML\":\n throw Error(formatProdErrorMessage3(91));\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$2,\n propValue$jscomp$2\n );\n }\n }\n null === value$jscomp$0 && null !== defaultValue && (value$jscomp$0 = defaultValue);\n target$jscomp$0.push(\">\");\n if (null != children$jscomp$2) {\n if (null != value$jscomp$0) throw Error(formatProdErrorMessage3(92));\n if (isArrayImpl(children$jscomp$2)) {\n if (1 < children$jscomp$2.length)\n throw Error(formatProdErrorMessage3(93));\n value$jscomp$0 = \"\" + children$jscomp$2[0];\n }\n value$jscomp$0 = \"\" + children$jscomp$2;\n }\n \"string\" === typeof value$jscomp$0 && \"\\n\" === value$jscomp$0[0] && target$jscomp$0.push(\"\\n\");\n null !== value$jscomp$0 && target$jscomp$0.push(escapeTextForBrowser(\"\" + value$jscomp$0));\n return null;\n case \"input\":\n target$jscomp$0.push(startChunkForTag(\"input\"));\n var name = null, formAction = null, formEncType = null, formMethod = null, formTarget = null, value$jscomp$1 = null, defaultValue$jscomp$0 = null, checked = null, defaultChecked = null, propKey$jscomp$3;\n for (propKey$jscomp$3 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$3)) {\n var propValue$jscomp$3 = props[propKey$jscomp$3];\n if (null != propValue$jscomp$3)\n switch (propKey$jscomp$3) {\n case \"children\":\n case \"dangerouslySetInnerHTML\":\n throw Error(formatProdErrorMessage3(399, \"input\"));\n case \"name\":\n name = propValue$jscomp$3;\n break;\n case \"formAction\":\n formAction = propValue$jscomp$3;\n break;\n case \"formEncType\":\n formEncType = propValue$jscomp$3;\n break;\n case \"formMethod\":\n formMethod = propValue$jscomp$3;\n break;\n case \"formTarget\":\n formTarget = propValue$jscomp$3;\n break;\n case \"defaultChecked\":\n defaultChecked = propValue$jscomp$3;\n break;\n case \"defaultValue\":\n defaultValue$jscomp$0 = propValue$jscomp$3;\n break;\n case \"checked\":\n checked = propValue$jscomp$3;\n break;\n case \"value\":\n value$jscomp$1 = propValue$jscomp$3;\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$3,\n propValue$jscomp$3\n );\n }\n }\n var formData = pushFormActionAttribute(\n target$jscomp$0,\n resumableState,\n renderState,\n formAction,\n formEncType,\n formMethod,\n formTarget,\n name\n );\n null !== checked ? pushBooleanAttribute(target$jscomp$0, \"checked\", checked) : null !== defaultChecked && pushBooleanAttribute(target$jscomp$0, \"checked\", defaultChecked);\n null !== value$jscomp$1 ? pushAttribute(target$jscomp$0, \"value\", value$jscomp$1) : null !== defaultValue$jscomp$0 && pushAttribute(target$jscomp$0, \"value\", defaultValue$jscomp$0);\n target$jscomp$0.push(\"/>\");\n null != formData && formData.forEach(pushAdditionalFormField, target$jscomp$0);\n return null;\n case \"button\":\n target$jscomp$0.push(startChunkForTag(\"button\"));\n var children$jscomp$3 = null, innerHTML$jscomp$2 = null, name$jscomp$0 = null, formAction$jscomp$0 = null, formEncType$jscomp$0 = null, formMethod$jscomp$0 = null, formTarget$jscomp$0 = null, propKey$jscomp$4;\n for (propKey$jscomp$4 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$4)) {\n var propValue$jscomp$4 = props[propKey$jscomp$4];\n if (null != propValue$jscomp$4)\n switch (propKey$jscomp$4) {\n case \"children\":\n children$jscomp$3 = propValue$jscomp$4;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$2 = propValue$jscomp$4;\n break;\n case \"name\":\n name$jscomp$0 = propValue$jscomp$4;\n break;\n case \"formAction\":\n formAction$jscomp$0 = propValue$jscomp$4;\n break;\n case \"formEncType\":\n formEncType$jscomp$0 = propValue$jscomp$4;\n break;\n case \"formMethod\":\n formMethod$jscomp$0 = propValue$jscomp$4;\n break;\n case \"formTarget\":\n formTarget$jscomp$0 = propValue$jscomp$4;\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$4,\n propValue$jscomp$4\n );\n }\n }\n var formData$jscomp$0 = pushFormActionAttribute(\n target$jscomp$0,\n resumableState,\n renderState,\n formAction$jscomp$0,\n formEncType$jscomp$0,\n formMethod$jscomp$0,\n formTarget$jscomp$0,\n name$jscomp$0\n );\n target$jscomp$0.push(\">\");\n null != formData$jscomp$0 && formData$jscomp$0.forEach(pushAdditionalFormField, target$jscomp$0);\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$2, children$jscomp$3);\n if (\"string\" === typeof children$jscomp$3) {\n target$jscomp$0.push(escapeTextForBrowser(children$jscomp$3));\n var JSCompiler_inline_result$jscomp$0 = null;\n } else JSCompiler_inline_result$jscomp$0 = children$jscomp$3;\n return JSCompiler_inline_result$jscomp$0;\n case \"form\":\n target$jscomp$0.push(startChunkForTag(\"form\"));\n var children$jscomp$4 = null, innerHTML$jscomp$3 = null, formAction$jscomp$1 = null, formEncType$jscomp$1 = null, formMethod$jscomp$1 = null, formTarget$jscomp$1 = null, propKey$jscomp$5;\n for (propKey$jscomp$5 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$5)) {\n var propValue$jscomp$5 = props[propKey$jscomp$5];\n if (null != propValue$jscomp$5)\n switch (propKey$jscomp$5) {\n case \"children\":\n children$jscomp$4 = propValue$jscomp$5;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$3 = propValue$jscomp$5;\n break;\n case \"action\":\n formAction$jscomp$1 = propValue$jscomp$5;\n break;\n case \"encType\":\n formEncType$jscomp$1 = propValue$jscomp$5;\n break;\n case \"method\":\n formMethod$jscomp$1 = propValue$jscomp$5;\n break;\n case \"target\":\n formTarget$jscomp$1 = propValue$jscomp$5;\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$5,\n propValue$jscomp$5\n );\n }\n }\n var formData$jscomp$1 = null, formActionName = null;\n if (\"function\" === typeof formAction$jscomp$1) {\n var customFields = getCustomFormFields(\n resumableState,\n formAction$jscomp$1\n );\n null !== customFields ? (formAction$jscomp$1 = customFields.action || \"\", formEncType$jscomp$1 = customFields.encType, formMethod$jscomp$1 = customFields.method, formTarget$jscomp$1 = customFields.target, formData$jscomp$1 = customFields.data, formActionName = customFields.name) : (target$jscomp$0.push(\n \" \",\n \"action\",\n '=\"',\n actionJavaScriptURL,\n '\"'\n ), formTarget$jscomp$1 = formMethod$jscomp$1 = formEncType$jscomp$1 = formAction$jscomp$1 = null, injectFormReplayingRuntime(resumableState, renderState));\n }\n null != formAction$jscomp$1 && pushAttribute(target$jscomp$0, \"action\", formAction$jscomp$1);\n null != formEncType$jscomp$1 && pushAttribute(target$jscomp$0, \"encType\", formEncType$jscomp$1);\n null != formMethod$jscomp$1 && pushAttribute(target$jscomp$0, \"method\", formMethod$jscomp$1);\n null != formTarget$jscomp$1 && pushAttribute(target$jscomp$0, \"target\", formTarget$jscomp$1);\n target$jscomp$0.push(\">\");\n null !== formActionName && (target$jscomp$0.push('\"), null != formData$jscomp$1 && formData$jscomp$1.forEach(pushAdditionalFormField, target$jscomp$0));\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$3, children$jscomp$4);\n if (\"string\" === typeof children$jscomp$4) {\n target$jscomp$0.push(escapeTextForBrowser(children$jscomp$4));\n var JSCompiler_inline_result$jscomp$1 = null;\n } else JSCompiler_inline_result$jscomp$1 = children$jscomp$4;\n return JSCompiler_inline_result$jscomp$1;\n case \"menuitem\":\n target$jscomp$0.push(startChunkForTag(\"menuitem\"));\n for (var propKey$jscomp$6 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$6)) {\n var propValue$jscomp$6 = props[propKey$jscomp$6];\n if (null != propValue$jscomp$6)\n switch (propKey$jscomp$6) {\n case \"children\":\n case \"dangerouslySetInnerHTML\":\n throw Error(formatProdErrorMessage3(400));\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$6,\n propValue$jscomp$6\n );\n }\n }\n target$jscomp$0.push(\">\");\n return null;\n case \"object\":\n target$jscomp$0.push(startChunkForTag(\"object\"));\n var children$jscomp$5 = null, innerHTML$jscomp$4 = null, propKey$jscomp$7;\n for (propKey$jscomp$7 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$7)) {\n var propValue$jscomp$7 = props[propKey$jscomp$7];\n if (null != propValue$jscomp$7)\n switch (propKey$jscomp$7) {\n case \"children\":\n children$jscomp$5 = propValue$jscomp$7;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$4 = propValue$jscomp$7;\n break;\n case \"data\":\n var sanitizedValue = sanitizeURL(\"\" + propValue$jscomp$7);\n if (\"\" === sanitizedValue) break;\n target$jscomp$0.push(\n \" \",\n \"data\",\n '=\"',\n escapeTextForBrowser(sanitizedValue),\n '\"'\n );\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$7,\n propValue$jscomp$7\n );\n }\n }\n target$jscomp$0.push(\">\");\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$4, children$jscomp$5);\n if (\"string\" === typeof children$jscomp$5) {\n target$jscomp$0.push(escapeTextForBrowser(children$jscomp$5));\n var JSCompiler_inline_result$jscomp$2 = null;\n } else JSCompiler_inline_result$jscomp$2 = children$jscomp$5;\n return JSCompiler_inline_result$jscomp$2;\n case \"title\":\n var noscriptTagInScope = formatContext.tagScope & 1, isFallback = formatContext.tagScope & 4;\n if (4 === formatContext.insertionMode || noscriptTagInScope || null != props.itemProp)\n var JSCompiler_inline_result$jscomp$3 = pushTitleImpl(\n target$jscomp$0,\n props\n );\n else\n isFallback ? JSCompiler_inline_result$jscomp$3 = null : (pushTitleImpl(renderState.hoistableChunks, props), JSCompiler_inline_result$jscomp$3 = void 0);\n return JSCompiler_inline_result$jscomp$3;\n case \"link\":\n var noscriptTagInScope$jscomp$0 = formatContext.tagScope & 1, isFallback$jscomp$0 = formatContext.tagScope & 4, rel = props.rel, href = props.href, precedence = props.precedence;\n if (4 === formatContext.insertionMode || noscriptTagInScope$jscomp$0 || null != props.itemProp || \"string\" !== typeof rel || \"string\" !== typeof href || \"\" === href) {\n pushLinkImpl(target$jscomp$0, props);\n var JSCompiler_inline_result$jscomp$4 = null;\n } else if (\"stylesheet\" === props.rel)\n if (\"string\" !== typeof precedence || null != props.disabled || props.onLoad || props.onError)\n JSCompiler_inline_result$jscomp$4 = pushLinkImpl(\n target$jscomp$0,\n props\n );\n else {\n var styleQueue = renderState.styles.get(precedence), resourceState = resumableState.styleResources.hasOwnProperty(href) ? resumableState.styleResources[href] : void 0;\n if (null !== resourceState) {\n resumableState.styleResources[href] = null;\n styleQueue || (styleQueue = {\n precedence: escapeTextForBrowser(precedence),\n rules: [],\n hrefs: [],\n sheets: /* @__PURE__ */ new Map()\n }, renderState.styles.set(precedence, styleQueue));\n var resource = {\n state: 0,\n props: assign2({}, props, {\n \"data-precedence\": props.precedence,\n precedence: null\n })\n };\n if (resourceState) {\n 2 === resourceState.length && adoptPreloadCredentials(resource.props, resourceState);\n var preloadResource = renderState.preloads.stylesheets.get(href);\n preloadResource && 0 < preloadResource.length ? preloadResource.length = 0 : resource.state = 1;\n }\n styleQueue.sheets.set(href, resource);\n hoistableState && hoistableState.stylesheets.add(resource);\n } else if (styleQueue) {\n var resource$9 = styleQueue.sheets.get(href);\n resource$9 && hoistableState && hoistableState.stylesheets.add(resource$9);\n }\n textEmbedded && target$jscomp$0.push(\"\");\n JSCompiler_inline_result$jscomp$4 = null;\n }\n else\n props.onLoad || props.onError ? JSCompiler_inline_result$jscomp$4 = pushLinkImpl(\n target$jscomp$0,\n props\n ) : (textEmbedded && target$jscomp$0.push(\"\"), JSCompiler_inline_result$jscomp$4 = isFallback$jscomp$0 ? null : pushLinkImpl(renderState.hoistableChunks, props));\n return JSCompiler_inline_result$jscomp$4;\n case \"script\":\n var noscriptTagInScope$jscomp$1 = formatContext.tagScope & 1, asyncProp = props.async;\n if (\"string\" !== typeof props.src || !props.src || !asyncProp || \"function\" === typeof asyncProp || \"symbol\" === typeof asyncProp || props.onLoad || props.onError || 4 === formatContext.insertionMode || noscriptTagInScope$jscomp$1 || null != props.itemProp)\n var JSCompiler_inline_result$jscomp$5 = pushScriptImpl(\n target$jscomp$0,\n props\n );\n else {\n var key = props.src;\n if (\"module\" === props.type) {\n var resources = resumableState.moduleScriptResources;\n var preloads = renderState.preloads.moduleScripts;\n } else\n resources = resumableState.scriptResources, preloads = renderState.preloads.scripts;\n var resourceState$jscomp$0 = resources.hasOwnProperty(key) ? resources[key] : void 0;\n if (null !== resourceState$jscomp$0) {\n resources[key] = null;\n var scriptProps = props;\n if (resourceState$jscomp$0) {\n 2 === resourceState$jscomp$0.length && (scriptProps = assign2({}, props), adoptPreloadCredentials(scriptProps, resourceState$jscomp$0));\n var preloadResource$jscomp$0 = preloads.get(key);\n preloadResource$jscomp$0 && (preloadResource$jscomp$0.length = 0);\n }\n var resource$jscomp$0 = [];\n renderState.scripts.add(resource$jscomp$0);\n pushScriptImpl(resource$jscomp$0, scriptProps);\n }\n textEmbedded && target$jscomp$0.push(\"\");\n JSCompiler_inline_result$jscomp$5 = null;\n }\n return JSCompiler_inline_result$jscomp$5;\n case \"style\":\n var noscriptTagInScope$jscomp$2 = formatContext.tagScope & 1, precedence$jscomp$0 = props.precedence, href$jscomp$0 = props.href, nonce = props.nonce;\n if (4 === formatContext.insertionMode || noscriptTagInScope$jscomp$2 || null != props.itemProp || \"string\" !== typeof precedence$jscomp$0 || \"string\" !== typeof href$jscomp$0 || \"\" === href$jscomp$0) {\n target$jscomp$0.push(startChunkForTag(\"style\"));\n var children$jscomp$6 = null, innerHTML$jscomp$5 = null, propKey$jscomp$8;\n for (propKey$jscomp$8 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$8)) {\n var propValue$jscomp$8 = props[propKey$jscomp$8];\n if (null != propValue$jscomp$8)\n switch (propKey$jscomp$8) {\n case \"children\":\n children$jscomp$6 = propValue$jscomp$8;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$5 = propValue$jscomp$8;\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$8,\n propValue$jscomp$8\n );\n }\n }\n target$jscomp$0.push(\">\");\n var child = Array.isArray(children$jscomp$6) ? 2 > children$jscomp$6.length ? children$jscomp$6[0] : null : children$jscomp$6;\n \"function\" !== typeof child && \"symbol\" !== typeof child && null !== child && void 0 !== child && target$jscomp$0.push((\"\" + child).replace(styleRegex, styleReplacer));\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$5, children$jscomp$6);\n target$jscomp$0.push(endChunkForTag(\"style\"));\n var JSCompiler_inline_result$jscomp$6 = null;\n } else {\n var styleQueue$jscomp$0 = renderState.styles.get(precedence$jscomp$0);\n if (null !== (resumableState.styleResources.hasOwnProperty(href$jscomp$0) ? resumableState.styleResources[href$jscomp$0] : void 0)) {\n resumableState.styleResources[href$jscomp$0] = null;\n styleQueue$jscomp$0 || (styleQueue$jscomp$0 = {\n precedence: escapeTextForBrowser(precedence$jscomp$0),\n rules: [],\n hrefs: [],\n sheets: /* @__PURE__ */ new Map()\n }, renderState.styles.set(precedence$jscomp$0, styleQueue$jscomp$0));\n var nonceStyle = renderState.nonce.style;\n if (!nonceStyle || nonceStyle === nonce) {\n styleQueue$jscomp$0.hrefs.push(escapeTextForBrowser(href$jscomp$0));\n var target = styleQueue$jscomp$0.rules, children$jscomp$7 = null, innerHTML$jscomp$6 = null, propKey$jscomp$9;\n for (propKey$jscomp$9 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$9)) {\n var propValue$jscomp$9 = props[propKey$jscomp$9];\n if (null != propValue$jscomp$9)\n switch (propKey$jscomp$9) {\n case \"children\":\n children$jscomp$7 = propValue$jscomp$9;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$6 = propValue$jscomp$9;\n }\n }\n var child$jscomp$0 = Array.isArray(children$jscomp$7) ? 2 > children$jscomp$7.length ? children$jscomp$7[0] : null : children$jscomp$7;\n \"function\" !== typeof child$jscomp$0 && \"symbol\" !== typeof child$jscomp$0 && null !== child$jscomp$0 && void 0 !== child$jscomp$0 && target.push(\n (\"\" + child$jscomp$0).replace(styleRegex, styleReplacer)\n );\n pushInnerHTML(target, innerHTML$jscomp$6, children$jscomp$7);\n }\n }\n styleQueue$jscomp$0 && hoistableState && hoistableState.styles.add(styleQueue$jscomp$0);\n textEmbedded && target$jscomp$0.push(\"\");\n JSCompiler_inline_result$jscomp$6 = void 0;\n }\n return JSCompiler_inline_result$jscomp$6;\n case \"meta\":\n var noscriptTagInScope$jscomp$3 = formatContext.tagScope & 1, isFallback$jscomp$1 = formatContext.tagScope & 4;\n if (4 === formatContext.insertionMode || noscriptTagInScope$jscomp$3 || null != props.itemProp)\n var JSCompiler_inline_result$jscomp$7 = pushSelfClosing(\n target$jscomp$0,\n props,\n \"meta\"\n );\n else\n textEmbedded && target$jscomp$0.push(\"\"), JSCompiler_inline_result$jscomp$7 = isFallback$jscomp$1 ? null : \"string\" === typeof props.charSet ? pushSelfClosing(renderState.charsetChunks, props, \"meta\") : \"viewport\" === props.name ? pushSelfClosing(renderState.viewportChunks, props, \"meta\") : pushSelfClosing(renderState.hoistableChunks, props, \"meta\");\n return JSCompiler_inline_result$jscomp$7;\n case \"listing\":\n case \"pre\":\n target$jscomp$0.push(startChunkForTag(type));\n var children$jscomp$8 = null, innerHTML$jscomp$7 = null, propKey$jscomp$10;\n for (propKey$jscomp$10 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$10)) {\n var propValue$jscomp$10 = props[propKey$jscomp$10];\n if (null != propValue$jscomp$10)\n switch (propKey$jscomp$10) {\n case \"children\":\n children$jscomp$8 = propValue$jscomp$10;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$7 = propValue$jscomp$10;\n break;\n default:\n pushAttribute(\n target$jscomp$0,\n propKey$jscomp$10,\n propValue$jscomp$10\n );\n }\n }\n target$jscomp$0.push(\">\");\n if (null != innerHTML$jscomp$7) {\n if (null != children$jscomp$8) throw Error(formatProdErrorMessage3(60));\n if (\"object\" !== typeof innerHTML$jscomp$7 || !(\"__html\" in innerHTML$jscomp$7))\n throw Error(formatProdErrorMessage3(61));\n var html = innerHTML$jscomp$7.__html;\n null !== html && void 0 !== html && (\"string\" === typeof html && 0 < html.length && \"\\n\" === html[0] ? target$jscomp$0.push(\"\\n\", html) : target$jscomp$0.push(\"\" + html));\n }\n \"string\" === typeof children$jscomp$8 && \"\\n\" === children$jscomp$8[0] && target$jscomp$0.push(\"\\n\");\n return children$jscomp$8;\n case \"img\":\n var pictureOrNoScriptTagInScope = formatContext.tagScope & 3, src = props.src, srcSet = props.srcSet;\n if (!(\"lazy\" === props.loading || !src && !srcSet || \"string\" !== typeof src && null != src || \"string\" !== typeof srcSet && null != srcSet || \"low\" === props.fetchPriority || pictureOrNoScriptTagInScope) && (\"string\" !== typeof src || \":\" !== src[4] || \"d\" !== src[0] && \"D\" !== src[0] || \"a\" !== src[1] && \"A\" !== src[1] || \"t\" !== src[2] && \"T\" !== src[2] || \"a\" !== src[3] && \"A\" !== src[3]) && (\"string\" !== typeof srcSet || \":\" !== srcSet[4] || \"d\" !== srcSet[0] && \"D\" !== srcSet[0] || \"a\" !== srcSet[1] && \"A\" !== srcSet[1] || \"t\" !== srcSet[2] && \"T\" !== srcSet[2] || \"a\" !== srcSet[3] && \"A\" !== srcSet[3])) {\n null !== hoistableState && formatContext.tagScope & 64 && (hoistableState.suspenseyImages = true);\n var sizes = \"string\" === typeof props.sizes ? props.sizes : void 0, key$jscomp$0 = srcSet ? srcSet + \"\\n\" + (sizes || \"\") : src, promotablePreloads = renderState.preloads.images, resource$jscomp$1 = promotablePreloads.get(key$jscomp$0);\n if (resource$jscomp$1) {\n if (\"high\" === props.fetchPriority || 10 > renderState.highImagePreloads.size)\n promotablePreloads.delete(key$jscomp$0), renderState.highImagePreloads.add(resource$jscomp$1);\n } else if (!resumableState.imageResources.hasOwnProperty(key$jscomp$0)) {\n resumableState.imageResources[key$jscomp$0] = PRELOAD_NO_CREDS;\n var input2 = props.crossOrigin;\n var JSCompiler_inline_result$jscomp$8 = \"string\" === typeof input2 ? \"use-credentials\" === input2 ? input2 : \"\" : void 0;\n var headers = renderState.headers, header;\n headers && 0 < headers.remainingCapacity && \"string\" !== typeof props.srcSet && (\"high\" === props.fetchPriority || 500 > headers.highImagePreloads.length) && (header = getPreloadAsHeader(src, \"image\", {\n imageSrcSet: props.srcSet,\n imageSizes: props.sizes,\n crossOrigin: JSCompiler_inline_result$jscomp$8,\n integrity: props.integrity,\n nonce: props.nonce,\n type: props.type,\n fetchPriority: props.fetchPriority,\n referrerPolicy: props.refererPolicy\n }), 0 <= (headers.remainingCapacity -= header.length + 2)) ? (renderState.resets.image[key$jscomp$0] = PRELOAD_NO_CREDS, headers.highImagePreloads && (headers.highImagePreloads += \", \"), headers.highImagePreloads += header) : (resource$jscomp$1 = [], pushLinkImpl(resource$jscomp$1, {\n rel: \"preload\",\n as: \"image\",\n href: srcSet ? void 0 : src,\n imageSrcSet: srcSet,\n imageSizes: sizes,\n crossOrigin: JSCompiler_inline_result$jscomp$8,\n integrity: props.integrity,\n type: props.type,\n fetchPriority: props.fetchPriority,\n referrerPolicy: props.referrerPolicy\n }), \"high\" === props.fetchPriority || 10 > renderState.highImagePreloads.size ? renderState.highImagePreloads.add(resource$jscomp$1) : (renderState.bulkPreloads.add(resource$jscomp$1), promotablePreloads.set(key$jscomp$0, resource$jscomp$1)));\n }\n }\n return pushSelfClosing(target$jscomp$0, props, \"img\");\n case \"base\":\n case \"area\":\n case \"br\":\n case \"col\":\n case \"embed\":\n case \"hr\":\n case \"keygen\":\n case \"param\":\n case \"source\":\n case \"track\":\n case \"wbr\":\n return pushSelfClosing(target$jscomp$0, props, type);\n case \"annotation-xml\":\n case \"color-profile\":\n case \"font-face\":\n case \"font-face-src\":\n case \"font-face-uri\":\n case \"font-face-format\":\n case \"font-face-name\":\n case \"missing-glyph\":\n break;\n case \"head\":\n if (2 > formatContext.insertionMode) {\n var preamble = preambleState || renderState.preamble;\n if (preamble.headChunks)\n throw Error(formatProdErrorMessage3(545, \"``\"));\n null !== preambleState && target$jscomp$0.push(\"\");\n preamble.headChunks = [];\n var JSCompiler_inline_result$jscomp$9 = pushStartSingletonElement(\n preamble.headChunks,\n props,\n \"head\"\n );\n } else\n JSCompiler_inline_result$jscomp$9 = pushStartGenericElement(\n target$jscomp$0,\n props,\n \"head\"\n );\n return JSCompiler_inline_result$jscomp$9;\n case \"body\":\n if (2 > formatContext.insertionMode) {\n var preamble$jscomp$0 = preambleState || renderState.preamble;\n if (preamble$jscomp$0.bodyChunks)\n throw Error(formatProdErrorMessage3(545, \"``\"));\n null !== preambleState && target$jscomp$0.push(\"\");\n preamble$jscomp$0.bodyChunks = [];\n var JSCompiler_inline_result$jscomp$10 = pushStartSingletonElement(\n preamble$jscomp$0.bodyChunks,\n props,\n \"body\"\n );\n } else\n JSCompiler_inline_result$jscomp$10 = pushStartGenericElement(\n target$jscomp$0,\n props,\n \"body\"\n );\n return JSCompiler_inline_result$jscomp$10;\n case \"html\":\n if (0 === formatContext.insertionMode) {\n var preamble$jscomp$1 = preambleState || renderState.preamble;\n if (preamble$jscomp$1.htmlChunks)\n throw Error(formatProdErrorMessage3(545, \"``\"));\n null !== preambleState && target$jscomp$0.push(\"\");\n preamble$jscomp$1.htmlChunks = [\"\"];\n var JSCompiler_inline_result$jscomp$11 = pushStartSingletonElement(\n preamble$jscomp$1.htmlChunks,\n props,\n \"html\"\n );\n } else\n JSCompiler_inline_result$jscomp$11 = pushStartGenericElement(\n target$jscomp$0,\n props,\n \"html\"\n );\n return JSCompiler_inline_result$jscomp$11;\n default:\n if (-1 !== type.indexOf(\"-\")) {\n target$jscomp$0.push(startChunkForTag(type));\n var children$jscomp$9 = null, innerHTML$jscomp$8 = null, propKey$jscomp$11;\n for (propKey$jscomp$11 in props)\n if (hasOwnProperty.call(props, propKey$jscomp$11)) {\n var propValue$jscomp$11 = props[propKey$jscomp$11];\n if (null != propValue$jscomp$11) {\n var attributeName = propKey$jscomp$11;\n switch (propKey$jscomp$11) {\n case \"children\":\n children$jscomp$9 = propValue$jscomp$11;\n break;\n case \"dangerouslySetInnerHTML\":\n innerHTML$jscomp$8 = propValue$jscomp$11;\n break;\n case \"style\":\n pushStyleAttribute(target$jscomp$0, propValue$jscomp$11);\n break;\n case \"suppressContentEditableWarning\":\n case \"suppressHydrationWarning\":\n case \"ref\":\n break;\n case \"className\":\n attributeName = \"class\";\n default:\n if (isAttributeNameSafe(propKey$jscomp$11) && \"function\" !== typeof propValue$jscomp$11 && \"symbol\" !== typeof propValue$jscomp$11 && false !== propValue$jscomp$11) {\n if (true === propValue$jscomp$11) propValue$jscomp$11 = \"\";\n else if (\"object\" === typeof propValue$jscomp$11) continue;\n target$jscomp$0.push(\n \" \",\n attributeName,\n '=\"',\n escapeTextForBrowser(propValue$jscomp$11),\n '\"'\n );\n }\n }\n }\n }\n target$jscomp$0.push(\">\");\n pushInnerHTML(target$jscomp$0, innerHTML$jscomp$8, children$jscomp$9);\n return children$jscomp$9;\n }\n }\n return pushStartGenericElement(target$jscomp$0, props, type);\n }\n var endTagCache = /* @__PURE__ */ new Map();\n function endChunkForTag(tag) {\n var chunk = endTagCache.get(tag);\n void 0 === chunk && (chunk = \"\", endTagCache.set(tag, chunk));\n return chunk;\n }\n function hoistPreambleState(renderState, preambleState) {\n renderState = renderState.preamble;\n null === renderState.htmlChunks && preambleState.htmlChunks && (renderState.htmlChunks = preambleState.htmlChunks);\n null === renderState.headChunks && preambleState.headChunks && (renderState.headChunks = preambleState.headChunks);\n null === renderState.bodyChunks && preambleState.bodyChunks && (renderState.bodyChunks = preambleState.bodyChunks);\n }\n function writeBootstrap(destination, renderState) {\n renderState = renderState.bootstrapChunks;\n for (var i = 0; i < renderState.length - 1; i++)\n destination.push(renderState[i]);\n return i < renderState.length ? (i = renderState[i], renderState.length = 0, destination.push(i)) : true;\n }\n function writeStartPendingSuspenseBoundary(destination, renderState, id) {\n destination.push('');\n }\n function writeStartSegment(destination, renderState, formatContext, id) {\n switch (formatContext.insertionMode) {\n case 0:\n case 1:\n case 3:\n case 2:\n return destination.push('