Skip to content

Commit 4ba69d2

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/wait-until-idempotency-retry-rewait
# Conflicts: # apps/webapp/app/routes/realtime.v1.streams.$runId.$streamId.ts
2 parents d8c6bf4 + cecdfd9 commit 4ba69d2

31 files changed

Lines changed: 1804 additions & 492 deletions

.github/workflows/pr_checks.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
- '.github/workflows/pr_checks.yml'
5757
- '.github/workflows/unit-tests-webapp.yml'
5858
- '.github/workflows/e2e-webapp.yml'
59+
- '.github/workflows/runops-guard.yml'
5960
- '.configs/**'
6061
- 'package.json'
6162
- 'pnpm-lock.yaml'
@@ -111,6 +112,11 @@ jobs:
111112
if: needs.changes.outputs.code == 'true' || needs.changes.outputs.typecheck_self == 'true'
112113
uses: ./.github/workflows/typecheck.yml
113114

115+
runops-guard:
116+
needs: changes
117+
if: needs.changes.outputs.webapp == 'true'
118+
uses: ./.github/workflows/runops-guard.yml
119+
114120
webapp:
115121
needs: changes
116122
if: needs.changes.outputs.webapp == 'true'
@@ -161,6 +167,7 @@ jobs:
161167
- changes
162168
- code-quality
163169
- typecheck
170+
- runops-guard
164171
- webapp
165172
- e2e-webapp
166173
- packages

.github/workflows/runops-guard.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: "🛡️ Run-ops Legacy Guard"
2+
3+
on:
4+
workflow_call:
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
runops-guard:
11+
runs-on: warp-ubuntu-latest-x64-16x
12+
13+
steps:
14+
- name: ⬇️ Checkout repo
15+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16+
with:
17+
fetch-depth: 0
18+
persist-credentials: false
19+
20+
- name: ⎔ Setup pnpm
21+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
22+
with:
23+
version: 10.33.2
24+
25+
- name: ⎔ Setup node
26+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
27+
with:
28+
node-version: 24.18.0
29+
cache: "pnpm"
30+
31+
- name: 📥 Download deps
32+
run: pnpm install --frozen-lockfile
33+
34+
- name: 📀 Generate Prisma Client
35+
run: pnpm run generate
36+
37+
- name: 🛡️ Run-ops legacy guard
38+
run: pnpm --filter webapp run guard:runops-legacy -- --check

.oxlintrc.json

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"$schema": "./node_modules/oxlint/configuration_schema.json",
33
"plugins": ["typescript", "import", "react"],
4-
"jsPlugins": ["./oxlint-plugins/no-thrown-unawaited-redirect.mjs"],
4+
"jsPlugins": [
5+
"./oxlint-plugins/no-thrown-unawaited-redirect.mjs",
6+
"./oxlint-plugins/runops-residency.mjs"
7+
],
58
"ignorePatterns": [
69
"**/dist/**",
710
"**/build/**",
@@ -34,5 +37,21 @@
3437
"react-hooks/exhaustive-deps": "off",
3538
"react-hooks/rules-of-hooks": "off",
3639
"trigger/no-thrown-unawaited-redirect": "error"
37-
}
40+
},
41+
"overrides": [
42+
{
43+
"files": ["apps/webapp/app/**/*.ts", "apps/webapp/app/**/*.tsx"],
44+
"rules": {
45+
"trigger-runops/no-control-plane-run-graph-access": "error",
46+
"trigger-runops/no-control-plane-in-runops-slot": "error"
47+
}
48+
},
49+
{
50+
"files": ["apps/webapp/app/**/*.test.ts", "apps/webapp/app/**/*.test.tsx"],
51+
"rules": {
52+
"trigger-runops/no-control-plane-run-graph-access": "off",
53+
"trigger-runops/no-control-plane-in-runops-slot": "off"
54+
}
55+
}
56+
]
3857
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
The "Preview branches" usage on the Limits page now counts only preview branches.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Remove the deprecated realtime stream write endpoint used by retired v3 task clients.

.server-changes/stale-deploy-asset-recovery.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: fix
44
---
55

6-
Fix pages occasionally loading unstyled or failing to load during deploys. The dashboard now detects this and reloads to recover automatically, or prompts you to reload if it can't.
6+
Fix pages occasionally loading unstyled or failing to load during a deploy. The dashboard now reloads automatically to recover.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @vitest-environment jsdom
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
import { staleAssetRecoveryScript } from "./StaleAssetRecovery";
4+
5+
// Each staleAssetRecoveryScript() call models a fresh page load: it reads the shared
6+
// sessionStorage budget and returns its own `recover`. We drive recover() directly rather
7+
// than dispatching resource-error events, so accumulated window listeners never fire.
8+
describe("staleAssetRecoveryScript", () => {
9+
let reload: ReturnType<typeof vi.fn>;
10+
11+
beforeEach(() => {
12+
sessionStorage.clear();
13+
reload = vi.fn();
14+
vi.stubGlobal("location", { reload });
15+
vi.stubGlobal("navigator", { onLine: true });
16+
});
17+
18+
afterEach(() => {
19+
vi.unstubAllGlobals();
20+
vi.restoreAllMocks();
21+
});
22+
23+
it("reloads on a recovery", () => {
24+
staleAssetRecoveryScript().recover();
25+
expect(reload).toHaveBeenCalledTimes(1);
26+
});
27+
28+
it("reloads only once per page even if several assets fail (re-entrancy guard)", () => {
29+
const { recover } = staleAssetRecoveryScript();
30+
recover();
31+
recover();
32+
recover();
33+
expect(reload).toHaveBeenCalledTimes(1);
34+
});
35+
36+
it("stops reloading once the budget is spent across reloads", () => {
37+
staleAssetRecoveryScript().recover(); // reload 1
38+
staleAssetRecoveryScript().recover(); // reload 2
39+
staleAssetRecoveryScript().recover(); // budget spent -> no reload
40+
expect(reload).toHaveBeenCalledTimes(2);
41+
});
42+
43+
it("does not reload when offline", () => {
44+
vi.stubGlobal("navigator", { onLine: false });
45+
staleAssetRecoveryScript().recover();
46+
expect(reload).not.toHaveBeenCalled();
47+
});
48+
49+
it("does not reload when sessionStorage is unavailable", () => {
50+
vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
51+
throw new Error("blocked");
52+
});
53+
staleAssetRecoveryScript().recover();
54+
expect(reload).not.toHaveBeenCalled();
55+
});
56+
});

0 commit comments

Comments
 (0)