Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
55ae30a
feat: add branch deployment selector UI for project admins
ericpgreen2 Mar 9, 2026
a5bfbb8
refactor: move branch selector into avatar dropdown menu
ericpgreen2 Mar 10, 2026
9fe8766
feat: migrate branch selector from query params to `@branch` path seg…
ericpgreen2 Mar 10, 2026
43af2e4
feat: move branch selector to ProjectHeader as breadcrumb pill
ericpgreen2 Mar 11, 2026
658edce
test: add unit tests for branch-utils
ericpgreen2 Mar 11, 2026
fe65af0
fix: use `isPending` instead of `isLoading` for TanStack Query v5 mut…
ericpgreen2 Mar 11, 2026
33d1ef9
fix: harden branch deployment UI
ericpgreen2 Mar 11, 2026
4fa8fb7
refactor: consolidate branch code into `features/branches/`
ericpgreen2 Mar 11, 2026
63ca57c
fix: remove stale "Back to production" banner references in comments
ericpgreen2 Mar 11, 2026
2fbbca2
refactor: extract `handleBranchNavigation` from project layout
ericpgreen2 Mar 11, 2026
46212ee
refactor: simplify branch-utils and remove duplicate state
ericpgreen2 Mar 11, 2026
6543e45
fix: show branch selector on stopped/errored deployment pages
ericpgreen2 Mar 11, 2026
0a5b394
Cloud editing: edit session lifecycle, iframe embed, and single-edito…
ericpgreen2 Feb 19, 2026
b00fa8c
Cloud editing: replace iframe with native shared components
ericpgreen2 Feb 23, 2026
86f84fc
feat: replace Edit tab with header button + Dev Environments status page
ericpgreen2 Mar 11, 2026
e7d20da
feat: unify edit flow with `@branch` URL scheme
ericpgreen2 Mar 11, 2026
00b9bfe
fix: reset SSE retry counter on pause and fresh connections
ericpgreen2 Mar 11, 2026
0607289
feat: render ProjectHeader on edit pages instead of custom toolbar
ericpgreen2 Mar 11, 2026
e9d390f
refactor: collapse edit toolbar into ProjectHeader
ericpgreen2 Mar 11, 2026
d627bc1
fix: stop perpetual `ListResources` polling on branch deployments
ericpgreen2 Mar 12, 2026
9210918
fix: pass JWT to SSE connection in cloud editing
ericpgreen2 Mar 12, 2026
8ba46f1
fix: CodeMirror line number gutter recreated on every update
ericpgreen2 Mar 12, 2026
5c75205
fix: hide empty footer in cloud editing navigation
ericpgreen2 Mar 12, 2026
675921b
refactor: render ProjectHeader in edit layout with `editContext` prop
ericpgreen2 Mar 12, 2026
041ba63
fix: resolve blank page and broken button in branch deployment UX
ericpgreen2 Mar 12, 2026
ed55955
fix: keep BranchSelector status in sync with deployment changes
ericpgreen2 Mar 12, 2026
f708890
refactor: extract LoadingSpinner component and show branch-aware message
ericpgreen2 Mar 12, 2026
75be877
refactor: edit session UX refinements and polling optimization
ericpgreen2 Mar 12, 2026
46ca9ac
fix: fail fast on non-retryable provisioning errors and improve error UX
ericpgreen2 Mar 12, 2026
306fe19
refactor: remove dead code from legacy iframe edit session
ericpgreen2 Mar 13, 2026
ae1c265
fix: grant repo permissions for branch JWTs and improve editing UX
ericpgreen2 Mar 13, 2026
af880a2
fix: keep SSE connection alive in cloud editor to prevent save failures
ericpgreen2 Mar 13, 2026
2ad0e20
refactor: polish Edit button dropdown and show it on dashboard pages
ericpgreen2 Mar 13, 2026
daf7ef7
refactor: unify deployments page with slot awareness and edit UX polish
ericpgreen2 Mar 13, 2026
8f01735
fix: show info banners on project-scoped pages when viewing a branch
ericpgreen2 Mar 13, 2026
36ce7f9
fix: show current branch deployment on Status Overview page
ericpgreen2 Mar 13, 2026
5a22bb1
fix: cloud editing UX improvements and deployment reliability
ericpgreen2 Mar 16, 2026
b36ba6a
merge: resolve conflicts with main
ericpgreen2 Mar 24, 2026
5c6626e
Fix deployment start/stop UI transitions and add start/stop actions t…
ericpgreen2 Mar 24, 2026
fa24c05
Merge branch 'main' into ericgreen/cloud-editing-mvp
ericpgreen2 Mar 26, 2026
79b0b4c
Redesign deployments status page
ericpgreen2 Mar 26, 2026
5df3040
fix: Svelte 5 migration and refetch interval improvements
ericpgreen2 Mar 26, 2026
01dd092
refactor: convert project layout to Svelte 5 runes and extract shared…
ericpgreen2 Mar 30, 2026
5368c81
chore: clean up `features/projects` directory
ericpgreen2 Mar 30, 2026
fb407db
fix: update remaining `constants` import paths after file move
ericpgreen2 Mar 30, 2026
a0f9672
fix: update `ResourceError` import to use `web-common` version
ericpgreen2 Mar 30, 2026
1520660
refactor: bundle mock user params in `resolveRuntimeConnection`
ericpgreen2 Mar 30, 2026
3629e36
refactor: remove unnecessary derived wrappers in project layout
ericpgreen2 Mar 30, 2026
081ddb4
feat: add branch deployment selector and Deployments management page
ericpgreen2 Mar 30, 2026
ad43959
fix: eliminate branch switch lag by optimistically seeding project cache
ericpgreen2 Mar 30, 2026
d9cb63f
Revert "fix: eliminate branch switch lag by optimistically seeding pr…
ericpgreen2 Mar 30, 2026
b53ee01
refactor: simplify `DeploymentsSection` and migrate to Svelte 5
ericpgreen2 Mar 31, 2026
4009957
Merge origin/main into ericgreen/branch-selector-ui
ericpgreen2 Apr 1, 2026
81ef06c
feat: show spinner for transitory deployment statuses
ericpgreen2 Apr 1, 2026
278dac6
fix: surface project parser errors on deployment overview page
ericpgreen2 Apr 1, 2026
ffeca15
fix: review feedback — deduplicate status set, fix dark mode border, …
ericpgreen2 Apr 2, 2026
ed92693
fix: revert unrelated `download-report.ts` change from bad merge
ericpgreen2 Apr 2, 2026
30b6ed8
Merge branch 'ericgreen/branch-selector-ui' into ericgreen/cloud-edit…
ericpgreen2 Apr 2, 2026
c2d0283
fix: review feedback — data-driven tests, remove `deduplicateDeployme…
ericpgreen2 Apr 9, 2026
6dd2e24
Merge branch 'ericgreen/branch-selector-ui' into ericgreen/cloud-edit…
ericpgreen2 Apr 9, 2026
2c6c63e
Merge branch 'main' into ericgreen/cloud-editing-mvp
ericpgreen2 Apr 10, 2026
449bfcb
Merge branch 'main' into ericgreen/cloud-editing-mvp
ericpgreen2 Apr 13, 2026
b8d9a99
Add cloud editing feature flag, commit button, and Open PR placeholder
ericpgreen2 Apr 13, 2026
61285eb
Fix GitStatus on new branches, update commit popover copy, and polish UI
ericpgreen2 Apr 13, 2026
efd3837
Fix hard-coded local routes in `web-common` for cloud editing
ericpgreen2 Apr 13, 2026
991af64
Add loading and error states to file explorer and connector explorer
ericpgreen2 Apr 13, 2026
a771292
Add AI developer chat to cloud editor pages
ericpgreen2 Apr 13, 2026
a0354c8
feat: push new edit branch immediately
k-anshul Apr 14, 2026
fc3c572
Merge remote-tracking branch 'origin/main' into ericgreen/cloud-editi…
ericpgreen2 Apr 15, 2026
afcf6e3
Fix import
ericpgreen2 Apr 15, 2026
ca2e05b
Merge branch 'push_edit_branch' into ericgreen/cloud-editing-mvp
ericpgreen2 Apr 15, 2026
cabd293
fix: CI failures, restore dropped behavior, and add missing edit routes
ericpgreen2 Apr 15, 2026
518492d
Merge remote-tracking branch 'origin/main' into ericgreen/cloud-editi…
ericpgreen2 Apr 15, 2026
2b8f158
Push remaining DAG viewer code into `web-common`
ericpgreen2 Apr 15, 2026
3845f92
Clean up backend: extract retry helpers, fix branch permissions, add …
ericpgreen2 Apr 16, 2026
8d77609
Merge remote-tracking branch 'origin/main' into ericgreen/cloud-editi…
ericpgreen2 Apr 16, 2026
016535b
Fix test failures due to SSE manager changes
ericpgreen2 Apr 17, 2026
5596bb8
Merge remote-tracking branch 'origin/main' into ericgreen/cloud-editi…
ericpgreen2 Apr 17, 2026
c264dac
Move `edit-routing.ts` to `layout/navigation/editor-routing.ts`
ericpgreen2 Apr 17, 2026
6f57b91
Rename `editRoute` → `withEditorPrefix`, `editRoutePrefix` → `editorR…
ericpgreen2 Apr 17, 2026
1ea7e6e
Add CI check for edit route parity between `web-local` and `web-admin`
ericpgreen2 Apr 17, 2026
ea8949c
Mirror missing editing routes from `web-local` into `web-admin/-/edit/`
ericpgreen2 Apr 17, 2026
0ba70fc
Update stale `edit-routing` imports to `layout/navigation/editor-rout…
ericpgreen2 Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions admin/jobs/river/reconcile_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package river
import (
"context"
"errors"
"strings"
"time"

"github.com/jackc/pgx/v5"
"github.com/rilldata/rill/admin"
Expand All @@ -24,6 +26,12 @@ type ReconcileDeploymentWorker struct {
admin *admin.Service
}

// NextRetryAt uses exponential backoff starting at 15s: ~15s, ~30s, ~60s, ~120s.
// This keeps total retry duration under ~4 minutes so users aren't stuck waiting.
func (w *ReconcileDeploymentWorker) NextRetryAt(job *river.Job[ReconcileDeploymentArgs]) time.Time {
return time.Now().Add(15 * time.Second * time.Duration(1<<(job.Attempt-1)))
}

// NewReconcileDeploymentWorker creates a new ReconcileDeploymentWorker. Only to be used in tests to trigger the worker directly.
func NewReconcileDeploymentWorker(admin *admin.Service) *ReconcileDeploymentWorker {
return &ReconcileDeploymentWorker{
Expand Down Expand Up @@ -74,8 +82,10 @@ func (w *ReconcileDeploymentWorker) Work(ctx context.Context, job *river.Job[Rec
}

// Initialize the deployment (by provisioning a runtime and creating an instance on it)
err := w.admin.StartDeploymentInner(ctx, depl)
if err != nil {
if err := w.admin.StartDeploymentInner(ctx, depl); err != nil {
if w.isNonRetryable(err) || job.Attempt >= job.MaxAttempts {
return w.cancelAsErrored(ctx, depl.ID, err)
}
return err
}
}
Expand Down Expand Up @@ -115,12 +125,18 @@ func (w *ReconcileDeploymentWorker) Work(ctx context.Context, job *river.Job[Rec
// Delete the deployment and all its resources.
err := w.admin.StopDeploymentInner(ctx, depl)
if err != nil {
if job.Attempt >= job.MaxAttempts {
return w.cancelAsErrored(ctx, depl.ID, err)
}
return err
}

// Delete the deployment
err = w.admin.DB.DeleteDeployment(ctx, depl.ID)
if err != nil {
if job.Attempt >= job.MaxAttempts {
return w.cancelAsErrored(ctx, depl.ID, err)
}
return err
}

Expand Down Expand Up @@ -153,3 +169,20 @@ func (w *ReconcileDeploymentWorker) Work(ctx context.Context, job *river.Job[Rec

return nil
}

// isNonRetryable returns true for errors that won't resolve with retries,
// such as capacity limits or configuration errors.
func (w *ReconcileDeploymentWorker) isNonRetryable(err error) bool {
msg := err.Error()
return strings.Contains(msg, "no runtimes found with sufficient available slots") ||
strings.Contains(msg, "Invalid environment") ||
strings.Contains(msg, "not a valid version")
}

// cancelAsErrored marks the deployment as errored and cancels the river job.
func (w *ReconcileDeploymentWorker) cancelAsErrored(ctx context.Context, deplID string, err error) error {
if _, dbErr := w.admin.DB.UpdateDeploymentStatus(ctx, deplID, database.DeploymentStatusErrored, err.Error()); dbErr != nil {
w.admin.Logger.Error("reconcile deployment: failed to set errored status", observability.ZapCtx(ctx), zap.Error(dbErr))
}
return river.JobCancel(err)
}
2 changes: 1 addition & 1 deletion admin/jobs/river/river.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func (c *Client) ReconcileDeployment(ctx context.Context, deploymentID string) (
res, err := c.riverClient.Insert(ctx, ReconcileDeploymentArgs{
DeploymentID: deploymentID,
}, &river.InsertOpts{
MaxAttempts: 25, // Last retry, ~3 weeks after first run
MaxAttempts: 5, // Retries at ~15s, 30s, 60s, 120s (see NextRetryAt override)
UniqueOpts: river.UniqueOpts{
ByArgs: true,
ByState: []rivertype.JobState{
Expand Down
1 change: 1 addition & 0 deletions admin/server/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func (s *Server) GetDeployment(ctx context.Context, req *adminv1.GetDeploymentRe
}
if depl.Environment == "dev" {
instancePermissions = append(instancePermissions,
runtime.ReadInstance,
runtime.ReadOLAP,
runtime.ReadProfiling,
runtime.ReadRepo,
Expand Down
21 changes: 20 additions & 1 deletion admin/server/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (

const devDeplTTL = 6 * time.Hour

const devSlots = 8
const devSlots = 4

const prodDeplTTL = 14 * 24 * time.Hour

Expand Down Expand Up @@ -456,6 +456,24 @@ func (s *Server) GetProject(ctx context.Context, req *adminv1.GetProjectRequest)
runtime.EditTrigger,
)
}
// Grant permissions for branch deployments: viewers get dashboard access, editors get full dev access
if req.Branch != "" {
instancePermissions = append(
instancePermissions,
runtime.ReadInstance,
runtime.ReadOLAP,
runtime.ReadResolvers,
)
if permissions.ManageDev {
instancePermissions = append(
instancePermissions,
runtime.ReadProfiling,
runtime.ReadRepo,
runtime.EditRepo,
runtime.EditTrigger,
)
}
}

var systemPermissions []runtime.Permission
if req.IssueSuperuserToken {
Expand Down Expand Up @@ -2200,6 +2218,7 @@ func (s *Server) projToDTO(p *database.Project, orgName string) *adminv1.Project
Provisioner: p.Provisioner,
ProdVersion: p.ProdVersion,
ProdSlots: int64(p.ProdSlots),
DevSlots: int64(p.DevSlots),
PrimaryBranch: p.PrimaryBranch,
Subpath: p.Subpath,
GitRemote: safeStr(p.GitRemote),
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"local-test": "cd web-local && npx playwright test --headed --project=e2e-chrome -g",
"gen:colors": "node web-common/src/features/themes/gen-colors.ts",
"quality": "bash ./scripts/web-test-code-quality.sh",
"quality:ci": "FAIL_FAST=true bash ./scripts/web-test-code-quality.sh"
"quality:ci": "FAIL_FAST=true bash ./scripts/web-test-code-quality.sh",
"check:edit-route-parity": "node ./scripts/check-edit-route-parity.js"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
Expand Down
7 changes: 2 additions & 5 deletions runtime/drivers/admin/repo_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,7 @@ func (r *gitRepo) commitAndPushToPrimaryBranch(ctx context.Context, message stri
return fmt.Errorf("failed to open repository: %w", err)
}
_, err = r.commitAll(repo, message)
if err != nil {
if errors.Is(err, git.ErrEmptyCommit) {
return nil // No changes to commit
}
if err != nil && !errors.Is(err, git.ErrEmptyCommit) {
return fmt.Errorf("failed to commit changes: %w", err)
}

Expand Down Expand Up @@ -401,7 +398,7 @@ func (r *gitRepo) fetchCurrentBranch(ctx context.Context) error {
RemoteURL: r.remoteURL,
RefSpecs: []config.RefSpec{config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/remotes/origin/%s", head.Name().Short(), head.Name().Short()))},
})
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
if err != nil && !(errors.Is(err, git.NoErrAlreadyUpToDate) || git.NoMatchingRefSpecError{}.Is(err)) {
return err
}
return nil
Expand Down
2 changes: 2 additions & 0 deletions runtime/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ var defaultFeatureFlags = map[string]string{
"developer_agent": "true",
// Controls if the dashboard state is persisted when navigating to a different dashboard.
"sticky_dashboard_state": "false",
// Controls visibility of the cloud editing feature (Edit button and edit routes)
"cloud_editing": "false",
// Controls visibility of the custom chart option in canvas dashboards
"custom_charts": "false",
}
Expand Down
4 changes: 4 additions & 0 deletions runtime/feature_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"deploy": true,
"developerAgent": true,
"stickyDashboardState": false,
"cloudEditing": false,
"customCharts": false,
},
},
Expand All @@ -68,6 +69,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"deploy": true,
"developerAgent": true,
"stickyDashboardState": false,
"cloudEditing": false,
"customCharts": false,
},
},
Expand All @@ -93,6 +95,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"deploy": true,
"developerAgent": true,
"stickyDashboardState": false,
"cloudEditing": false,
"customCharts": false,
},
},
Expand All @@ -118,6 +121,7 @@ func Test_ResolveFeatureFlags(t *testing.T) {
"deploy": true,
"developerAgent": true,
"stickyDashboardState": false,
"cloudEditing": false,
"customCharts": false,
},
},
Expand Down
7 changes: 4 additions & 3 deletions runtime/server/auth/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ type jwtClaims struct {
var _ ClaimsProvider = (*jwtClaims)(nil)

func (c *jwtClaims) Claims(instanceID string) *runtime.SecurityClaims {
attrs := c.Attrs
if attrs == nil {
attrs = make(map[string]any)
// Copy attrs to avoid concurrent map writes when Claims is called from multiple goroutines.
attrs := make(map[string]any, len(c.Attrs)+1)
for k, v := range c.Attrs {
attrs[k] = v
}
if _, ok := attrs["id"]; !ok {
attrs["id"] = c.RegisteredClaims.Subject
Expand Down
54 changes: 37 additions & 17 deletions runtime/server/sse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"slices"
"strconv"
"strings"
"time"

runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1"
"github.com/rilldata/rill/runtime/pkg/observability"
Expand Down Expand Up @@ -296,29 +297,48 @@ func serveSSEUntilClose(w http.ResponseWriter, events chan *sseEvent) {
fmt.Fprint(w, ":ok\n\n")
flusher.Flush()

// Send periodic keep-alive comments to prevent proxies and load balancers
// from closing idle connections. SSE comments (lines starting with ':')
// are no-ops per the spec and ignored by clients.
keepAlive := time.NewTicker(30 * time.Second)
defer keepAlive.Stop()

// Consume events from channel and write to response (the loop ends when the channel is closed)
for ev := range events {
// Skip empty events
if ev == nil || len(ev.Data) == 0 {
continue
}
for {
select {
case ev, ok := <-events:
if !ok {
return
}
// Skip empty events
if ev == nil || len(ev.Data) == 0 {
continue
}

// Write the event
if ev.Event != "" {
_, err := fmt.Fprintf(w, "event: %s\n", ev.Event)
// Write the event
if ev.Event != "" {
_, err := fmt.Fprintf(w, "event: %s\n", ev.Event)
if err != nil {
return
}
}
_, err := fmt.Fprintf(w, "data: %s\n", ev.Data)
if err != nil {
return
}
_, err = fmt.Fprint(w, "\n")
if err != nil {
return
}
flusher.Flush()

case <-keepAlive.C:
_, err := fmt.Fprint(w, ":keepalive\n\n")
if err != nil {
return
}
flusher.Flush()
}
_, err := fmt.Fprintf(w, "data: %s\n", ev.Data)
if err != nil {
return
}
_, err = fmt.Fprint(w, "\n")
if err != nil {
return
}
flusher.Flush()
}
}

Expand Down
Loading
Loading