From 3d27a1605e19b54cedeec7f54886b4248a42d8ca Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:17:58 -0400 Subject: [PATCH 01/76] feat: limit navbar on branch views Hide sharing, bookmarks, alerts, reports, and settings entries when viewing a non-primary branch. The branch view is intended as a preview surface, so cloud-only features that operate on production (sharing the project, creating alerts, managing settings, etc.) are gated off to keep the experience focused on previewing the branch. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 7 ++++--- web-admin/src/features/projects/ProjectTabs.svelte | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index a04f968802a..783e685f931 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -77,6 +77,7 @@ $: onPublicURLPage = isPublicURLPage($page); $: activeBranch = extractBranchFromPath($page.url.pathname); + $: isBranchView = !!activeBranch && activeBranch !== primaryBranch; $: loggedIn = !!$user.data?.user; $: rillLogoHref = !loggedIn ? "https://www.rilldata.com" : "/"; @@ -214,7 +215,7 @@ {#if $cloudEditing && onProjectPage && projectPermissions.manageDev} {/if} - {#if onProjectPage && projectPermissions.manageProjectMembers} + {#if onProjectPage && projectPermissions.manageProjectMembers && !isBranchView} {/if} - {#if hasUserAccess} + {#if hasUserAccess && !isBranchView} {/if} - {#if hasUserAccess} + {#if hasUserAccess && !isBranchView} Date: Fri, 1 May 2026 11:27:50 -0400 Subject: [PATCH 02/76] feat: hide Branches status tab and always show Cluster Size Drop the plan-based gate on Cluster Size so it always renders when slots are reported, and hide the Branches sub-tab from the status page nav. Co-Authored-By: Claude Opus 4.7 --- .../status/overview/DeploymentSection.svelte | 14 +------------- .../[project]/-/status/+layout.svelte | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte index 2a19a002909..12bfdb755af 100644 --- a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte +++ b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte @@ -2,14 +2,8 @@ import { page } from "$app/stores"; import { createAdminServiceGetProject, - createAdminServiceGetBillingSubscription, V1DeploymentStatus, } from "@rilldata/web-admin/client"; - import { - isFreePlan, - isProPlan, - isTrialPlan, - } from "@rilldata/web-admin/features/billing/plans/utils"; import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; import { useDashboardsLastUpdated } from "@rilldata/web-admin/features/dashboards/listing/selectors"; import { useGithubLastSynced } from "@rilldata/web-admin/features/projects/selectors"; @@ -117,12 +111,6 @@ // Slots $: currentSlots = Number(projectData?.prodSlots) || 0; - - // Billing plan detection - $: subscriptionQuery = createAdminServiceGetBillingSubscription(organization); - $: planName = $subscriptionQuery?.data?.subscription?.plan?.name ?? ""; - $: showSlots = - isTrialPlan(planName) || isFreePlan(planName) || isProPlan(planName); @@ -159,7 +147,7 @@ - {#if !$subscriptionQuery?.isLoading && showSlots} + {#if currentSlots > 0}
Cluster Size diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte index be33a56dc01..07a4d77adba 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte @@ -16,7 +16,7 @@ { label: "Branches", route: "/branches", - hasPermission: true, + hasPermission: false, }, { label: "Resources", From ad579d7f6498b759ce437ee024e529510513949d Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:28:45 -0400 Subject: [PATCH 03/76] feat: only hide Branches status tab on branch views Show the Branches tab in production view; hide it only when viewing a non-primary branch (`/@branch/...`), where the section is not relevant. Co-Authored-By: Claude Opus 4.7 --- .../routes/[organization]/[project]/-/status/+layout.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte index 07a4d77adba..92a59f57a0d 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte @@ -4,10 +4,12 @@ import { page } from "$app/stores"; import ContentContainer from "@rilldata/web-common/components/layout/ContentContainer.svelte"; import LeftNav from "@rilldata/web-admin/components/nav/LeftNav.svelte"; + import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; $: basePage = `/${$page.params.organization}/${$page.params.project}/-/status`; + $: isBranchView = !!extractBranchFromPath($page.url.pathname); - const navItems = [ + $: navItems = [ { label: "Overview", route: "", @@ -16,7 +18,7 @@ { label: "Branches", route: "/branches", - hasPermission: false, + hasPermission: !isBranchView, }, { label: "Resources", From 6cda11fee5e6fee60345da4f1171a60d07fe5ad7 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:35:43 -0400 Subject: [PATCH 04/76] feat: redirect hidden sections to branch home on branch views Visiting `/-/alerts`, `/-/reports`, `/-/status`, or `/-/settings` on a branch view now redirects to the branch home, since these sections are hidden from the project nav on branch views. Also hides the Status tab on branch views for consistency. Co-Authored-By: Claude Opus 4.7 --- .../src/features/projects/ProjectTabs.svelte | 2 +- .../routes/[organization]/[project]/+layout.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index c3f87e27af7..a3e915f75df 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -52,7 +52,7 @@ { route: `/${organization}/${project}${branchPrefix}/-/status`, label: "Status", - hasPermission: projectPermissions.manageProject, + hasPermission: projectPermissions.manageProject && !isBranchView, }, { route: `/${organization}/${project}${branchPrefix}/-/settings`, diff --git a/web-admin/src/routes/[organization]/[project]/+layout.ts b/web-admin/src/routes/[organization]/[project]/+layout.ts index fddf4fc0aba..d888907c58f 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/+layout.ts @@ -1,17 +1,32 @@ import { type RpcStatus } from "@rilldata/web-admin/client"; import { hasBlockerIssues } from "@rilldata/web-admin/features/billing/selectors"; +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; import { fetchAllProjectsHibernating } from "@rilldata/web-admin/features/organizations/selectors"; import { error, redirect } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { maybeRedirectToEditableDeployment } from "@rilldata/web-admin/features/branches/deployment-utils.ts"; import { isEditPage } from "@rilldata/web-admin/features/navigation/nav-utils.ts"; +// Sections hidden on branch views; visiting them redirects to the branch home. +const BRANCH_HIDDEN_SECTIONS = /\/-\/(alerts|reports|status|settings)(\/|$)/; + export const load = async ({ params: { organization, project }, parent, route, url, }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch && BRANCH_HIDDEN_SECTIONS.test(url.pathname)) { + throw redirect( + 307, + `/${organization}/${project}${branchPathPrefix(activeBranch)}`, + ); + } + const { organizationPermissions, issues } = await parent(); if (!organizationPermissions.manageOrg) return; From 4470427a820f2ed46df27debb4f3503b3310388d Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:43:18 -0400 Subject: [PATCH 05/76] feat: jump straight into edit mode from branch views Clicking Edit on a branch view now navigates directly to the branch's edit URL instead of opening the picker dialog. The dialog was the right flow on production view (pick or create a dev branch), but on a branch view the target is already known. Co-Authored-By: Claude Opus 4.7 --- .../features/edit-session/EditButton.svelte | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/web-admin/src/features/edit-session/EditButton.svelte b/web-admin/src/features/edit-session/EditButton.svelte index 13364773ce6..b1afcdd4ca7 100644 --- a/web-admin/src/features/edit-session/EditButton.svelte +++ b/web-admin/src/features/edit-session/EditButton.svelte @@ -1,9 +1,5 @@ - - - - - {#if reconciling} - Dashboard preview available after reconciliation - {:else if disabled} - File errors must be resolved before previewing - {:else} - Preview dashboard - {/if} - - diff --git a/web-common/src/features/workspaces/CanvasWorkspace.svelte b/web-common/src/features/workspaces/CanvasWorkspace.svelte index 4676d9926d0..cef1398adac 100644 --- a/web-common/src/features/workspaces/CanvasWorkspace.svelte +++ b/web-common/src/features/workspaces/CanvasWorkspace.svelte @@ -1,15 +1,11 @@ + + diff --git a/web-common/src/layout/preview-mode-store.ts b/web-common/src/layout/preview-mode-store.ts index 7ecb4128f84..bd88afa687b 100644 --- a/web-common/src/layout/preview-mode-store.ts +++ b/web-common/src/layout/preview-mode-store.ts @@ -7,3 +7,10 @@ import { writable } from "svelte/store"; * closing the browser and returning always defaults to developer mode. */ export const previewModeStore = writable(false); + +/** + * True only when the runtime was started with `--preview`. The Preview/Edit + * toggle in the navbar is hidden when this is true: the user cannot exit + * preview mode without restarting the CLI. + */ +export const previewModeLocked = writable(false); diff --git a/web-local/src/routes/+layout.svelte b/web-local/src/routes/+layout.svelte index 48748e23cd4..98ba68ac886 100644 --- a/web-local/src/routes/+layout.svelte +++ b/web-local/src/routes/+layout.svelte @@ -21,7 +21,10 @@ initMetrics, } from "@rilldata/web-common/metrics/initMetrics"; import { isDeployPage } from "@rilldata/web-common/layout/navigation/route-utils"; - import { previewModeStore } from "@rilldata/web-common/layout/preview-mode-store"; + import { + previewModeLocked, + previewModeStore, + } from "@rilldata/web-common/layout/preview-mode-store"; import { LOCAL_HOST, LOCAL_INSTANCE_ID } from "../lib/runtime-client"; import RuntimeProvider from "@rilldata/web-common/runtime-client/v2/RuntimeProvider.svelte"; import type { Query } from "@tanstack/query-core"; @@ -60,6 +63,8 @@ } } + $: previewModeLocked.set(data.previewMode); + let removeJavascriptListeners: () => void; onMount(async () => { const config = data.metadata; From ecd238af75fec6d909e3ff430337c0ef2f79b361 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:11:58 -0400 Subject: [PATCH 08/76] Show project Status tab to editors Gate the Status tab on `readProdStatus` instead of `manageProject` so editors (not just admins) can view deployment status. The `read_prod_status` permission is already true for admin and editor roles and false for viewers, matching the desired access. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectTabs.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index 0c951097c68..e719465cd45 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -50,7 +50,7 @@ { route: `/${organization}/${project}${branchPrefix}/-/status`, label: "Status", - hasPermission: projectPermissions.manageProject, + hasPermission: projectPermissions.readProdStatus, }, { route: `/${organization}/${project}${branchPrefix}/-/settings`, From 50ad577360ae4492be26396500c412ef123bcc6d Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 11:39:35 -0400 Subject: [PATCH 09/76] Grant `ReadInstance` to editors via `read_prod_status` Map `read_prod_status` / `read_dev_status` to `runtime.ReadInstance` so non-managers (editors) can call `GetInstance({ sensitive: true })`. Without this, the Status page's Tables sub-page hangs forever for editors because it can't resolve the OLAP connector name. Co-Authored-By: Claude Opus 4.7 --- admin/server/runtime_jwt.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/admin/server/runtime_jwt.go b/admin/server/runtime_jwt.go index a1b5a320583..86dc7465c11 100644 --- a/admin/server/runtime_jwt.go +++ b/admin/server/runtime_jwt.go @@ -173,14 +173,16 @@ func (s *Server) issueRuntimeToken(ctx context.Context, opts *issueRuntimeTokenO } } - // Check if allowed to manage the deployment's environment. + // Check if allowed to manage the deployment's environment, or to read its status. // NOTE: Only applicable for tokens issued for the claims owner (not possible to delegate to other end users). - var manageDepl bool + var manageDepl, readDeplStatus bool if opts.forOwner { if opts.deployment.Environment == "prod" { manageDepl = opts.projectPermissions.ManageProd + readDeplStatus = opts.projectPermissions.ReadProdStatus } else { manageDepl = opts.projectPermissions.ManageDev + readDeplStatus = opts.projectPermissions.ReadDevStatus } } @@ -191,6 +193,10 @@ func (s *Server) issueRuntimeToken(ctx context.Context, opts *issueRuntimeTokenO runtime.ReadObjects, runtime.UseAI, } + if readDeplStatus { + // Status visibility: lets non-managers (e.g. editors) view the project Status page. + instancePermissions = append(instancePermissions, runtime.ReadInstance) + } if manageDepl { instancePermissions = append( instancePermissions, From f353fd0b619cad1088271b65d2803b262c23118d Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 12:09:30 -0400 Subject: [PATCH 10/76] Merge navbar branch-view limits and keep Status visible on branch views Bundles `worktree-feat-preview-pr-1-update-dev-navbar` (limits Reports, Alerts, Settings, and the Branches sub-tab on branch views; jumps straight into edit mode from branch views) and keeps the project Status tab visible on branch views so editors can read deployment status from any branch URL. Co-Authored-By: Claude Opus 4.7 --- .../features/edit-session/EditButton.svelte | 31 +++---------------- .../features/projects/ProjectHeader.svelte | 7 +++-- .../src/features/projects/ProjectTabs.svelte | 10 +++--- .../status/overview/DeploymentSection.svelte | 14 +-------- .../[organization]/[project]/+layout.ts | 15 +++++++++ .../[project]/-/status/+layout.svelte | 6 ++-- 6 files changed, 35 insertions(+), 48 deletions(-) diff --git a/web-admin/src/features/edit-session/EditButton.svelte b/web-admin/src/features/edit-session/EditButton.svelte index 13364773ce6..9f3d73a0365 100644 --- a/web-admin/src/features/edit-session/EditButton.svelte +++ b/web-admin/src/features/edit-session/EditButton.svelte @@ -1,9 +1,5 @@ @@ -159,7 +147,7 @@
- {#if !$subscriptionQuery?.isLoading && showSlots} + {#if currentSlots > 0}
Cluster Size diff --git a/web-admin/src/routes/[organization]/[project]/+layout.ts b/web-admin/src/routes/[organization]/[project]/+layout.ts index fddf4fc0aba..3b7d29c9e27 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/+layout.ts @@ -1,17 +1,32 @@ import { type RpcStatus } from "@rilldata/web-admin/client"; import { hasBlockerIssues } from "@rilldata/web-admin/features/billing/selectors"; +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; import { fetchAllProjectsHibernating } from "@rilldata/web-admin/features/organizations/selectors"; import { error, redirect } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { maybeRedirectToEditableDeployment } from "@rilldata/web-admin/features/branches/deployment-utils.ts"; import { isEditPage } from "@rilldata/web-admin/features/navigation/nav-utils.ts"; +// Sections hidden on branch views; visiting them redirects to the branch home. +const BRANCH_HIDDEN_SECTIONS = /\/-\/(alerts|reports|settings)(\/|$)/; + export const load = async ({ params: { organization, project }, parent, route, url, }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch && BRANCH_HIDDEN_SECTIONS.test(url.pathname)) { + throw redirect( + 307, + `/${organization}/${project}${branchPathPrefix(activeBranch)}`, + ); + } + const { organizationPermissions, issues } = await parent(); if (!organizationPermissions.manageOrg) return; diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte index be33a56dc01..92a59f57a0d 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte @@ -4,10 +4,12 @@ import { page } from "$app/stores"; import ContentContainer from "@rilldata/web-common/components/layout/ContentContainer.svelte"; import LeftNav from "@rilldata/web-admin/components/nav/LeftNav.svelte"; + import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; $: basePage = `/${$page.params.organization}/${$page.params.project}/-/status`; + $: isBranchView = !!extractBranchFromPath($page.url.pathname); - const navItems = [ + $: navItems = [ { label: "Overview", route: "", @@ -16,7 +18,7 @@ { label: "Branches", route: "/branches", - hasPermission: true, + hasPermission: !isBranchView, }, { label: "Resources", From fef193094f40160e48df76083530ac0a1f92d624 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 12:17:03 -0400 Subject: [PATCH 11/76] Show Status tab on branch views The earlier merge resolution accidentally kept `&& !isBranchView` on the Status tab, contradicting the intent. Removing it so editors can view deployment status from any branch URL. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectTabs.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index cbf80755994..2ff35fb92ec 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -52,7 +52,7 @@ { route: `/${organization}/${project}${branchPrefix}/-/status`, label: "Status", - hasPermission: projectPermissions.readProdStatus && !isBranchView, + hasPermission: projectPermissions.readProdStatus, }, { route: `/${organization}/${project}${branchPrefix}/-/settings`, From 8e3affb1b60b62b120cb0e2e2c19a083e419a45b Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:10:09 -0400 Subject: [PATCH 12/76] Hide branch pill for viewers Gate the navbar `BranchSelector` on `manageDev` instead of `readDev`. Viewers can only see the production branch, so the pill is redundant for them. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 50913605531..d2eebce08ed 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -206,7 +206,7 @@ {activeBranch} - {:else if !onPublicURLPage && projectPermissions?.readDev} + {:else if !onPublicURLPage && projectPermissions?.manageDev} {/if} From c6507ebe8c10456ac029c6cb2f86c49999ff8927 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:15:49 -0400 Subject: [PATCH 13/76] Smart-route Preview toggle to the current dashboard Match the file under edit (`/files/dashboards/foo.yaml`) against the project's explores and canvases, then route Preview to `/explore/foo` or `/canvas/foo` for that dashboard. In preview mode, route Edit back to the dashboard's file path. Falls back to `/dashboards` and `/` when no dashboard is in context. Co-Authored-By: Claude Opus 4.7 --- .../src/layout/ApplicationHeader.svelte | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 74efc800e89..80d5dc9f2ce 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -47,7 +47,6 @@ $: showDeployCTA = $deploy && !onDeployPage; $: showDeveloperChat = $developerChat && !onDeployPage; $: showPreviewToggle = !onDeployPage && !$previewModeLocked; - $: previewToggleHref = mode === "Preview" ? "/" : "/dashboards"; $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); @@ -58,6 +57,43 @@ $: explores = $exploresQuery?.data ?? []; $: canvases = $canvasQuery?.data ?? []; + // Resolve the dashboard the user is currently editing (if any) so the + // Preview toggle can navigate directly to that dashboard's preview route + // (and back to its file in Edit mode), instead of bouncing through the + // /dashboards listing. + $: editedFilePath = $page.url.pathname.startsWith("/files") + ? $page.url.pathname.slice("/files".length) + : null; + + $: editedExplore = editedFilePath + ? explores.find((e) => e?.meta?.filePaths?.includes(editedFilePath)) + : null; + $: editedCanvas = + !editedExplore && editedFilePath + ? canvases.find((c) => c?.meta?.filePaths?.includes(editedFilePath)) + : null; + + $: viewedExplore = + mode === "Preview" && route.id?.includes("explore") && dashboardName + ? explores.find((e) => e?.meta?.name?.name === dashboardName) + : null; + $: viewedCanvas = + mode === "Preview" && route.id?.includes("canvas") && dashboardName + ? canvases.find((c) => c?.meta?.name?.name === dashboardName) + : null; + + $: previewToggleHref = (() => { + if (mode === "Preview") { + const filePath = + viewedExplore?.meta?.filePaths?.[0] ?? + viewedCanvas?.meta?.filePaths?.[0]; + return filePath ? `/files${filePath}` : "/"; + } + if (editedExplore) return `/explore/${editedExplore.meta?.name?.name}`; + if (editedCanvas) return `/canvas/${editedCanvas.meta?.name?.name}`; + return "/dashboards"; + })(); + $: defaultDashboard = explores[0] ?? canvases[0] ?? null; $: hasValidDashboard = Boolean(defaultDashboard); From fd9cd111101fc09a83e8c005f788c430d2639922 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:21:14 -0400 Subject: [PATCH 14/76] Hide navbar Preview/Edit toggle on `/explore` and `/canvas` routes The viz routes already render `ExplorePreviewCTAs` / `CanvasPreviewCTAs`, which carry their own Edit affordance. Showing the navbar toggle here would (a) display Preview on a page that already is the preview, and (b) duplicate Edit when in preview mode. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 80d5dc9f2ce..f49d90cfacb 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -46,7 +46,7 @@ $: onDeployPage = isDeployPage($page); $: showDeployCTA = $deploy && !onDeployPage; $: showDeveloperChat = $developerChat && !onDeployPage; - $: showPreviewToggle = !onDeployPage && !$previewModeLocked; + $: showPreviewToggle = !onDeployPage && !$previewModeLocked && !onVizRoute; $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); From 092bc6ec2a556ec31e80fca15ec56c486b44dde3 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:24:36 -0400 Subject: [PATCH 15/76] Show "View as" in the local project preview navbar When in Preview mode on non-viz routes (`/dashboards`, `/ai`, `/status`) and `rill.yaml` defines a project-wide security policy, render the ViewAsButton next to the Preview/Edit toggle. Per-dashboard ViewAs in ExplorePreviewCTAs / CanvasPreviewCTAs continues to handle viz routes. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index f49d90cfacb..87f6ce5e49a 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -13,6 +13,8 @@ useValidCanvases, useValidExplores, } from "@rilldata/web-common/features/dashboards/selectors.js"; + import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; + import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import DeployProjectCTA from "@rilldata/web-common/features/dashboards/workspace/DeployProjectCTA.svelte"; import ExplorePreviewCTAs from "@rilldata/web-common/features/explores/ExplorePreviewCTAs.svelte"; import { featureFlags } from "@rilldata/web-common/features/feature-flags.ts"; @@ -48,6 +50,13 @@ $: showDeveloperChat = $developerChat && !onDeployPage; $: showPreviewToggle = !onDeployPage && !$previewModeLocked && !onVizRoute; + // Show "View as" alongside the project preview chrome when the project + // defines security policies in rill.yaml. Per-dashboard ViewAs already + // lives in ExplorePreviewCTAs / CanvasPreviewCTAs on viz routes. + $: rillYamlPolicyCheck = useRillYamlPolicyCheck(runtimeClient); + $: showProjectViewAs = + mode === "Preview" && !onVizRoute && !!$rillYamlPolicyCheck?.data; + $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); $: projectTitleQuery = useProjectTitle(runtimeClient); @@ -169,6 +178,9 @@ {:else if showDeveloperChat} {/if} + {#if showProjectViewAs} + + {/if} {#if showPreviewToggle} Date: Fri, 1 May 2026 13:27:20 -0400 Subject: [PATCH 16/76] Widen project-preview ViewAs check to per-dashboard policies Show the navbar `ViewAsButton` when any explore or canvas resource has `securityRules` on its valid spec, in addition to the existing rill.yaml project-wide check. Previously a project with only per-dashboard `security:` blocks did not surface ViewAs on `/dashboards`. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 87f6ce5e49a..25d60078149 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -51,11 +51,21 @@ $: showPreviewToggle = !onDeployPage && !$previewModeLocked && !onVizRoute; // Show "View as" alongside the project preview chrome when the project - // defines security policies in rill.yaml. Per-dashboard ViewAs already - // lives in ExplorePreviewCTAs / CanvasPreviewCTAs on viz routes. + // — via rill.yaml or any individual dashboard — defines a security + // policy. Per-dashboard ViewAs already lives in ExplorePreviewCTAs / + // CanvasPreviewCTAs on viz routes. $: rillYamlPolicyCheck = useRillYamlPolicyCheck(runtimeClient); + $: anyDashboardHasPolicy = + explores.some( + (e) => (e?.explore?.state?.validSpec?.securityRules?.length ?? 0) > 0, + ) || + canvases.some( + (c) => (c?.canvas?.state?.validSpec?.securityRules?.length ?? 0) > 0, + ); $: showProjectViewAs = - mode === "Preview" && !onVizRoute && !!$rillYamlPolicyCheck?.data; + mode === "Preview" && + !onVizRoute && + (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); From dbfc9ec483320bfb215f97d426bd722774167409 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:28:53 -0400 Subject: [PATCH 17/76] Hide AI chat toggle on the project preview pages Skip rendering `ChatToggle` in the navbar when the user is in Preview mode (covers `/dashboards`, `/ai`, `/status`). Viz routes already use `ExplorePreviewCTAs` / `CanvasPreviewCTAs`, which carry their own chat affordance. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 25d60078149..218e45b2706 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -47,7 +47,10 @@ $: ({ size: unsavedFileCount } = $unsavedFiles); $: onDeployPage = isDeployPage($page); $: showDeployCTA = $deploy && !onDeployPage; - $: showDeveloperChat = $developerChat && !onDeployPage; + // Hide the chat toggle on the preview-only project pages (/dashboards, + // /ai, /status). Viz routes have their own chat affordance via + // Explore/CanvasPreviewCTAs. + $: showDeveloperChat = $developerChat && !onDeployPage && mode !== "Preview"; $: showPreviewToggle = !onDeployPage && !$previewModeLocked && !onVizRoute; // Show "View as" alongside the project preview chrome when the project From 0af0436fe991a1c09c03e45fb1e41dca7db07ba3 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:31:04 -0400 Subject: [PATCH 18/76] Reset chat and View as on mode toggle When the local app navigates between Developer and Preview, close the AI chat sidebar and clear any selected mock user (resetting the dev JWT). Without this the chat panel stayed open across the switch and a stale "View as" persona kept rewriting requests, neither of which makes sense in the new mode. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 218e45b2706..58ead650aad 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -13,6 +13,9 @@ useValidCanvases, useValidExplores, } from "@rilldata/web-common/features/dashboards/selectors.js"; + import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; + import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; + import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import DeployProjectCTA from "@rilldata/web-common/features/dashboards/workspace/DeployProjectCTA.svelte"; @@ -24,6 +27,7 @@ import PreviewModeToggleButton from "@rilldata/web-common/layout/header/PreviewModeToggleButton.svelte"; import { isDeployPage } from "@rilldata/web-common/layout/navigation/route-utils"; import { previewModeLocked } from "@rilldata/web-common/layout/preview-mode-store"; + import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import { get } from "svelte/store"; import { parseDocument } from "yaml"; @@ -36,6 +40,20 @@ export let mode: string; + // Reset the AI chat panel and any active "View as" impersonation when the + // user toggles between Developer and Preview, so each mode starts from a + // clean slate. + let previousMode: string | null = null; + $: { + if (previousMode !== null && previousMode !== mode) { + sidebarActions.closeChat(); + if (get(selectedMockUserStore) !== null) { + updateDevJWT(queryClient, runtimeClient, null).catch(console.error); + } + } + previousMode = mode; + } + $: ({ params: { name: dashboardName }, route, From 919dc1425f02caad397634b9e690c9197296d755 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 13:56:11 -0400 Subject: [PATCH 19/76] Consolidate `View as` into a split Preview button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the standalone navbar `ViewAsButton` (local) and the avatar `View as` submenu (cloud) with a unified split-button on the Preview control: - Left half: navigates to the dashboard preview (or back to the file in Edit mode). Switches its label to "Viewing as " with an inline clear affordance when an impersonation is active. - Right half: a `UserRoundSearch` dropdown trigger that opens the existing user picker — `ViewAsUserPopover` (cloud, gated on `manageProject`) or the mock-user list from `useMockUsers` (local, gated on any project security policy). Drops the duplicate `View as` submenu from `AvatarButton`. Co-Authored-By: Claude Opus 4.7 --- .../authentication/AvatarButton.svelte | 25 ----- .../features/projects/ProjectHeader.svelte | 22 +++- .../src/layout/ApplicationHeader.svelte | 55 ++++++++-- .../header/PreviewModeToggleButton.svelte | 102 ++++++++++++++++-- 4 files changed, 164 insertions(+), 40 deletions(-) diff --git a/web-admin/src/features/authentication/AvatarButton.svelte b/web-admin/src/features/authentication/AvatarButton.svelte index 437aaf26727..edd5d583dcc 100644 --- a/web-admin/src/features/authentication/AvatarButton.svelte +++ b/web-admin/src/features/authentication/AvatarButton.svelte @@ -26,7 +26,6 @@ createAdminServiceGetCurrentUser, type V1ProjectPermissions, } from "../../client"; - import ViewAsUserPopover from "../view-as-user/ViewAsUserPopover.svelte"; import ThemeToggle from "@rilldata/web-common/features/themes/ThemeToggle.svelte"; export let projectPermissions: V1ProjectPermissions | undefined = undefined; @@ -35,7 +34,6 @@ let imgContainer: HTMLElement; let primaryMenuOpen = false; - let subMenuOpen = false; onMount(() => { const photoUrl = $user.data?.user?.photoUrl; @@ -90,29 +88,6 @@ {#if params.organization && params.project && projectPermissions} - {#if projectPermissions.manageProject} - - { - subMenuOpen = !subMenuOpen; - }} - > - View as - - - { - subMenuOpen = false; - primaryMenuOpen = false; - }} - /> - - - {/if} {#if params.dashboard} {/if} - + { + viewAsUserStore.set(null); + }} + > + + (editorViewAsOpen = false)} + /> + + (c?.canvas?.state?.validSpec?.securityRules?.length ?? 0) > 0, ); $: showProjectViewAs = - mode === "Preview" && !onVizRoute && (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); + $: mockUsers = useMockUsers(runtimeClient); + let localViewAsOpen = false; + $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); $: projectTitleQuery = useProjectTitle(runtimeClient); @@ -209,14 +216,50 @@ {:else if showDeveloperChat} {/if} - {#if showProjectViewAs} - - {/if} {#if showPreviewToggle} + showViewAs={showProjectViewAs} + bind:dropdownOpen={localViewAsOpen} + activeViewAsLabel={$selectedMockUserStore?.email ?? null} + onClearViewAs={() => { + updateDevJWT(queryClient, runtimeClient, null).catch(console.error); + }} + > + + {#if !$mockUsers.data || $mockUsers.data?.length === 0} + No mock users + {:else} + {#each $mockUsers.data as user (user?.email)} + { + updateDevJWT(queryClient, runtimeClient, user).catch( + console.error, + ); + localViewAsOpen = false; + }} + class="flex gap-x-2 items-center" + > + {#if $selectedMockUserStore?.email === user?.email} + + {:else} + + {/if} + {user.email} + + {/each} + {/if} + + + + Add mock user + + + {/if} {#if showDeployCTA} diff --git a/web-common/src/layout/header/PreviewModeToggleButton.svelte b/web-common/src/layout/header/PreviewModeToggleButton.svelte index 0935d56d1bd..9285be49841 100644 --- a/web-common/src/layout/header/PreviewModeToggleButton.svelte +++ b/web-common/src/layout/header/PreviewModeToggleButton.svelte @@ -1,19 +1,105 @@ - + {/if} + {:else if mode === "Preview"} + Preview {:else} + Edit {/if} - {mode} -
- + + + {#if showViewAs} + + + {#snippet child({ props })} + + {/snippet} + + + + + + {/if} + + + From 2adec7ed8f1f02076fb1b29948b8d45519943879 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:04:42 -0400 Subject: [PATCH 20/76] Fix circular `@apply truncate` in `PreviewModeToggleButton` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostCSS rejects `@apply truncate` on a selector that also matches elements with `class="truncate"`. The inline class on the span already applies the utility — drop the redundant rule. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/header/PreviewModeToggleButton.svelte | 3 --- 1 file changed, 3 deletions(-) diff --git a/web-common/src/layout/header/PreviewModeToggleButton.svelte b/web-common/src/layout/header/PreviewModeToggleButton.svelte index 9285be49841..597b91bcdc5 100644 --- a/web-common/src/layout/header/PreviewModeToggleButton.svelte +++ b/web-common/src/layout/header/PreviewModeToggleButton.svelte @@ -88,9 +88,6 @@ .left.viewing { @apply max-w-[280px]; } - .left.viewing .truncate { - @apply truncate; - } .right { @apply flex items-center justify-center px-2 bg-primary-50 border-l border-primary-500; @apply text-primary-600 hover:bg-primary-100 transition-colors; From ab8580670e5503077a90817d0754b58bb09b4077 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:09:27 -0400 Subject: [PATCH 21/76] Polish split Preview button: square corners, hover-only tint, auto-nav on user pick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop the always-on light-purple background and rounded corners on the right (View as) half. The tint now appears only on hover/active. - After picking a user from the dropdown, navigate straight into the preview surface (cloud → `editPreviewHref`, local → `previewToggleHref`) so "Viewing as" lands the user where they can see the impersonated view immediately. - Stop auto-clearing the local mock user when mode toggles; selecting a user triggers a Developer → Preview transition and the previous reset was undoing the impersonation it had just set. Chat panel still closes on mode change. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index dd5c3295f67..8de932341f8 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -89,8 +89,7 @@ (c) => (c?.canvas?.state?.validSpec?.securityRules?.length ?? 0) > 0, ); $: showProjectViewAs = - !onVizRoute && - (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); + !onVizRoute && (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); $: mockUsers = useMockUsers(runtimeClient); let localViewAsOpen = false; From a90cb1e8ad90b801dd8f08ca1c61b81c4d4a8b05 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:12:00 -0400 Subject: [PATCH 22/76] Apply split-button polish and auto-nav on user pick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Earlier commit `ab8580670` only landed a stray prettier reflow from a shadow checkout; this commit lands the actual changes.) - Split button: square corners + the right (View as) half is transparent by default; tint applies only on hover/active. - Picking a user from the dropdown navigates straight into the preview surface — `editPreviewHref` (cloud) or `previewToggleHref` (local, when not already in Preview). - Local: stop auto-clearing the mock user on mode toggle, since the Developer → Preview navigation now happens as part of the pick; clear stays available via the inline × on the "Viewing as" chip. Co-Authored-By: Claude Opus 4.7 --- .../src/features/projects/ProjectHeader.svelte | 6 +++++- web-common/src/layout/ApplicationHeader.svelte | 13 +++++++------ .../layout/header/PreviewModeToggleButton.svelte | 8 ++++---- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index db96ea2a80a..5ad306a4aef 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -1,4 +1,5 @@
- - {#if isViewing} - - - Viewing as {activeViewAsLabel} - - {#if onClearViewAs} - - {/if} - {:else if mode === "Preview"} + + {#if mode === "Preview"} - Preview {:else} - Edit {/if} + {mode} {#if showViewAs} @@ -85,9 +57,6 @@ @apply flex items-center gap-x-1.5 px-3 text-primary-600 text-xs font-medium; @apply hover:bg-primary-50 transition-colors; } - .left.viewing { - @apply max-w-[280px]; - } .right { @apply flex items-center justify-center px-2 border-l border-primary-500; @apply text-primary-600 hover:bg-primary-50 transition-colors; @@ -95,8 +64,4 @@ .right.active { @apply bg-primary-50; } - .clear-btn { - @apply flex items-center justify-center rounded-full p-0.5 ml-1; - @apply text-primary-600 hover:bg-primary-100; - } From 208e4a3a2af4889b6b098b71df1b7d41aaafd98c Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:34:11 -0400 Subject: [PATCH 25/76] feat: redirect hidden sections to branch home (section-scoped) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-introduces the redirect from the reverted commit, but in a way that doesn't break in-flight client-side navigations like home-bookmark URL restoration. The previous attempt put the check on `[organization]/[project]/+layout.ts`, which made `url` a dependency of that loader and caused it to re-run on every URL change in the project — including the `goto(redirectUrl, { replaceState: true })` that `DashboardStateSync` fires to restore bookmarked filters. The re-run interfered with the goto and the URL ended up empty after bookmark restoration (regressed `bookmarks.spec.ts:265 "Visiting home should restore home bookmark"`). Section-scoped loaders only re-run on URL changes within the same section, so they don't disturb bookmark restoration on `/explore/`. --- .../[project]/-/alerts/+layout.ts | 25 +++++++++++++++++++ .../[project]/-/reports/+layout.ts | 19 ++++++++++++++ .../[project]/-/settings/+layout.ts | 19 ++++++++++++++ .../[project]/-/status/+layout.ts | 19 ++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts create mode 100644 web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts create mode 100644 web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts create mode 100644 web-admin/src/routes/[organization]/[project]/-/status/+layout.ts diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts new file mode 100644 index 00000000000..7e420392f0a --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts @@ -0,0 +1,25 @@ +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutLoad } from "./$types"; + +// Alerts is a cloud-only feature; on a branch view send the user back to +// the branch home so deep links (bookmarks, share URLs, stale tabs) don't +// dead-end at a hidden section. +// +// Scoped to this section's loader on purpose — putting the same check on +// the project-wide `[organization]/[project]/+layout.ts` registers `url` +// as a dependency of that loader, which then re-runs on every in-project +// URL change and clobbers in-flight client-side `goto()`s such as the +// home-bookmark URL restoration in `DashboardStateSync`. +export const load: LayoutLoad = ({ url, params }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch) { + throw redirect( + 307, + `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + ); + } +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts new file mode 100644 index 00000000000..e3dc369ccac --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts @@ -0,0 +1,19 @@ +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutLoad } from "./$types"; + +// Reports is a cloud-only feature; on a branch view send the user back to +// the branch home. See alerts/+layout.ts for why this lives in a +// section-scoped loader rather than the project-wide layout. +export const load: LayoutLoad = ({ url, params }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch) { + throw redirect( + 307, + `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + ); + } +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts new file mode 100644 index 00000000000..8dc27edae6c --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts @@ -0,0 +1,19 @@ +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutLoad } from "./$types"; + +// Settings is hidden on branch views; redirect deep links back to the +// branch home. See alerts/+layout.ts for why this lives in a +// section-scoped loader rather than the project-wide layout. +export const load: LayoutLoad = ({ url, params }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch) { + throw redirect( + 307, + `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + ); + } +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts new file mode 100644 index 00000000000..d987f1fef94 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts @@ -0,0 +1,19 @@ +import { + branchPathPrefix, + extractBranchFromPath, +} from "@rilldata/web-admin/features/branches/branch-utils"; +import { redirect } from "@sveltejs/kit"; +import type { LayoutLoad } from "./$types"; + +// Status is hidden on branch views; redirect deep links back to the +// branch home. See alerts/+layout.ts for why this lives in a +// section-scoped loader rather than the project-wide layout. +export const load: LayoutLoad = ({ url, params }) => { + const activeBranch = extractBranchFromPath(url.pathname); + if (activeBranch) { + throw redirect( + 307, + `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + ); + } +}; From 3d9fb37cff72b0adb24ae47246b655dfe5cf4095 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:38:35 -0400 Subject: [PATCH 26/76] Reset chat + View as on explicit Preview/Edit click; decouple in RD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New `onPreviewClick` callback on `PreviewModeToggleButton` fires only on the Preview/Edit half. Picking a user from the dropdown still flips the mode but no longer triggers the reset. - Local: replace the integrated split-button dropdown with the standalone `ViewAsButton` (always rendered when a project policy exists). Its default state shows the `👤 View as` pill; impersonating state shows the `Viewing as ` chip. - Local: `onPreviewClick` resets the chat panel and clears any active mock-user impersonation. - Cloud: `onPreviewClick` clears `viewAsUserStore` if set. Editor retains the split-button design. Co-Authored-By: Claude Opus 4.7 --- .../features/projects/ProjectHeader.svelte | 3 + .../src/layout/ApplicationHeader.svelte | 70 ++++--------------- .../header/PreviewModeToggleButton.svelte | 12 +++- 3 files changed, 26 insertions(+), 59 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 9bfda192da8..1fd930072cb 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -230,6 +230,9 @@ href={editPreviewHref} showViewAs={projectPermissions?.manageProject ?? false} bind:dropdownOpen={editorViewAsOpen} + onPreviewClick={() => { + if ($viewAsUserStore) viewAsUserStore.set(null); + }} > - import { goto } from "$app/navigation"; import { page } from "$app/stores"; import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { @@ -17,14 +16,8 @@ import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; - import { useMockUsers } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers"; import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; - import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; - import Add from "@rilldata/web-common/components/icons/Add.svelte"; - import Check from "@rilldata/web-common/components/icons/Check.svelte"; - import Spacer from "@rilldata/web-common/components/icons/Spacer.svelte"; - import { getFileHref } from "@rilldata/web-common/layout/navigation/editor-routing"; import DeployProjectCTA from "@rilldata/web-common/features/dashboards/workspace/DeployProjectCTA.svelte"; import ExplorePreviewCTAs from "@rilldata/web-common/features/explores/ExplorePreviewCTAs.svelte"; import { featureFlags } from "@rilldata/web-common/features/feature-flags.ts"; @@ -47,17 +40,16 @@ export let mode: string; - // Close the AI chat panel when the user toggles between Developer and - // Preview. The active "View as" mock user is intentionally preserved - // across the toggle so a user picked from the dropdown survives the - // Developer → Preview navigation; clearing happens via the inline × on - // the "Viewing as" chip. - let previousMode: string | null = null; - $: { - if (previousMode !== null && previousMode !== mode) { - sidebarActions.closeChat(); + // Reset session state (chat panel, mock-user impersonation) when the + // user explicitly clicks the Preview/Edit toggle. This is wired to the + // button click, not to mode changes in general — picking a user from + // the View-as dropdown also flips the mode, but should preserve the + // impersonation it just set. + function resetOnModeToggle() { + sidebarActions.closeChat(); + if (get(selectedMockUserStore) !== null) { + updateDevJWT(queryClient, runtimeClient, null).catch(console.error); } - previousMode = mode; } $: ({ @@ -92,9 +84,6 @@ $: showProjectViewAs = !onVizRoute && (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); - $: mockUsers = useMockUsers(runtimeClient); - let localViewAsOpen = false; - $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); $: projectTitleQuery = useProjectTitle(runtimeClient); @@ -216,50 +205,15 @@ {:else if showDeveloperChat} {/if} - {#if $selectedMockUserStore} + {#if showProjectViewAs} {/if} {#if showPreviewToggle} - - {#if !$mockUsers.data || $mockUsers.data?.length === 0} - No mock users - {:else} - {#each $mockUsers.data as user (user?.email)} - { - updateDevJWT(queryClient, runtimeClient, user).catch( - console.error, - ); - localViewAsOpen = false; - if (mode !== "Preview") void goto(previewToggleHref); - }} - class="flex gap-x-2 items-center" - > - {#if $selectedMockUserStore?.email === user?.email} - - {:else} - - {/if} - {user.email} - - {/each} - {/if} - - - - Add mock user - - - + onPreviewClick={resetOnModeToggle} + /> {/if} {#if showDeployCTA} diff --git a/web-common/src/layout/header/PreviewModeToggleButton.svelte b/web-common/src/layout/header/PreviewModeToggleButton.svelte index 9c2d44621dc..d2aec49e61e 100644 --- a/web-common/src/layout/header/PreviewModeToggleButton.svelte +++ b/web-common/src/layout/header/PreviewModeToggleButton.svelte @@ -9,10 +9,20 @@ /** When true, render the trailing UserRoundSearch dropdown trigger and a * `dropdown` slot for the user-list popover. */ export let showViewAs = false; + /** Fires alongside navigation when the user clicks the Preview/Edit + * half. Use this to reset session state (chat, impersonation, etc.) + * on an explicit mode swap — distinct from picking a user from the + * dropdown, which intentionally preserves impersonation. */ + export let onPreviewClick: (() => void) | null = null;
- + onPreviewClick?.()} + > {#if mode === "Preview"} {:else} From 3224ee837e0622fa9330e1ee872355b861facf7a Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:39:55 -0400 Subject: [PATCH 27/76] docs: explain branch redirect scoping on each section loader --- .../[project]/-/alerts/+layout.ts | 6 ++++-- .../[project]/-/reports/+layout.ts | 14 +++++++++++--- .../[project]/-/settings/+layout.ts | 17 ++++++++++++++--- .../[project]/-/status/+layout.ts | 17 ++++++++++++++--- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts index 7e420392f0a..56663e477b8 100644 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts @@ -5,8 +5,10 @@ import { import { redirect } from "@sveltejs/kit"; import type { LayoutLoad } from "./$types"; -// Alerts is a cloud-only feature; on a branch view send the user back to -// the branch home so deep links (bookmarks, share URLs, stale tabs) don't +// Alerts is a cloud-only feature: assertions evaluate against the +// production deployment, so it doesn't make sense to expose it inside a +// branch view. On a branch URL we redirect deep links (bookmarks, shared +// URLs, stale tabs) back to the branch home rather than letting the user // dead-end at a hidden section. // // Scoped to this section's loader on purpose — putting the same check on diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts index e3dc369ccac..7c82d0044ce 100644 --- a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts @@ -5,9 +5,17 @@ import { import { redirect } from "@sveltejs/kit"; import type { LayoutLoad } from "./$types"; -// Reports is a cloud-only feature; on a branch view send the user back to -// the branch home. See alerts/+layout.ts for why this lives in a -// section-scoped loader rather than the project-wide layout. +// Reports is a cloud-only feature: schedules run against the production +// deployment, so it doesn't make sense to expose it inside a branch view. +// On a branch URL we redirect deep links (bookmarks, shared URLs, stale +// tabs) back to the branch home rather than letting the user dead-end at +// a hidden section. +// +// Scoped to this section's loader on purpose — putting the same check on +// the project-wide `[organization]/[project]/+layout.ts` registers `url` +// as a dependency of that loader, which then re-runs on every in-project +// URL change and clobbers in-flight client-side `goto()`s such as the +// home-bookmark URL restoration in `DashboardStateSync`. export const load: LayoutLoad = ({ url, params }) => { const activeBranch = extractBranchFromPath(url.pathname); if (activeBranch) { diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts index c66217f5d93..244f420531b 100644 --- a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts @@ -4,9 +4,20 @@ import { } from "@rilldata/web-admin/features/branches/branch-utils"; import { redirect } from "@sveltejs/kit"; -// Branch views: redirect deep links back to the branch home. -// Production view: gate on manageProject so users without permission -// can't open Settings via a direct URL. +// Settings (env vars, public URLs, billing, members) only applies to the +// production deployment, so it's hidden from the project nav on branch +// views. This loader catches deep links — bookmarks, shared URLs, stale +// tabs — and bounces them to the branch home so users don't dead-end at +// a section that's intentionally hidden. +// +// Scoped to this section's loader on purpose — putting the same check on +// the project-wide `[organization]/[project]/+layout.ts` registers `url` +// as a dependency of that loader, which then re-runs on every in-project +// URL change and clobbers in-flight client-side `goto()`s such as the +// home-bookmark URL restoration in `DashboardStateSync`. +// +// Production view also gates on `manageProject` so non-admin users can't +// reach Settings via a direct URL. export const load = async ({ url, parent, diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts index 786443666f7..66ea345d083 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts @@ -4,9 +4,20 @@ import { } from "@rilldata/web-admin/features/branches/branch-utils"; import { redirect } from "@sveltejs/kit"; -// Branch views: redirect deep links back to the branch home. -// Production view: gate on manageProject so users without permission -// can't open Status via a direct URL. +// Status (deployments, resources, logs, tables, branches list) reports on +// the production deployment, so it's hidden from the project nav on +// branch views. This loader catches deep links — bookmarks, shared URLs, +// stale tabs — and bounces them to the branch home so users don't +// dead-end at a section that's intentionally hidden. +// +// Scoped to this section's loader on purpose — putting the same check on +// the project-wide `[organization]/[project]/+layout.ts` registers `url` +// as a dependency of that loader, which then re-runs on every in-project +// URL change and clobbers in-flight client-side `goto()`s such as the +// home-bookmark URL restoration in `DashboardStateSync`. +// +// Production view also gates on `manageProject` so non-admin users can't +// reach Status via a direct URL. export const load = async ({ url, parent, From 04b4dc2c45688bf09a2fa914ccf197a363e08207 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:42:01 -0400 Subject: [PATCH 28/76] RD: split Preview button in Dev mode, standalone View as in Preview mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Developer mode the navbar shows the split Preview + UserRoundSearch button (matching the Figma design and bringing back the inline mock-user dropdown). In Preview mode it shows the standalone `ViewAsButton` (default `👤 View as` pill / `Viewing as ` chip when active) alongside a plain Edit button. Co-Authored-By: Claude Opus 4.7 --- .../src/layout/ApplicationHeader.svelte | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index e40a3b52701..62ce293921a 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -16,8 +16,15 @@ import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; + import { useMockUsers } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers"; import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; + import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; + import Add from "@rilldata/web-common/components/icons/Add.svelte"; + import Check from "@rilldata/web-common/components/icons/Check.svelte"; + import Spacer from "@rilldata/web-common/components/icons/Spacer.svelte"; + import { getFileHref } from "@rilldata/web-common/layout/navigation/editor-routing"; + import { goto } from "$app/navigation"; import DeployProjectCTA from "@rilldata/web-common/features/dashboards/workspace/DeployProjectCTA.svelte"; import ExplorePreviewCTAs from "@rilldata/web-common/features/explores/ExplorePreviewCTAs.svelte"; import { featureFlags } from "@rilldata/web-common/features/feature-flags.ts"; @@ -84,6 +91,9 @@ $: showProjectViewAs = !onVizRoute && (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); + $: mockUsers = useMockUsers(runtimeClient); + let localViewAsOpen = false; + $: exploresQuery = useValidExplores(runtimeClient); $: canvasQuery = useValidCanvases(runtimeClient); $: projectTitleQuery = useProjectTitle(runtimeClient); @@ -205,12 +215,54 @@ {:else if showDeveloperChat} {/if} - {#if showProjectViewAs} - - {/if} - {#if showPreviewToggle} + {#if showPreviewToggle && mode === "Developer"} + + + {#if !$mockUsers.data || $mockUsers.data?.length === 0} + No mock users + {:else} + {#each $mockUsers.data as user (user?.email)} + { + updateDevJWT(queryClient, runtimeClient, user).catch( + console.error, + ); + localViewAsOpen = false; + void goto(previewToggleHref); + }} + class="flex gap-x-2 items-center" + > + {#if $selectedMockUserStore?.email === user?.email} + + {:else} + + {/if} + {user.email} + + {/each} + {/if} + + + + Add mock user + + + + {:else if showPreviewToggle && mode === "Preview"} + {#if showProjectViewAs} + + {/if} From 4934379678c9905bf42e27c66f83ed0279dad0d4 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:49:42 -0400 Subject: [PATCH 29/76] fix: drop unresolved `./$types` import from new section loaders --- .../src/routes/[organization]/[project]/-/alerts/+layout.ts | 5 ++--- .../src/routes/[organization]/[project]/-/reports/+layout.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts index 56663e477b8..e54fdc97e8e 100644 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts @@ -3,7 +3,6 @@ import { extractBranchFromPath, } from "@rilldata/web-admin/features/branches/branch-utils"; import { redirect } from "@sveltejs/kit"; -import type { LayoutLoad } from "./$types"; // Alerts is a cloud-only feature: assertions evaluate against the // production deployment, so it doesn't make sense to expose it inside a @@ -16,12 +15,12 @@ import type { LayoutLoad } from "./$types"; // as a dependency of that loader, which then re-runs on every in-project // URL change and clobbers in-flight client-side `goto()`s such as the // home-bookmark URL restoration in `DashboardStateSync`. -export const load: LayoutLoad = ({ url, params }) => { +export const load = ({ url, params: { organization, project } }) => { const activeBranch = extractBranchFromPath(url.pathname); if (activeBranch) { throw redirect( 307, - `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + `/${organization}/${project}${branchPathPrefix(activeBranch)}`, ); } }; diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts index 7c82d0044ce..3adab7d4adc 100644 --- a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts @@ -3,7 +3,6 @@ import { extractBranchFromPath, } from "@rilldata/web-admin/features/branches/branch-utils"; import { redirect } from "@sveltejs/kit"; -import type { LayoutLoad } from "./$types"; // Reports is a cloud-only feature: schedules run against the production // deployment, so it doesn't make sense to expose it inside a branch view. @@ -16,12 +15,12 @@ import type { LayoutLoad } from "./$types"; // as a dependency of that loader, which then re-runs on every in-project // URL change and clobbers in-flight client-side `goto()`s such as the // home-bookmark URL restoration in `DashboardStateSync`. -export const load: LayoutLoad = ({ url, params }) => { +export const load = ({ url, params: { organization, project } }) => { const activeBranch = extractBranchFromPath(url.pathname); if (activeBranch) { throw redirect( 307, - `/${params.organization}/${params.project}${branchPathPrefix(activeBranch)}`, + `/${organization}/${project}${branchPathPrefix(activeBranch)}`, ); } }; From a0da6dd034b0998153726d7401ae6a9a785c1b47 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 14:49:45 -0400 Subject: [PATCH 30/76] RD: always show View as in the navbar (drop policy gate) Both the split-button human icon (Dev mode) and the standalone View as pill (Preview mode) now render unconditionally on non-viz routes. The dropdown handles the empty case by showing "No mock users" and the "Add mock user" link, so the entry point is reachable even before a policy or mock user exists. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 62ce293921a..e12eb75f0ec 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -219,7 +219,7 @@ @@ -258,9 +258,7 @@ {:else if showPreviewToggle && mode === "Preview"} - {#if showProjectViewAs} - - {/if} + Date: Fri, 1 May 2026 14:55:37 -0400 Subject: [PATCH 31/76] Restyle `View as` default pill: bg-primary tint, user icon, no caret - Swap the eye icon for `UserRoundSearch` to match the split-button right half. - Drop the `CaretDownIcon`; the tint on hover already signals a menu. - Use `bg-primary-50` / `text-primary-600` so the pill reads as a primary action across local and cloud surfaces. Co-Authored-By: Claude Opus 4.7 --- .../granular-access-policies/ViewAsButton.svelte | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web-common/src/features/dashboards/granular-access-policies/ViewAsButton.svelte b/web-common/src/features/dashboards/granular-access-policies/ViewAsButton.svelte index 2a2f31fa5e0..b0721434fcd 100644 --- a/web-common/src/features/dashboards/granular-access-policies/ViewAsButton.svelte +++ b/web-common/src/features/dashboards/granular-access-policies/ViewAsButton.svelte @@ -4,10 +4,9 @@ import { Chip } from "../../../components/chip"; import Add from "../../../components/icons/Add.svelte"; - import CaretDownIcon from "../../../components/icons/CaretDownIcon.svelte"; import Check from "../../../components/icons/Check.svelte"; - import EyeIcon from "../../../components/icons/EyeIcon.svelte"; import Spacer from "../../../components/icons/Spacer.svelte"; + import { UserRoundSearch } from "lucide-svelte"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import { selectedMockUserStore } from "./stores"; import { useMockUsers } from "./useMockUsers"; @@ -28,12 +27,10 @@ {#if $selectedMockUserStore === null} {:else} + {:else} + + {/if} + {/snippet} + + + (open = false)} + /> + + From a5b5ed9137ebb0da83485071a9239c6cbc45df37 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 15:58:25 -0400 Subject: [PATCH 38/76] Cloud editor: drop sidebar + chat on dev-preview sub-routes Hide `Navigation` (file tree) and `DeveloperChat` on `/-/edit/dashboards|status|ai` so those pages stand alone, the way local's `/dashboards`, `/status`, and `/ai` do. Co-Authored-By: Claude Opus 4.7 --- .../[organization]/[project]/-/edit/+layout.svelte | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 858859ee42b..5bebaf283b1 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -126,6 +126,11 @@ $: branchUrl = `/${organization}/${project}${branchPathPrefix(branch)}`; $: inProjectWelcomePage = isProjectWelcomePage($page); + // Dev-preview sub-routes (`/-/edit/dashboards|status|ai`) render + // standalone, without the editor's file tree or chat sidebar. + $: inEditDevPreview = !!$page.route.id?.match( + /\/-\/edit\/(dashboards|status|ai)(\/|$)/, + ); // Invalidating this query refetches a fresh JWT; `runtimeClient.getJwt()` // reads the updated value on the next call. Branch must be part of the @@ -197,7 +202,7 @@ errorBody="Lost connection to the editing environment. Try ending the session and starting a new one." >
- {#if !inProjectWelcomePage} + {#if !inProjectWelcomePage && !inEditDevPreview} {/if} @@ -205,7 +210,9 @@
- + {#if !inEditDevPreview} + + {/if}
From cb95dea89323c9ebaa707f51a3e39c1f8942be8c Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:04:43 -0400 Subject: [PATCH 39/76] Add Dashboards/AI/Status tab nav to cloud dev-preview routes Render `EditDevPreviewNav` above the page content on `/-/edit/dashboards|status|ai`, mirroring local's `PreviewModeNav`: animated underline, primary-tinted active tab, and a project status indicator (loading / red error / green ok) on the Status tab. Co-Authored-By: Claude Opus 4.7 --- .../features/preview/EditDevPreviewNav.svelte | 178 ++++++++++++++++++ .../[project]/-/edit/+layout.svelte | 4 + 2 files changed, 182 insertions(+) create mode 100644 web-admin/src/features/preview/EditDevPreviewNav.svelte diff --git a/web-admin/src/features/preview/EditDevPreviewNav.svelte b/web-admin/src/features/preview/EditDevPreviewNav.svelte new file mode 100644 index 00000000000..52379fb11f8 --- /dev/null +++ b/web-admin/src/features/preview/EditDevPreviewNav.svelte @@ -0,0 +1,178 @@ + + + + +
+ + diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 5bebaf283b1..fdd543e6900 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -14,6 +14,7 @@ import BranchDeploymentStopped from "@rilldata/web-admin/features/branches/BranchDeploymentStopped.svelte"; import EditSessionLoading from "@rilldata/web-admin/features/edit-session/EditSessionLoading.svelte"; import EditSessionTimeoutBanner from "@rilldata/web-admin/features/edit-session/EditSessionTimeoutBanner.svelte"; + import EditDevPreviewNav from "@rilldata/web-admin/features/preview/EditDevPreviewNav.svelte"; import ProjectHeader from "@rilldata/web-admin/features/projects/ProjectHeader.svelte"; import SlimProjectHeader from "@rilldata/web-admin/features/projects/SlimProjectHeader.svelte"; import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; @@ -201,6 +202,9 @@ {onBeforeReconnect} errorBody="Lost connection to the editing environment. Try ending the session and starting a new one." > + {#if inEditDevPreview} + + {/if}
{#if !inProjectWelcomePage && !inEditDevPreview} From 76bad6d4493e4eb9dceedbde85489bd7d7a07d5e Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:14:45 -0400 Subject: [PATCH 40/76] Cloud dev preview: gate View as on `manageDev` instead of `manageProject` Editors (collaborators) can enter the cloud editor but `manageProject` is admin-only. The previous gate hid the View as button from non-admin editors. Show it whenever the user has `manageDev`, matching who can realistically use the dev preview. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index ad10d380ca4..46b225f5e2e 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -229,7 +229,7 @@
{#if editContext && inEditDevPreview} - {#if projectPermissions?.manageProject} + {#if projectPermissions?.manageDev} {/if} From 14bf3dcaff0203f027230545248aa9d03834fe49 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:18:38 -0400 Subject: [PATCH 41/76] Surface View as on cloud non-edit preview + RD `--preview` lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RD: split the standalone View-as gate from the Edit toggle gate so the View-as pill renders in Preview mode even when `--preview` locks the runtime (the Edit toggle still hides correctly in that case). - Cloud non-edit `ProjectHeader`: replace the active-only `ViewAsUserChip` with `CloudViewAsButton`, gated on `manageDev`. Editors and admins now see the default `👤 View as` pill on `/{org}/{project}/-/dashboards`, `/-/status`, dashboard pages, etc. Co-Authored-By: Claude Opus 4.7 --- .../features/projects/ProjectHeader.svelte | 4 ++-- .../src/layout/ApplicationHeader.svelte | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 46b225f5e2e..2d988da5d12 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -267,8 +267,8 @@ {primaryBranch} /> {:else} - {#if $viewAsUserStore} - + {#if projectPermissions?.manageDev} + {/if} {#if $cloudEditing && onProjectPage && projectPermissions.manageDev} diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index e12eb75f0ec..c86feec1945 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -75,6 +75,9 @@ // Explore/CanvasPreviewCTAs. $: showDeveloperChat = $developerChat && !onDeployPage && mode !== "Preview"; $: showPreviewToggle = !onDeployPage && !$previewModeLocked && !onVizRoute; + // View as is independent of the toggle: it should still be available on + // non-viz routes even when `--preview` locks the mode. + $: showStandaloneViewAs = !onDeployPage && !onVizRoute; // Show "View as" alongside the project preview chrome when the project // — via rill.yaml or any individual dashboard — defines a security @@ -257,13 +260,17 @@ - {:else if showPreviewToggle && mode === "Preview"} - - + {:else if mode === "Preview"} + {#if showStandaloneViewAs} + + {/if} + {#if showPreviewToggle} + + {/if} {/if} {#if showDeployCTA} From 3f4fffc23302047eebfbe37b4b7c7c7e688d13fb Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:19:05 -0400 Subject: [PATCH 42/76] Remove Play/Pencil icons from Preview/Edit buttons Drop the leading SVG icons on the Preview and Edit halves of `PreviewModeToggleButton`. The label alone reads cleanly across local, cloud editor, and cloud dev preview chrome. Co-Authored-By: Claude Opus 4.7 --- .../src/layout/header/PreviewModeToggleButton.svelte | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web-common/src/layout/header/PreviewModeToggleButton.svelte b/web-common/src/layout/header/PreviewModeToggleButton.svelte index 7c0da684c5c..ccd66110ae3 100644 --- a/web-common/src/layout/header/PreviewModeToggleButton.svelte +++ b/web-common/src/layout/header/PreviewModeToggleButton.svelte @@ -1,6 +1,6 @@ -
+
{#if onPublicURLPage} diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte index a065903363c..11b4d9d5d3e 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte @@ -1,4 +1,6 @@ - + From 71aa08c0b0822cf1c2154dba9619d6f77ae49ab7 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:27:37 -0400 Subject: [PATCH 44/76] Cloud dev preview: status side nav + resources sub-route Add a `/-/edit/status/+layout.svelte` mirroring local's status layout (Overview / Resources side nav inside `ContentContainer`) and a matching `/-/edit/status/resources/+page.svelte` driven by the shared `ResourcesFilterableTable` and URL filter sync. Tables sub-route is intentionally omitted for now. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/status/+layout.svelte | 53 +++++++++++++ .../-/edit/status/resources/+page.svelte | 77 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte create mode 100644 web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte new file mode 100644 index 00000000000..f66e8a516a6 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte @@ -0,0 +1,53 @@ + + + +
+ +
+ +
+
+
+ + diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte new file mode 100644 index 00000000000..0b700a69dbf --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte @@ -0,0 +1,77 @@ + + +{#if parseErrors.length > 0} + +{/if} + + + r.meta?.reconcileStatus === V1ReconcileStatus.RECONCILE_STATUS_RUNNING, + )} + onFilterChange={(next) => filterSync.update(next)} + onTrigger={async (names) => { + await $createTrigger.mutateAsync({ + instanceId: runtimeClient.instanceId, + data: { resources: names }, + }); + void queryClient.invalidateQueries({ + queryKey: getRuntimeServiceListResourcesQueryKey( + runtimeClient.instanceId, + {}, + ), + }); + }} +/> From 3ae857cd263b5276a8920431006dcb62a49dcdb9 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:31:55 -0400 Subject: [PATCH 45/76] Strip PR1/PR2 changes from this branch Revert the navbar branch-view limits (PR1) and editor Status-tab permission work (PR2) from this branch so the diff scope is just the preview consolidation: - Restore `admin/server/runtime_jwt.go`, `ProjectTabs.svelte`, `EditActions.svelte`, `EditButton.svelte`, `MergePopover.svelte`, `DeploymentSection.svelte`, `deployment-utils*.ts`, and the per-section `+layout.ts` files (`/-/settings`, `/-/deploying`, `/-/status`, `[organization]/[project]/+layout.ts`) to `origin/main`. - Restore `PublishPopover.svelte`, `ProjectPublishing.svelte`, and `/-/deploying/+page.ts` that PR1 removed. - Drop the `+layout.ts` files PR1 added under `/-/alerts` and `/-/reports`. - `ProjectHeader.svelte`: remove the `isBranchView` derived value and the `!isBranchView` gates on Share, ExploreBookmarks, and CanvasBookmarks. Preview button + dev-preview chrome + viewer pill gate stay. Co-Authored-By: Claude Opus 4.7 --- admin/server/runtime_jwt.go | 10 +- .../branches/deployment-utils.spec.ts | 18 ++ .../src/features/branches/deployment-utils.ts | 4 + .../features/edit-session/EditActions.svelte | 38 ++- .../features/edit-session/EditButton.svelte | 31 ++- .../features/edit-session/MergePopover.svelte | 73 ++---- .../edit-session/PublishPopover.svelte | 218 ++++++++++++++++++ .../features/projects/ProjectHeader.svelte | 7 +- .../projects/ProjectPublishing.svelte | 21 ++ .../src/features/projects/ProjectTabs.svelte | 10 +- .../status/overview/DeploymentSection.svelte | 14 +- .../[organization]/[project]/+layout.ts | 15 -- .../[project]/-/alerts/+layout.ts | 26 --- .../[project]/-/deploying/+page.svelte | 13 +- .../[project]/-/deploying/+page.ts | 2 + .../[project]/-/reports/+layout.ts | 26 --- .../[project]/-/settings/+layout.ts | 31 +-- .../[project]/-/status/+layout.svelte | 6 +- .../[project]/-/status/+layout.ts | 6 +- 19 files changed, 383 insertions(+), 186 deletions(-) create mode 100644 web-admin/src/features/edit-session/PublishPopover.svelte create mode 100644 web-admin/src/features/projects/ProjectPublishing.svelte delete mode 100644 web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts delete mode 100644 web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts diff --git a/admin/server/runtime_jwt.go b/admin/server/runtime_jwt.go index 86dc7465c11..a1b5a320583 100644 --- a/admin/server/runtime_jwt.go +++ b/admin/server/runtime_jwt.go @@ -173,16 +173,14 @@ func (s *Server) issueRuntimeToken(ctx context.Context, opts *issueRuntimeTokenO } } - // Check if allowed to manage the deployment's environment, or to read its status. + // Check if allowed to manage the deployment's environment. // NOTE: Only applicable for tokens issued for the claims owner (not possible to delegate to other end users). - var manageDepl, readDeplStatus bool + var manageDepl bool if opts.forOwner { if opts.deployment.Environment == "prod" { manageDepl = opts.projectPermissions.ManageProd - readDeplStatus = opts.projectPermissions.ReadProdStatus } else { manageDepl = opts.projectPermissions.ManageDev - readDeplStatus = opts.projectPermissions.ReadDevStatus } } @@ -193,10 +191,6 @@ func (s *Server) issueRuntimeToken(ctx context.Context, opts *issueRuntimeTokenO runtime.ReadObjects, runtime.UseAI, } - if readDeplStatus { - // Status visibility: lets non-managers (e.g. editors) view the project Status page. - instancePermissions = append(instancePermissions, runtime.ReadInstance) - } if manageDepl { instancePermissions = append( instancePermissions, diff --git a/web-admin/src/features/branches/deployment-utils.spec.ts b/web-admin/src/features/branches/deployment-utils.spec.ts index fc021948156..59828ac7e80 100644 --- a/web-admin/src/features/branches/deployment-utils.spec.ts +++ b/web-admin/src/features/branches/deployment-utils.spec.ts @@ -261,5 +261,23 @@ describe("deployment-utils", () => { const result = await call("/rilldata/openrtb"); expect(result).toBeUndefined(); }); + + it("does not redirect away from the deploying page", async () => { + // Conditions that would otherwise trigger a redirect: + // no prod deployment, an active editable dev deployment, no @branch in URL. + listDeploymentsMock.mockResolvedValue({ + deployments: [ + makeDeployment({ + environment: "dev", + editable: true, + branch: "edit-branch", + status: V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING, + }), + ], + }); + + const result = await call("/rilldata/openrtb/-/deploying"); + expect(result).toBeUndefined(); + }); }); }); diff --git a/web-admin/src/features/branches/deployment-utils.ts b/web-admin/src/features/branches/deployment-utils.ts index 4d680171316..6f4ef46344d 100644 --- a/web-admin/src/features/branches/deployment-utils.ts +++ b/web-admin/src/features/branches/deployment-utils.ts @@ -40,6 +40,10 @@ export async function maybeRedirectToEditableDeployment( project: string, url: URL, ) { + // The deploying page is a transitional progress screen for a prod deployment + // that is still provisioning. Do not redirect away from it. + if (url.pathname.endsWith("/-/deploying")) return; + const deploymentsResp = await queryClient.fetchQuery({ queryKey: getAdminServiceListDeploymentsQueryKey(organization, project, {}), queryFn: () => adminServiceListDeployments(organization, project, {}), diff --git a/web-admin/src/features/edit-session/EditActions.svelte b/web-admin/src/features/edit-session/EditActions.svelte index f1f21613035..92b4632da14 100644 --- a/web-admin/src/features/edit-session/EditActions.svelte +++ b/web-admin/src/features/edit-session/EditActions.svelte @@ -3,20 +3,52 @@ import { Button } from "@rilldata/web-common/components/button"; import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte"; import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; - import { LogOut } from "lucide-svelte"; + import { extractErrorMessage } from "@rilldata/web-common/lib/errors"; + import { createRuntimeServiceGitStatus } from "@rilldata/web-common/runtime-client"; + import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; + import { GitBranch, LogOut } from "lucide-svelte"; import CommitPopover from "./CommitPopover.svelte"; import MergePopover from "./MergePopover.svelte"; + import PublishPopover from "./PublishPopover.svelte"; export let organization: string; export let project: string; export let branch: string; export let primaryBranch: string | undefined = undefined; + const client = useRuntimeClient(); + const gitStatusQuery = createRuntimeServiceGitStatus(client, {}); + $: closeHref = `/${organization}/${project}${branchPathPrefix(branch)}`; + $: managedGit = $gitStatusQuery.data?.managedGit; + $: gitStatusLoaded = $gitStatusQuery.data !== undefined; + // Show the parent-level error UI only when GitStatus has never loaded. + // After a successful load, TanStack keeps `data` populated through transient + // refetch errors, so the popovers stay mounted and the user keeps the toolbar. + $: gitStatusErrorMessage = + !gitStatusLoaded && $gitStatusQuery.isError + ? extractErrorMessage($gitStatusQuery.error) + : ""; - - +{#if gitStatusLoaded} + {#if managedGit} + + {:else} + + + {/if} +{:else if gitStatusErrorMessage} + + + + {gitStatusErrorMessage} + + +{/if} + {/snippet} + + +
+

+ Publish your changes to production and return to the project home. + Viewers will see updates as the project reconciles. +

+ + {#if errorMessage} +

{errorMessage}

+ {/if} +
+
+ + + + {#if alreadyOnPrimary} + Already on production + {:else if !primaryBranch || !currentBranch || !deploymentsLoaded} + Loading project... + {:else} + Publish your latest changes + {/if} + + +
diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index adef9445d4a..2a39394f9fb 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -107,7 +107,6 @@ $: onPublicURLPage = isPublicURLPage($page); $: activeBranch = extractBranchFromPath($page.url.pathname); - $: isBranchView = !!activeBranch && activeBranch !== primaryBranch; $: loggedIn = !!$user.data?.user; $: rillLogoHref = !loggedIn ? "https://www.rilldata.com" : "/"; @@ -273,7 +272,7 @@ {#if $cloudEditing && onProjectPage && projectPermissions.manageDev} {/if} - {#if onProjectPage && projectPermissions.manageProjectMembers && !isBranchView} + {#if onProjectPage && projectPermissions.manageProjectMembers} {/if} - {#if hasUserAccess && !isBranchView} + {#if hasUserAccess} {/if} - {#if hasUserAccess && !isBranchView} + {#if hasUserAccess} + import CtaContentContainer from "@rilldata/web-common/components/calls-to-action/CTAContentContainer.svelte"; + import CtaHeader from "@rilldata/web-common/components/calls-to-action/CTAHeader.svelte"; + import CtaLayoutContainer from "@rilldata/web-common/components/calls-to-action/CTALayoutContainer.svelte"; + import CtaNeedHelp from "@rilldata/web-common/components/calls-to-action/CTANeedHelp.svelte"; + import LoadingSpinner from "@rilldata/web-common/components/LoadingSpinner.svelte"; + + + + + + + Publishing your project to production... + +

+ Setting up your production environment for the first time. This usually + takes about a minute. +

+ +
+
diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index 2ff35fb92ec..0c951097c68 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -16,8 +16,6 @@ const { chat, reports, alerts } = featureFlags; - $: isBranchView = branchPrefix !== ""; - $: tabs = [ { route: `/${organization}/${project}${branchPrefix}`, @@ -42,22 +40,22 @@ { route: `/${organization}/${project}${branchPrefix}/-/reports`, label: "Reports", - hasPermission: $reports && !isBranchView, + hasPermission: $reports, }, { route: `/${organization}/${project}${branchPrefix}/-/alerts`, label: "Alerts", - hasPermission: $alerts && !isBranchView, + hasPermission: $alerts, }, { route: `/${organization}/${project}${branchPrefix}/-/status`, label: "Status", - hasPermission: projectPermissions.readProdStatus, + hasPermission: projectPermissions.manageProject, }, { route: `/${organization}/${project}${branchPrefix}/-/settings`, label: "Settings", - hasPermission: projectPermissions.manageProject && !isBranchView, + hasPermission: projectPermissions.manageProject, }, ]; diff --git a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte index 12bfdb755af..2a19a002909 100644 --- a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte +++ b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte @@ -2,8 +2,14 @@ import { page } from "$app/stores"; import { createAdminServiceGetProject, + createAdminServiceGetBillingSubscription, V1DeploymentStatus, } from "@rilldata/web-admin/client"; + import { + isFreePlan, + isProPlan, + isTrialPlan, + } from "@rilldata/web-admin/features/billing/plans/utils"; import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; import { useDashboardsLastUpdated } from "@rilldata/web-admin/features/dashboards/listing/selectors"; import { useGithubLastSynced } from "@rilldata/web-admin/features/projects/selectors"; @@ -111,6 +117,12 @@ // Slots $: currentSlots = Number(projectData?.prodSlots) || 0; + + // Billing plan detection + $: subscriptionQuery = createAdminServiceGetBillingSubscription(organization); + $: planName = $subscriptionQuery?.data?.subscription?.plan?.name ?? ""; + $: showSlots = + isTrialPlan(planName) || isFreePlan(planName) || isProPlan(planName); @@ -147,7 +159,7 @@
- {#if currentSlots > 0} + {#if !$subscriptionQuery?.isLoading && showSlots}
Cluster Size diff --git a/web-admin/src/routes/[organization]/[project]/+layout.ts b/web-admin/src/routes/[organization]/[project]/+layout.ts index 3b7d29c9e27..fddf4fc0aba 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/+layout.ts @@ -1,32 +1,17 @@ import { type RpcStatus } from "@rilldata/web-admin/client"; import { hasBlockerIssues } from "@rilldata/web-admin/features/billing/selectors"; -import { - branchPathPrefix, - extractBranchFromPath, -} from "@rilldata/web-admin/features/branches/branch-utils"; import { fetchAllProjectsHibernating } from "@rilldata/web-admin/features/organizations/selectors"; import { error, redirect } from "@sveltejs/kit"; import { isAxiosError } from "axios"; import { maybeRedirectToEditableDeployment } from "@rilldata/web-admin/features/branches/deployment-utils.ts"; import { isEditPage } from "@rilldata/web-admin/features/navigation/nav-utils.ts"; -// Sections hidden on branch views; visiting them redirects to the branch home. -const BRANCH_HIDDEN_SECTIONS = /\/-\/(alerts|reports|settings)(\/|$)/; - export const load = async ({ params: { organization, project }, parent, route, url, }) => { - const activeBranch = extractBranchFromPath(url.pathname); - if (activeBranch && BRANCH_HIDDEN_SECTIONS.test(url.pathname)) { - throw redirect( - 307, - `/${organization}/${project}${branchPathPrefix(activeBranch)}`, - ); - } - const { organizationPermissions, issues } = await parent(); if (!organizationPermissions.manageOrg) return; diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts deleted file mode 100644 index e54fdc97e8e..00000000000 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/+layout.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - branchPathPrefix, - extractBranchFromPath, -} from "@rilldata/web-admin/features/branches/branch-utils"; -import { redirect } from "@sveltejs/kit"; - -// Alerts is a cloud-only feature: assertions evaluate against the -// production deployment, so it doesn't make sense to expose it inside a -// branch view. On a branch URL we redirect deep links (bookmarks, shared -// URLs, stale tabs) back to the branch home rather than letting the user -// dead-end at a hidden section. -// -// Scoped to this section's loader on purpose — putting the same check on -// the project-wide `[organization]/[project]/+layout.ts` registers `url` -// as a dependency of that loader, which then re-runs on every in-project -// URL change and clobbers in-flight client-side `goto()`s such as the -// home-bookmark URL restoration in `DashboardStateSync`. -export const load = ({ url, params: { organization, project } }) => { - const activeBranch = extractBranchFromPath(url.pathname); - if (activeBranch) { - throw redirect( - 307, - `/${organization}/${project}${branchPathPrefix(activeBranch)}`, - ); - } -}; diff --git a/web-admin/src/routes/[organization]/[project]/-/deploying/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/deploying/+page.svelte index 1d80acbe29d..c6e3daf50b8 100644 --- a/web-admin/src/routes/[organization]/[project]/-/deploying/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/deploying/+page.svelte @@ -1,13 +1,14 @@ - +{#if source === "publish"} + +{:else} + +{/if} diff --git a/web-admin/src/routes/[organization]/[project]/-/deploying/+page.ts b/web-admin/src/routes/[organization]/[project]/-/deploying/+page.ts index 2a2be6254f0..0d77b2e21b1 100644 --- a/web-admin/src/routes/[organization]/[project]/-/deploying/+page.ts +++ b/web-admin/src/routes/[organization]/[project]/-/deploying/+page.ts @@ -2,8 +2,10 @@ import { DeployingDashboardUrlParam } from "@rilldata/web-common/features/projec export const load = ({ url: { searchParams } }) => { const deployingDashboard = searchParams.get(DeployingDashboardUrlParam); + const source = searchParams.get("source"); return { deployingDashboard, + source, }; }; diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts deleted file mode 100644 index 3adab7d4adc..00000000000 --- a/web-admin/src/routes/[organization]/[project]/-/reports/+layout.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - branchPathPrefix, - extractBranchFromPath, -} from "@rilldata/web-admin/features/branches/branch-utils"; -import { redirect } from "@sveltejs/kit"; - -// Reports is a cloud-only feature: schedules run against the production -// deployment, so it doesn't make sense to expose it inside a branch view. -// On a branch URL we redirect deep links (bookmarks, shared URLs, stale -// tabs) back to the branch home rather than letting the user dead-end at -// a hidden section. -// -// Scoped to this section's loader on purpose — putting the same check on -// the project-wide `[organization]/[project]/+layout.ts` registers `url` -// as a dependency of that loader, which then re-runs on every in-project -// URL change and clobbers in-flight client-side `goto()`s such as the -// home-bookmark URL restoration in `DashboardStateSync`. -export const load = ({ url, params: { organization, project } }) => { - const activeBranch = extractBranchFromPath(url.pathname); - if (activeBranch) { - throw redirect( - 307, - `/${organization}/${project}${branchPathPrefix(activeBranch)}`, - ); - } -}; diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts index 244f420531b..379fcade71a 100644 --- a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.ts @@ -1,35 +1,6 @@ -import { - branchPathPrefix, - extractBranchFromPath, -} from "@rilldata/web-admin/features/branches/branch-utils"; import { redirect } from "@sveltejs/kit"; -// Settings (env vars, public URLs, billing, members) only applies to the -// production deployment, so it's hidden from the project nav on branch -// views. This loader catches deep links — bookmarks, shared URLs, stale -// tabs — and bounces them to the branch home so users don't dead-end at -// a section that's intentionally hidden. -// -// Scoped to this section's loader on purpose — putting the same check on -// the project-wide `[organization]/[project]/+layout.ts` registers `url` -// as a dependency of that loader, which then re-runs on every in-project -// URL change and clobbers in-flight client-side `goto()`s such as the -// home-bookmark URL restoration in `DashboardStateSync`. -// -// Production view also gates on `manageProject` so non-admin users can't -// reach Settings via a direct URL. -export const load = async ({ - url, - parent, - params: { organization, project }, -}) => { - const activeBranch = extractBranchFromPath(url.pathname); - if (activeBranch) { - throw redirect( - 307, - `/${organization}/${project}${branchPathPrefix(activeBranch)}`, - ); - } +export const load = async ({ parent, params: { organization, project } }) => { const { projectPermissions } = await parent(); if (!projectPermissions?.manageProject) { throw redirect(307, `/${organization}/${project}`); diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte index 92a59f57a0d..be33a56dc01 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte @@ -4,12 +4,10 @@ import { page } from "$app/stores"; import ContentContainer from "@rilldata/web-common/components/layout/ContentContainer.svelte"; import LeftNav from "@rilldata/web-admin/components/nav/LeftNav.svelte"; - import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; $: basePage = `/${$page.params.organization}/${$page.params.project}/-/status`; - $: isBranchView = !!extractBranchFromPath($page.url.pathname); - $: navItems = [ + const navItems = [ { label: "Overview", route: "", @@ -18,7 +16,7 @@ { label: "Branches", route: "/branches", - hasPermission: !isBranchView, + hasPermission: true, }, { label: "Resources", diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts index 3f8988537d8..379fcade71a 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.ts @@ -1,12 +1,8 @@ import { redirect } from "@sveltejs/kit"; -// Status reports on the production deployment but is also accessible from -// branch views. Editors and admins can reach it; viewers (no -// `readProdStatus`) cannot, so this loader bounces them to project home -// to avoid letting them dead-end at a 403. export const load = async ({ parent, params: { organization, project } }) => { const { projectPermissions } = await parent(); - if (!projectPermissions?.readProdStatus) { + if (!projectPermissions?.manageProject) { throw redirect(307, `/${organization}/${project}`); } }; From af1cd8a72e117cc1544317aa40117523768c04bf Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:33:18 -0400 Subject: [PATCH 46/76] RD: project name + logo target `/dashboards` on viz routes When the user is on `/explore/` or `/canvas/` (shared routes that don't auto-flip the preview store), clicking the project name in the breadcrumb or the header logo now navigates to `/dashboards` instead of `/`. Already routed there in `mode === "Preview"`. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index c86feec1945..ffa3f3c78fd 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -156,7 +156,7 @@ label: projectTitle, section: "project", depth: -1, - href: mode === "Preview" ? "/dashboards" : "/", + href: mode === "Preview" || onVizRoute ? "/dashboards" : "/", }; $: pathParts = [ @@ -189,7 +189,7 @@
{#if !onDeployPage} - + From b9effbf5e6eb1d92b4407b23e7d54b4618ffbf02 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:40:34 -0400 Subject: [PATCH 47/76] Cloud: reset chat + View as on Preview/Edit swap; add Tables tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `resetOnPreviewSwap` in `ProjectHeader` and wire it to both the Preview button (editor → dev preview) and the Edit button (dev preview → editor) so the AI chat sidebar closes and any active `viewAsUserStore` impersonation clears on either swap, matching local. - Add a Tables tab to the cloud editor status side nav and a `/-/edit/status/tables/+page.svelte` rendering the existing `ProjectTables` component. Co-Authored-By: Claude Opus 4.7 --- .../features/projects/ProjectHeader.svelte | 19 +++++++++++++++---- .../[project]/-/edit/status/+layout.svelte | 1 + .../-/edit/status/tables/+page.svelte | 7 +++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 2a39394f9fb..283ac8f9c30 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -12,6 +12,7 @@ import EditButton from "@rilldata/web-admin/features/edit-session/EditButton.svelte"; import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; + import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { PathOption } from "@rilldata/web-common/components/navigation/breadcrumbs/types"; import { useCanvas } from "@rilldata/web-common/features/canvas/selector"; @@ -99,6 +100,14 @@ let editorViewAsOpen = false; + // Reset session state (View-as impersonation + AI chat panel) when the + // user explicitly swaps between editor and dev-preview chrome via the + // Preview/Edit toggle. Mirrors local's `resetOnModeToggle`. + function resetOnPreviewSwap() { + sidebarActions.closeChat(); + if ($viewAsUserStore) viewAsUserStore.set(null); + } + $: onAlertPage = !!alert; $: onReportPage = !!report; $: onProjectPage = isProjectPage($page); @@ -231,7 +240,11 @@ {#if projectPermissions?.manageDev} {/if} - + {:else if editContext} {#if $developerChat} @@ -244,9 +257,7 @@ href={editPreviewHref} showViewAs={projectPermissions?.manageProject ?? false} bind:dropdownOpen={editorViewAsOpen} - onPreviewClick={() => { - if ($viewAsUserStore) viewAsUserStore.set(null); - }} + onPreviewClick={resetOnPreviewSwap} > diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte new file mode 100644 index 00000000000..ab097b303e4 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte @@ -0,0 +1,7 @@ + + +
+ +
From f8fc49bd17592b15b8b96146c2b475837d345b55 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:44:18 -0400 Subject: [PATCH 48/76] Consolidate dashboards listing page into a shared web-common component Extract the project dashboards-listing UI into `web-common/src/features/preview-mode/DashboardsPage.svelte`. Both the local route (`/dashboards`) and the cloud editor dev-preview route (`/-/edit/dashboards`) now thinly wrap it; only the link builder differs. The web-local-only `DashboardList.svelte` shim is deleted. First step in unifying the local app's preview surface with the cloud editor's `/-/edit/...` chrome. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/dashboards/+page.svelte | 20 +------ .../preview-mode/DashboardsPage.svelte | 43 ++++++++++++++ .../features/dashboards/DashboardList.svelte | 57 ------------------- web-local/src/routes/dashboards/+page.svelte | 11 ++-- 4 files changed, 51 insertions(+), 80 deletions(-) create mode 100644 web-common/src/features/preview-mode/DashboardsPage.svelte delete mode 100644 web-local/src/features/dashboards/DashboardList.svelte diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte index 11b4d9d5d3e..90cbbebea09 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte @@ -1,14 +1,7 @@ - - - + diff --git a/web-common/src/features/preview-mode/DashboardsPage.svelte b/web-common/src/features/preview-mode/DashboardsPage.svelte new file mode 100644 index 00000000000..3a8327afc28 --- /dev/null +++ b/web-common/src/features/preview-mode/DashboardsPage.svelte @@ -0,0 +1,43 @@ + + + + + + + + + + + + + diff --git a/web-local/src/features/dashboards/DashboardList.svelte b/web-local/src/features/dashboards/DashboardList.svelte deleted file mode 100644 index 0d0f5e40599..00000000000 --- a/web-local/src/features/dashboards/DashboardList.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - - {#if $previewModeStore} - - - Create dashboards using your code editor, then return here to preview - them. - - - {:else} - - -
- Deploy your project - - to share dashboards with your team - - - {/if} - - diff --git a/web-local/src/routes/dashboards/+page.svelte b/web-local/src/routes/dashboards/+page.svelte index a54fea6dfcb..39d0ffd00a0 100644 --- a/web-local/src/routes/dashboards/+page.svelte +++ b/web-local/src/routes/dashboards/+page.svelte @@ -1,8 +1,9 @@ - - - + + + Create dashboards using your code editor, then return here to preview them. + + From 66a6e755b7881ac305fb14df89644a7806262ac4 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:45:32 -0400 Subject: [PATCH 49/76] Consolidate full-page AI chat into a shared web-common component Extract `LocalFullPageChat` into `web-common/src/features/preview-mode/AiChatPage.svelte` parameterized by `basePath`. Both the local `/ai` layout and the cloud editor `/-/edit/ai` page now thinly wrap it. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/ai/+page.svelte | 119 +----------------- .../features/preview-mode/AiChatPage.svelte | 35 ++---- web-local/src/routes/ai/+layout.svelte | 4 +- 3 files changed, 16 insertions(+), 142 deletions(-) rename web-local/src/features/chat/LocalFullPageChat.svelte => web-common/src/features/preview-mode/AiChatPage.svelte (86%) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/ai/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/ai/+page.svelte index b00f7a94c60..8b62815ce43 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/ai/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/ai/+page.svelte @@ -1,127 +1,12 @@ -
- chatInputComponent?.focusInput()} - onNewConversationClick={() => chatInputComponent?.focusInput()} - /> - -
-
-
- -
-
- -
-
- -
-
-
-
- - + diff --git a/web-local/src/features/chat/LocalFullPageChat.svelte b/web-common/src/features/preview-mode/AiChatPage.svelte similarity index 86% rename from web-local/src/features/chat/LocalFullPageChat.svelte rename to web-common/src/features/preview-mode/AiChatPage.svelte index 75fd78a5738..6ce1f40e961 100644 --- a/web-local/src/features/chat/LocalFullPageChat.svelte +++ b/web-common/src/features/preview-mode/AiChatPage.svelte @@ -1,10 +1,8 @@
- { - chatInputComponent?.focusInput(); - }} - onNewConversationClick={() => { - chatInputComponent?.focusInput(); - }} + onConversationClick={() => chatInputComponent?.focusInput()} + onNewConversationClick={() => chatInputComponent?.focusInput()} /> -
@@ -90,7 +85,6 @@ width: 100%; background: var(--surface); } - .chat-main { flex: 1; display: flex; @@ -98,7 +92,6 @@ overflow: hidden; background: var(--surface); } - .chat-content { flex: 1; overflow: hidden; @@ -106,7 +99,6 @@ display: flex; flex-direction: column; } - .chat-messages-wrapper { flex: 1; overflow-y: auto; @@ -114,7 +106,6 @@ display: flex; flex-direction: column; } - .chat-input-section { flex-shrink: 0; background: var(--surface); @@ -122,7 +113,6 @@ display: flex; justify-content: center; } - .chat-input-wrapper { width: 100%; max-width: 48rem; @@ -137,7 +127,6 @@ max-width: none; padding: 0 1rem; } - .chat-input-section { padding: 1rem; } diff --git a/web-local/src/routes/ai/+layout.svelte b/web-local/src/routes/ai/+layout.svelte index 5882ccf311a..8931a897cf7 100644 --- a/web-local/src/routes/ai/+layout.svelte +++ b/web-local/src/routes/ai/+layout.svelte @@ -1,9 +1,9 @@
- +
From 0ee9bee3598953eac812556af26da0853714a4e2 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:47:15 -0400 Subject: [PATCH 50/76] Consolidate Status layout + Overview page into shared web-common components - `StatusLayout.svelte`: side nav (Overview / Resources / Tables) parameterized by `basePath`. Both apps' status `+layout.svelte` thin- wrap it. - `StatusOverviewPage.svelte`: project info card + resources + errors sections. Local passes `runtimeVersion` (via local service) and slots `TablesSection`; cloud uses defaults. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/status/+layout.svelte | 48 +---- .../[project]/-/edit/status/+page.svelte | 152 ++-------------- .../features/preview-mode/StatusLayout.svelte | 56 ++++++ .../preview-mode/StatusOverviewPage.svelte | 166 ++++++++++++++++++ web-local/src/routes/status/+layout.svelte | 57 +----- web-local/src/routes/status/+page.svelte | 165 +---------------- 6 files changed, 247 insertions(+), 397 deletions(-) create mode 100644 web-common/src/features/preview-mode/StatusLayout.svelte create mode 100644 web-common/src/features/preview-mode/StatusOverviewPage.svelte diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte index 39b3e781378..da39225ed8a 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/+layout.svelte @@ -1,54 +1,14 @@ - -
- -
- -
-
-
- - + + + diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/+page.svelte index 423942eb914..3ef7493c75c 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/status/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/+page.svelte @@ -1,59 +1,15 @@ -
-
-

Project

-
-
-
- Status - - {#if $projectParserQuery.isLoading || $resourcesQuery.isLoading} - - Loading - {:else if totalErrors > 0} - - {totalErrors} - {totalErrors === 1 ? "error" : "errors"} - {:else} - - Running - {/if} - -
-
- Environment - Development -
-
- OLAP Engine - - {getOlapEngineLabel( - instance?.olapConnector - ? { name: instance.olapConnector } - : undefined, - )} - -
-
- AI Connector - - {instance?.aiConnector && instance.aiConnector !== "admin" - ? formatConnectorName(instance.aiConnector) - : "Rill Managed"} - -
-
-
- - goToResources()} - onChipClick={(kind) => goToResources([], [kind])} -/> - - goToResources(["error"])} - onParseErrorChipClick={() => goToResources(["error"])} - onKindChipClick={(kind) => goToResources(["error"], [kind])} -/> - - + diff --git a/web-common/src/features/preview-mode/StatusLayout.svelte b/web-common/src/features/preview-mode/StatusLayout.svelte new file mode 100644 index 00000000000..b63c0ad99fc --- /dev/null +++ b/web-common/src/features/preview-mode/StatusLayout.svelte @@ -0,0 +1,56 @@ + + + +
+ +
+ +
+
+
+ + diff --git a/web-common/src/features/preview-mode/StatusOverviewPage.svelte b/web-common/src/features/preview-mode/StatusOverviewPage.svelte new file mode 100644 index 00000000000..ff842f15790 --- /dev/null +++ b/web-common/src/features/preview-mode/StatusOverviewPage.svelte @@ -0,0 +1,166 @@ + + +
+
+

Project

+
+
+
+ Status + + {#if $projectParserQuery.isLoading || $resourcesQuery.isLoading} + + Loading + {:else if totalErrors > 0} + + {totalErrors} + {totalErrors === 1 ? "error" : "errors"} + {:else} + + Running + {/if} + +
+
+ Environment + {environmentLabel} +
+ {#if runtimeVersion} +
+ Runtime + {runtimeVersion} +
+ {/if} +
+ OLAP Engine + + {getOlapEngineLabel( + instance?.olapConnector + ? { name: instance.olapConnector } + : undefined, + )} + +
+
+ AI Connector + + {instance?.aiConnector && instance.aiConnector !== "admin" + ? formatConnectorName(instance.aiConnector) + : "Rill Managed"} + +
+
+
+ + handleClick()} + onChipClick={(kind) => handleClick([], [kind])} +/> + + + + handleClick(["error"])} + onParseErrorChipClick={() => handleClick(["error"])} + onKindChipClick={(kind) => handleClick(["error"], [kind])} +/> + + diff --git a/web-local/src/routes/status/+layout.svelte b/web-local/src/routes/status/+layout.svelte index 8192ec6d79d..173885c2711 100644 --- a/web-local/src/routes/status/+layout.svelte +++ b/web-local/src/routes/status/+layout.svelte @@ -1,56 +1,7 @@ - -
- -
- -
-
-
- - + + + diff --git a/web-local/src/routes/status/+page.svelte b/web-local/src/routes/status/+page.svelte index 4f2f8bbb478..3a018bd3f6b 100644 --- a/web-local/src/routes/status/+page.svelte +++ b/web-local/src/routes/status/+page.svelte @@ -1,69 +1,13 @@ - -
-
-

Project

-
-
-
- Status - - {#if $projectParserQuery.isLoading || $resourcesQuery.isLoading} - - Loading - {:else if totalErrors > 0} - - {totalErrors} - {totalErrors === 1 ? "error" : "errors"} - {:else} - - Running - {/if} - -
-
- Environment - Development -
- {#if version} -
- Runtime - {version} -
- {/if} -
- OLAP Engine - - {getOlapEngineLabel( - instance?.olapConnector - ? { name: instance.olapConnector } - : undefined, - )} - -
-
- AI Connector - - {instance?.aiConnector && instance.aiConnector !== "admin" - ? formatConnectorName(instance.aiConnector) - : "Rill Managed"} - -
-
-
- - goToResources()} - onChipClick={(kind) => goToResources([], [kind])} -/> - - - - goToResources(["error"])} - onParseErrorChipClick={() => goToResources(["error"])} - onKindChipClick={(kind) => goToResources(["error"], [kind])} -/> - - + + + From 1effc6ddcc17b18cb99ea927680e94af0b984fbf Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:48:16 -0400 Subject: [PATCH 51/76] Consolidate Status > Resources page into a shared web-common component Extract the resources filterable table + parse errors section into `web-common/src/features/preview-mode/StatusResourcesPage.svelte`. Both `/status/resources` and `/-/edit/status/resources` now thin-wrap it. Drops the local-only `ParseErrorsSection` shim. Co-Authored-By: Claude Opus 4.7 --- .../-/edit/status/resources/+page.svelte | 76 +---------- .../preview-mode/StatusResourcesPage.svelte | 123 ++++++++++++++++++ .../routes/status/ParseErrorsSection.svelte | 40 ------ .../src/routes/status/resources/+page.svelte | 100 +------------- 4 files changed, 127 insertions(+), 212 deletions(-) create mode 100644 web-common/src/features/preview-mode/StatusResourcesPage.svelte delete mode 100644 web-local/src/routes/status/ParseErrorsSection.svelte diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte index 0b700a69dbf..0a8f772d6fb 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/resources/+page.svelte @@ -1,77 +1,5 @@ -{#if parseErrors.length > 0} - -{/if} - - - r.meta?.reconcileStatus === V1ReconcileStatus.RECONCILE_STATUS_RUNNING, - )} - onFilterChange={(next) => filterSync.update(next)} - onTrigger={async (names) => { - await $createTrigger.mutateAsync({ - instanceId: runtimeClient.instanceId, - data: { resources: names }, - }); - void queryClient.invalidateQueries({ - queryKey: getRuntimeServiceListResourcesQueryKey( - runtimeClient.instanceId, - {}, - ), - }); - }} -/> + diff --git a/web-common/src/features/preview-mode/StatusResourcesPage.svelte b/web-common/src/features/preview-mode/StatusResourcesPage.svelte new file mode 100644 index 00000000000..bec47eb4474 --- /dev/null +++ b/web-common/src/features/preview-mode/StatusResourcesPage.svelte @@ -0,0 +1,123 @@ + + + $resourcesQuery.refetch()} + bind:selectedStatuses + bind:selectedTypes + bind:searchText +/> + + diff --git a/web-local/src/routes/status/ParseErrorsSection.svelte b/web-local/src/routes/status/ParseErrorsSection.svelte deleted file mode 100644 index ca3e071757b..00000000000 --- a/web-local/src/routes/status/ParseErrorsSection.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/web-local/src/routes/status/resources/+page.svelte b/web-local/src/routes/status/resources/+page.svelte index 33be0bae31a..0a8f772d6fb 100644 --- a/web-local/src/routes/status/resources/+page.svelte +++ b/web-local/src/routes/status/resources/+page.svelte @@ -1,101 +1,5 @@ - $resourcesQuery.refetch()} - bind:selectedStatuses - bind:selectedTypes - bind:searchText -/> - - + From 1d82c084776873aff066847b2e473ad2e1f24cf5 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:50:48 -0400 Subject: [PATCH 52/76] Consolidate Status > Tables page into a shared web-common component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move `LocalProjectTables.svelte` to `web-common/src/features/preview-mode/StatusTablesPage.svelte` and the supporting query helpers to `web-common/src/features/preview-mode/tables-selectors.ts`. - Both `/status/tables` (local) and `/-/edit/status/tables` (cloud editor dev preview) now thin-wrap the shared page. - Update local `TablesSection` to import the selector from web-common. The cloud non-edit `/-/status/tables` keeps using web-admin's `ProjectTables` for now — that surface is out of scope for this PR. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/status/tables/+page.svelte | 6 ++---- .../src/features/preview-mode/StatusTablesPage.svelte | 2 +- .../src/features/preview-mode/tables-selectors.ts | 0 web-local/src/features/tables/TablesSection.svelte | 2 +- web-local/src/routes/status/tables/+page.svelte | 4 ++-- 5 files changed, 6 insertions(+), 8 deletions(-) rename web-local/src/features/tables/LocalProjectTables.svelte => web-common/src/features/preview-mode/StatusTablesPage.svelte (99%) rename web-local/src/features/tables/selectors.ts => web-common/src/features/preview-mode/tables-selectors.ts (100%) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte index ab097b303e4..10c37562948 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/status/tables/+page.svelte @@ -1,7 +1,5 @@ -
- -
+ diff --git a/web-local/src/features/tables/LocalProjectTables.svelte b/web-common/src/features/preview-mode/StatusTablesPage.svelte similarity index 99% rename from web-local/src/features/tables/LocalProjectTables.svelte rename to web-common/src/features/preview-mode/StatusTablesPage.svelte index 53dd35f4865..20a0a9ec108 100644 --- a/web-local/src/features/tables/LocalProjectTables.svelte +++ b/web-common/src/features/preview-mode/StatusTablesPage.svelte @@ -17,7 +17,7 @@ import { writable } from "svelte/store"; import ModelsTable from "@rilldata/web-common/features/projects/status/tables/ModelsTable.svelte"; import ExternalTablesTable from "@rilldata/web-common/features/projects/status/tables/ExternalTablesTable.svelte"; - import { useInfiniteTablesList, useModelResources } from "./selectors"; + import { useInfiniteTablesList, useModelResources } from "./tables-selectors"; import { debounce } from "@rilldata/web-common/lib/create-debouncer"; import { filterTemporaryTables, diff --git a/web-local/src/features/tables/selectors.ts b/web-common/src/features/preview-mode/tables-selectors.ts similarity index 100% rename from web-local/src/features/tables/selectors.ts rename to web-common/src/features/preview-mode/tables-selectors.ts diff --git a/web-local/src/features/tables/TablesSection.svelte b/web-local/src/features/tables/TablesSection.svelte index 1671d07549e..e34d3faff39 100644 --- a/web-local/src/features/tables/TablesSection.svelte +++ b/web-local/src/features/tables/TablesSection.svelte @@ -1,7 +1,7 @@ - + From 08b2f68d44d138cbb75265e42305d80ff9c76e29 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:54:23 -0400 Subject: [PATCH 53/76] Consolidate preview-mode tab nav into a shared web-common component `PreviewModeNav` (web-common) replaces both web-local's `PreviewModeNav` + `LocalProjectStatusIndicator` and web-admin's `EditDevPreviewNav`. It takes a `basePath` prop so each consumer can plug in its own URL prefix (empty for local, branch-aware `/{org}/{project}/@{branch}/-/edit` for cloud). Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/+layout.svelte | 6 +- .../preview-mode/PreviewModeNav.svelte | 27 ++-- .../features/preview/PreviewModeNav.svelte | 118 ------------------ web-local/src/routes/+layout.svelte | 2 +- .../routes/LocalProjectStatusIndicator.svelte | 73 ----------- 5 files changed, 18 insertions(+), 208 deletions(-) rename web-admin/src/features/preview/EditDevPreviewNav.svelte => web-common/src/features/preview-mode/PreviewModeNav.svelte (87%) delete mode 100644 web-local/src/features/preview/PreviewModeNav.svelte delete mode 100644 web-local/src/routes/LocalProjectStatusIndicator.svelte diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index fdd543e6900..0bd73463db9 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -14,7 +14,7 @@ import BranchDeploymentStopped from "@rilldata/web-admin/features/branches/BranchDeploymentStopped.svelte"; import EditSessionLoading from "@rilldata/web-admin/features/edit-session/EditSessionLoading.svelte"; import EditSessionTimeoutBanner from "@rilldata/web-admin/features/edit-session/EditSessionTimeoutBanner.svelte"; - import EditDevPreviewNav from "@rilldata/web-admin/features/preview/EditDevPreviewNav.svelte"; + import PreviewModeNav from "@rilldata/web-common/features/preview-mode/PreviewModeNav.svelte"; import ProjectHeader from "@rilldata/web-admin/features/projects/ProjectHeader.svelte"; import SlimProjectHeader from "@rilldata/web-admin/features/projects/SlimProjectHeader.svelte"; import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; @@ -203,7 +203,9 @@ errorBody="Lost connection to the editing environment. Try ending the session and starting a new one." > {#if inEditDevPreview} - + {/if}
{#if !inProjectWelcomePage && !inEditDevPreview} diff --git a/web-admin/src/features/preview/EditDevPreviewNav.svelte b/web-common/src/features/preview-mode/PreviewModeNav.svelte similarity index 87% rename from web-admin/src/features/preview/EditDevPreviewNav.svelte rename to web-common/src/features/preview-mode/PreviewModeNav.svelte index 52379fb11f8..de3849972b6 100644 --- a/web-admin/src/features/preview/EditDevPreviewNav.svelte +++ b/web-common/src/features/preview-mode/PreviewModeNav.svelte @@ -14,30 +14,29 @@ } from "@rilldata/web-common/runtime-client"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import { tick } from "svelte"; - import { branchPathPrefix } from "../branches/branch-utils"; + + /** Base path of the preview surface. Defaults to local's flat + * `/dashboards|/status|/ai` paths; cloud editor passes a branch-aware + * prefix like `/{org}/{project}/@{branch}/-/edit`. */ + export let basePath: string = ""; const { chat } = featureFlags; const runtimeClient = useRuntimeClient(); - $: organization = $page.params.organization; - $: project = $page.params.project; - $: branch = $page.url.pathname.match(/\/@([^/]+)/)?.[1]; - $: editPrefix = `/${organization}/${project}${branchPathPrefix(branch)}/-/edit`; - - $: currentPath = $page.url.pathname; + $: dashboardsPath = `${basePath}/dashboards`; + $: statusPath = `${basePath}/status`; + $: aiPath = `${basePath}/ai`; $: baseTabs = [ - { id: "dashboards", label: "Dashboards", path: `${editPrefix}/dashboards` }, - { id: "status", label: "Status", path: `${editPrefix}/status` }, + { id: "dashboards", label: "Dashboards", path: dashboardsPath }, + { id: "status", label: "Status", path: statusPath }, ]; - - $: aiTab = { id: "ai", label: "AI", path: `${editPrefix}/ai` }; - + $: aiTab = { id: "ai", label: "AI", path: aiPath }; $: tabs = $chat ? [baseTabs[0], aiTab, baseTabs[1]] : baseTabs; + $: currentPath = $page.url.pathname; $: activeTab = tabs.find((t) => currentPath.startsWith(t.path))?.id ?? "dashboards"; - $: selectedIndex = tabs.findIndex((t) => t.id === activeTab); let tabElements: HTMLAnchorElement[] = []; @@ -56,7 +55,7 @@ void tick().then(updateIndicator); } - // Project status indicator (mirrors local's LocalProjectStatusIndicator) + // Project status indicator (consolidates LocalProjectStatusIndicator). $: hasResourceErrorsQuery = createRuntimeServiceListResources( runtimeClient, {}, diff --git a/web-local/src/features/preview/PreviewModeNav.svelte b/web-local/src/features/preview/PreviewModeNav.svelte deleted file mode 100644 index 160b46376c9..00000000000 --- a/web-local/src/features/preview/PreviewModeNav.svelte +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - diff --git a/web-local/src/routes/+layout.svelte b/web-local/src/routes/+layout.svelte index 98ba68ac886..77382eb3768 100644 --- a/web-local/src/routes/+layout.svelte +++ b/web-local/src/routes/+layout.svelte @@ -32,7 +32,7 @@ import { onMount } from "svelte"; import * as Tooltip from "@rilldata/web-common/components/tooltip-v2"; import type { LayoutData } from "./$types"; - import PreviewModeNav from "../features/preview/PreviewModeNav.svelte"; + import PreviewModeNav from "@rilldata/web-common/features/preview-mode/PreviewModeNav.svelte"; import { isPreviewRoute, isDeveloperRoute, diff --git a/web-local/src/routes/LocalProjectStatusIndicator.svelte b/web-local/src/routes/LocalProjectStatusIndicator.svelte deleted file mode 100644 index 9464e69eca3..00000000000 --- a/web-local/src/routes/LocalProjectStatusIndicator.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - -{#if hasResourceErrorsLoading || projectParserLoading} - -{:else if hasResourceErrorsError || projectParserError} - -{:else if hasResourceErrors || hasParseErrors} - -{:else} - -{/if} From 5ca50bb1f5bf18b0c024a305357ed6c498f6721a Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 16:57:10 -0400 Subject: [PATCH 54/76] Cloud dev preview: use mock_users-based View as (match local) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap `CloudViewAsButton` (which lists real project users via `createAdminServiceSearchProjectUsers`) for the shared web-common `ViewAsButton` in the cloud editor's dev-preview chrome. Both surfaces now drive impersonation through `updateDevJWT` → `runtimeServiceIssueDevJWT`, sourcing test users from `mock_users` in `rill.yaml` — the same security-policy testing flow available in local. The cloud non-edit prod surface keeps `CloudViewAsButton` (real project users) since that flow is about previewing what an actual user will see in production. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 283ac8f9c30..c64e3b330c0 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -13,6 +13,7 @@ import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; + import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { PathOption } from "@rilldata/web-common/components/navigation/breadcrumbs/types"; import { useCanvas } from "@rilldata/web-common/features/canvas/selector"; @@ -238,7 +239,7 @@
{#if editContext && inEditDevPreview} {#if projectPermissions?.manageDev} - + {/if} Date: Fri, 1 May 2026 16:59:09 -0400 Subject: [PATCH 55/76] Revert mock-user View as in cloud dev preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `runtimeServiceIssueDevJWT` rejects the cloud editor's JWT context ("invalid kid") — the dev-JWT issuer only trusts local-runtime keys. Restore `CloudViewAsButton` (real project users via the admin API) for the editor dev preview until the runtime side accepts editor JWTs as authorized to mint dev tokens. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index c64e3b330c0..283ac8f9c30 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -13,7 +13,6 @@ import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; - import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { PathOption } from "@rilldata/web-common/components/navigation/breadcrumbs/types"; import { useCanvas } from "@rilldata/web-common/features/canvas/selector"; @@ -239,7 +238,7 @@
{#if editContext && inEditDevPreview} {#if projectPermissions?.manageDev} - + {/if} Date: Fri, 1 May 2026 17:11:00 -0400 Subject: [PATCH 56/76] =?UTF-8?q?Cloud=20editor:=20reset=20chat=20+=20View?= =?UTF-8?q?=20as=20on=20dev-preview=20=E2=86=92=20edit=20transition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a reactive watcher in `ProjectHeader` that fires whenever the chrome flips out of dev-preview back into the editor, regardless of whether the user clicked the Edit toggle, used the back button, or typed a URL. Same reset behavior as the existing `onPreviewClick` hook, just covering the cases where the click handler doesn't run because the link unmounts before propagation. Co-Authored-By: Claude Opus 4.7 --- .../src/features/projects/ProjectHeader.svelte | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 283ac8f9c30..286628fcec3 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -108,6 +108,23 @@ if ($viewAsUserStore) viewAsUserStore.set(null); } + // Belt-and-suspenders: also reset whenever we transition out of the + // dev-preview chrome back into the editor (e.g., browser back button, + // direct URL, or any path that bypasses the Edit button's onClick). + let prevInEditDevPreview: boolean | null = null; + $: { + const inDevPreview = editContext && inEditDevPreview; + if ( + prevInEditDevPreview === true && + !inDevPreview && + editContext // only reset when staying inside the editor + ) { + sidebarActions.closeChat(); + if ($viewAsUserStore) viewAsUserStore.set(null); + } + prevInEditDevPreview = inDevPreview; + } + $: onAlertPage = !!alert; $: onReportPage = !!report; $: onProjectPage = isProjectPage($page); From 005ffa949fcf2efe34f69db658c1fea2ca72e684 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 17:14:50 -0400 Subject: [PATCH 57/76] Cloud editor: honor `viewAsUserStore` in the dev runtime Mirror the parent project layout's View-as flow inside `/-/edit/+layout.svelte`. When `viewAsUserStore` is set, fetch deployment credentials with `userId: mockedUserId` (branch-scoped when on a branch) and route the editor's `RuntimeProvider` through those credentials instead of `GetProject`'s editor JWT. Without this the editor kept using its own admin/dev JWT on `/-/edit/dashboards|status|ai`, so security policies were never enforced and impersonated users still saw every dashboard. The `{#key}` on RuntimeProvider now also includes `mockedUserId` so the runtime client re-mounts cleanly when impersonation toggles. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/+layout.svelte | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 0bd73463db9..f03e1becdfa 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -2,11 +2,13 @@ import { page } from "$app/stores"; import { createAdminServiceGetCurrentUser, + createAdminServiceGetDeploymentCredentials, createAdminServiceGetProject, getAdminServiceGetProjectQueryKey, V1DeploymentStatus, type V1Organization, } from "@rilldata/web-admin/client"; + import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; import { branchPathPrefix, extractBranchFromPath, @@ -86,9 +88,41 @@ // Deployment data and credentials come from GetProject (no separate API needed) $: deployment = $projectQuery.data?.deployment; $: deploymentStatus = deployment?.status; - $: runtimeHost = deployment?.runtimeHost ?? null; - $: instanceId = deployment?.runtimeInstanceId ?? null; - $: jwt = $projectQuery.data?.jwt ?? null; + + // View-as: when `viewAsUserStore` is set we fetch a separate set of + // runtime credentials with the impersonated user's `userId` so the + // dev-preview surface enforces that user's security policies. + $: mockedUserId = $viewAsUserStore?.id; + + $: mockedUserCredentialsQuery = createAdminServiceGetDeploymentCredentials( + organization, + project, + { + userId: mockedUserId, + ...(branch ? { branch } : {}), + }, + { query: { enabled: !!mockedUserId } }, + ); + $: mockedUserCredentials = $mockedUserCredentialsQuery.data; + + // Once a mock user is selected, route the runtime through the + // impersonated credentials. While the credentials query is in flight + // we render a loading state instead of falling back to the editor's + // own JWT (which would briefly leak the un-filtered view). + $: useImpersonatedRuntime = !!mockedUserId; + + $: runtimeHost = + (useImpersonatedRuntime + ? mockedUserCredentials?.runtimeHost + : deployment?.runtimeHost) ?? null; + $: instanceId = + (useImpersonatedRuntime + ? mockedUserCredentials?.runtimeInstanceId + : deployment?.runtimeInstanceId) ?? null; + $: jwt = + (useImpersonatedRuntime + ? mockedUserCredentials?.accessToken + : $projectQuery.data?.jwt) ?? null; const user = createAdminServiceGetCurrentUser(); @@ -180,7 +214,7 @@ {:else if isReady && deployment?.id && instanceId && runtimeHost && jwt} - {#key `${runtimeHost}::${instanceId}`} + {#key `${runtimeHost}::${instanceId}::${mockedUserId ?? ""}`} {#if !inProjectWelcomePage} Date: Fri, 1 May 2026 17:22:11 -0400 Subject: [PATCH 58/76] Cloud editor View as: pass authContext=mock so queries invalidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `RuntimeProvider.updateJwt` only invalidates the query cache when the `authContext` flips. With both the editor JWT and the impersonated JWT defaulting to "user", flipping impersonation re-mounted the provider but TanStack returned the cached unfiltered listResources data — so dashboards still appeared. Pass `authContext="mock"` while a mock user is active so the JWT swap actually busts the cache. Co-Authored-By: Claude Opus 4.7 --- .../routes/[organization]/[project]/-/edit/+layout.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index f03e1becdfa..22c44bfedf4 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -215,7 +215,12 @@ {:else if isReady && deployment?.id && instanceId && runtimeHost && jwt} {#key `${runtimeHost}::${instanceId}::${mockedUserId ?? ""}`} - + {#if !inProjectWelcomePage} Date: Fri, 1 May 2026 17:29:35 -0400 Subject: [PATCH 59/76] Revert "Cloud editor View as: pass authContext=mock so queries invalidate" This reverts commit c0997f87fb9649a0255135c7d70ddc20839baa97. --- .../routes/[organization]/[project]/-/edit/+layout.svelte | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 22c44bfedf4..f03e1becdfa 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -215,12 +215,7 @@ {:else if isReady && deployment?.id && instanceId && runtimeHost && jwt} {#key `${runtimeHost}::${instanceId}::${mockedUserId ?? ""}`} - + {#if !inProjectWelcomePage} Date: Fri, 1 May 2026 17:29:35 -0400 Subject: [PATCH 60/76] Revert "Cloud editor: honor `viewAsUserStore` in the dev runtime" This reverts commit 005ffa949fcf2efe34f69db658c1fea2ca72e684. --- .../[project]/-/edit/+layout.svelte | 42 ++----------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index f03e1becdfa..0bd73463db9 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -2,13 +2,11 @@ import { page } from "$app/stores"; import { createAdminServiceGetCurrentUser, - createAdminServiceGetDeploymentCredentials, createAdminServiceGetProject, getAdminServiceGetProjectQueryKey, V1DeploymentStatus, type V1Organization, } from "@rilldata/web-admin/client"; - import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; import { branchPathPrefix, extractBranchFromPath, @@ -88,41 +86,9 @@ // Deployment data and credentials come from GetProject (no separate API needed) $: deployment = $projectQuery.data?.deployment; $: deploymentStatus = deployment?.status; - - // View-as: when `viewAsUserStore` is set we fetch a separate set of - // runtime credentials with the impersonated user's `userId` so the - // dev-preview surface enforces that user's security policies. - $: mockedUserId = $viewAsUserStore?.id; - - $: mockedUserCredentialsQuery = createAdminServiceGetDeploymentCredentials( - organization, - project, - { - userId: mockedUserId, - ...(branch ? { branch } : {}), - }, - { query: { enabled: !!mockedUserId } }, - ); - $: mockedUserCredentials = $mockedUserCredentialsQuery.data; - - // Once a mock user is selected, route the runtime through the - // impersonated credentials. While the credentials query is in flight - // we render a loading state instead of falling back to the editor's - // own JWT (which would briefly leak the un-filtered view). - $: useImpersonatedRuntime = !!mockedUserId; - - $: runtimeHost = - (useImpersonatedRuntime - ? mockedUserCredentials?.runtimeHost - : deployment?.runtimeHost) ?? null; - $: instanceId = - (useImpersonatedRuntime - ? mockedUserCredentials?.runtimeInstanceId - : deployment?.runtimeInstanceId) ?? null; - $: jwt = - (useImpersonatedRuntime - ? mockedUserCredentials?.accessToken - : $projectQuery.data?.jwt) ?? null; + $: runtimeHost = deployment?.runtimeHost ?? null; + $: instanceId = deployment?.runtimeInstanceId ?? null; + $: jwt = $projectQuery.data?.jwt ?? null; const user = createAdminServiceGetCurrentUser(); @@ -214,7 +180,7 @@ {:else if isReady && deployment?.id && instanceId && runtimeHost && jwt} - {#key `${runtimeHost}::${instanceId}::${mockedUserId ?? ""}`} + {#key `${runtimeHost}::${instanceId}`} {#if !inProjectWelcomePage} Date: Fri, 1 May 2026 17:35:09 -0400 Subject: [PATCH 61/76] Reset View as + chat on platform navigation Add a reactive watcher in each app's project-scope layout that classifies the current path into a "platform" and clears `viewAsUserStore` / `selectedMockUserStore` (and closes the AI chat sidebar) whenever the classification changes: - Cloud: `cloud-editor` (`/-/edit/files|explore|canvas|...`), `cloud-dev-preview` (`/-/edit/dashboards|status|ai`), and `cloud-prod` (everything else under the project) are treated as separate platforms. - Local: `local-editor` and `local-preview` flip with `previewModeStore`. Each platform is a different runtime/permission context, so a stale impersonation makes no sense across the boundary. This catches the cases the per-button `onPreviewClick` doesn't (back button, address bar, nav-link clicks, branch selector, etc.). Co-Authored-By: Claude Opus 4.7 --- .../[organization]/[project]/+layout.svelte | 22 +++++++++++++++ web-local/src/routes/+layout.svelte | 27 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/web-admin/src/routes/[organization]/[project]/+layout.svelte b/web-admin/src/routes/[organization]/[project]/+layout.svelte index ec31aef10f0..dfdc41d1f4a 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/+layout.svelte @@ -47,6 +47,7 @@ import { cloudVersion } from "@rilldata/web-admin/features/telemetry/initCloudMetrics"; import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; + import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte"; import { themeControl } from "@rilldata/web-common/features/themes/theme-control"; import { metricsService } from "@rilldata/web-common/metrics/initMetrics"; @@ -109,6 +110,27 @@ }; }); + // Reset View as + AI chat whenever the user moves between cloud "platforms": + // editor (`/-/edit/files|explore|canvas|...`), editor dev preview + // (`/-/edit/dashboards|status|ai`), and cloud prod (everything else). + // Each is a different runtime/permission context, so a stale impersonation + // makes no sense across the boundary. + function classifyCloudPlatform(pathname: string): string { + if (/\/-\/edit\/(dashboards|status|ai)(\/|$)/.test(pathname)) + return "cloud-dev-preview"; + if (/\/-\/edit(\/|$)/.test(pathname)) return "cloud-editor"; + return "cloud-prod"; + } + let prevCloudPlatform: string | null = null; + $effect(() => { + const platform = classifyCloudPlatform(page.url.pathname); + if (prevCloudPlatform !== null && prevCloudPlatform !== platform) { + if (viewAsUserStore.get()) viewAsUserStore.set(null); + sidebarActions.closeChat(); + } + prevCloudPlatform = platform; + }); + // --- Queries (three auth strategies; cookie and token are mutually exclusive, // mock is an overlay that runs in parallel when View As is active) --- diff --git a/web-local/src/routes/+layout.svelte b/web-local/src/routes/+layout.svelte index 77382eb3768..d557e9eaff7 100644 --- a/web-local/src/routes/+layout.svelte +++ b/web-local/src/routes/+layout.svelte @@ -25,7 +25,15 @@ previewModeLocked, previewModeStore, } from "@rilldata/web-common/layout/preview-mode-store"; - import { LOCAL_HOST, LOCAL_INSTANCE_ID } from "../lib/runtime-client"; + import { + getLocalRuntimeClient, + LOCAL_HOST, + LOCAL_INSTANCE_ID, + } from "../lib/runtime-client"; + import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; + import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; + import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; + import { get } from "svelte/store"; import RuntimeProvider from "@rilldata/web-common/runtime-client/v2/RuntimeProvider.svelte"; import type { Query } from "@tanstack/query-core"; import { QueryClientProvider } from "@tanstack/svelte-query"; @@ -65,6 +73,23 @@ $: previewModeLocked.set(data.previewMode); + // Reset View as + AI chat whenever the user crosses the local + // Developer ↔ Preview boundary. Each mode is a different runtime + // context, so a stale impersonation makes no sense across the swap. + let prevLocalPlatform: string | null = null; + $: { + const platform = $previewModeStore ? "local-preview" : "local-editor"; + if (prevLocalPlatform !== null && prevLocalPlatform !== platform) { + sidebarActions.closeChat(); + if (get(selectedMockUserStore) !== null) { + updateDevJWT(queryClient, getLocalRuntimeClient(), null).catch( + console.error, + ); + } + } + prevLocalPlatform = platform; + } + let removeJavascriptListeners: () => void; onMount(async () => { const config = data.metadata; From fca28aa1afd72a860c6bc352e05fffab5f846b4f Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 17:48:55 -0400 Subject: [PATCH 62/76] Preserve View as across user-pick navigation; allowlist new edit routes - Add a one-shot `skipNextPlatformReset` flag in `web-common/features/preview-mode/platform-reset.ts`. The local mock-user dropdown and the cloud editor's `ViewAsUserPopover` set it before navigating so the impersonation they just established survives the platform-change reset that fires immediately after. - Allowlist the new cloud editor dev-preview routes (`/-/edit/dashboards|status|ai|status/{resources,tables}`) in `scripts/check-edit-route-parity.js`. Local serves the same surfaces from the top-level routes (`/dashboards|status|ai`) under preview mode rather than nesting them under the editor. Co-Authored-By: Claude Opus 4.7 --- scripts/check-edit-route-parity.js | 12 +++++++++++ .../features/projects/ProjectHeader.svelte | 4 ++++ .../[organization]/[project]/+layout.svelte | 10 +++++++-- .../features/preview-mode/platform-reset.ts | 21 +++++++++++++++++++ .../src/layout/ApplicationHeader.svelte | 4 ++++ web-local/src/routes/+layout.svelte | 16 +++++++++----- 6 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 web-common/src/features/preview-mode/platform-reset.ts diff --git a/scripts/check-edit-route-parity.js b/scripts/check-edit-route-parity.js index edaf73bf3f9..4c02864975c 100644 --- a/scripts/check-edit-route-parity.js +++ b/scripts/check-edit-route-parity.js @@ -50,6 +50,18 @@ const ADMIN_ONLY_ALLOWLIST = [ "/welcome/+page.svelte", "/welcome/add-data/+page.svelte", "/welcome/add-data/+page.ts", + + // Dev-preview routes inside the cloud editor. Locally these surfaces live + // at the top level (`/dashboards`, `/status*`, `/ai`) since the local app + // toggles into Preview mode via the navbar instead of nesting it under an + // editor URL. Each editor page thinly wraps the same shared + // `web-common/features/preview-mode/*` component the local routes use. + "/dashboards/+page.svelte", + "/status/+layout.svelte", + "/status/+page.svelte", + "/status/resources/+page.svelte", + "/status/tables/+page.svelte", + "/ai/+page.svelte", ]; function walkRoutes(absRoot) { diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 286628fcec3..198853a845d 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -13,6 +13,7 @@ import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; + import { skipNextPlatformReset } from "@rilldata/web-common/features/preview-mode/platform-reset"; import Breadcrumbs from "@rilldata/web-common/components/navigation/breadcrumbs/Breadcrumbs.svelte"; import type { PathOption } from "@rilldata/web-common/components/navigation/breadcrumbs/types"; import { useCanvas } from "@rilldata/web-common/features/canvas/selector"; @@ -282,6 +283,9 @@ {project} onSelectUser={() => { editorViewAsOpen = false; + // Preserve the impersonation across the editor → cloud-prod + // transition this navigation triggers. + skipNextPlatformReset(); void goto(editPreviewHref); }} /> diff --git a/web-admin/src/routes/[organization]/[project]/+layout.svelte b/web-admin/src/routes/[organization]/[project]/+layout.svelte index dfdc41d1f4a..4447770333e 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/+layout.svelte @@ -48,6 +48,7 @@ import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; + import { consumePlatformResetSkip } from "@rilldata/web-common/features/preview-mode/platform-reset"; import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte"; import { themeControl } from "@rilldata/web-common/features/themes/theme-control"; import { metricsService } from "@rilldata/web-common/metrics/initMetrics"; @@ -125,8 +126,13 @@ $effect(() => { const platform = classifyCloudPlatform(page.url.pathname); if (prevCloudPlatform !== null && prevCloudPlatform !== platform) { - if (viewAsUserStore.get()) viewAsUserStore.set(null); - sidebarActions.closeChat(); + if (consumePlatformResetSkip()) { + // The user just picked an impersonation from the View-as dropdown + // and the dropdown handler asked us to preserve it across this nav. + } else { + if (viewAsUserStore.get()) viewAsUserStore.set(null); + sidebarActions.closeChat(); + } } prevCloudPlatform = platform; }); diff --git a/web-common/src/features/preview-mode/platform-reset.ts b/web-common/src/features/preview-mode/platform-reset.ts new file mode 100644 index 00000000000..2e7e2dab625 --- /dev/null +++ b/web-common/src/features/preview-mode/platform-reset.ts @@ -0,0 +1,21 @@ +/** + * One-shot flag the View-as user pickers can set just before navigating, + * to tell the project-layout's platform-change watcher *not* to clear + * the impersonation it just established. + * + * Without this, picking a mock/real user from the split-button dropdown + * triggers a navigation across a platform boundary (editor → preview), + * the platform watcher fires, and the freshly-set impersonation gets + * wiped before the new page can render with it. + */ +let skip = false; + +export function skipNextPlatformReset(): void { + skip = true; +} + +export function consumePlatformResetSkip(): boolean { + if (!skip) return false; + skip = false; + return true; +} diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index ffa3f3c78fd..d3a3fc79427 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -19,6 +19,7 @@ import { useMockUsers } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers"; import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; + import { skipNextPlatformReset } from "@rilldata/web-common/features/preview-mode/platform-reset"; import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; import Add from "@rilldata/web-common/components/icons/Add.svelte"; import Check from "@rilldata/web-common/components/icons/Check.svelte"; @@ -237,6 +238,9 @@ console.error, ); localViewAsOpen = false; + // Preserve the impersonation across the Developer → + // Preview transition this navigation triggers. + skipNextPlatformReset(); void goto(previewToggleHref); }} class="flex gap-x-2 items-center" diff --git a/web-local/src/routes/+layout.svelte b/web-local/src/routes/+layout.svelte index d557e9eaff7..b1026c1a5e1 100644 --- a/web-local/src/routes/+layout.svelte +++ b/web-local/src/routes/+layout.svelte @@ -33,6 +33,7 @@ import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; + import { consumePlatformResetSkip } from "@rilldata/web-common/features/preview-mode/platform-reset"; import { get } from "svelte/store"; import RuntimeProvider from "@rilldata/web-common/runtime-client/v2/RuntimeProvider.svelte"; import type { Query } from "@tanstack/query-core"; @@ -80,11 +81,16 @@ $: { const platform = $previewModeStore ? "local-preview" : "local-editor"; if (prevLocalPlatform !== null && prevLocalPlatform !== platform) { - sidebarActions.closeChat(); - if (get(selectedMockUserStore) !== null) { - updateDevJWT(queryClient, getLocalRuntimeClient(), null).catch( - console.error, - ); + if (consumePlatformResetSkip()) { + // The user just picked an impersonation from the View-as dropdown + // and the dropdown handler asked us to preserve it across this nav. + } else { + sidebarActions.closeChat(); + if (get(selectedMockUserStore) !== null) { + updateDevJWT(queryClient, getLocalRuntimeClient(), null).catch( + console.error, + ); + } } } prevLocalPlatform = platform; From 37a8006c6f38837875767ce2b5cbd55d8f251e4a Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:02:07 -0400 Subject: [PATCH 63/76] Drop unused `reconcileError` from `MetricsWorkspace` Was only consumed by the workspace `PreviewButton` (removed earlier in this PR). Leaving the dangling reactive declaration confuses `@typescript-eslint/no-unused-vars` enough to crash CI on this file. Co-Authored-By: Claude Opus 4.7 --- web-common/src/features/workspaces/MetricsWorkspace.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/web-common/src/features/workspaces/MetricsWorkspace.svelte b/web-common/src/features/workspaces/MetricsWorkspace.svelte index c75c0116053..692f0dcdc86 100644 --- a/web-common/src/features/workspaces/MetricsWorkspace.svelte +++ b/web-common/src/features/workspaces/MetricsWorkspace.svelte @@ -64,8 +64,6 @@ $: parseErrorQuery = fileArtifact.getParseError(queryClient); $: parseError = $parseErrorQuery; - $: reconcileError = resource?.meta?.reconcileError; - async function onChangeCallback(newTitle: string) { const newRoute = await handleEntityRename( runtimeClient, From 5f74091401383b2ccbf1c1fd0d7f0595c755e96b Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:12:04 -0400 Subject: [PATCH 64/76] Drop unused `showProjectViewAs` + its deps from `ApplicationHeader` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `showProjectViewAs`, `anyDashboardHasPolicy`, `rillYamlPolicyCheck`, and the `useRillYamlPolicyCheck` import are leftover from an earlier iteration where the standalone View-as pill was gated on policy existence. The current rendering uses `showStandaloneViewAs` (gated only on non-viz, non-deploy routes), so the rest is dead code — and the dangling reactive declarations were tripping the `@typescript-eslint/no-unused-vars` rule into a crash on this file. Co-Authored-By: Claude Opus 4.7 --- web-common/src/layout/ApplicationHeader.svelte | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index d3a3fc79427..17bd7bb64c0 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -17,7 +17,6 @@ import { selectedMockUserStore } from "@rilldata/web-common/features/dashboards/granular-access-policies/stores"; import { updateDevJWT } from "@rilldata/web-common/features/dashboards/granular-access-policies/updateDevJWT"; import { useMockUsers } from "@rilldata/web-common/features/dashboards/granular-access-policies/useMockUsers"; - import { useRillYamlPolicyCheck } from "@rilldata/web-common/features/dashboards/granular-access-policies/useSecurityPolicyCheck"; import ViewAsButton from "@rilldata/web-common/features/dashboards/granular-access-policies/ViewAsButton.svelte"; import { skipNextPlatformReset } from "@rilldata/web-common/features/preview-mode/platform-reset"; import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; @@ -80,21 +79,6 @@ // non-viz routes even when `--preview` locks the mode. $: showStandaloneViewAs = !onDeployPage && !onVizRoute; - // Show "View as" alongside the project preview chrome when the project - // — via rill.yaml or any individual dashboard — defines a security - // policy. Per-dashboard ViewAs already lives in ExplorePreviewCTAs / - // CanvasPreviewCTAs on viz routes. - $: rillYamlPolicyCheck = useRillYamlPolicyCheck(runtimeClient); - $: anyDashboardHasPolicy = - explores.some( - (e) => (e?.explore?.state?.validSpec?.securityRules?.length ?? 0) > 0, - ) || - canvases.some( - (c) => (c?.canvas?.state?.validSpec?.securityRules?.length ?? 0) > 0, - ); - $: showProjectViewAs = - !onVizRoute && (!!$rillYamlPolicyCheck?.data || anyDashboardHasPolicy); - $: mockUsers = useMockUsers(runtimeClient); let localViewAsOpen = false; From 351def56aeb82fe4fa60475cd7eeb03889115f45 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:19:29 -0400 Subject: [PATCH 65/76] Add Developer/Preview pill to cloud editor header Mirror the local app's mode pill in the cloud editor: render `Developer` on `/-/edit/files|explore|canvas|...` and `Preview` on the dev-preview sub-routes (`/-/edit/dashboards|status|ai`). Same `` component local uses, gated on `editContext` so it only appears inside the editor. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 198853a845d..b2f3b0f85fc 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -25,6 +25,7 @@ import { featureFlags } from "@rilldata/web-common/features/feature-flags"; import Header from "@rilldata/web-common/layout/header/Header.svelte"; import HeaderLogo from "@rilldata/web-common/layout/header/HeaderLogo.svelte"; + import Tag from "@rilldata/web-common/components/tag/Tag.svelte"; import PreviewModeToggleButton from "@rilldata/web-common/layout/header/PreviewModeToggleButton.svelte"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import type { V1ProjectPermissions } from "../../client"; @@ -232,6 +233,9 @@
+ {#if editContext} + + {/if} {#if onPublicURLPage} {:else if organization} From 363e167089a0e1f44cf7ffc6adc7363ff3447707 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:26:11 -0400 Subject: [PATCH 66/76] Add Publish (Merge to production) button to cloud dev-preview chrome Render `MergePopover` on the cloud editor's dev-preview routes so users can publish from the same surface where they verify the change. Sits next to the Edit-back button and `CloudViewAsButton`. The editor (non dev-preview) chrome already includes the merge action via `EditActions`; this just surfaces it in the Preview chrome too. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index b2f3b0f85fc..d64deed7329 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -10,6 +10,7 @@ import ShareDashboardPopover from "@rilldata/web-admin/features/dashboards/share/ShareDashboardPopover.svelte"; import EditActions from "@rilldata/web-admin/features/edit-session/EditActions.svelte"; import EditButton from "@rilldata/web-admin/features/edit-session/EditButton.svelte"; + import MergePopover from "@rilldata/web-admin/features/edit-session/MergePopover.svelte"; import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; @@ -267,6 +268,7 @@ href={editBackHref} onPreviewClick={resetOnPreviewSwap} /> + {:else if editContext} {#if $developerChat} From 7dd6d5ab9171104ea40ab8de8547376006f7daee Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:28:36 -0400 Subject: [PATCH 67/76] Revert "Add Publish (Merge to production) button to cloud dev-preview chrome" This reverts commit 363e167089a0e1f44cf7ffc6adc7363ff3447707. --- web-admin/src/features/projects/ProjectHeader.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index d64deed7329..b2f3b0f85fc 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -10,7 +10,6 @@ import ShareDashboardPopover from "@rilldata/web-admin/features/dashboards/share/ShareDashboardPopover.svelte"; import EditActions from "@rilldata/web-admin/features/edit-session/EditActions.svelte"; import EditButton from "@rilldata/web-admin/features/edit-session/EditButton.svelte"; - import MergePopover from "@rilldata/web-admin/features/edit-session/MergePopover.svelte"; import ShareProjectPopover from "@rilldata/web-admin/features/projects/user-management/ShareProjectPopover.svelte"; import CloudViewAsButton from "@rilldata/web-admin/features/view-as-user/CloudViewAsButton.svelte"; import { sidebarActions } from "@rilldata/web-common/features/chat/layouts/sidebar/sidebar-store"; @@ -268,7 +267,6 @@ href={editBackHref} onPreviewClick={resetOnPreviewSwap} /> - {:else if editContext} {#if $developerChat} From 9588300418abbd51cbd05c29aaef15be0e88a738 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 18:28:45 -0400 Subject: [PATCH 68/76] Revert "Add Developer/Preview pill to cloud editor header" This reverts commit 351def56aeb82fe4fa60475cd7eeb03889115f45. --- web-admin/src/features/projects/ProjectHeader.svelte | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index b2f3b0f85fc..198853a845d 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -25,7 +25,6 @@ import { featureFlags } from "@rilldata/web-common/features/feature-flags"; import Header from "@rilldata/web-common/layout/header/Header.svelte"; import HeaderLogo from "@rilldata/web-common/layout/header/HeaderLogo.svelte"; - import Tag from "@rilldata/web-common/components/tag/Tag.svelte"; import PreviewModeToggleButton from "@rilldata/web-common/layout/header/PreviewModeToggleButton.svelte"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import type { V1ProjectPermissions } from "../../client"; @@ -233,9 +232,6 @@
- {#if editContext} - - {/if} {#if onPublicURLPage} {:else if organization} From 3881fde5e2131c739dfedc305917995f062540ea Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:05:39 -0400 Subject: [PATCH 69/76] Treat `/-/edit/{explore,canvas}/[name]` as full-screen preview Extend the `inEditDevPreview` regex in the cloud edit layout and `ProjectHeader` to also match `/-/edit/explore/[name]` and `/-/edit/canvas/[name]`. The dashboard route still renders the actual dashboard, but the editor's file tree and chat sidebar are now hidden and the dev-preview chrome takes over, so a click from `/-/edit/dashboards` lands in a full-screen preview. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 5 ++++- .../[organization]/[project]/-/edit/+layout.svelte | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 198853a845d..18a5c82e165 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -93,8 +93,11 @@ // Cloud editor sub-routes that should adopt a "Dev preview" chrome // (View-as pill + Edit-back button) rather than the editor chrome // (split Preview button + EditActions). Mirrors local's preview mode. + // Includes the dashboard view routes so a click into a dashboard from + // the listing keeps the dev-preview chrome instead of falling back to + // the in-workspace editor. $: inEditDevPreview = !!route.id?.match( - /\/-\/edit\/(dashboards|status|ai)(\/|$)/, + /\/-\/edit\/(dashboards|status|ai|explore|canvas)(\/|$)/, ); $: editBackHref = `/${organization}/${project}${branchPathPrefix(activeBranch)}/-/edit`; diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 0bd73463db9..39ae9558c18 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -127,10 +127,13 @@ $: branchUrl = `/${organization}/${project}${branchPathPrefix(branch)}`; $: inProjectWelcomePage = isProjectWelcomePage($page); - // Dev-preview sub-routes (`/-/edit/dashboards|status|ai`) render - // standalone, without the editor's file tree or chat sidebar. + // Dev-preview sub-routes render standalone, without the editor's file + // tree or chat sidebar. Includes the dashboard view routes + // (`/-/edit/{explore,canvas}/[name]`) so a click from the dashboards + // listing lands in a full-screen preview rather than the in-workspace + // edit view. $: inEditDevPreview = !!$page.route.id?.match( - /\/-\/edit\/(dashboards|status|ai)(\/|$)/, + /\/-\/edit\/(dashboards|status|ai|explore|canvas)(\/|$)/, ); // Invalidating this query refetches a fresh JWT; `runtimeClient.getJwt()` From 948a9f8799997644c1b6a33ccc5619bc019d0694 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:06:14 -0400 Subject: [PATCH 70/76] Render `ProjectTabs` instead of `PreviewModeNav` on dev-preview Swap `PreviewModeNav` for the existing cloud `ProjectTabs` on the dev-preview chrome. The Home/Dashboards/Status/AI tab set already covers the surfaces the dev-preview cares about and avoids carrying a second navigation paradigm in the editor. Gate `ProjectTabs` on a new `showProjectTabs` flag that only matches `/-/edit/{dashboards,status,ai}` so dashboard views (now inside `inEditDevPreview` too) fill the full frame instead of stacking a tab row above the dashboard. Co-Authored-By: Claude Opus 4.7 --- .../[project]/-/edit/+layout.svelte | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 39ae9558c18..88f0e674ce5 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -14,8 +14,8 @@ import BranchDeploymentStopped from "@rilldata/web-admin/features/branches/BranchDeploymentStopped.svelte"; import EditSessionLoading from "@rilldata/web-admin/features/edit-session/EditSessionLoading.svelte"; import EditSessionTimeoutBanner from "@rilldata/web-admin/features/edit-session/EditSessionTimeoutBanner.svelte"; - import PreviewModeNav from "@rilldata/web-common/features/preview-mode/PreviewModeNav.svelte"; import ProjectHeader from "@rilldata/web-admin/features/projects/ProjectHeader.svelte"; + import ProjectTabs from "@rilldata/web-admin/features/projects/ProjectTabs.svelte"; import SlimProjectHeader from "@rilldata/web-admin/features/projects/SlimProjectHeader.svelte"; import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; import CtaButton from "@rilldata/web-common/components/calls-to-action/CTAButton.svelte"; @@ -135,6 +135,11 @@ $: inEditDevPreview = !!$page.route.id?.match( /\/-\/edit\/(dashboards|status|ai|explore|canvas)(\/|$)/, ); + // ProjectTabs sits above the page content, but only on the project-level + // surfaces — hide it on dashboard views so the dashboard fills the frame. + $: showProjectTabs = !!$page.route.id?.match( + /\/-\/edit\/(dashboards|status|ai)(\/|$)/, + ); // Invalidating this query refetches a fresh JWT; `runtimeClient.getJwt()` // reads the updated value on the next call. Branch must be part of the @@ -205,9 +210,13 @@ {onBeforeReconnect} errorBody="Lost connection to the editing environment. Try ending the session and starting a new one." > - {#if inEditDevPreview} - {/if}
From 126fab9732ede8e61a5ba0f5794a662e37329bb7 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:06:44 -0400 Subject: [PATCH 71/76] Route Preview button and dashboard rows into the dev-preview chrome Two link target changes that keep the user inside the editor session: - `editPreviewHref` fallback in `ProjectHeader` now lands on `${branch}/-/edit/dashboards` instead of the production project home, so clicking Preview from a non-explore/canvas editor route enters the dev-preview chrome. - The dev-preview dashboards listing builds rows as `${branch}/-/edit/{explore,canvas}/{name}` so a click into a dashboard stays under `/-/edit/...` and renders inside the dev-preview chrome (full-screen preview without the in-workspace editor). Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 2 +- .../[organization]/[project]/-/edit/dashboards/+page.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 18a5c82e165..acdd507cd99 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -88,7 +88,7 @@ $: editPreviewHref = editPreviewKind && name ? `/${organization}/${project}${branchPathPrefix(activeBranch)}/${editPreviewKind}/${name}` - : `/${organization}/${project}${branchPathPrefix(activeBranch)}`; + : `/${organization}/${project}${branchPathPrefix(activeBranch)}/-/edit/dashboards`; // Cloud editor sub-routes that should adopt a "Dev preview" chrome // (View-as pill + Edit-back button) rather than the editor chrome diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte index 90cbbebea09..40531fed252 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/dashboards/+page.svelte @@ -10,7 +10,7 @@ function getHref(name: string, isMetricsExplorer: boolean): string { const slug = isMetricsExplorer ? "explore" : "canvas"; - return `/${organization}/${project}${branchPart}/${slug}/${name}`; + return `/${organization}/${project}${branchPart}/-/edit/${slug}/${name}`; } From d6cc20d1761e3bcc77bc727239d216e25a38b2cc Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:07:01 -0400 Subject: [PATCH 72/76] Render `EditActions` on the dev-preview right cluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `EditActions` (Publish / Commit + Merge / Exit) was previously only on the editor side. Render it on `inEditDevPreview` too so users can publish or commit without bouncing back to the editor first — the dev-preview chrome now stands on its own as a full editing context. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index acdd507cd99..e259cc8c1ec 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -266,6 +266,12 @@ href={editBackHref} onPreviewClick={resetOnPreviewSwap} /> + {:else if editContext} {#if $developerChat} From 43563534be9c166a2f393473403dc081c3b53a9e Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:07:20 -0400 Subject: [PATCH 73/76] Surface dashboard breadcrumb on `/-/edit/{explore,canvas}/[name]` The dev-preview dashboard routes use a `name` route param, not `dashboard`, so the depth-2 breadcrumb segment came up empty in the header. Fall back to `name` when on those routes so the trail reads `org / project / branch / dashboard` like the production project view. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index e259cc8c1ec..fa8db111490 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -230,7 +230,10 @@ : $exploreQuery.data?.explore?.explore?.state?.validSpec?.displayName || dashboard; - $: currentPath = [organization, project, dashboard, report || alert]; + // On `/-/edit/{explore,canvas}/[name]`, the route param is `name`, not + // `dashboard`; fall back so the breadcrumb still surfaces the resource. + $: currentDashboard = dashboard || (editPreviewKind ? name : undefined); + $: currentPath = [organization, project, currentDashboard, report || alert];
From f8e47317fef84bdfd6f6c7f461e0c09caabe2293 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:20:59 -0400 Subject: [PATCH 74/76] Keep nav inside `/-/edit/...` from the dev-preview chrome Two routing fixes for the cloud editor's dev-preview chrome: - `ProjectTabs` gains an `editMode` prop. When set, AI / Dashboards / Status / Reports / Alerts / Settings routes use `${branch}/-/edit/...` instead of `${branch}/-/...`, and the Home tab points to `${branch}/-/edit/dashboards` so all in-tab navigation stays inside the editor session. The cloud edit layout passes `editMode` whenever it renders the tabs. - The cloud `ProjectHeader` builds a per-render copy of the project breadcrumb options that pins each option's `href` to `${branch}/-/edit/dashboards` while `inEditDevPreview` is true, so clicking the project crumb from `/-/edit/explore/[name]` lands back on the dev-preview listing instead of dropping the user out to the branch's production view. Co-Authored-By: Claude Opus 4.7 --- .../features/projects/ProjectHeader.svelte | 20 +++++++++++++++- .../src/features/projects/ProjectTabs.svelte | 24 ++++++++++++------- .../[project]/-/edit/+layout.svelte | 1 + 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index fa8db111490..781b177bcdf 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -202,9 +202,27 @@ }, new Map()), }; + // In the dev-preview chrome, the project breadcrumb should keep the user + // inside the editor session — link each project option to its + // `${branch}/-/edit/dashboards` instead of the production project root. + $: projectOptions = (() => { + const raw = $projectPathsQuery.data ?? new Map(); + if (!inEditDevPreview) return raw; + const branchPart = branchPathPrefix(activeBranch); + return new Map( + [...raw].map(([key, option]) => [ + key, + { + ...option, + href: `/${organization}/${key}${branchPart}/-/edit/dashboards`, + }, + ]), + ); + })(); + $: pathParts = [ { options: $orgPathsQuery.data ?? new Map() }, - { options: $projectPathsQuery.data ?? new Map() }, + { options: projectOptions }, visualizationPaths, report ? reportPaths : alert ? alertPaths : null, ]; diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index 0c951097c68..9be3357a39d 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -13,47 +13,55 @@ export let project: string; export let pathname: string; export let branchPrefix: string = ""; + /** When rendered inside the cloud editor's dev-preview chrome, route the + * tabs to `/-/edit/{section}` so navigation stays inside the editor + * session instead of dropping the user into the production project view. */ + export let editMode: boolean = false; const { chat, reports, alerts } = featureFlags; + $: base = `/${organization}/${project}${branchPrefix}`; + $: sectionPrefix = editMode ? `${base}/-/edit` : `${base}/-`; + $: homeRoute = editMode ? `${base}/-/edit/dashboards` : base; + $: tabs = [ { - route: `/${organization}/${project}${branchPrefix}`, + route: homeRoute, label: "Home", hasPermission: true, }, { - route: `/${organization}/${project}${branchPrefix}/-/ai`, + route: `${sectionPrefix}/ai`, label: "AI", hasPermission: $chat, }, { - route: `/${organization}/${project}${branchPrefix}/-/dashboards`, + route: `${sectionPrefix}/dashboards`, label: "Dashboards", hasPermission: true, }, { - route: `/${organization}/${project}${branchPrefix}/-/query`, + route: `${sectionPrefix}/query`, label: "Query", hasPermission: false, }, { - route: `/${organization}/${project}${branchPrefix}/-/reports`, + route: `${sectionPrefix}/reports`, label: "Reports", hasPermission: $reports, }, { - route: `/${organization}/${project}${branchPrefix}/-/alerts`, + route: `${sectionPrefix}/alerts`, label: "Alerts", hasPermission: $alerts, }, { - route: `/${organization}/${project}${branchPrefix}/-/status`, + route: `${sectionPrefix}/status`, label: "Status", hasPermission: projectPermissions.manageProject, }, { - route: `/${organization}/${project}${branchPrefix}/-/settings`, + route: `${sectionPrefix}/settings`, label: "Settings", hasPermission: projectPermissions.manageProject, }, diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte index 88f0e674ce5..61ea2bcd960 100644 --- a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -217,6 +217,7 @@ {project} pathname={$page.url.pathname} branchPrefix={branchPathPrefix(branch)} + editMode /> {/if}
From 5fac16d34cbe8aa4c9c23362b4b4685f067a7f03 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:29:45 -0400 Subject: [PATCH 75/76] Hide Home tab in `ProjectTabs` editMode In `editMode`, Home and Dashboards both pointed at `${branch}/-/edit/dashboards`, which fed the keyed `{#each}` two duplicate `tab.route` keys and tripped Svelte's `each_key_duplicate` runtime error. There's no separate project-home page on the dev-preview surface, so simply gate Home off when `editMode` is true. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectTabs.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index 9be3357a39d..e277fbfe36d 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -22,13 +22,15 @@ $: base = `/${organization}/${project}${branchPrefix}`; $: sectionPrefix = editMode ? `${base}/-/edit` : `${base}/-`; - $: homeRoute = editMode ? `${base}/-/edit/dashboards` : base; $: tabs = [ { - route: homeRoute, + route: base, label: "Home", - hasPermission: true, + // In dev-preview chrome there's no separate project-home page — + // Home would collide with Dashboards (`/-/edit/dashboards`), so + // drop it. + hasPermission: !editMode, }, { route: `${sectionPrefix}/ai`, From 6b3e077b9743803c900bb3e9ba8305828ade8ea8 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Fri, 1 May 2026 19:36:38 -0400 Subject: [PATCH 76/76] Pin breadcrumb resource swaps to `/-/edit/...` in dev-preview When `inEditDevPreview` is true, set an explicit `href` on each visualization breadcrumb option so picking a different dashboard from the resource dropdown stays inside the dev-preview chrome (`${branch}/-/edit/{explore|canvas}/{name}`) instead of falling back to the production `${section}/${name}` path the breadcrumb linkMaker would otherwise build. Co-Authored-By: Claude Opus 4.7 --- web-admin/src/features/projects/ProjectHeader.svelte | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 781b177bcdf..740de149697 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -168,12 +168,22 @@ .reduce((map, resource) => { const name = resource.meta.name.name; const isMetricsExplorer = !!resource?.explore; + const slug = isMetricsExplorer ? "explore" : "canvas"; + // In the dev-preview chrome, swapping resources via the breadcrumb + // dropdown should keep the user inside `/-/edit/...`. Set an + // explicit `href` that pins the edit prefix; otherwise fall back + // to the production-style `${section}/${name}` path linkMaker + // builds from `section` alone. + const href = inEditDevPreview + ? `/${organization}/${project}${branchPathPrefix(activeBranch)}/-/edit/${slug}/${name}` + : undefined; return map.set(name.toLowerCase(), { label: (isMetricsExplorer ? resource?.explore?.spec?.displayName : resource?.canvas?.spec?.displayName) || name, - section: isMetricsExplorer ? "explore" : "canvas", + section: slug, + href, resourceKind: isMetricsExplorer ? ResourceKind.Explore : ResourceKind.Canvas,