Skip to content

Cloud editing MVP#8912

Open
ericpgreen2 wants to merge 89 commits intomainfrom
ericgreen/cloud-editing-mvp
Open

Cloud editing MVP#8912
ericpgreen2 wants to merge 89 commits intomainfrom
ericgreen/cloud-editing-mvp

Conversation

@ericpgreen2
Copy link
Copy Markdown
Contributor

@ericpgreen2 ericpgreen2 commented Feb 23, 2026

MVP — merging behind the cloud_editing feature flag (default off) so we can test on cloud infrastructure.

Cloud Editing lets users edit Rill projects directly in the browser. Click "Edit" on a project, a dev deployment spins up on a branch, and you get the same file explorer, code editor, and workspace views as Rill Developer — running in the cloud.

What's in this PR

Edit session (new /-/edit/ route tree in web-admin)
Its own RuntimeProvider, a persistent file/resource watcher, and workspace pages for files, explores, canvases, and the resource graph. Includes the EditButton branch picker, CommitPopover for committing + pushing, a session-timeout banner, and single-editor lock with loading / stopped / errored states.

Shared routing abstraction (web-common/layout/navigation/editor-routing.ts)
Replaces hardcoded /files/... paths with helpers (navigateToFile, navigateToHome, getFileHref, …) so the same components work in both local (/files/...) and cloud (/<org>/<project>/@<branch>/-/edit/files/...) contexts.

A new CI check (scripts/check-edit-route-parity.js) enforces route-tree parity between web-local and web-admin/.../-/edit/: if you add a route on one side without mirroring it on the other (or explicitly allowlisting it with a reason), CI fails. This keeps the two surfaces from drifting as we add new route types.

Backend

  • Fast-fail on non-retryable provisioning errors
  • Branch JWT grants the dev permissions needed for editing
  • SSE keepalive to prevent proxy/LB idle disconnects
  • devSlots reduced from 8 to 4

Known gaps (before removing the feature flag)

  • A more comprehensive git client
  • E2E tests

Closes APP-174

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Developed in collaboration with Claude Code

@ericpgreen2 ericpgreen2 changed the title WIP: Cloud editing MVP — native editing surface WIP: Cloud editing MVP Feb 23, 2026
Add a branch selector chip to the project header that lets admins
switch between branch deployments directly in the cloud UI. Gated
on `readDev` permission.

- New `BranchSelector` component (chip + dropdown, modeled after `ViewAsUserChip`)
- Read `?branch=X` query param in project layout, pass to `GetProject` API
- Warning banner when viewing a non-production branch deployment
- Branch param preserved across tab navigation via `branchSearchSuffix`
- "Start deployment" button for stopped branch deployments
- Deduplicates `ListDeployments` by branch; live status from `GetProject`
- "View As" composes with branch context
Move `BranchSelector` from a standalone header chip into the avatar
button dropdown as a submenu (like "View as"), reducing visual clutter
when viewing branch deployments. Replace `ProjectAccessControls` wrapper
in `AvatarButton` with a `projectPermissions` prop passed from
`ProjectHeader`, eliminating a redundant `GetProject` query. Clean up
branch-related props from `SlimProjectHeader` and `ProjectHeader`.
…ments

Replace `?branch=X` query params with `@branch` path segments for branch
deployment previews (e.g., `/org/project/@feature-x/explore/dashboard`).

- Add `reroute` hook to strip `@branch` before route matching
- Add `beforeNavigate` hook to inject `@branch` into branch-unaware links
- Add `branch-utils.ts` with path manipulation helpers
- Use `~` encoding for `/` in branch names (git disallows `~` in refs)
- Branch menu items render as `<a>` tags for href preview on hover
- Back/forward browser history works correctly (skip injection on popstate)
- Rewrite BranchSelector from avatar dropdown submenu to standalone
  dropdown styled as a compact pill (matching the Chip component's
  dimension style) positioned after the project breadcrumb
- Add `after-project` slot to Breadcrumbs component for the pill
- Remove the yellow branch preview banner (redundant with the pill)
- Show branch selector on all project pages, not just Home
- Branch-centric labels: trigger shows truncated branch name,
  dropdown shows branch names with "production" suffix for primary
- Gate on `readDev` permission
- Add TTL-based auto-expiry (500ms) to skip-branch-injection flag to
  prevent stale flags from leaking across navigations
- Extract stopped deployment UI into `BranchDeploymentStopped` component
  using the project's Button and CTA layout patterns
- Show spinner for STOPPING state instead of the "Start deployment" button
- Move `startDeploymentMutation` into the extracted component so it is
  not instantiated on every project page load
- Replace hardcoded "main" fallback with `primaryBranch` from the
  project query
- Add clarifying comments for ISO 8601 string comparison and breadcrumb
  depth assumption
Move branch-related files into a dedicated feature directory and
centralize logic that was scattered across the codebase:

- Move BranchSelector, BranchDeploymentStopped, branch-utils to features/branches/
- Extract `getBranchRedirect` from the project layout's beforeNavigate hook
- Reuse `removeBranchFromPath` in hooks.ts reroute (removes duplicate regex)
Move the beforeNavigate branch injection logic into a single testable
function in branch-utils.ts. The layout's callback becomes a one-liner
that delegates to the feature module.
- Replace BranchSelector's `getStatusColor` with shared `getStatusDotClass`
- Remove duplicate `GetProject` query from BranchSelector; accept
  `primaryBranch` as a prop threaded from the layout
- Change `getBranchRedirect` to accept a `URL` object instead of three
  string parameters
- Reorder `handleBranchNavigation` guards so `activeBranch` is checked
  before consuming the skip flag
- Simplify `consumeSkipBranchInjection` to always-clear-on-read
- Replace redundant integration tests with a search+hash test and add
  prefix-collision edge case for `getBranchRedirect`
`SlimProjectHeader` is rendered when the deployment isn't running.
Without the `BranchSelector`, users on a stopped branch deployment
had no way to switch back to production or another branch.
…r lock

- Add edit session feature in web-admin with iframe-based Rill Developer embed
- Add "Edit" tab to ProjectTabs (gated on `manageDev` permission)
- Add `/<org>/<project>/-/edit/` route for the edit session view
- Add single-editor lock in `CreateDeployment` to prevent concurrent edit sessions
- Edit session auto-creates a dev deployment, polls for readiness, fetches
  credentials, and embeds web-local in an iframe
- Toolbar provides "Push to production" (git push) and "End session" (teardown)
- Timeout warning banner shown 10 minutes before session expiry
- Create workspace routing abstraction (`workspaceRoutePrefix` store) so
  shared web-common components build correct paths in both web-local and
  web-admin edit sessions
- Extract `WorkspaceDispatcher` component (resource kind to workspace mapping)
  from web-local into web-common for reuse
- Update ~15 web-common navigation/file-explorer files to use routing helpers
  instead of hardcoded paths
- Add SvelteKit routes for edit session: files, explore, canvas
- Refactor edit layout to render Navigation sidebar + WorkspaceDispatcher
  instead of iframe
- Skip parent RuntimeProvider on edit routes to prevent runtime store conflicts
  between production and dev deployment credentials
- Fix concurrent map write crash in `auth/claims.go` (copy Attrs map instead
  of mutating shared instance)
- Add `ReadInstance` permission to dev deployment JWTs so Data Explorer works
- Fix `ProjectGlobalStatusIndicator` crash from dev deployment response shape
- Defer file content fetch until runtime credentials are available (fixes 404
  on full page refresh)
- Replace the "Edit" tab in `ProjectTabs` with a session-aware "Edit"
  button in `ProjectHeader` (shows "Resume editing" / "Editing locked"
  based on session state)
- Add "Dev Environments" tab to the Status page left nav with a table
  of dev deployments (branch, status, last updated, actions via
  three-dot menu)
- Migrate edit session pages from legacy runtime store to
  `RuntimeProvider` / `useRuntimeClient`
- Fix race condition where `FileArtifact` objects created during
  `+page.ts` load received an undefined client; `setClient()` now
  propagates to existing artifacts
- Guard `useResource` / `useResourceV2` against undefined client
@ericpgreen2 ericpgreen2 force-pushed the ericgreen/cloud-editing-mvp branch from c4ea74d to 86f84fc Compare March 11, 2026 13:31
- Edit layout extracts branch from `@branch` URL segment and looks up
  deployment by branch name instead of "first active deployment"
- EditButton navigates to `/@branch/-/edit` using the deployment's branch
- DevEnvironmentsSection links use `@branch` for Resume and adds Preview
- EditSessionToolbar shows branch name and navigates to production on end
- Edit layout shows 403 error page when visiting another user's edit URL
  with a "Preview this branch" link
- Add `useDevDeploymentByBranch` and export `isActiveDeployment` from
  use-edit-session
Cherry-pick d8c165f (reset retries on client-initiated pause) plus
reset retryAttempts in start() so that switching to a new endpoint
(e.g. entering a cloud edit session) doesn't inherit stale retry counts
from a previous connection.
The project layout now renders ProjectHeader for edit pages when the
deployment is available. This gives edit sessions the same breadcrumbs,
branch selector, and navigation as other project pages. The EditButton
is hidden when on an edit page since the user is already editing.
Replace the two-row header (ProjectHeader + EditSessionToolbar) with a
single header row. The edit layout publishes session state via an
`editSessionState` store; ProjectHeader reads it to render "End session"
and "Push to production" buttons in place of Share/EditButton when in
edit mode. The redundant "EDITING ON branch" label is removed since the
branch selector already shows the active branch.
The ProjectParser resource stays RUNNING indefinitely on dev/branch
deployments (it watches for file changes). `smartRefetchIntervalFunc`
saw this in the unfiltered `ListResources` response and polled forever.

Add `createSmartRefetchInterval(predicate)` factory so callers can
scope polling to only the resources they display. Apply it to
`useDashboards` (canvas/explore only) and `useResources` on the
status page (visible, non-internal kinds only).
FileAndResourceWatcher never passed a JWT to its SSE connection.
In cloud, the SSE endpoint requires authentication, causing an
infinite reconnect loop. Pass `getJwt` callback and add a URL
idempotency guard to prevent redundant reconnections.
`lineMarkerChange` had `effectPresent || update` where `update` is
always truthy, causing gutter markers to be destroyed and recreated
on every view update (scroll, hover, focus). Check `docChanged`,
`selectionSet`, or lineStatus effects instead.
When showFooterLinks was false, Footer rendered an empty bordered
strip. Instead, don't render Footer at all and remove the prop;
Footer now always shows its full content when rendered.
Replace the edit layout's inline header with the real ProjectHeader
for visual parity (breadcrumbs, BranchSelector, dropdowns). Add
`editContext` prop to ProjectHeader so it renders EditActions instead
of EditButton/ShareProjectPopover when in an edit session.

- Edit layout fetches its own GetProject({branch}) for metadata
- Remove `editSessionState` global store; pass props directly
- Exclude edit routes from `isProjectPage` in nav-utils
- Project layout renders only `<slot />` for edit pages
Use `window.location.href` instead of `goto()` when ending an edit
session to avoid a race where `useRuntimeClient()` is called before the
layout's RuntimeProvider remounts. Also fix the "Start deployment"
button in BranchDeploymentStopped which used `on:click` instead of the
Button component's `onClick` prop.
The BranchSelector's ListDeployments query (keyed with `{}` params) was
never invalidated when deployments changed — only the dev-scoped query
(`{ environment: "dev" }`) was. This caused stale green dots for stopped
branches.

Three changes:
1. Add refetchInterval to BranchSelector that polls every 2s while any
   deployment is in a transitional state (pending/updating/stopping).
2. Invalidate all ListDeployments queries (using base key without
   params) when the layout detects a deployment status change via
   GetProject polling.
3. Rename `invalidateDevDeployments` → `invalidateDeployments` and
   broaden its key to match all deployment queries, not just dev-scoped.
Extract the repeated `<div class="h-36"><Spinner size="7rem" ...></div>`
pattern into a shared `LoadingSpinner` component (3rem, h-16) used
across 13 files. Also add a `branch` prop to `ProjectBuilding` so it
shows "Starting branch deployment..." instead of the generic deploy
message when viewing a non-production branch.
ericpgreen2 and others added 12 commits April 13, 2026 20:29
Wire up DeveloperChat panel and ChatToggle button to the cloud edit
pages, matching the same layout pattern used in web-local.
…ng-mvp

# Conflicts:
#	admin/server/deployment.go
#	web-common/src/features/canvas/CanvasPreviewCTAs.svelte
- Gate `EditButton` behind `$cloudEditing` feature flag (was `true`)
- Restore `@apply ui-copy-number` in vega.css (was accidentally commented out)
- Restore `generatingCanvas` overlay and `resource`/`remoteContent` props
  in `WorkspaceDispatcher` (dropped during extraction from web-local)
- Remove invalid `branch` prop passed to `ProjectOverview`
- Suppress `a11y_autofocus` warnings in `EditButton`
- Add missing cloud editing routes: 8 connector table previews and graph
…feature flag test

- Extract `cancelAsErrored` and promote `isNonRetryable` to methods on `ReconcileDeploymentWorker` to deduplicate error handling
- Fix branch permissions in `GetProject`: split read/write so viewers get dashboard access (`ReadInstance`, `ReadOLAP`, `ReadResolvers`) and only `ManageDev` editors get full dev access (`ReadProfiling`, `ReadRepo`, `EditRepo`, `EditTrigger`)
- Add `cloudEditing` to feature flags test expectations
- Extract `GraphWorkspace` into `web-common` to share between web-local and web-admin
…ng-mvp

# Conflicts:
#	web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte
@ericpgreen2 ericpgreen2 self-assigned this Apr 17, 2026
@ericpgreen2 ericpgreen2 requested a review from AdityaHegde April 17, 2026 11:51
The module is imported across ~30 features (sources, connectors, entity-management, models, metrics-views, file-explorer, resource-graph, etc.), not just workspaces. Relocate it alongside the other cross-cutting navigation helpers and rename to `editor-routing` since it covers the editor surface (local + cloud edit session), not "edit mode" only.
…outePrefix`

Align symbol names with the `editor-routing.ts` filename, and clarify that the helper just prepends the editor route prefix.
@ericpgreen2 ericpgreen2 marked this pull request as ready for review April 17, 2026 12:36
The cloud editing surface at `web-admin/[organization]/[project]/-/edit/`
is supposed to mirror the editing routes in `web-local/src/routes/`, with
shared components navigating via `editorRoutePrefix`. Nothing enforced
that — the two trees could drift as the editing surface evolves.

`scripts/check-edit-route-parity.js` walks both trees, compares logical
paths symmetrically, and fails on unexpected divergence. Known intentional
gaps are tracked in per-side allowlists with reason comments. Pattern
mirrors `scripts/tsc-with-whitelist.sh`; wired into
`scripts/web-test-code-quality.sh` and exposed as
`npm run check:edit-route-parity` for local runs.
Fill in five routes that existed only in Rill Developer:

- `connector/{clickhouse,druid,duckdb,pinot}/+page.ts`: 307 redirect to
  the edit home when the user hits a connector root without instance
  params. Preserves the `@branch` segment via path splitting.
- `explore/[name]/+layout.svelte`: adds a `DeveloperChat` sidebar next
  to the preview explore, matching the chat layout in Rill Developer.

Also tightens the parity-check allowlist: removes the five entries
above, groups the remaining AI citation fallback routes under a single
TODO about preserving edit-session context on citation navigation, and
keeps the legacy `/dashboard/[name]` redirect as a permanent local-only.
`web-local` mounts `DeveloperChat` once in `(workspace)/+layout.svelte`,
but `web-admin/-/edit/` had it duplicated across `+page.svelte`,
`files/[...file]/+page.svelte`, and `explore/[name]/+layout.svelte`,
while `canvas`, `connector`, and `graph` routes had no chat at all.

Hoist `DeveloperChat` into `-/edit/+layout.svelte` next to `<slot />` so
every edit route gets it consistently, matching the `web-local` pattern.

Also fix the `explore/[name]/+layout.svelte` divergence: `web-local` uses
`DashboardChat` there (dashboard-scoped), not `DeveloperChat`. Mirror that.
Copy link
Copy Markdown
Collaborator

@AdityaHegde AdityaHegde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File links from add-data are not replaced. ImportDataStatus.svelte and GenerateDashboardStatus.svelte need updating.

Comment thread web-common/src/features/file-explorer/NavFile.svelte Outdated
Comment thread web-common/src/features/entity-management/RenameAssetModal.svelte Outdated
Comment thread web-common/src/layout/navigation/editor-routing.ts
private client = new SSEFetchClient();

private autoCloseThrottler: Throttler | undefined;
private autoCloseDisabled = false;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about calling this keepAlive?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, this PR currently hacks the SSE client in various ways– the layers between abstractions are blurring together. Here is a standalone PR (WIP) for a proper refactor.

Route add-data and model CTA navigation through editor routing helpers so edit sessions stay scoped to /-/edit, and invalidate git status after push so commit UI reflects repo state immediately.
Copy link
Copy Markdown
Collaborator

@AdityaHegde AdityaHegde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another round of comments. Mainly on the backend.

Comment thread runtime/server/auth/claims.go Outdated
Comment thread admin/jobs/river/reconcile_deployment.go Outdated
Comment thread admin/server/projects.go Outdated
Comment thread runtime/drivers/admin/repo_git.go Outdated
Comment thread scripts/check-edit-route-parity.js
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need @kaspersjo / @begelundmuller inputs on changes here.

  1. If we need to retry for resources availability(which I think we should) then just 4 minutes worth retry seems little less and retrying after 15s - 30s may be too aggressive.
  2. The ErrNoCapacity seems to be only returned for static runtimes which are not used in production as of now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 is not valid since dynamic provisioner is never out of capacity. But then it means we still want to wait for scale up in which case the retry time is insufficient (point no 1).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My override to River's retry timing was an earlier effort to fast-fail when the deployment couldn't be provisioned due to hitting capacity with the static provisioner (in development). Since then, I've classified a few errors as "non-retryable" to fast-fail immediately. So I just removed that "NextRetry" override, to just use the River default for now.

Copy link
Copy Markdown
Collaborator

@AdityaHegde AdityaHegde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Changing project while in edit should not keep the -/edit path, it can lead to unwanted edit session.
  2. Creating a dev branch on self managed git kept failing for me. I got repo is not watchable error through SSE.
  3. Another missing link update in web-common/src/components/ErrorPage.svelte. Might need to update once #9268 is merged.

Couldnt really check more since the edit session kept erroring out for me.

const filePath = resource?.meta?.filePaths?.[0];
if (filePath) {
await navigateToFile(
filePath.startsWith("/") ? filePath : `/${filePath}`,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to continue using removeLeadingSlash. Will be easy to fix if and when we normalise all sources of paths.

</Button>
</div>
{:else}
<!-- Raw button (not DropdownMenu.Item) so clicking doesn't close the menu -->
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using a Popover instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants