diff --git a/editor/app/(site)/organizations/[organization_name]/settings/_shell.tsx b/editor/app/(site)/organizations/[organization_name]/settings/_shell.tsx
index 6813e496ff..614c073041 100644
--- a/editor/app/(site)/organizations/[organization_name]/settings/_shell.tsx
+++ b/editor/app/(site)/organizations/[organization_name]/settings/_shell.tsx
@@ -3,7 +3,7 @@
import * as React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { ArrowLeftIcon } from "lucide-react";
+import { ArrowLeftIcon, ArrowUpRightIcon, SquareTerminal } from "lucide-react";
import {
Sidebar,
SidebarContent,
@@ -23,9 +23,12 @@ type SettingsCategory = { href: string; label: string };
export default function SettingsShell({
orgName,
+ consoleHref,
children,
}: {
orgName: string;
+ /** The organization's Console (AI Gateway) page. */
+ consoleHref: string;
children: React.ReactNode;
}) {
const pathname = usePathname();
@@ -84,6 +87,22 @@ export default function SettingsShell({
+ {/* TODO(org-home-gate): Console entry added with the `/` gate; revisit with the product catalogue / links cleanup. */}
+
+
+
+
+
+
+
+ Console
+
+
+
+
+
+
+ {children}
diff --git a/editor/app/(site)/organizations/[organization_name]/settings/layout.tsx b/editor/app/(site)/organizations/[organization_name]/settings/layout.tsx
index 862041de02..82c6da0b60 100644
--- a/editor/app/(site)/organizations/[organization_name]/settings/layout.tsx
+++ b/editor/app/(site)/organizations/[organization_name]/settings/layout.tsx
@@ -1,4 +1,5 @@
import SettingsShell from "./_shell";
+import { ConsoleLinks } from "@/lib/platform/console-links";
type Params = { organization_name: string };
@@ -12,5 +13,12 @@ export default async function SettingsLayout({
const { organization_name } = await params;
// Billing handoffs and the profile view enforce auth and the caller's RLS
// organization lookup. Keep their resource-specific sign-in continuation.
- return {children};
+ return (
+
+ {children}
+
+ );
}
diff --git a/editor/app/(workspace)/[org]/[proj]/layout.tsx b/editor/app/(workspace)/[org]/[proj]/layout.tsx
new file mode 100644
index 0000000000..e9f9eb6225
--- /dev/null
+++ b/editor/app/(workspace)/[org]/[proj]/layout.tsx
@@ -0,0 +1,12 @@
+import { WorkspaceShell } from "../_components/workspace-shell";
+
+export default async function ProjectLayout({
+ params,
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+ params: Promise<{ org: string }>;
+}>) {
+ const { org } = await params;
+ return {children};
+}
diff --git a/editor/app/(workspace)/[org]/_components/home-gate.tsx b/editor/app/(workspace)/[org]/_components/home-gate.tsx
new file mode 100644
index 0000000000..c22678e2cf
--- /dev/null
+++ b/editor/app/(workspace)/[org]/_components/home-gate.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import React from "react";
+import { useRouter } from "next/navigation";
+import {
+ ArrowRightIcon,
+ ArrowUpRightIcon,
+ FolderKanban,
+ SquareTerminal,
+} from "lucide-react";
+import { useWorkspace } from "@/scaffolds/workspace";
+import { OrgHome } from "./org-home";
+
+// TODO(org-home-gate): TEMPORARY. Stopgap so Console users (AI Gateway / FX)
+// landing on grida.co aren't pushed into the legacy "Work" (projects / forms /
+// database) onboarding. Remove it when the product catalogue and its links
+// (the `/` home, `/dashboard`, sitemap product items, Console handoffs) are
+// reorganized for real. `rg "TODO\(org-home-gate\)"` lists every touchpoint;
+// removal checklist:
+// - `[org]/page.tsx`: render `WorkHome` unconditionally; delete this file and
+// `org-home.ts` (the `grida-org-home` cookie, `Path=/`, needs no
+// cleanup — it is simply ignored).
+// - Decide whether to keep the sidebar "Console" links (workspace sidebar and
+// org settings sidebar) and the `WorkspaceShell` split — both outlive the
+// gate and are fine to keep.
+// - Deprecate the manual TC (see test/workspace-org-home-gate.md).
+/**
+ * First stop at `/`: pick the Console (AI Gateway / FX, keys, credits) or
+ * "Work" (projects and documents). Choosing Work is remembered for this org
+ * in this browser; the Console stays one click away in the sidebar.
+ */
+export function HomeGate({ consoleHref }: { consoleHref: string }) {
+ const router = useRouter();
+ const { organization } = useWorkspace();
+
+ const chooseWork = () => {
+ document.cookie = OrgHome.workCookie(organization.name);
+ router.refresh();
+ };
+
+ return (
+
+
+
+ }
+ title="Console"
+ description="For AI Gateway and FX — API keys, credits, usage and billing."
+ hint={new URL(consoleHref).host}
+ arrow={}
+ />
+ }
+ title="Work"
+ description="Projects, forms, databases and documents in this organization."
+ hint="Remembered on this device"
+ arrow={}
+ />
+
+ );
+}
diff --git a/editor/app/(workspace)/[org]/_components/workspace-shell.tsx b/editor/app/(workspace)/[org]/_components/workspace-shell.tsx
new file mode 100644
index 0000000000..7a3dfc6a38
--- /dev/null
+++ b/editor/app/(workspace)/[org]/_components/workspace-shell.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { EditorHelpFab } from "@/scaffolds/globals/editor-help-fab";
+import { EditorCommandK } from "@/scaffolds/globals/editor-cmdk";
+import WorkspaceSidebar from "@/scaffolds/workspace/sidebar";
+import { SidebarProvider } from "@app/ui/components/sidebar";
+import { ConsoleLinks } from "@/lib/platform/console-links";
+import Header from "../header";
+
+// TODO(org-home-gate): split out of `[org]/layout.tsx` so the gate renders
+// bare; may stay after the gate is removed (see `home-gate.tsx`).
+/**
+ * The organization workspace chrome — sidebar, breadcrumb header, help and
+ * command palette. Wraps the "Work" home and project pages; the home gate
+ * renders without it.
+ */
+export function WorkspaceShell({
+ org,
+ children,
+}: React.PropsWithChildren<{ org: string }>) {
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/editor/app/(workspace)/[org]/layout.tsx b/editor/app/(workspace)/[org]/layout.tsx
index 708a21cd92..8f5a78a291 100644
--- a/editor/app/(workspace)/[org]/layout.tsx
+++ b/editor/app/(workspace)/[org]/layout.tsx
@@ -1,11 +1,6 @@
import { createClient } from "@/lib/supabase/server";
import { Workspace } from "@/scaffolds/workspace";
import { notFound, redirect } from "next/navigation";
-import { EditorHelpFab } from "@/scaffolds/globals/editor-help-fab";
-import { EditorCommandK } from "@/scaffolds/globals/editor-cmdk";
-import WorkspaceSidebar from "@/scaffolds/workspace/sidebar";
-import { SidebarProvider } from "@app/ui/components/sidebar";
-import Header from "./header";
type Params = { org: string };
@@ -41,19 +36,7 @@ export default async function Layout({
return notFound();
}
- return (
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
- );
+ // The workspace chrome (sidebar, header) is applied per page by
+ // `WorkspaceShell`, so the home gate at `/` can render without it.
+ return {children};
}
diff --git a/editor/app/(workspace)/[org]/page.tsx b/editor/app/(workspace)/[org]/page.tsx
index e5b99a6df7..2556c768fc 100644
--- a/editor/app/(workspace)/[org]/page.tsx
+++ b/editor/app/(workspace)/[org]/page.tsx
@@ -1,33 +1,11 @@
-"use client";
+import { cookies } from "next/headers";
+import { WorkHome } from "./_components/work-home";
+import { HomeGate } from "./_components/home-gate";
+import { OrgHome } from "./_components/org-home";
+import { ConsoleLinks } from "@/lib/platform/console-links";
+import { WorkspaceShell } from "./_components/workspace-shell";
-import React, { use } from "react";
-import Link from "next/link";
-import {
- ChevronDownIcon,
- OpenInNewWindowIcon,
- PlusIcon,
- ViewGridIcon,
- ViewHorizontalIcon,
-} from "@radix-ui/react-icons";
-import { CreateNewDocumentButton } from "@/scaffolds/workspace/create-new-document-button";
-import { ProjectStats } from "@/scaffolds/analytics/stats";
-import { PoweredByGridaFooter } from "@/grida-forms-hosted/e/powered-by-brand-footer";
-import { FolderPlus } from "lucide-react";
-import { CreateNewProjectDialog } from "@/scaffolds/workspace/new-project-dialog";
-import {
- Empty,
- EmptyContent,
- EmptyDescription,
- EmptyHeader,
- EmptyMedia,
- EmptyTitle,
-} from "@app/ui/components/empty";
-import { Button } from "@app/ui/components/button";
-import { useWorkspace } from "@/scaffolds/workspace";
-import { Skeleton } from "@app/ui/components/skeleton";
-import { DocumentsGrid } from "./_components/documents-grid";
-
-export default function OrganizationDashboardPage(props: {
+export default async function OrganizationHomePage(props: {
params: Promise<{
org: string;
}>;
@@ -35,115 +13,21 @@ export default function OrganizationDashboardPage(props: {
layout?: "grid" | "list";
}>;
}) {
- const searchParams = use(props.searchParams);
- const layout = searchParams.layout ?? "list";
-
- const { loading, organization, projects, documents, refresh } =
- useWorkspace();
+ const [{ org }, searchParams, cookieStore] = await Promise.all([
+ props.params,
+ props.searchParams,
+ cookies(),
+ ]);
- return (
-
-
-
- );
-}
+ // TODO(org-home-gate): temporary gate — see `_components/home-gate.tsx`.
+ // The cookie is scoped to `Path=/`, so it is only present for this org.
+ if (OrgHome.isWork(cookieStore.get(OrgHome.COOKIE)?.value)) {
+ return (
+
+
+
+ );
+ }
-function ProjectsLoading() {
- return (
-
-
-
-
-
-
- );
+ return ;
}
diff --git a/editor/app/(www)/(ai-gateway)/ai/gateway/page.tsx b/editor/app/(www)/(ai-gateway)/ai/gateway/page.tsx
index 6491746b30..d95a5ee15b 100644
--- a/editor/app/(www)/(ai-gateway)/ai/gateway/page.tsx
+++ b/editor/app/(www)/(ai-gateway)/ai/gateway/page.tsx
@@ -13,6 +13,7 @@ import { GridaLogo } from "@/components/grida-logo";
import Header from "@/www/header";
import Footer from "@/www/footer";
import { sitemap } from "@/www/data/sitemap";
+import { ConsoleLinks } from "@/lib/platform/console-links";
import Showcase from "./_sections/showcase";
import { CREATIVE_CODE, OPENAI_CODE } from "./_sections/content";
import { highlightCode } from "./_sections/highlight";
@@ -93,10 +94,7 @@ export default async function AIGatewayPage() {
apps, agents, and workflows.
-
+
Get API key
diff --git a/editor/lib/platform/README.md b/editor/lib/platform/README.md
index ddc074cf6c..4ca941a736 100644
--- a/editor/lib/platform/README.md
+++ b/editor/lib/platform/README.md
@@ -11,6 +11,8 @@ Grida client adapters; financial policy and provider operations are not owned he
IDs remain the authorization and service API identity. The management handoff
rechecks that the requested locator belongs to that same organization.
Numeric-only names are valid, with no ID fallback or old-name redirect.
+- `console-links.ts` is the single source for outbound Console page URLs
+ (AI Gateway); update its templates when the Console URL spec changes.
- `native-credits.ts` preserves the installed clients' credit response shape.
- `product-billing.ts` and `product-consumer.ts` retain admission and completion
custody for native operations and accepted work.
diff --git a/editor/lib/platform/console-links.test.ts b/editor/lib/platform/console-links.test.ts
new file mode 100644
index 0000000000..5303baab3d
--- /dev/null
+++ b/editor/lib/platform/console-links.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, it } from "vitest";
+import { ConsoleLinks } from "./console-links";
+
+describe("ConsoleLinks.aiGateway", () => {
+ it("links to the organization's AI Gateway page", () => {
+ expect(
+ ConsoleLinks.aiGateway("acme", {
+ GRIDA_PLATFORM_CONSOLE_ORIGIN: "https://console.example.com",
+ })
+ ).toBe("https://console.example.com/organizations/acme/ai-gateway");
+ });
+
+ it("uses the universal entry without a routable organization slug", () => {
+ const env = {
+ GRIDA_PLATFORM_CONSOLE_ORIGIN: "https://console.example.com",
+ };
+ expect(ConsoleLinks.aiGateway(undefined, env)).toBe(
+ "https://console.example.com/_/ai-gateway"
+ );
+ expect(ConsoleLinks.aiGateway("Not A Slug", env)).toBe(
+ "https://console.example.com/_/ai-gateway"
+ );
+ });
+
+ it("falls back to the public Console when no valid origin is configured", () => {
+ for (const env of [
+ {},
+ { GRIDA_PLATFORM_CONSOLE_ORIGIN: "" },
+ { GRIDA_PLATFORM_CONSOLE_ORIGIN: "http://console.example.com" },
+ { GRIDA_PLATFORM_CONSOLE_ORIGIN: "https://console.example.com/path" },
+ ]) {
+ expect(ConsoleLinks.aiGateway("acme", env)).toBe(
+ "https://console.grida.co/organizations/acme/ai-gateway"
+ );
+ }
+ });
+});
diff --git a/editor/lib/platform/console-links.ts b/editor/lib/platform/console-links.ts
new file mode 100644
index 0000000000..918918beaf
--- /dev/null
+++ b/editor/lib/platform/console-links.ts
@@ -0,0 +1,41 @@
+// Outbound links into the Console (console.grida.co).
+import "server-only";
+import { configuredOrigin } from "./billing-consumer";
+import { validOrganizationSlug } from "./console-destination";
+
+type Environment = Readonly>;
+
+/**
+ * Single source for Console page URLs that Grida links out to. When the
+ * Console's URL spec changes, update the templates here.
+ *
+ * Billing pages are built by `consoleBillingUrl` (billing-consumer.ts), and the
+ * inbound return-destination grammar lives in `organizationRoute`
+ * (console-destination.ts) — keep all three in step.
+ */
+export namespace ConsoleLinks {
+ /** Public Console origin, used when no deployment origin is configured. */
+ const PUBLIC_ORIGIN = "https://console.grida.co";
+
+ /** Configured Console origin, or the public one when unset (e.g. local). */
+ function origin(env: Environment): string {
+ try {
+ return configuredOrigin(env.GRIDA_PLATFORM_CONSOLE_ORIGIN, env);
+ } catch {
+ return PUBLIC_ORIGIN;
+ }
+ }
+
+ /**
+ * The Console AI Gateway page — the organization's own when `org` is a
+ * Console-routable slug, otherwise the universal entry (where the Console
+ * asks for an organization).
+ */
+ export function aiGateway(org?: string, env: Environment = process.env) {
+ const path =
+ org && validOrganizationSlug(org)
+ ? `/organizations/${org}/ai-gateway`
+ : "/_/ai-gateway";
+ return origin(env) + path;
+ }
+}
diff --git a/editor/scaffolds/workspace/sidebar.tsx b/editor/scaffolds/workspace/sidebar.tsx
index 8cb9903419..9979ef5e73 100644
--- a/editor/scaffolds/workspace/sidebar.tsx
+++ b/editor/scaffolds/workspace/sidebar.tsx
@@ -28,6 +28,7 @@ import {
ChevronRight,
ChevronDown,
Trash2Icon,
+ SquareTerminal,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import {
@@ -124,8 +125,12 @@ export function projectstree(
}
export default function WorkspaceSidebar({
+ consoleHref,
...props
-}: React.ComponentProps) {
+}: React.ComponentProps & {
+ /** The organization's Console (AI Gateway) page. */
+ consoleHref: string;
+}) {
const workspace = useWorkspace();
const { organization, organizations, projects, documents } = workspace;
@@ -142,7 +147,16 @@ export default function WorkspaceSidebar({
target: "_blank",
};
+ // TODO(org-home-gate): Console entry added with the `/` gate; revisit
+ // with the product catalogue / links cleanup.
+ const consoleNav = {
+ title: "Console",
+ icon: SquareTerminal,
+ url: consoleHref,
+ };
+
const navSecondary = [
+ consoleNav,
settings,
// // TODO:
// {
diff --git a/test/workspace-org-home-gate.md b/test/workspace-org-home-gate.md
new file mode 100644
index 0000000000..fc161cf477
--- /dev/null
+++ b/test/workspace-org-home-gate.md
@@ -0,0 +1,54 @@
+---
+id: TC-WORKSPACE-HOME-001
+title: Organization home gate offers Console or Work, and remembers Work
+module: workspace
+area: home
+tags: [workspace, onboarding, console, ai-gateway, fx, temporary]
+status: untested
+severity: medium
+date: 2026-10-13
+updated: 2026-10-13
+automatable: false
+covered_by: []
+---
+
+## Behavior
+
+`grida.co/` first shows a bare gate (no sidebar, header, help or command
+palette) with two large cards: **Console** (AI Gateway and FX — keys, credits,
+usage, billing) and **Work** (projects, forms, databases, documents). This keeps
+people who signed up for the Console products from landing in the projects
+onboarding ("Create your first project").
+
+Console is a plain link to the organization's AI Gateway page in the Console
+and is never remembered — remembering it would turn the organization's own home
+into a redirect off grida.co. Work is remembered per organization and per
+browser (a cookie scoped to `/`), so after choosing it once, `/`
+renders the projects home with the full workspace sidebar. The URL never
+changes. The Console stays reachable from the workspace sidebar (above
+Settings) and from the organization settings sidebar.
+
+The gate is temporary (`TODO(org-home-gate)`); mark this TC `deprecated` when it
+is removed.
+
+## Steps
+
+1. Sign in, clear cookies for the site (or use a fresh profile) and open `/`.
+2. Expected: only the Console and Work cards are visible — no sidebar, header,
+ help button or command palette. The URL is still `/`.
+3. Hover the Console card. Expected: it links to the Console's
+ `/organizations//ai-gateway` page.
+4. Click Work. Expected: the projects home renders with the workspace sidebar;
+ the URL is unchanged.
+5. Reload `/`. Expected: the projects home renders directly (no gate).
+6. In the workspace sidebar, find Console directly above Settings. Expected: it
+ links to the same Console page as step 3.
+7. Open another organization you belong to that you have not chosen Work for.
+ Expected: that organization shows the gate.
+8. Open `/organizations//settings`. Expected: a Console item is pinned to
+ the bottom of the settings sidebar.
+
+## Notes
+
+Choosing Console does not persist anything, so a later visit to `/` shows
+the gate again.