diff --git a/admin/jobs/river/reconcile_deployment.go b/admin/jobs/river/reconcile_deployment.go index c3d9fddc41a..0ef394a21fa 100644 --- a/admin/jobs/river/reconcile_deployment.go +++ b/admin/jobs/river/reconcile_deployment.go @@ -3,6 +3,8 @@ package river import ( "context" "errors" + "strings" + "time" "github.com/jackc/pgx/v5" "github.com/rilldata/rill/admin" @@ -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{ @@ -74,8 +82,14 @@ 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 { + // Check if this is a non-retryable error (fail fast instead of waiting for all retries) + if isNonRetryable(err) || job.Attempt >= job.MaxAttempts { + if _, dbErr := w.admin.DB.UpdateDeploymentStatus(ctx, depl.ID, 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) + } return err } } @@ -115,12 +129,24 @@ 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 { + if _, dbErr := w.admin.DB.UpdateDeploymentStatus(ctx, depl.ID, database.DeploymentStatusErrored, err.Error()); dbErr != nil { + w.admin.Logger.Error("reconcile deployment: failed to set errored status during deletion", observability.ZapCtx(ctx), zap.Error(dbErr)) + } + return river.JobCancel(err) + } return err } // Delete the deployment err = w.admin.DB.DeleteDeployment(ctx, depl.ID) if err != nil { + if job.Attempt >= job.MaxAttempts { + if _, dbErr := w.admin.DB.UpdateDeploymentStatus(ctx, depl.ID, database.DeploymentStatusErrored, err.Error()); dbErr != nil { + w.admin.Logger.Error("reconcile deployment: failed to set errored status during deletion", observability.ZapCtx(ctx), zap.Error(dbErr)) + } + return river.JobCancel(err) + } return err } @@ -153,3 +179,12 @@ 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 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") +} diff --git a/admin/jobs/river/river.go b/admin/jobs/river/river.go index 2b95e94cc29..ba211f968af 100644 --- a/admin/jobs/river/river.go +++ b/admin/jobs/river/river.go @@ -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{ diff --git a/admin/server/deployment.go b/admin/server/deployment.go index 79da9a168be..8ad6f3aee92 100644 --- a/admin/server/deployment.go +++ b/admin/server/deployment.go @@ -241,6 +241,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, @@ -1031,3 +1032,4 @@ func (s *Server) getAttributesForUser(ctx context.Context, orgID, projID, userID return attr, userID, forProjPerms.ReadProd, nil } + diff --git a/admin/server/projects.go b/admin/server/projects.go index 99618b65495..867f5a71af1 100644 --- a/admin/server/projects.go +++ b/admin/server/projects.go @@ -31,7 +31,7 @@ import ( const devDeplTTL = 6 * time.Hour -const devSlots = 8 +const devSlots = 4 const prodDeplTTL = 14 * 24 * time.Hour @@ -456,6 +456,13 @@ func (s *Server) GetProject(ctx context.Context, req *adminv1.GetProjectRequest) runtime.EditTrigger, ) } + if req.Branch != "" && permissions.ManageDev { + instancePermissions = append( + instancePermissions, + runtime.ReadRepo, + runtime.EditRepo, + ) + } var systemPermissions []runtime.Permission if req.IssueSuperuserToken { @@ -2178,6 +2185,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), diff --git a/package-lock.json b/package-lock.json index 1f359b299f3..68f610e67da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,7 @@ "version": "1.88.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -249,6 +250,7 @@ "integrity": "sha512-y1IOpG6OSmTpGg/CT0YBb/EAhR2nsC18QWp9Jy8HO9iGySpcwaTvs5kHa17daP3BMTwWyaX9/1tDTDQshZzXdg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.49.2", "@algolia/requester-browser-xhr": "5.49.2", @@ -438,6 +440,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -545,6 +548,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2366,7 +2370,8 @@ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", "dev": true, - "license": "(Apache-2.0 AND BSD-3-Clause)" + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.1.2", @@ -2570,6 +2575,7 @@ "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -2585,6 +2591,7 @@ "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", @@ -2623,6 +2630,7 @@ "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -2633,6 +2641,7 @@ "integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", @@ -2657,6 +2666,7 @@ "integrity": "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w==", "dev": true, "license": "Apache-2.0", + "peer": true, "peerDependencies": { "@bufbuild/protobuf": "^1.10.0" } @@ -2784,6 +2794,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2807,6 +2818,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2921,6 +2933,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3358,6 +3371,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -4210,6 +4224,7 @@ "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/babel": "3.9.2", "@docusaurus/bundler": "3.9.2", @@ -4599,6 +4614,7 @@ "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4878,6 +4894,7 @@ "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/mdx-loader": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2", @@ -5030,6 +5047,7 @@ "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/logger": "3.9.2", "@docusaurus/types": "3.9.2", @@ -5245,7 +5263,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" @@ -5258,7 +5275,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -5270,7 +5286,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -5312,7 +5327,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -5330,7 +5344,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -5348,7 +5361,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -5366,7 +5378,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -5384,7 +5395,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -5402,7 +5412,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -5420,7 +5429,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5438,7 +5446,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5456,7 +5463,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5474,7 +5480,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5492,7 +5497,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5510,7 +5514,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5528,7 +5531,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5546,7 +5548,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5564,7 +5565,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5582,7 +5582,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5600,7 +5599,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -5618,7 +5616,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5636,7 +5633,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5654,7 +5650,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5672,7 +5667,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -5690,7 +5684,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -5708,7 +5701,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -5726,7 +5718,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -5744,7 +5735,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -5762,7 +5752,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -6263,6 +6252,7 @@ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz", "integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/helpers": "^0.5.0" } @@ -6942,7 +6932,8 @@ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@lezer/css": { "version": "1.3.3", @@ -7096,6 +7087,7 @@ "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -7125,7 +7117,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", @@ -7193,6 +7184,7 @@ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -8894,7 +8886,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -8912,7 +8903,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -8930,7 +8920,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -8948,7 +8937,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -8966,7 +8954,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -8984,7 +8971,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9002,7 +8988,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9020,7 +9005,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9038,7 +9022,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9056,7 +9039,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9074,7 +9056,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9092,7 +9073,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9107,7 +9087,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, @@ -9128,7 +9107,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9146,7 +9124,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -9546,6 +9523,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9667,6 +9645,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9807,6 +9786,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9931,6 +9911,7 @@ "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -9973,6 +9954,7 @@ "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", @@ -10156,6 +10138,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -10539,6 +10522,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.5.tgz", "integrity": "sha512-Pkjd41UJ4F6Z8cPV+gEvqnt1VhY2g66xMjbpxREs0ECA5jRezCNKSZcc2pueQRTMtmn1SaSzGM9U/ifhVlVYOA==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -10625,6 +10609,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.5.tgz", "integrity": "sha512-yJhDa7Chx2EqJMX/jlewBv0za7slf1dKHWYve1XaApuVHEkxl0Ul3EDbwnx316vIITkuFW/pWSwkSsAplyBeCw==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -10655,6 +10640,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.20.5.tgz", "integrity": "sha512-5fqRNgnzYdJ1oDpyLqwrbVsZwvI+5VW/U89LPMvBYM7sFS7Xd0xfyxyAOWcJN4V0zLeTcuElWN3R+IUTLKbU+Q==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -10671,7 +10657,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -11374,6 +11359,7 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -11635,6 +11621,7 @@ "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", @@ -11674,6 +11661,7 @@ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", @@ -12322,6 +12310,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -12405,6 +12394,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12474,6 +12464,7 @@ "integrity": "sha512-1K0wtDaRONwfhL4h8bbJ9qTjmY6rhGgRvvagXkMBsAOMNr+3Q2SffHECh9DIuNVrMA1JwA0zCwhyepgBZVakng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.15.2", "@algolia/client-abtesting": "5.49.2", @@ -13539,6 +13530,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -14006,6 +13998,7 @@ "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", @@ -14758,6 +14751,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -15062,6 +15056,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15431,6 +15426,7 @@ "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -15740,7 +15736,6 @@ "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", "license": "ISC", - "peer": true, "dependencies": { "commander": "7", "d3-array": "1 - 3", @@ -15762,7 +15757,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 10" } @@ -15913,6 +15907,7 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -17281,6 +17276,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -17389,6 +17385,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -21143,6 +21140,7 @@ "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -21560,7 +21558,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21582,7 +21579,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21604,7 +21600,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21626,7 +21621,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21648,7 +21642,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21670,7 +21663,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21692,7 +21684,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21714,7 +21705,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21736,7 +21726,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21758,7 +21747,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21780,7 +21768,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -24162,6 +24149,7 @@ "integrity": "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -25071,6 +25059,7 @@ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -25948,6 +25937,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -26608,6 +26598,7 @@ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" @@ -27056,6 +27047,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -27736,6 +27728,7 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -28067,6 +28060,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", "license": "MIT", + "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -28096,6 +28090,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -28144,6 +28139,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -28472,6 +28468,7 @@ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -28485,6 +28482,7 @@ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -28546,6 +28544,7 @@ "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -28576,6 +28575,7 @@ "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -29853,6 +29853,7 @@ "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", @@ -29975,6 +29976,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -31331,6 +31333,7 @@ "integrity": "sha512-hFR6xsVkVYbsdcUlzPYFvFfoc6o2KlV0VvgRIQwSYMtdThM7SCxnjX9efh/cWce2kTq16I/Kl3xM98xiLptsXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.4.0", "@emotion/unitless": "0.10.0", @@ -31488,6 +31491,7 @@ "integrity": "sha512-SThllKq6TRMBwPtat7ASnm/9CDXnIhBR0NPGw0ujn2DVYx9rVwsPZxDaDQcYGdUz/3BYVsCzdq7pZarRQoGvtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -32039,6 +32043,7 @@ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -32577,7 +32582,6 @@ "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", "license": "ISC", - "peer": true, "dependencies": { "commander": "2" }, @@ -32591,8 +32595,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/toposort": { "version": "2.0.2", @@ -32844,7 +32847,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.21.0", @@ -32852,6 +32856,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -32877,7 +32882,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -33050,6 +33054,7 @@ "integrity": "sha512-NTWTUOFRQ9+SGKKTuWKUioUkjxNwtS3JDRPVKZAXGHZy2wCA8bdv2iJiyeePn0xkmK+TCCqZFT0X7+2+FLjngA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@gerrit0/mini-shiki": "^3.23.0", "lunr": "^2.3.9", @@ -33100,6 +33105,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -33811,15 +33817,13 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.7.tgz", "integrity": "sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/vega-crossfilter": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.1.4.tgz", "integrity": "sha512-5E/i60Y80CUt+4O+U89X8ZnauaFuq0ztq/Hx4i4sT/crk4Lfn1oplRAjNOo7dFEZ04TahyBbYiJKIhR0yvmHpw==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "vega-dataflow": "^5.7.8", @@ -33831,7 +33835,6 @@ "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.7.8.tgz", "integrity": "sha512-jrllcIjSYU5Jh130RDR44o/SbUbJndLuoiM9IsKWW+a7HayKnfmbdHWm7MvCrj/YLupFZVojRaS1tTs53EXTdA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-format": "^1.1.4", "vega-loader": "^4.5.4", @@ -33843,6 +33846,7 @@ "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-7.1.0.tgz", "integrity": "sha512-ZmEIn5XJrQt7fSh2lwtSdXG/9uf3yIqZnvXFEwBJRppiBgrEWZcZbj6VK3xn8sNTFQ+sQDXW5sl/6kmbAW3s5A==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "fast-json-patch": "^3.1.1", "json-stringify-pretty-compact": "^4.0.0", @@ -33866,7 +33870,6 @@ "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.10.3.tgz", "integrity": "sha512-245ebBuN1TjwVVDFG7JZaZ/ExLAhZ4kDTK2zlIoXs/g2P5rRgL4Q6oRixr+Pgr43G7ISCEHrUBgUjRcSoG8eEA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-interpolate": "^3.0.1", @@ -33896,7 +33899,6 @@ "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.2.3.tgz", "integrity": "sha512-7Yp1uOPy3eyzK/hnAIU0v/yQ9mIhtoJ+tq8AMaZiWqy67SUYsE3olmgdllY6R9M81D/n/rv8K+8JjbHMU5/sUA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-force": "^3.0.0", "vega-dataflow": "^5.7.8", @@ -33908,7 +33910,6 @@ "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.4.tgz", "integrity": "sha512-+oz6UvXjQSbweW9P8q+1o2qFYyBYPFax94j6a9PQMnCIWMovFSss1wEElljOT8CEpnHyS15yiGlmz4qbWTQwnQ==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-format": "^3.1.0", @@ -33922,7 +33923,6 @@ "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.18.1.tgz", "integrity": "sha512-qEBAbo0jxGGebRvbX1zmxzmjwFz8/UtncRhzwk9/KcI0WudULNmCM1iTu+DGFRnNHdcKi6kUlwJBPIp7zDu3HQ==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-color": "^3.1.0", @@ -33942,7 +33942,6 @@ "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.4.4.tgz", "integrity": "sha512-jTLYrDvhXJinWQCfuVLP1nSlOdFlA9blVS6K7uOcBTJ4382Aw3ALGZKluUQyJkFdrkGfqxoDJo5MLHLu2zlc6g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-color": "^3.1.0", @@ -33959,7 +33958,6 @@ "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.1.4.tgz", "integrity": "sha512-/iSh1YqdgsHFB20QxJ6IAVzRZQBKuMpZ/GsN5sZDP3bBJdVWl3we48L/r6w7FP9NU+JzFHcDUPJ0S0UzpMhaqg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-hierarchy": "^3.1.2", "vega-dataflow": "^5.7.8", @@ -33986,7 +33984,6 @@ "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-1.3.2.tgz", "integrity": "sha512-YlUCUZNsp1FHkpPjOpD+aVVmVLFw6xa+bFQGBCECuY1DuRyN6l8oCRmUOjBrr70ip8fbqfGk+czHw543J1PJgg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-canvas": "^1.2.7", "vega-dataflow": "^5.7.8", @@ -33999,6 +33996,7 @@ "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-5.23.0.tgz", "integrity": "sha512-l4J6+AWE3DIjvovEoHl2LdtCUkfm4zs8Xxx7INwZEAv+XVb6kR6vIN1gt3t2gN2gs/y4DYTs/RPoTeYAuEg6mA==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "json-stringify-pretty-compact": "~4.0.0", "tslib": "~2.8.1", @@ -34035,7 +34033,6 @@ "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.4.tgz", "integrity": "sha512-AOJPsDVz009aTdD9hzigUaO/NFmuN1o83rzvZu/g37TJfhU+3DOvgnO0rnqJbnSOfcBkLWER6XghlKS3j77w4A==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-dsv": "^3.0.1", "node-fetch": "^2.6.7", @@ -34049,7 +34046,6 @@ "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-6.6.1.tgz", "integrity": "sha512-FIez+huStzgjsxLqOkxDAooTkDC2XruYCIFWhd3HuM/QrqKtj9JKGuIUJjpVVkE7by7S5ejrucpRzVVgQfbzSg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-dataflow": "^5.7.8", "vega-event-selector": "^3.0.1", @@ -34063,7 +34059,6 @@ "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.6.3.tgz", "integrity": "sha512-vusyaPi3EFIHrBVs0chNv2bCqxw4X+XgI1m3+yOgUD/dutsIGTcqy+PjlNlspPQvMI9JjTeIUTGt8u1V7oDwAA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-geo": "^3.1.0", "d3-geo-projection": "^4.0.0", @@ -34075,7 +34070,6 @@ "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.3.2.tgz", "integrity": "sha512-DtGToopuJGmxeoymaOXTDKlWkkYiDVvZFV1IWQNuRlChTBI6YBpil4WmA3U+ECePDQuk2+/mWlw+vafrbgF8Tg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "vega-dataflow": "^5.7.8", @@ -34088,7 +34082,6 @@ "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-6.2.2.tgz", "integrity": "sha512-5c+i7y5XBw5phIA1mBeqF/gtOQcDjUzroUAQ0g3y3weNx0K4mzj7V360yZiBLNcuHv65xgTlijstbKQttxeY/g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-dataflow": "^5.7.8", "vega-util": "^1.17.4" @@ -34099,7 +34092,6 @@ "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.4.3.tgz", "integrity": "sha512-f7SSN2YJowtrdkt7nJIR6YYhjDk8oB37q5So2/OxXQv5CBHipFPQSHS1ZVw9vD3V5wLnrZCxC4Ji27gmsTefgA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-interpolate": "^3.0.1", @@ -34114,7 +34106,6 @@ "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.13.2.tgz", "integrity": "sha512-eCutgcLzdUg23HLc6MTZ9pHCdH0hkqSmlbcoznspwT0ajjATk6M09JNyJddiaKR55HuQo03mBWsPeRCd5kOi0g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-path": "^3.1.0", "d3-shape": "^3.2.0", @@ -34135,7 +34126,6 @@ "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.6.3.tgz", "integrity": "sha512-DXd+XVKcIjBAtSCcgtPx7cXuqG/7L98SWoFh6GKNu26EBUyn3zm0GAlZxNLPoI01Jz9Fb3YpSsewk2aIAbM68g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "3.2.4", "vega-expression": "^5.2.1", @@ -34147,7 +34137,6 @@ "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.9.0.tgz", "integrity": "sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2" } @@ -34170,7 +34159,6 @@ "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.4.tgz", "integrity": "sha512-DBMRps5myYnSAlvQ+oiX8CycJZjGQNqyGE04xaZrpOgHll7vlvezpET2FnGZC7wS3DsqMcPjnpnI1h7+qJox1Q==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-time": "^3.1.0", @@ -34200,7 +34188,6 @@ "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.12.2.tgz", "integrity": "sha512-VuNLzB0DavFn5GIkFvR2XvwPavjY7VXl2oPUOFvNgSfyQywmCoC7VOX+iHeOdxoZdoy+XEOGAqRs9imxVo2HVA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "vega-dataflow": "^5.7.8", @@ -34232,7 +34219,6 @@ "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.16.1.tgz", "integrity": "sha512-YyG4i2JDkRCacHd9xNAwyov2cELxwzPGY224PA2o0gEhkoOjPkVElTmo9QHJvKeVxMoyad6wvoq+Jj+TB6zhLw==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-array": "^3.2.2", "d3-timer": "^3.0.1", @@ -34249,7 +34235,6 @@ "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.6.2.tgz", "integrity": "sha512-udnYutgX7T7E0crqKWSs4hcxTdUmZHR2F4rKj0+3nN9+CfYMWbGgUkCkP2pMu7j2OH0ETX2uDn5xL8+YJWeXSg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-dataflow": "^5.7.8", "vega-scenegraph": "^4.13.2", @@ -34261,7 +34246,6 @@ "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.2.5.tgz", "integrity": "sha512-u0TLSQboiLyoM6V9S0E6cc77EfWati+ApZ6zE+NhH3h/zZRzYZmElnDn46hUof2o9jNyh09xFXTXykVHmt7IGg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "d3-delaunay": "^6.0.2", "vega-dataflow": "^5.7.8", @@ -34273,7 +34257,6 @@ "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.1.7.tgz", "integrity": "sha512-xdMykDXdWEp3/7Hil7Yx8uNVmjvqxwGibHJLSfiubNNO9OErrH3ZUgcxWv5hLe9wdK+KSGP94SSqTOAaqH7r/A==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "vega-canvas": "^1.2.7", "vega-dataflow": "^5.7.8", @@ -34333,6 +34316,7 @@ "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", @@ -34416,7 +34400,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -34667,6 +34650,7 @@ "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -35807,6 +35791,7 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/runtime/drivers/admin/repo_git.go b/runtime/drivers/admin/repo_git.go index ee874d4f011..61e08ac0abd 100644 --- a/runtime/drivers/admin/repo_git.go +++ b/runtime/drivers/admin/repo_git.go @@ -249,10 +249,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) } diff --git a/runtime/server/auth/claims.go b/runtime/server/auth/claims.go index 347dc55fb4f..737299acbd0 100644 --- a/runtime/server/auth/claims.go +++ b/runtime/server/auth/claims.go @@ -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 } attrs["id"] = c.RegisteredClaims.Subject diff --git a/runtime/server/sse.go b/runtime/server/sse.go index 0b99787ea51..4dfdf805b18 100644 --- a/runtime/server/sse.go +++ b/runtime/server/sse.go @@ -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" @@ -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() } } diff --git a/web-admin/src/features/edit-session/EditActions.svelte b/web-admin/src/features/edit-session/EditActions.svelte new file mode 100644 index 00000000000..e5161984292 --- /dev/null +++ b/web-admin/src/features/edit-session/EditActions.svelte @@ -0,0 +1,78 @@ + + + + + + Return to project home + + + diff --git a/web-admin/src/features/edit-session/EditButton.svelte b/web-admin/src/features/edit-session/EditButton.svelte new file mode 100644 index 00000000000..3ecdaf38ffd --- /dev/null +++ b/web-admin/src/features/edit-session/EditButton.svelte @@ -0,0 +1,249 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + + {#if hasOwnSessions} + + Your branches + + {#each ownDeployments as deployment (deployment.id)} + + + + {deployment.branch || "main"} + + + {/each} + + + + {#if showNewBranchInput} + + +
e.stopPropagation()} + > + { + e.stopPropagation(); + handleKeydown(e); + }} + placeholder="branch-name" + autofocus + /> + +
+ {:else} + + + {/if} + {:else} + + Create a branch + + + +
e.stopPropagation()} + > + { + e.stopPropagation(); + handleKeydown(e); + }} + placeholder="branch-name" + autofocus + /> + +
+ {/if} +
+
+ + diff --git a/web-admin/src/features/edit-session/EditSessionLoading.svelte b/web-admin/src/features/edit-session/EditSessionLoading.svelte new file mode 100644 index 00000000000..cea56bd45f9 --- /dev/null +++ b/web-admin/src/features/edit-session/EditSessionLoading.svelte @@ -0,0 +1,43 @@ + + +
+
+ +

{getHeading(status)}

+
+ +
+ + diff --git a/web-admin/src/features/edit-session/EditSessionTimeoutBanner.svelte b/web-admin/src/features/edit-session/EditSessionTimeoutBanner.svelte new file mode 100644 index 00000000000..434bfc0fbdc --- /dev/null +++ b/web-admin/src/features/edit-session/EditSessionTimeoutBanner.svelte @@ -0,0 +1,54 @@ + + +{#if showWarning} + +{/if} + + diff --git a/web-admin/src/features/edit-session/use-edit-session.ts b/web-admin/src/features/edit-session/use-edit-session.ts new file mode 100644 index 00000000000..a974ae1c400 --- /dev/null +++ b/web-admin/src/features/edit-session/use-edit-session.ts @@ -0,0 +1,76 @@ +import { + createAdminServiceCreateDeployment, + createAdminServiceListDeployments, + getAdminServiceListDeploymentsQueryKey, + V1DeploymentStatus, + type V1Deployment, +} from "@rilldata/web-admin/client"; +import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient"; +import { derived } from "svelte/store"; + +/** + * Lists all deployments for a project (no polling). + * + * Uses an empty params object (`{}`) so the TanStack Query cache key matches + * the BranchSelector's query. This avoids duplicate ListDeployments requests + * when both components are mounted on the same page; callers filter to dev + * deployments client-side. + * + * Freshness is maintained by: + * - BranchSelector polling at 2s while its dropdown is open + * - invalidateDeployments() called after create/delete mutations + */ +export function useAllDeployments(org: string, project: string) { + return createAdminServiceListDeployments(org, project, {}); +} + +/** + * Lists dev deployments for a project. Shares the same underlying query as + * useAllDeployments (and BranchSelector) to avoid duplicate network requests. + */ +export function useDevDeployments(org: string, project: string) { + const allQuery = useAllDeployments(org, project); + return derived(allQuery, ($query) => ({ + ...$query, + data: $query.data + ? { + ...$query.data, + deployments: $query.data.deployments?.filter( + (d) => d.environment === "dev", + ), + } + : $query.data, + })); +} + +/** + * Mutation to create a dev deployment with editable=true. + */ +export function useCreateDevDeployment() { + return createAdminServiceCreateDeployment({ + mutation: { + onSuccess: (_data, variables) => { + void invalidateDeployments(variables.org, variables.project); + }, + }, + }); +} + +/** + * Invalidates all deployment queries for a project, triggering a refetch. + * Uses the base key (no params) so it matches both dev-scoped and + * unscoped queries (e.g., BranchSelector). + */ +export function invalidateDeployments(org: string, project: string) { + return queryClient.invalidateQueries({ + queryKey: getAdminServiceListDeploymentsQueryKey(org, project), + }); +} + +export function isActiveDeployment(d: V1Deployment): boolean { + return ( + d.status === V1DeploymentStatus.DEPLOYMENT_STATUS_PENDING || + d.status === V1DeploymentStatus.DEPLOYMENT_STATUS_RUNNING || + d.status === V1DeploymentStatus.DEPLOYMENT_STATUS_UPDATING + ); +} diff --git a/web-admin/src/features/navigation/nav-utils.ts b/web-admin/src/features/navigation/nav-utils.ts index 42e0db4d0fb..f827dae22a1 100644 --- a/web-admin/src/features/navigation/nav-utils.ts +++ b/web-admin/src/features/navigation/nav-utils.ts @@ -20,7 +20,8 @@ export function isProjectPage(page: Page): boolean { routeId === "/[organization]/[project]" || (routeId.startsWith("/[organization]/[project]/-/") && !routeId.startsWith("/[organization]/[project]/-/invite") && - !routeId.startsWith("/[organization]/[project]/-/share")) + !routeId.startsWith("/[organization]/[project]/-/share") && + !routeId.startsWith("/[organization]/[project]/-/edit")) ); } @@ -92,6 +93,10 @@ export function isPublicAlertPage(page: Page): boolean { ); } +export function isEditPage(page: Page): boolean { + return !!page.route?.id?.startsWith("/[organization]/[project]/-/edit"); +} + export function isProjectRequestAccessPage(page: Page): boolean { return !!page.route.id?.startsWith( "/[organization]/[project]/-/request-access", diff --git a/web-admin/src/features/projects/ProjectHeader.svelte b/web-admin/src/features/projects/ProjectHeader.svelte index 796109a0aab..dc83ca0c1d6 100644 --- a/web-admin/src/features/projects/ProjectHeader.svelte +++ b/web-admin/src/features/projects/ProjectHeader.svelte @@ -1,5 +1,6 @@ {#if hasResourceErrorsLoading || projectParserLoading} diff --git a/web-admin/src/routes/[organization]/-/upgrade-callback/+page.svelte b/web-admin/src/routes/[organization]/-/upgrade-callback/+page.svelte index 2f4999a0ba4..437117d0496 100644 --- a/web-admin/src/routes/[organization]/-/upgrade-callback/+page.svelte +++ b/web-admin/src/routes/[organization]/-/upgrade-callback/+page.svelte @@ -20,8 +20,7 @@ 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 Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; - import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; + import LoadingSpinner from "@rilldata/web-common/components/LoadingSpinner.svelte"; import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus"; import { onMount } from "svelte"; import type { PageData } from "./$types"; @@ -103,9 +102,7 @@ -
- -
+ {#if cancelled} Renewing team plan... diff --git a/web-admin/src/routes/[organization]/[project]/+layout.svelte b/web-admin/src/routes/[organization]/[project]/+layout.svelte index 0047c1378cf..115213ad66e 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/+layout.svelte @@ -28,6 +28,7 @@ getAdminServiceListDeploymentsQueryKey, } from "@rilldata/web-admin/client"; import { + isEditPage, isProjectPage, isPublicAlertPage, isPublicReportPage, @@ -77,6 +78,7 @@ }); let onProjectPage = $derived(isProjectPage(page)); + let onEditPage = $derived(isEditPage(page)); let onPublicURLPage = $derived(isPublicURLPage(page)); // From root layout; passed through to header components @@ -114,13 +116,16 @@ * `GetProject` with default cookie-based auth. * When `activeBranch` is set, the branch param is passed so the API * returns the branch deployment instead of production. + * + * On the edit page, the edit layout manages its own readiness detection, + * so we skip aggressive polling here to avoid unnecessary requests. */ let cookieProjectQuery = $derived( createAdminServiceGetProject( organization, project, activeBranch ? { branch: activeBranch } : undefined, - { query: baseGetProjectQueryOptions }, + { query: onEditPage ? {} : baseGetProjectQueryOptions }, ), ); @@ -257,7 +262,10 @@ body={error.response.data?.message} /> {:else if projectData} - {#if isProjectAvailable && runtime.host != null && runtime.instanceId} + {#if onEditPage} + + {@render children()} + {:else if isProjectAvailable && runtime.host != null && runtime.instanceId} {#key `${runtime.host}::${runtime.instanceId}`} diff --git a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte index 26dc34c1fb6..0e262c9ca41 100644 --- a/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/alerts/[alert]/open/+page.svelte @@ -4,8 +4,7 @@ import CtaContentContainer from "@rilldata/web-common/components/calls-to-action/CTAContentContainer.svelte"; import CtaLayoutContainer from "@rilldata/web-common/components/calls-to-action/CTALayoutContainer.svelte"; import CtaMessage from "@rilldata/web-common/components/calls-to-action/CTAMessage.svelte"; - import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; - import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; + import LoadingSpinner from "@rilldata/web-common/components/LoadingSpinner.svelte"; import { mapQueryToDashboard } from "@rilldata/web-common/features/explore-mappers/map-to-explore"; import { getExplorePageUrlSearchParams } from "@rilldata/web-common/features/explore-mappers/utils"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; @@ -86,8 +85,8 @@ {#if loading} -
- +
+
{:else if $dashboardStateForAlert.error}
diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte new file mode 100644 index 00000000000..e58b8b6df09 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+layout.svelte @@ -0,0 +1,245 @@ + + +
+ {#if isOtherOwner} + + + +

+ 403 +

+

+ This editing session belongs to another user +

+ You can preview this branch in read-only mode. + + Preview this branch + +
+
+ {:else if isReady && deployment?.id && instanceId && runtimeHost && jwt} + {#key `${runtimeHost}::${instanceId}`} + + + + +
+ +
+ +
+
+
+
+ {/key} + {:else if isErrored} + + + {:else if isStopped && deployment?.id} + + (starting = true)} + /> + {:else if isLoading} + + {:else} + + + {/if} +
+ + diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/+page.svelte new file mode 100644 index 00000000000..cc25d299c38 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/+page.svelte @@ -0,0 +1,7 @@ + + +
+ +
diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.svelte new file mode 100644 index 00000000000..fa0a6639207 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.svelte @@ -0,0 +1,24 @@ + + + + Rill | {canvasName} + + +{#key client.instanceId} +
+ + + +
+{/key} diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.ts b/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.ts new file mode 100644 index 00000000000..5eac70894c4 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/canvas/[name]/+page.ts @@ -0,0 +1,5 @@ +export const load = ({ params }) => { + const canvasName = params.name; + + return { canvasName }; +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.svelte new file mode 100644 index 00000000000..41c411e6baa --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.svelte @@ -0,0 +1,48 @@ + + + + Rill | {exploreName} + + +{#if measures.length === 0} + +{:else if metricsViewName} +
+ {#key exploreName} + + + + + + {/key} +
+{/if} diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.ts b/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.ts new file mode 100644 index 00000000000..2125d3ec9b1 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/explore/[name]/+page.ts @@ -0,0 +1,5 @@ +export const load = async ({ params }) => { + return { + exploreName: params.name, + }; +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.svelte new file mode 100644 index 00000000000..7cf7b8c603b --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.svelte @@ -0,0 +1,19 @@ + + + diff --git a/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.ts b/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.ts new file mode 100644 index 00000000000..687c02746a6 --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/edit/files/[...file]/+page.ts @@ -0,0 +1,24 @@ +import { addLeadingSlash } from "@rilldata/web-common/features/entity-management/entity-mappers.js"; +import { fileArtifacts } from "@rilldata/web-common/features/entity-management/file-artifacts.js"; +import { error } from "@sveltejs/kit"; + +export const load = async ({ params: { file } }) => { + const path = addLeadingSlash(file); + const fileArtifact = fileArtifacts.getFileArtifact(path); + + if (fileArtifact.fileTypeUnsupported) { + throw error( + 400, + "No renderer available for file type: " + fileArtifact.fileExtension, + ); + } + + // Don't eagerly fetch content here. Unlike web-local, the runtime + // credentials aren't available until the edit layout fetches them + // asynchronously. The workspace components will fetch content + // reactively once the runtime is ready. + + return { + fileArtifact, + }; +}; diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte index 40d37547ab8..9ee1995227b 100644 --- a/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/reports/[report]/open/+page.svelte @@ -5,8 +5,7 @@ import CtaLayoutContainer from "@rilldata/web-common/components/calls-to-action/CTALayoutContainer.svelte"; import CtaMessage from "@rilldata/web-common/components/calls-to-action/CTAMessage.svelte"; import type { ExploreState } from "@rilldata/web-common/features/dashboards/stores/explore-state"; - import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; - import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; + import LoadingSpinner from "@rilldata/web-common/components/LoadingSpinner.svelte"; import { mapQueryToDashboard } from "@rilldata/web-common/features/explore-mappers/map-to-explore"; import { getExplorePageUrlSearchParams } from "@rilldata/web-common/features/explore-mappers/utils"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; @@ -89,8 +88,8 @@ {#if $dashboardStateForReport.isLoading} -
- +
+
{:else if $dashboardStateForReport.error}
diff --git a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.svelte index 07a8d2e9741..71580c82ae1 100644 --- a/web-admin/src/routes/[organization]/[project]/-/settings/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/settings/+layout.svelte @@ -2,11 +2,14 @@
- +
diff --git a/web-common/src/components/LoadingPage.svelte b/web-common/src/components/LoadingPage.svelte index 87439f01a13..a6da30f8886 100644 --- a/web-common/src/components/LoadingPage.svelte +++ b/web-common/src/components/LoadingPage.svelte @@ -1,14 +1,13 @@ -
- +
+
diff --git a/web-common/src/components/LoadingSpinner.svelte b/web-common/src/components/LoadingSpinner.svelte new file mode 100644 index 00000000000..f474be43799 --- /dev/null +++ b/web-common/src/components/LoadingSpinner.svelte @@ -0,0 +1,8 @@ + + +
+ +
diff --git a/web-common/src/components/editor/line-status/line-number-gutter.svelte.ts b/web-common/src/components/editor/line-status/line-number-gutter.svelte.ts index e963ed48517..d506eaf8b8d 100644 --- a/web-common/src/components/editor/line-status/line-number-gutter.svelte.ts +++ b/web-common/src/components/editor/line-status/line-number-gutter.svelte.ts @@ -93,11 +93,9 @@ export const createLineNumberGutter = () => ); }, lineMarkerChange(update) { - return update.transactions.some((tr) => { - const effectPresent = tr.effects.some((effect) => - effect.is(updateLineStatuses), - ); - return effectPresent || update; - }); + if (update.docChanged || update.selectionSet) return true; + return update.transactions.some((tr) => + tr.effects.some((effect) => effect.is(updateLineStatuses)), + ); }, }); diff --git a/web-common/src/features/connectors/connectors-utils.ts b/web-common/src/features/connectors/connectors-utils.ts index 166faba0632..17bd3a0c249 100644 --- a/web-common/src/features/connectors/connectors-utils.ts +++ b/web-common/src/features/connectors/connectors-utils.ts @@ -1,3 +1,4 @@ +import { workspaceRoute } from "@rilldata/web-common/features/workspaces/workspace-routing"; import type { V1AnalyzedConnector } from "../../runtime-client"; export const OLAP_DRIVERS_WITHOUT_MODELING = ["clickhouse", "druid", "pinot"]; @@ -87,21 +88,35 @@ export function makeTablePreviewHref( ): string | null { switch (driver) { case "clickhouse": - return `/connector/clickhouse/${connectorName}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/clickhouse/${connectorName}/${databaseSchema}/${table}`, + ); case "druid": - return `/connector/druid/${connectorName}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/druid/${connectorName}/${databaseSchema}/${table}`, + ); case "duckdb": - return `/connector/duckdb/${connectorName}/${database}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/duckdb/${connectorName}/${database}/${databaseSchema}/${table}`, + ); case "snowflake": - return `/connector/snowflake/${connectorName}/${database}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/snowflake/${connectorName}/${database}/${databaseSchema}/${table}`, + ); case "bigquery": - return `/connector/bigquery/${connectorName}/${database}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/bigquery/${connectorName}/${database}/${databaseSchema}/${table}`, + ); case "redshift": - return `/connector/redshift/${connectorName}/${database}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/redshift/${connectorName}/${database}/${databaseSchema}/${table}`, + ); case "athena": - return `/connector/athena/${connectorName}/${database}/${databaseSchema}/${table}`; + return workspaceRoute( + `/connector/athena/${connectorName}/${database}/${databaseSchema}/${table}`, + ); case "pinot": - return `/connector/pinot/${connectorName}/${table}`; + return workspaceRoute(`/connector/pinot/${connectorName}/${table}`); default: return null; } diff --git a/web-common/src/features/connectors/explorer/TableMenuItems.svelte b/web-common/src/features/connectors/explorer/TableMenuItems.svelte index 617c71e3987..7b56ffca46b 100644 --- a/web-common/src/features/connectors/explorer/TableMenuItems.svelte +++ b/web-common/src/features/connectors/explorer/TableMenuItems.svelte @@ -1,6 +1,6 @@ -
- -
+ Hang tight! We're building your dashboard{multipleDashboards ? "s" diff --git a/web-common/src/features/dashboards/state-managers/loaders/DashboardLoading.svelte b/web-common/src/features/dashboards/state-managers/loaders/DashboardLoading.svelte index f64ca7ba66c..8cbb0948567 100644 --- a/web-common/src/features/dashboards/state-managers/loaders/DashboardLoading.svelte +++ b/web-common/src/features/dashboards/state-managers/loaders/DashboardLoading.svelte @@ -1,6 +1,5 @@ diff --git a/web-common/src/features/entity-management/FileAndResourceWatcher.svelte b/web-common/src/features/entity-management/FileAndResourceWatcher.svelte index c500eeabe7b..aee1b3e3589 100644 --- a/web-common/src/features/entity-management/FileAndResourceWatcher.svelte +++ b/web-common/src/features/entity-management/FileAndResourceWatcher.svelte @@ -17,6 +17,9 @@ export let host: string; export let instanceId: string; + export let errorBody = "Try restarting the Rill via the CLI"; + /** Keep the SSE connection open indefinitely (no auto-close on idle). */ + export let keepAlive = false; // Set client synchronously so children can access it during initial render. // init() (in onMount) handles the async resource prefetch. @@ -32,15 +35,23 @@ $: status = $statusStore; onMount(() => { + if (keepAlive) { + fileAndResourceWatcher.disableAutoClose(); + } void fileArtifacts.init(runtimeClient, queryClient); - return () => fileAndResourceWatcher.close(true); + return () => { + if (keepAlive) { + fileAndResourceWatcher.enableAutoClose(); + } + fileAndResourceWatcher.close(true); + }; }); function handleVisibilityChange() { if (document.visibilityState === "visible") { heartbeat(); - } else { + } else if (!keepAlive) { scheduleAutoClose(true); } } @@ -48,7 +59,9 @@ scheduleAutoClose()} + onblur={() => { + if (!keepAlive) scheduleAutoClose(); + }} onclick={heartbeat} onkeydown={heartbeat} onfocus={heartbeat} @@ -59,7 +72,7 @@ fatal statusCode={500} header="Error connecting to runtime" - body="Try restarting the Rill via the CLI" + body={errorBody} /> {:else} diff --git a/web-common/src/features/entity-management/RenameAssetModal.svelte b/web-common/src/features/entity-management/RenameAssetModal.svelte index 66d75eabce9..b0b04af9c19 100644 --- a/web-common/src/features/entity-management/RenameAssetModal.svelte +++ b/web-common/src/features/entity-management/RenameAssetModal.svelte @@ -1,7 +1,10 @@ + + + Rill Developer | {fileName} + + +
+
+ {#if workspace} + + {:else} + + + + + + + {/if} +
+
diff --git a/web-common/src/features/workspaces/workspace-routing.ts b/web-common/src/features/workspaces/workspace-routing.ts new file mode 100644 index 00000000000..2a6e61e3e71 --- /dev/null +++ b/web-common/src/features/workspaces/workspace-routing.ts @@ -0,0 +1,31 @@ +import { goto } from "$app/navigation"; +import { writable, get } from "svelte/store"; + +/** + * Route prefix for workspace navigation. + * Empty string for web-local (default), "///-/edit" for web-admin. + */ +export const workspaceRoutePrefix = writable(""); + +/** + * Build a workspace route path with the current prefix. + * Example: workspaceRoute("/files/models/foo.sql") → "///-/edit/files/models/foo.sql" + */ +export function workspaceRoute(path: string): string { + return `${get(workspaceRoutePrefix)}${path}`; +} + +export function navigateToFile( + filePath: string, + options?: Parameters[1], +) { + return goto(workspaceRoute(`/files${filePath}`), options); +} + +export function getFileHref(filePath: string): string { + return workspaceRoute(`/files${filePath}`); +} + +export function navigateToHome(options?: Parameters[1]) { + return goto(workspaceRoute("/"), options); +} diff --git a/web-common/src/layout/navigation/Footer.svelte b/web-common/src/layout/navigation/Footer.svelte index 67b34148a46..55890b52d7e 100644 --- a/web-common/src/layout/navigation/Footer.svelte +++ b/web-common/src/layout/navigation/Footer.svelte @@ -12,10 +12,8 @@ const metadataQuery = createLocalServiceGetMetadata(); - $: ({ data } = $metadataQuery); - - $: version = data?.version; - $: commitHash = data?.buildCommit; + $: version = $metadataQuery?.data?.version ?? null; + $: commitHash = $metadataQuery?.data?.buildCommit ?? null; const lineItems = [ { diff --git a/web-common/src/layout/navigation/Navigation.svelte b/web-common/src/layout/navigation/Navigation.svelte index 287a58433ed..5054a6554a7 100644 --- a/web-common/src/layout/navigation/Navigation.svelte +++ b/web-common/src/layout/navigation/Navigation.svelte @@ -22,6 +22,10 @@ import Footer from "./Footer.svelte"; import SurfaceControlButton from "./SurfaceControlButton.svelte"; + // When false, hides the footer (version info, links, traffic light). + // Used in cloud editing where the footer isn't relevant. + export let showFooterLinks = true; + const DEFAULT_PERCENTAGE = 0.4; let width = DEFAULT_NAV_WIDTH; @@ -162,7 +166,9 @@ {/if}
-
+ {#if showFooterLinks} +
+ {/if}
diff --git a/web-common/src/runtime-client/sse-connection-manager.ts b/web-common/src/runtime-client/sse-connection-manager.ts index 954ee7083dd..85763375288 100644 --- a/web-common/src/runtime-client/sse-connection-manager.ts +++ b/web-common/src/runtime-client/sse-connection-manager.ts @@ -59,6 +59,7 @@ export class SSEConnectionManager { private client = new SSEFetchClient(); private autoCloseThrottler: Throttler | undefined; + private autoCloseDisabled = false; private retryAttempts = writable(0); private isReconnecting = false; private connectionCount = 0; @@ -151,10 +152,28 @@ export class SSEConnectionManager { * Enable auto-close behavior to manage HTTP connection quota (browsers limit ~6 concurrent connections per host) */ public scheduleAutoClose(prioritize: boolean = false) { + if (this.autoCloseDisabled) return; this.autoCloseThrottler?.cancel(); this.autoCloseThrottler?.throttle(() => this.pause(), prioritize); } + /** + * Disable auto-close so the SSE connection stays open indefinitely. + * Used by the cloud editor where a persistent connection is required + * to receive file-write events for save confirmation. + */ + public disableAutoClose() { + this.autoCloseDisabled = true; + this.autoCloseThrottler?.cancel(); + } + + /** + * Re-enable auto-close (reverses disableAutoClose). + */ + public enableAutoClose() { + this.autoCloseDisabled = false; + } + private handleError = (error: Error) => { const status = get(this.status); @@ -239,6 +258,9 @@ export class SSEConnectionManager { this.url = url; this.options = options; + // Reset retries for fresh connections; previous failures shouldn't + // prevent a new endpoint from connecting. + this.retryAttempts.set(0); this.status.set(ConnectionStatus.CONNECTING); void this.client.start(url, options); diff --git a/web-local/src/routes/(application)/(workspace)/+layout.svelte b/web-local/src/routes/(application)/(workspace)/+layout.svelte index a0be35323c6..656605eb232 100644 --- a/web-local/src/routes/(application)/(workspace)/+layout.svelte +++ b/web-local/src/routes/(application)/(workspace)/+layout.svelte @@ -1,8 +1,12 @@
diff --git a/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte b/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte index e3a5f886a00..979b5eb5008 100644 --- a/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte +++ b/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte @@ -1,115 +1,16 @@ - - Rill Developer | {fileName} - -
- {#if $generatingCanvas} - - {:else if workspace} - - {:else} - - - - - - - {/if} +
diff --git a/web-local/src/routes/(misc)/deploy/+page.svelte b/web-local/src/routes/(misc)/deploy/+page.svelte index eaec1ed5e0e..03d858ffe09 100644 --- a/web-local/src/routes/(misc)/deploy/+page.svelte +++ b/web-local/src/routes/(misc)/deploy/+page.svelte @@ -3,7 +3,7 @@ import { page } from "$app/stores"; import CTAMessage from "@rilldata/web-common/components/calls-to-action/CTAMessage.svelte"; import CancelCircleInverse from "@rilldata/web-common/components/icons/CancelCircleInverse.svelte"; - import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; + import LoadingSpinner from "@rilldata/web-common/components/LoadingSpinner.svelte"; import { getCreateOrganizationRoute, getSelectOrganizationRoute, @@ -21,7 +21,6 @@ } from "@rilldata/web-common/runtime-client/local-service"; import CTAHeader from "@rilldata/web-common/components/calls-to-action/CTAHeader.svelte"; import CTANeedHelp from "@rilldata/web-common/components/calls-to-action/CTANeedHelp.svelte"; - import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; import { onMount } from "svelte"; import { get } from "svelte/store"; @@ -108,9 +107,7 @@
{#if loading} -
- -
+ Hang tight! We're deploying your project... diff --git a/web-local/src/routes/(misc)/deploy/project/create/+page.svelte b/web-local/src/routes/(misc)/deploy/project/create/+page.svelte index 4b0f47e9082..86c42c47dd4 100644 --- a/web-local/src/routes/(misc)/deploy/project/create/+page.svelte +++ b/web-local/src/routes/(misc)/deploy/project/create/+page.svelte @@ -1,6 +1,6 @@ {#if loading} -
- -
+ Hang tight! We're deploying your project... diff --git a/web-local/src/routes/(misc)/deploy/project/update/+page.svelte b/web-local/src/routes/(misc)/deploy/project/update/+page.svelte index d4f645a73b1..c692072afbb 100644 --- a/web-local/src/routes/(misc)/deploy/project/update/+page.svelte +++ b/web-local/src/routes/(misc)/deploy/project/update/+page.svelte @@ -1,5 +1,5 @@ {#if loading} -
- -
+ Hang tight! We're deploying your project...