diff --git a/.github/workflows/cmux-next.yml b/.github/workflows/cmux-next.yml
index 87d5f29f0b14..c1105ecf83df 100644
--- a/.github/workflows/cmux-next.yml
+++ b/.github/workflows/cmux-next.yml
@@ -105,8 +105,6 @@ on:
- tests/test_cmux_next_checks_workflow.py
- tests/test_cmux_next_generated_catch_up.py
- .github/workflows/cmux-next-generated-catch-up.yml
- - tests/test_cmux_next_regenerate_bundles_workflow.py
- - .github/workflows/cmux-next-regenerate-bundles.yml
- tests/test_next_batch.py
- .github/workflows/cmux-next-batch.yml
- tests/test_cmux_next_tree_notify.py
@@ -205,8 +203,6 @@ on:
- tests/test_cmux_next_checks_workflow.py
- tests/test_cmux_next_generated_catch_up.py
- .github/workflows/cmux-next-generated-catch-up.yml
- - tests/test_cmux_next_regenerate_bundles_workflow.py
- - .github/workflows/cmux-next-regenerate-bundles.yml
- tests/test_next_batch.py
- .github/workflows/cmux-next-batch.yml
- tests/test_cmux_next_tree_notify.py
@@ -757,14 +753,13 @@ jobs:
python3 tests/test_cmux_next_route.py
python3 tests/test_cmux_next_route_replay.py
- # The companion workflows' structure: generated-files catch-up, the bundle
- # regeneration dispatch and the next-batch landing queue.
+ # The companion workflows' structure: generated-files catch-up and the
+ # next-batch landing queue.
- name: Companion workflows
id: companion-workflows
continue-on-error: true
run: |
python3 tests/test_cmux_next_generated_catch_up.py
- python3 tests/test_cmux_next_regenerate_bundles_workflow.py
python3 tests/test_next_batch.py
# A pull request that changes the router replays the last 30 merged PRs' changed files
diff --git a/tests/test-execution.toml b/tests/test-execution.toml
index a074c4494b49..1f3debaaa86f 100644
--- a/tests/test-execution.toml
+++ b/tests/test-execution.toml
@@ -996,10 +996,6 @@ lane = "linux-guard"
path = "tests/test_cmux_next_route.py"
lane = "linux-guard"
-[[test]]
-path = "tests/test_cmux_next_regenerate_bundles_workflow.py"
-lane = "linux-guard"
-
[[test]]
path = "tests/test_gh_merge_green.py"
lane = "linux-guard"
diff --git a/tests/test_build_web_bundles_from.sh b/tests/test_build_web_bundles_from.sh
index 91b7e97422cc..4061bd043158 100755
--- a/tests/test_build_web_bundles_from.sh
+++ b/tests/test_build_web_bundles_from.sh
@@ -3,39 +3,52 @@
# source key (cmux-next.yml's Linux web-bundles job caches them by that key) and stamps them,
# so a Mac job that restores them skips the build. A tree missing a bundle is refused and the
# checkout is left alone.
+#
+# The bundles are build output, not committed (41b907dce7b), so the test runs a copy of the
+# script in a scratch repository with a synthetic prebuilt tree. It never touches this
+# checkout's generated bundles or its .web-bundles.key.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-SCRIPT="$ROOT/scripts/cmux-next/build-web-bundles.sh"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
-cd "$ROOT"
+
+REPO="$WORK/repo"
+mkdir -p "$REPO/scripts/cmux-next"
+cp "$ROOT/scripts/cmux-next/build-web-bundles.sh" "$ROOT/scripts/cmux-next/web-bundle-key.py" "$REPO/scripts/cmux-next/"
+SCRIPT="$REPO/scripts/cmux-next/build-web-bundles.sh"
+git -C "$REPO" init -q
PANE="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane"
PAGES="Packages/macOS/CmuxNext/Sources/CmuxNextPages/Resources/pages"
ACTIVITY="Packages/macOS/CmuxNext/Sources/CmuxNextAgentActivity/Resources/agent-activity"
PALETTE="Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js"
APP="Resources/markdown-viewer/webviews-app"
+INSPECTOR="Native/OptChat/optchat-chief/inspector"
+
+# The palette ranker lands in a Resources directory that holds tracked files in a checkout.
+mkdir -p "$REPO/$(dirname "$PALETTE")"
-# A prebuilt tree: the committed bundles stand in for the cached build of this source key.
-for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP"; do
- mkdir -p "$WORK/out/$(dirname "$path")"
- cp -R "$path" "$WORK/out/$path"
+# A prebuilt tree for this source key, as the Linux web-bundles job caches it.
+for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do
+ mkdir -p "$WORK/out/$path"
+ printf '
%s\n' "$path" > "$WORK/out/$path/index.html"
done
mkdir -p "$WORK/out/$(dirname "$PALETTE")"
-cp "$PALETTE" "$WORK/out/$PALETTE"
+printf 'export const rank = () => 0;\n' > "$WORK/out/$PALETTE"
-rm -f .web-bundles.key
"$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1 || { cat "$WORK/log"; echo "FAIL: --from must install a complete prebuilt tree"; exit 1; }
"$SCRIPT" --verify > /dev/null 2>&1 || { echo "FAIL: after --from the bundles must be current without a build"; exit 1; }
-git diff --quiet -- "$PANE" "$PAGES" "$ACTIVITY" "$PALETTE" "$APP" \
- || { echo "FAIL: --from changed the committed bundles"; exit 1; }
+for path in "$PANE" "$PAGES" "$ACTIVITY" "$APP" "$INSPECTOR"; do
+ diff -r "$WORK/out/$path" "$REPO/$path" > /dev/null || { echo "FAIL: --from did not install $path as built"; exit 1; }
+done
+cmp -s "$WORK/out/$PALETTE" "$REPO/$PALETTE" || { echo "FAIL: --from did not install $PALETTE as built"; exit 1; }
rm -rf "$WORK/out/$PAGES"
-rm -f .web-bundles.key
+rm -f "$REPO/.web-bundles.key"
if "$SCRIPT" --from "$WORK/out" > "$WORK/log" 2>&1; then
echo "FAIL: --from must refuse a tree that is missing a bundle"; exit 1
fi
-[ -d "$PAGES" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; }
-[ ! -f .web-bundles.key ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; }
+[ -f "$REPO/$PAGES/index.html" ] || { echo "FAIL: a refused --from must leave the checkout's bundles alone"; exit 1; }
+[ ! -f "$REPO/.web-bundles.key" ] || { echo "FAIL: a refused --from must not stamp the bundles current"; exit 1; }
echo "PASS: build-web-bundles.sh --from installs and stamps a prebuilt tree, and refuses an incomplete one"
diff --git a/tests/test_cmux_next_checks_workflow.py b/tests/test_cmux_next_checks_workflow.py
index 864382dbe003..490dcf853c64 100755
--- a/tests/test_cmux_next_checks_workflow.py
+++ b/tests/test_cmux_next_checks_workflow.py
@@ -144,6 +144,20 @@ def test_rust_ratchet_is_its_own_step(self):
self.assertEqual(sum("--only rust" in run for run in godfile_runs), 1, godfile_runs)
+class ReferencedFilesExist(unittest.TestCase):
+ """Every test a checks step runs, and every test or workflow a path filter names, exists.
+ The bundle removal (ce0b9e76a9b) deleted tests/test_cmux_next_regenerate_bundles_workflow.py
+ and its workflow, but the Companion workflows step still ran the test, so every pull
+ request's checks went red (run 37646119996)."""
+
+ def test_named_tests_and_workflows_exist(self):
+ text = WORKFLOW.read_text(encoding="utf-8")
+ named = set(re.findall(r"(?:python3|bash) (tests/[A-Za-z0-9_./-]+\.(?:py|sh))", text))
+ named |= set(re.findall(r"^\s+- ((?:tests|\.github/workflows)/[A-Za-z0-9_./-]+\.(?:py|sh|yml))$", text, re.M))
+ missing = sorted(path for path in named if not (ROOT / path).exists())
+ self.assertEqual(missing, [])
+
+
class GodfileScopes(unittest.TestCase):
"""`--only swift` and `--only rust` each check their half, at unchanged budgets."""
diff --git a/tests/test_cmux_next_route.py b/tests/test_cmux_next_route.py
index c7764335ee2f..15342b4c0d00 100644
--- a/tests/test_cmux_next_route.py
+++ b/tests/test_cmux_next_route.py
@@ -109,8 +109,14 @@ def test_cmux_tui_runs_the_daemon_tier_and_the_scheme_compile(self):
self.assertEqual(result["swift_targets"], "")
def test_a_file_a_test_reads_selects_that_test(self):
+ # CmuxNextSettingsTests reads the schema. Since the bundles are build output
+ # (ce0b9e76a9b) the schema is also a web-bundle input, so the targets that ship
+ # a bundle, and the app that embeds them, are selected with it, and nothing else.
result = tiers(["schemas/settings/settings-schema.json"])
- self.assertEqual(result["swift_targets"], "CmuxNextSettingsTests")
+ self.assertEqual(result["swift_targets"].split(), sorted([
+ "CmuxNextSettingsTests", "CmuxNextAppTests",
+ "CmuxNextAgentPaneTests", "CmuxNextPagesTests", "CmuxNextAgentActivityTests", "CmuxNextPaletteTests",
+ ]))
def test_a_local_package_selects_the_targets_that_use_it(self):
result = tiers(["Packages/Shared/CmuxHomeCore/Sources/CmuxHomeCore/Thread.swift"])
diff --git a/webviews/src/agent-session/acpmux/ModelPicker.tsx b/webviews/src/agent-session/acpmux/ModelPicker.tsx
index 07038c970377..398607f476ea 100644
--- a/webviews/src/agent-session/acpmux/ModelPicker.tsx
+++ b/webviews/src/agent-session/acpmux/ModelPicker.tsx
@@ -498,7 +498,7 @@ export function ModelPicker(props: ModelPickerProps) {
{entry.name}
{folderNote(entry) && {folderNote(entry)}}
{entry.ids.includes(harness ?? "") && }
-
+ ,
])}
diff --git a/webviews/src/agent-session/acpmux/composerPickers.test.tsx b/webviews/src/agent-session/acpmux/composerPickers.test.tsx
index d27a7cebf0f0..6c7bd1d2c992 100644
--- a/webviews/src/agent-session/acpmux/composerPickers.test.tsx
+++ b/webviews/src/agent-session/acpmux/composerPickers.test.tsx
@@ -239,12 +239,16 @@ describe("acpmux composer pickers", () => {
// The highlight opens on the current mode, the last one.
await key(mode, "ArrowDown");
expect(
- [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => row.textContent?.includes("Full access")),
+ [...doc.querySelectorAll("[role=menuitemradio]")].find((row) =>
+ row.textContent?.includes("Full access"),
+ ),
).toBeTruthy();
// A live update drops that option while it is highlighted.
await render(snapshot({ modes: { ...full, availableModes: [full.availableModes[0]!] } }));
expect(
- [...doc.querySelectorAll("[role=menuitemradio]")].find((row) => row.textContent?.includes("Ask for approval")),
+ [...doc.querySelectorAll("[role=menuitemradio]")].find((row) =>
+ row.textContent?.includes("Ask for approval"),
+ ),
).toBeTruthy();
await key(mode, " ");
expect(mode.getAttribute("aria-expanded")).toBe("true");
diff --git a/webviews/test/gallery-coverage.allowlist.json b/webviews/test/gallery-coverage.allowlist.json
index 94aa6f461c47..60aaa32a640b 100644
--- a/webviews/test/gallery-coverage.allowlist.json
+++ b/webviews/test/gallery-coverage.allowlist.json
@@ -12,6 +12,7 @@
"agent-session/acpmux/ComposerPickers.tsx#ChevronIcon",
"agent-session/acpmux/ComposerPickers.tsx#ChevronRightIcon",
"agent-session/acpmux/ComposerPickers.tsx#ContextRing",
+ "agent-session/acpmux/ComposerPickers.tsx#LockIcon",
"agent-session/acpmux/ComposerPickers.tsx#MicIcon",
"agent-session/acpmux/ComposerPickers.tsx#PaperclipIcon",
"agent-session/acpmux/ComposerPickers.tsx#Picker",
@@ -33,12 +34,12 @@
"agent-session/acpmux/Keycap.tsx#Keycap",
"agent-session/acpmux/MenuLevel.tsx#MenuLevel",
"agent-session/acpmux/MenuRow.tsx#MenuRow",
- "agent-session/acpmux/ModelPicker.tsx#ModelPicker",
"agent-session/acpmux/ModelPickerCascade.tsx#ModelPickerCascade",
"agent-session/acpmux/ModelPickerDrill.tsx#ModelPickerDrill",
"agent-session/acpmux/ModelPickerShell.tsx#ModelPickerShell",
"agent-session/acpmux/NewTabPage.tsx#KindIcon",
"agent-session/acpmux/NewTabPage.tsx#NewTabPage",
+ "agent-session/acpmux/ProjectBadge.tsx#ProjectBadge",
"agent-session/acpmux/QuickKeyHints.tsx#QuickKeyHints",
"agent-session/acpmux/QuickSurface.tsx#QuickSurface",
"agent-session/acpmux/SessionSidebar.tsx#SessionSidebar",
@@ -114,6 +115,7 @@
"agent-session/acpmux/icons/Icon.tsx#Icon",
"agent-session/acpmux/icons/IconsContext.tsx#IconsProvider",
"agent-session/acpmux/permissions/Panel.tsx#PermissionPanel",
+ "agent-session/acpmux/question/QuestionCard.tsx#QuestionCard",
"agent-session/acpmux/shell/MoveRow.tsx#MoveRow",
"agent-session/acpmux/shell/ShellRow.tsx#ShellActionsContext",
"agent-session/acpmux/shell/ShellRow.tsx#ShellRow",
@@ -134,6 +136,7 @@
"pages/markdown/linkOverlays.tsx#LinkCardView",
"pages/markdown/linkOverlays.tsx#LinkOverlayHost",
"pages/markdown/linkOverlays.tsx#LinkPopoverView",
+ "pages/settings/editors/ChatRootsEditor.tsx#ChatRootsEditor",
"pages/settings/editors/OrderedChoicesEditor.tsx#OrderedChoicesEditor",
"pages/shared/PageMenu.tsx#PageMenu",
"ui/Breadcrumbs.tsx#Breadcrumbs",
@@ -154,6 +157,12 @@
"ui/Menu.tsx#MenuRadioItem",
"ui/Menu.tsx#MenuSeparator",
"ui/Menu.tsx#Submenu",
+ "ui/MinimapTick.tsx#MinimapTick",
+ "ui/PickerPrimitives.tsx#PickerButton",
+ "ui/PickerPrimitives.tsx#PickerComboboxInput",
+ "ui/PickerPrimitives.tsx#PickerDialog",
+ "ui/PickerPrimitives.tsx#PickerOption",
+ "ui/PickerPrimitives.tsx#PickerOptionList",
"ui/Popover.tsx#Popover",
"ui/Select.tsx#Select",
"ui/Sheet.tsx#Sheet",
diff --git a/webviews/test/palette-ranker.test.ts b/webviews/test/palette-ranker.test.ts
index f2ecd2415456..4e61140242c8 100644
--- a/webviews/test/palette-ranker.test.ts
+++ b/webviews/test/palette-ranker.test.ts
@@ -1,4 +1,7 @@
import { describe, expect, test } from "bun:test";
+import { mkdtempSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
import { rankPalette, rankPaletteEmpty, type PaletteFrecency, type PaletteRankEntry } from "../src/palette/ranker";
const now = 800_000_000;
@@ -153,12 +156,16 @@ describe("shared palette ranker", () => {
expect(result[0]?.rows[1]?.highlights).toEqual([]);
});
- test("the checked-in JavaScriptCore bridge exposes the same ranker", async () => {
- const bundle = await Bun.file(
- "../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js",
- ).text();
+ test("the built JavaScriptCore bridge exposes the same ranker", async () => {
+ // The bridge is build output (scripts/cmux-next/build-web-bundles.sh), not a committed file:
+ // build it into a scratch directory, as the app build does into the package resources.
+ const out = mkdtempSync(join(tmpdir(), "palette-ranker-"));
+ const built = Bun.spawnSync(["sh", "../scripts/cmux-next/build-palette-ranker.sh", "--out", out]);
+ expect(built.exitCode).toBe(0);
+ const bundlePath = join(out, "palette-ranker.js");
+ const bundle = await Bun.file(bundlePath).text();
expect(bundle.length).toBeGreaterThan(0);
- await import("../../Packages/macOS/CmuxNext/Sources/CmuxNextPalette/Resources/palette-ranker.js");
+ await import(bundlePath);
const bridge = (globalThis as typeof globalThis & { __cmuxPaletteRank?: (request: string) => string })
.__cmuxPaletteRank;
expect(typeof bridge).toBe("function");