From b35e100d029fe612dc8849f4766edef2a88e9eb5 Mon Sep 17 00:00:00 2001
From: The-AarushiSingh <175547726+The-AarushiSingh@users.noreply.github.com>
Date: Sat, 10 Oct 2026 15:05:33 +0530
Subject: [PATCH 1/3] feat(hosted-web): add blank app path to Add app
---
.../web/src/contracts/dashboard-bindings.ts | 18 +++++++
.../web/src/implementation/pages/add-app.tsx | 47 ++++++++++++++++++-
2 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/apps/hosted/web/src/contracts/dashboard-bindings.ts b/apps/hosted/web/src/contracts/dashboard-bindings.ts
index 233b73ccb3..91b48e332e 100644
--- a/apps/hosted/web/src/contracts/dashboard-bindings.ts
+++ b/apps/hosted/web/src/contracts/dashboard-bindings.ts
@@ -9,6 +9,16 @@ import { HostedClient, catalogAtom } from "./api.ts";
import { inventoryAtom } from "./organization.ts";
import { acknowledgeApp, toolsAtom } from "./apps.ts";
+/** Minimal app the Add app "blank" path deploys; must include root index.ts. */
+const blankAppFiles = [
+ {
+ path: "index.ts",
+ content: `import { defineApp, router } from "apps";
+export default defineApp({ accounts: {} }, async () => ({ tools: router({}) }));
+`,
+ },
+] as const;
+
/** A separate set of atom identities per organization prevents cross-organization cache reuse. */
export const dashboardAtoms = Atom.family((organization: OrganizationReference) => ({
inventory: Atom.map(
@@ -53,4 +63,12 @@ export const dashboardAtoms = Atom.family((organization: OrganizationReference)
client.apps.importCustom({ params: { organization }, payload: { source: input } }),
).pipe(Effect.tap((saved) => Effect.sync(() => acknowledgeApp(get, organization, saved)))),
),
+ createBlank: HostedClient.runtime.fn((input: { name: string }, get) =>
+ Effect.flatMap(HostedClient, (client) =>
+ client.apps.deploy({
+ params: { organization },
+ payload: { name: input.name, files: [...blankAppFiles] },
+ }),
+ ).pipe(Effect.tap((saved) => Effect.sync(() => acknowledgeApp(get, organization, saved)))),
+ ),
}));
diff --git a/apps/hosted/web/src/implementation/pages/add-app.tsx b/apps/hosted/web/src/implementation/pages/add-app.tsx
index 07840eeb77..0c91a9a1c4 100644
--- a/apps/hosted/web/src/implementation/pages/add-app.tsx
+++ b/apps/hosted/web/src/implementation/pages/add-app.tsx
@@ -7,6 +7,7 @@ import { Link, useNavigate } from "@tanstack/react-router";
import { Button } from "@executor-js/ui/components/button";
import { CatalogPage as Catalog, CatalogInstall } from "@executor-js/ui/dashboard/catalog";
import { InstallPublication } from "@executor-js/ui/dashboard/install-publication";
+import { AppCreateForm } from "@executor-js/ui/dashboard/app-create";
import { type AppAcknowledgement } from "@executor-js/ui/contracts/app-management";
import type { CatalogEntry } from "@executor-js/catalog/contracts";
import type { Publication } from "@executor-js/sdk";
@@ -19,7 +20,9 @@ import { acknowledgeApp } from "../../contracts/apps.ts";
type Selection =
| { readonly kind: "catalog"; readonly entry: CatalogEntry }
- | { readonly kind: "publication"; readonly publication: typeof Publication.Type };
+ | { readonly kind: "publication"; readonly publication: typeof Publication.Type }
+ | { readonly kind: "blank" };
+
/** Public publications and integration templates share Add app and the same organization-owned app records. */
export function AddAppPage() {
const mcpOrigin = useMcpOrigin();
@@ -64,6 +67,37 @@ export function AddAppPage() {
onInstalled={installed}
/>
);
+ if (selection?.kind === "blank")
+ return (
+
+
+
+
+
+ Create blank app
+
+
Start from an empty app and build with your agent.
+
+
+
({ name })}
+ onCreated={installed}
+ onCancel={back}
+ label="Create app"
+ />
+
+ );
return (
+