Skip to content

Commit d2aac7d

Browse files
committed
ci(studio): run the timeline viewport gate on studio changes
The gate has existed since the row virtualization stack landed but nothing under `.github/` referenced it, so it only ever ran when someone ran it by hand. That is how the flag-off scroll regression reached eight merged-ready PRs without anything noticing. Adds a `studio-timeline-viewport` job that boots two Studio dev servers, one per flag state, and runs both arms of the gate against them. Two servers are needed because row virtualization is read from `import.meta.env` at module load, so one process cannot serve both builds. Scoped to a new `studio` paths filter rather than the broad `code` one: the gate only says anything about `packages/studio`, `packages/core` and `packages/studio-server`. Adds a `ci` tier. It applies the constrained budgets without any emulation, because a hosted runner is already slower and noisier than the machine the strict numbers were recorded on, while the existing `low-resource` tier would throttle it a further 4x and measure the throttle rather than the build. The fixture composition is tracked under `tests/e2e/fixtures` but Studio resolves projects from the gitignored `data/projects`, so the job copies it into place instead of a project directory being committed. Both arms run in about 7 seconds each locally, so the job cost is almost entirely dependency install and the workspace build it shares with `studio-load-smoke`.
1 parent b97d8c7 commit d2aac7d

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
skills: ${{ steps.filter.outputs.skills }}
3737
codex_plugin: ${{ steps.filter.outputs.codex_plugin }}
3838
gcp_beginframe: ${{ steps.filter.outputs.gcp_beginframe }}
39+
studio: ${{ steps.filter.outputs.studio }}
3940
steps:
4041
# Force git-based change detection instead of the pull_request REST API.
4142
# The API path can fail the whole workflow on transient listFiles
@@ -77,6 +78,12 @@ jobs:
7778
- "scripts/package-codex-plugin.mjs"
7879
- "package.json"
7980
- ".github/workflows/ci.yml"
81+
studio:
82+
- "packages/studio/**"
83+
- "packages/core/**"
84+
- "packages/studio-server/**"
85+
- "bun.lock"
86+
- ".github/workflows/ci.yml"
8087
gcp_beginframe:
8188
- "packages/gcp-cloud-run/Dockerfile"
8289
- "packages/aws-lambda/scripts/probe-beginframe.ts"
@@ -496,6 +503,81 @@ jobs:
496503
497504
kill $SERVER_PID 2>/dev/null || true
498505
506+
studio-timeline-viewport:
507+
name: "Studio: timeline viewport gate"
508+
needs: [changes]
509+
if: needs.changes.outputs.studio == 'true'
510+
runs-on: ubuntu-latest
511+
timeout-minutes: 12
512+
steps:
513+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
514+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
515+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
516+
with:
517+
node-version: 22
518+
- run: bun install --frozen-lockfile
519+
# Same reason as studio-load-smoke: vite.config.ts is loaded by Node and
520+
# resolves the workspace packages through their "node" export condition.
521+
- run: bun run --filter '@hyperframes/{parsers,lint,studio-server}' build
522+
- run: bun run --cwd packages/core build
523+
- run: bun run --cwd packages/core build:hyperframes-runtime
524+
- name: Install the fixture as a Studio project
525+
# Studio resolves projects from packages/studio/data/projects, which is
526+
# gitignored. The fixture composition is tracked under tests/e2e, so
527+
# copy it into place rather than committing a project directory.
528+
run: |
529+
mkdir -p packages/studio/data/projects
530+
cp -R packages/studio/tests/e2e/fixtures/timeline-virtualization \
531+
packages/studio/data/projects/timeline-virtualization
532+
- name: Run both arms of the timeline viewport gate
533+
run: |
534+
set -euo pipefail
535+
536+
# Two servers, because row virtualization is read from import.meta.env
537+
# at module load: one process cannot serve both builds.
538+
bun run --cwd packages/studio dev -- --port 5313 --strictPort &
539+
DEFAULT_PID=$!
540+
VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED=1 \
541+
bun run --cwd packages/studio dev -- --port 5314 --strictPort &
542+
VIRTUALIZED_PID=$!
543+
trap 'kill $DEFAULT_PID $VIRTUALIZED_PID 2>/dev/null || true' EXIT
544+
545+
for i in $(seq 1 60); do
546+
if curl -sf http://localhost:5313/ >/dev/null 2>&1 \
547+
&& curl -sf http://localhost:5314/ >/dev/null 2>&1; then break; fi
548+
sleep 1
549+
done
550+
if ! curl -sf http://localhost:5313/ >/dev/null 2>&1 \
551+
|| ! curl -sf http://localhost:5314/ >/dev/null 2>&1; then
552+
echo "FAIL: studio dev servers did not start"
553+
exit 1
554+
fi
555+
556+
# The default build first. It is the one users get, and the arm that
557+
# caught the regression this gate exists for.
558+
STUDIO_URL="http://localhost:5313/#project/timeline-virtualization" \
559+
TIMELINE_ROW_VIRTUALIZATION=off \
560+
TIMELINE_ELEMENT_COUNT=1000 \
561+
TIMELINE_TIER=ci \
562+
node packages/studio/tests/e2e/timeline-virtualization.mjs \
563+
| tee /tmp/timeline-gate-default.json
564+
565+
STUDIO_URL="http://localhost:5314/#project/timeline-virtualization" \
566+
TIMELINE_ROW_VIRTUALIZATION=on \
567+
TIMELINE_ELEMENT_COUNT=50000 \
568+
TIMELINE_TIER=ci \
569+
node packages/studio/tests/e2e/timeline-virtualization.mjs \
570+
| tee /tmp/timeline-gate-virtualized.json
571+
- name: Upload gate evidence
572+
# The gate's whole output is machine-readable evidence, and a red run is
573+
# exactly when someone needs to read it. Keep it on failure too.
574+
if: always()
575+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
576+
with:
577+
name: timeline-viewport-gate-evidence
578+
path: /tmp/timeline-gate-*.json
579+
if-no-files-found: warn
580+
499581
smoke-global-install:
500582
name: "Smoke: global install"
501583
needs: [changes, build]

packages/studio/tests/e2e/timeline-virtualization.mjs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
* STUDIO_URL=http://127.0.0.1:5190/#project/timeline-virtualization \
88
* node packages/studio/tests/e2e/timeline-virtualization.mjs
99
*
10+
* TIMELINE_TIER selects the budget set and the emulation applied. "primary" is
11+
* a developer machine and holds the strict budgets. "low-resource" and
12+
* "high-dpr" add CPU throttling and a 2x scale factor respectively. "ci" is a
13+
* shared runner: no emulation, but the constrained budgets, because a hosted
14+
* runner is already slower and noisier than the machine the strict numbers were
15+
* recorded on. Throttling it further would measure the throttle, not the build.
16+
*
1017
* TIMELINE_ROW_VIRTUALIZATION selects which build is under test and defaults to
1118
* "off", the product default. The gate previously only ever ran against a server
1219
* with row virtualization enabled, so the configuration users actually get was
@@ -34,12 +41,12 @@ if (!STUDIO_URL) {
3441
}
3542
if (
3643
![1_000, 50_000].includes(ELEMENT_COUNT) ||
37-
!["primary", "low-resource", "high-dpr"].includes(TIER) ||
44+
!["primary", "low-resource", "high-dpr", "ci"].includes(TIER) ||
3845
!["off", "on"].includes(ROW_VIRTUALIZATION)
3946
) {
4047
console.error(
4148
"TIMELINE_ELEMENT_COUNT must be 1000 or 50000; " +
42-
"TIMELINE_TIER must be primary, low-resource, or high-dpr; " +
49+
"TIMELINE_TIER must be primary, low-resource, high-dpr, or ci; " +
4350
"TIMELINE_ROW_VIRTUALIZATION must be off or on",
4451
);
4552
process.exit(2);

0 commit comments

Comments
 (0)