From e62a31def814f27aac352c9708b938de7620dbf6 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Thu, 29 Oct 2026 10:43:42 +0100 Subject: [PATCH] fix: drop the Cowork glow switch, claude.ai removed the glow claude.ai no longer ships .cowork-hero-glow or its cowork-hero-glow-pulse keyframes: Cowork moved into the Claude tab and the old Cowork home went with it. The switch matched nothing, so remove add_feature_cowork_glow, its Community Features row, the bridge methods, the cdb-glow IPC handlers and the coworkGlow / coworkGlowOpacity template keys. A leftover key is ignored. Lowers EXPECTED_PATCH_COUNT 61 -> 60 (cc @patrickjaja). --- AGENTS.md | 2 +- CHANGELOG.md | 6 + README.md | 2 +- docs/claude-desktop-extra.jsonc | 15 -- docs/patches.md | 5 +- js/diff_views_main.js | 8 +- js/extra_settings_bridge.js | 10 - js/extra_settings_main.js | 36 +--- js/extra_settings_page.js | 41 +--- js/files_quick_open_main.js | 4 +- js/growthbook_overrides.js | 15 -- js/panel_tabs_main.js | 4 +- js/transcript_limits_main.js | 9 +- js/window_controls_main.js | 2 +- ...add_feature_allow_non_anthropic_models.nim | 2 +- patches/community/add_feature_cowork_glow.nim | 203 ------------------ patches/core/add_feature_extra_settings.nim | 2 +- scripts/apply_patches.py | 2 +- .../community/test-transcript-limits-main.mjs | 12 +- .../tests/core/test-extra-settings-dom.mjs | 25 +-- 20 files changed, 42 insertions(+), 363 deletions(-) delete mode 100644 patches/community/add_feature_cowork_glow.nim diff --git a/AGENTS.md b/AGENTS.md index 986a05af..a88e89d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -202,7 +202,7 @@ Runtime logs are in `~/.config/Claude/logs/`. When 3P mode is active (an `infere ``` patches/ # Nim patch sources (.nim) + Makefile, compiled to native binaries (ls patches/*/*.nim) patches/linux/ # Linux compatibility - always on, not user-configurable (37) -patches/community/ # Opt-in features, each with a switch in Settings -> Extra -> Community Features (17) +patches/community/ # Opt-in features, each with a switch in Settings -> Extra -> Community Features (16) patches/core/ # Always-on infrastructure: Extra settings pages, theme engine, GrowthBook overrides, multi-profile (7) js/ # Shared JS snippets embedded by Nim patches via staticRead ("../../js/..." from a patch) scripts/ # Build, validation, and launcher scripts diff --git a/CHANGELOG.md b/CHANGELOG.md index 99195fc5..f8e96e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to the claude-desktop-extra packages will be documented in this file. +## 2026-10-29 + +### The "Calm the Cowork glow" switch is gone + +- **claude.ai no longer has the glow it calmed.** Cowork now lives in the Claude tab, and the pulsing hero glow went with the old Cowork home: `.cowork-hero-glow` and its `cowork-hero-glow-pulse` keyframes are in none of claude.ai's current stylesheets, so the switch matched nothing and changed nothing. `add_feature_cowork_glow` and its Community Features row are removed; a leftover `coworkGlow` / `coworkGlowOpacity` key in `claude-desktop-extra.json(c)` is ignored. + ## 2026-10-28 ### Upstream v4.29.0 diff --git a/README.md b/README.md index 6614546d..624142a7 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ Override Anthropic's server-side feature flags in `~/.config/Claude/claude-deskt ## Patches -We patch the official `app.asar` at repackage time: [`patches/community/`](docs/patches.md#community-features) (17 patches, opt-in features), [`patches/core/`](docs/patches.md#core-infrastructure) (7, infrastructure) and [`patches/linux/`](docs/patches.md#linux-compatibility) (37, Linux fixes). A patch that stops matching fails the build, and a patch is removed once upstream ships the behavior. Catalog: [docs/patches.md](docs/patches.md). +We patch the official `app.asar` at repackage time: [`patches/community/`](docs/patches.md#community-features) (16 patches, opt-in features), [`patches/core/`](docs/patches.md#core-infrastructure) (7, infrastructure) and [`patches/linux/`](docs/patches.md#linux-compatibility) (37, Linux fixes). A patch that stops matching fails the build, and a patch is removed once upstream ships the behavior. Catalog: [docs/patches.md](docs/patches.md). ## Command-line flags diff --git a/docs/claude-desktop-extra.jsonc b/docs/claude-desktop-extra.jsonc index c09cf18d..b15fcc56 100644 --- a/docs/claude-desktop-extra.jsonc +++ b/docs/claude-desktop-extra.jsonc @@ -33,21 +33,6 @@ { // "activeTheme": "mario", - // coworkGlow: for laptops and machines without much graphics power. The glow - // behind the Cowork hero pulses between 55% and 100% opacity every 3.2s and - // never stops, so the compositor never settles - and where the launcher falls - // back to software rendering (--disable-gpu-compositing / --disable-gpu) those - // frames are redrawn on the CPU. "calm" stops the pulse and holds the glow at a - // fixed opacity; omit it (or "pulse") for claude.ai's own behaviour. - // Settings -> Extra -> Features has a switch for this and applies it live; a - // value set HERE wins the merge, so that switch shows itself as locked while - // this line is active. - // "coworkGlow": "calm", - - // coworkGlowOpacity: the fixed opacity "calm" holds, 0..1 (default 0.55, the - // dim end of upstream's own pulse). Hand edits need a restart. - // "coworkGlowOpacity": 0.55, - // transcriptLimits: for long Code sessions, especially ones with many browser // screenshots. The app only loads the last 50 MiB of a session's transcript, plus // one shared 32 MiB for all of its subagents' transcripts, and hides the rest behind diff --git a/docs/patches.md b/docs/patches.md index b0fb2912..5d9366aa 100644 --- a/docs/patches.md +++ b/docs/patches.md @@ -18,7 +18,7 @@ Each row below says what a patch does and why you would want it. The mechanism - ## Community features -**17 patches**, each with a switch in Settings → **Extra** → **Community Features**. They reshape first-party surfaces, so they are asked for rather than assumed: turning one off is a full retreat to upstream's own behavior. Off by default, except the theme picker. +**16 patches**, each with a switch in Settings → **Extra** → **Community Features**. They reshape first-party surfaces, so they are asked for rather than assumed: turning one off is a full retreat to upstream's own behavior. Off by default, except the theme picker. The Community Features panel also holds one **Experimental** switch that has no patch of its own: [Computer Use on a background display](computer-use.md#computer-use-on-a-background-display-experimental), drawn by the Extra settings page and read by the Computer Use executor. @@ -27,7 +27,6 @@ The Community Features panel also holds one **Experimental** switch that has no | [`add_feature_theme_picker.nim`](../patches/community/add_feature_theme_picker.nim) | Ctrl+Shift+T opens a [searchable gallery](../themes/README.md#theme-picker-ctrlshiftt) of every bundled theme; one click applies it live in every open window and saves your choice | | [`add_feature_allow_non_anthropic_models.nim`](../patches/community/add_feature_allow_non_anthropic_models.nim) | Opt-in **Allow non-Anthropic models** switch for third-party gateways: relaxes Anthropic's built-in name check that drops model IDs which do not look Anthropic, so a gateway route like `deepseek-v4.1-flash` or `zai-org/glm-5.3` is kept instead of being silently removed with the session falling back to the default model. Covers every gateway check: the Setup model list, the picker filter, the runtime session gate, `/v1/models` discovery and the tier pins. Off by default (behaves exactly as stock); the hand-owned `/claude-desktop-extra.jsonc` key `allowNonAnthropicModels` locks the switch. The setting is per mode, so set it while in 3P mode (`~/.config/Claude-3p`). The model list is built at startup, so the switch takes effect on the next start. Only the Anthropic-shape check is relaxed - the HIPAA (`model_restricted`), admin allowlist (`not_in_allowed_models`) and app-catalog (`desktop_update_required`) gates are untouched | | [`add_feature_allow_non_anthropic_models_ion.nim`](../patches/community/add_feature_allow_non_anthropic_models_ion.nim) | The Setup window half of **Allow non-Anthropic models**: Developer → Configure Third-Party Inference checks the Model list in the window itself, so with the switch on it no longer marks a gateway route as "Doesn't look like an Anthropic model" or blocks **Apply Changes** with "Invalid: Model list". Like the rest of the feature, a change to the switch takes effect on the next start | -| [`add_feature_cowork_glow.nim`](../patches/community/add_feature_cowork_glow.nim) | Holds the pulsing Cowork glow still - it otherwise never stops redrawing, which costs real power on laptops and machines without much graphics muscle | | [`add_feature_dev_tools_toggle.nim`](../patches/community/add_feature_dev_tools_toggle.nim) | Adds a **Developer tools** switch: writes the official build's own opt-in file (`developer_settings.json` in the profile directory, `allowDevTools`), after which Ctrl+Alt+I opens Chromium DevTools for the focused window and "Toggle Dev Tools" appears in the app menu after a restart. The upstream gate itself is untouched: a workspace with developer mode disabled keeps DevTools off | | [`add_feature_diff_views.nim`](../patches/community/add_feature_diff_views.nim) | Adds a scope dropdown to the Code tab's diff panel (working tree, branch changes, latest turn), an expand/collapse-all button, and picks the base branch your work actually forked from | | [`add_feature_diff_views_bridge.nim`](../patches/community/add_feature_diff_views_bridge.nim) | The narrow preload bridge the diff dropdown talks through: one fixed channel per call, because the page behind it is remote code | @@ -111,7 +110,7 @@ Every feature on this page started as one `.nim` file. If Claude Desktop doesn't Pick where it goes: `patches/community/` if users should be able to turn it off - it gets a switch in Settings → **Extra** → **Community Features** for the cost of one `renderToggleRow` spec, and the setting is persisted per profile in `/claude-desktop-extra.jsonc`/`.json`, so people change their mind without rebuilding anything. `patches/core/` if it is always-on infrastructure others build on, and `patches/linux/` if it is a compatibility fix that everyone needs and nobody should have to find. The five steps below cover the community case; drop steps 2 and 3 for the other two. `add_feature_panel_tabs.nim` and its `js/panel_tabs_*.js` modules are the reference implementation to copy from. 1. **Write the patch.** Create `patches/community/.nim` with the usual `# @patch-target:` and `# @patch-type: nim` headers. Match minified code with `[\w$]+` wildcards and capture/replace rather than hardcoded names, and keep the real logic in a `js/` file embedded with `staticRead("../../js/_main.js")` so it stays readable and testable. Every sub-patch must fail loud and the "already patched" branch must assert your own injected result is present - see the patch strictness rules in [AGENTS.md](../AGENTS.md#patch-strictness-rules). If the patch embeds any `js/` file, add a prerequisite line for it in `patches/Makefile`, otherwise editing the JS will not rebuild the binary. -2. **Give it one config key.** Pick a single top-level key in `/claude-desktop-extra.jsonc` (hand-owned, wins) / `.json` (written by the UI), named after the feature - `panelTabs`, `diffViewModes`, `coworkGlow`, `themePicker`. Read it leniently at runtime: a missing key means the default, a hand-set key in the `.jsonc` locks the switch. `PREF_KEY` in [`js/panel_tabs_main.js`](../js/panel_tabs_main.js) is the reference reader. +2. **Give it one config key.** Pick a single top-level key in `/claude-desktop-extra.jsonc` (hand-owned, wins) / `.json` (written by the UI), named after the feature - `panelTabs`, `diffViewModes`, `themePicker`. Read it leniently at runtime: a missing key means the default, a hand-set key in the `.jsonc` locks the switch. `PREF_KEY` in [`js/panel_tabs_main.js`](../js/panel_tabs_main.js) is the reference reader. 3. **Add the switch.** Three small pieces: an IPC read/set handler pair in your own patch (`cdb-tabs:pref-read` / `cdb-tabs:pref-set` is the pattern), two wrapper methods in [`js/extra_settings_bridge.js`](../js/extra_settings_bridge.js), and one `renderToggleRow` spec in the Community Features panel of [`js/extra_settings_page.js`](../js/extra_settings_page.js). The row handles the locked-by-`.jsonc` state and the restart notice for you. 4. **Register it.** Bump `EXPECTED_PATCH_COUNT` in `scripts/apply_patches.py` - the orchestrator refuses to run if discovery and that number disagree. 5. **Document it.** Add a row to the Community features table above stating what the patch does and why it exists, and a `CHANGELOG.md` entry. diff --git a/js/diff_views_main.js b/js/diff_views_main.js index 9a8009c0..dc7bb5db 100644 --- a/js/diff_views_main.js +++ b/js/diff_views_main.js @@ -243,8 +243,8 @@ var prefEnabled = PREF_DEFAULT; var prefSource = "default"; // "default" | "json" | "jsonc-locked" - // Same stripper and the same .json-then-.jsonc precedence the cowork-glow - // patch uses, legacy claude-desktop-bin.* fallback included: the one-time + // Same stripper and the same .json-then-.jsonc precedence the theme engine + // uses, legacy claude-desktop-bin.* fallback included: the one-time // rename migration lives in the custom-themes patch and same-anchor // injections stack in reverse, so it may not have run yet when we read. function cfgStrip(s) { @@ -2424,8 +2424,8 @@ // as locked instead of silently disagreeing with the file. A separate // boolean rather than a value-or-null field, because the value here IS a // boolean and `false` must not read as "not locked". The FIELD NAME - // matches the glow handler's (js/extra_settings_main.js) so the settings - // page speaks of exactly one spelling. + // matches the other rows' handlers so the settings page speaks of + // exactly one spelling. lockedByJsonc: prefSource === "jsonc-locked", key: PREF_KEY, jsonPath: p.json, diff --git a/js/extra_settings_bridge.js b/js/extra_settings_bridge.js index fd85cbc1..2831b69b 100644 --- a/js/extra_settings_bridge.js +++ b/js/extra_settings_bridge.js @@ -74,16 +74,6 @@ return ipcRenderer.invoke("cdb-flags:unset", id); }, - // Cowork glow. set() takes only the fixed strings "pulse"/"calm"; the main - // side re-validates and rejects anything else, so the page cannot smuggle a - // stylesheet or an arbitrary opacity through here. - glowRead: function () { - return ipcRenderer.invoke("cdb-glow:read"); - }, - glowSet: function (mode) { - return ipcRenderer.invoke("cdb-glow:set", String(mode)); - }, - // Theme picker (Ctrl+Shift+T). The window and the hotkey are owned by // patches/community/add_feature_theme_picker.nim; these two channels only // read and persist the pref, which that patch re-reads on every press. diff --git a/js/extra_settings_main.js b/js/extra_settings_main.js index 2114b2e5..8868e84c 100644 --- a/js/extra_settings_main.js +++ b/js/extra_settings_main.js @@ -2118,44 +2118,10 @@ return { ok: true, paths: __cdbEx_paths() }; }, - // Cowork glow. The live half lives in patches/add_feature_cowork_glow.nim - // (globalThis.__cdbCoworkGlow); persistence is ours because this file is the - // single writer of the .json. - "cdb-glow:read": function () { - var g = globalThis.__cdbCoworkGlow; - if (!g) return { ok: false, error: "the Cowork glow patch is not installed in this build" }; - var s = g.read(); - var jsonc = __cdbEx_readFileKey(__cdbEx_paths().jsonc, "coworkGlow"); - return { - ok: true, - mode: s.mode, - opacity: s.opacity, - defaultOpacity: s.defaultOpacity, - // A hand-edited .jsonc wins the per-key merge at startup, so the switch - // must show itself as locked rather than silently disagree with the file. - lockedByJsonc: jsonc === "pulse" || jsonc === "calm" ? jsonc : null - }; - }, - - "cdb-glow:set": function (mode) { - var g = globalThis.__cdbCoworkGlow; - if (!g) return { ok: false, error: "the Cowork glow patch is not installed in this build" }; - if (mode !== "pulse" && mode !== "calm") return { ok: false, error: "mode must be \"pulse\" or \"calm\"" }; - var live = g.set(mode); - if (!live || live.ok !== true) return live || { ok: false, error: "could not apply the glow mode" }; - var res = __cdbEx_writeCfg(function (cfg) { - if (mode === "calm") cfg.coworkGlow = "calm"; - else delete cfg.coworkGlow; - return mode; - }); - if (!res.ok) return { ok: false, error: "applied to " + live.windows + " window(s) but could not save: " + res.error }; - return { ok: true, mode: mode, windows: live.windows, path: res.path }; - }, - // Theme picker (Ctrl+Shift+T). The window and the hotkey live in // patches/community/add_feature_theme_picker.nim, which reads this same key // itself on every press - persistence is ours because this file is the - // single writer of the .json, the same cross-patch split as the glow above. + // single writer of the .json. // // ON IS THE DEFAULT: the shortcut is how a fresh install finds the themes, // so an absent key means enabled and only an explicit false takes it away. diff --git a/js/extra_settings_page.js b/js/extra_settings_page.js index d81cdcee..0fa795f2 100644 --- a/js/extra_settings_page.js +++ b/js/extra_settings_page.js @@ -1179,9 +1179,8 @@ // A hand-edited .jsonc wins the startup merge, so the switch must not // pretend it can override it. - // Truthy, not === true: the two handlers answer differently in KIND - the - // diff-views one with a boolean, the glow one with the locking value - // itself ("pulse"/"calm") or null. Both mean "the .jsonc decided this". + // Truthy, not === true: some handlers answer null rather than false when + // the .jsonc leaves the key alone. if (res.lockedByJsonc) { toggle.title = "Edit " + spec.lockFile + " to change this"; stateLine.textContent = describe() + " - set in " + spec.lockFile; @@ -1965,7 +1964,7 @@ // activeSubagentMiB) next to the saved value, so describe() can say "after a // restart" for exactly as long as that is true, and stop saying it once the // app has been restarted. The numbers themselves stay hand-edited in the - // .jsonc (like coworkGlowOpacity); this row only shows them. + // .jsonc; this row only shows them. function renderTranscriptLimitsRow(panel) { return renderToggleRow(panel, { section: "Sessions", @@ -2436,37 +2435,6 @@ } } - // --- motion: the pulsing Cowork glow ------------------------------------ - // Ours and it applies live, so it is a row in Community Features rather than a - // nav entry of its own. - function renderGlowRow(panel) { - return renderToggleRow(panel, { - section: "Motion", - title: "Calm the Cowork glow", - // Keep this short: the mechanism and the GPU reasoning belong in the README - // and the patch header, not in front of the user. - note: "Stops the Cowork hero glow from pulsing and dims it. Easier on laptops and weak GPUs. " + - "Applies live.", - ariaLabel: "calm the Cowork glow", - read: "glowRead", - write: "glowSet", - lockFile: "claude-desktop-extra.jsonc", - isOn: function (res) { return res.mode === "calm"; }, - describe: function (on, res) { - return on - ? "calm - static at " + Math.round((res.opacity || 0) * 100) + "% opacity" - : "pulsing (claude.ai default)"; - }, - writeArg: function (next) { return next ? "calm" : "pulse"; }, - toast: function (next, r) { - return next - ? "Cowork glow calmed in " + r.windows + " window(s)" - : "Cowork glow restored to pulsing"; - }, - errorPrefix: "Could not change the glow: " - }); - } - // --- shortcuts: the Ctrl+Shift+T theme gallery --------------------------- // The one row here that is ON unless you say otherwise: the shortcut is how a // fresh install finds the themes at all, so an absent key means on and only an @@ -2770,8 +2738,6 @@ // expand button with it. // * panel tabs - same shape: the page half polls state() every 5s and // setEnabled(false) removes the tab bar and untags the columns. - // * cowork glow - the set() handler insertCSS/removeInsertedCSS-es every - // tracked claude.ai view immediately, so it is instant. // * theme picker - the hotkey re-reads the config file on every press. // The two polled ones are the reason their notes promise "within a few // seconds" rather than "instantly". @@ -2796,7 +2762,6 @@ renderWindowControlsRow, renderNativeTitlebarRow, renderWindowTransparencyRow, - renderGlowRow, renderThemePickerRow, renderDevToolsRow, renderCuBackgroundRow diff --git a/js/files_quick_open_main.js b/js/files_quick_open_main.js index c20a2e41..eb26a4d8 100644 --- a/js/files_quick_open_main.js +++ b/js/files_quick_open_main.js @@ -58,7 +58,7 @@ try { return _app.getPath("userData"); } catch (e) { return null; } } // Same as every other config consumer (js/diff_views_main.js, extra_settings_main.js, - // growthbook_overrides.js, patches/add_feature_cowork_glow.nim): the one-time + // growthbook_overrides.js): the one-time // claude-desktop-bin.* -> claude-desktop-extra.* rename migration is installed by // the custom-themes patch and same-anchor prefix injections stack in reverse, so // it may not have run yet when we get here. Nudging it before every path @@ -193,7 +193,7 @@ } mirrorEnv(readPrefFromDisk().value === true); - // Exact-origin allowlist, same posture as the diff-views and cowork-glow + // Exact-origin allowlist, same posture as the diff-views and Extra settings // sender checks: comparing parsed origins instead of testing whether the raw // URL STRING contains "claude.ai"/"claude.com" anywhere. A substring test // would also pass a lookalike host such as "https://evil.example/?next= diff --git a/js/growthbook_overrides.js b/js/growthbook_overrides.js index cf51cb97..dbd649d5 100644 --- a/js/growthbook_overrides.js +++ b/js/growthbook_overrides.js @@ -110,21 +110,6 @@ "{", " // \"activeTheme\": \"mario\",", "", - " // coworkGlow: for laptops and machines without much graphics power. The glow", - " // behind the Cowork hero pulses between 55% and 100% opacity every 3.2s and", - " // never stops, so the compositor never settles - and where the launcher falls", - " // back to software rendering (--disable-gpu-compositing / --disable-gpu) those", - " // frames are redrawn on the CPU. \"calm\" stops the pulse and holds the glow at a", - " // fixed opacity; omit it (or \"pulse\") for claude.ai's own behaviour.", - " // Settings -> Extra -> Features has a switch for this and applies it live; a", - " // value set HERE wins the merge, so that switch shows itself as locked while", - " // this line is active.", - " // \"coworkGlow\": \"calm\",", - "", - " // coworkGlowOpacity: the fixed opacity \"calm\" holds, 0..1 (default 0.55, the", - " // dim end of upstream's own pulse). Hand edits need a restart.", - " // \"coworkGlowOpacity\": 0.55,", - "", " // transcriptLimits: for long Code sessions, especially ones with many browser", " // screenshots. The app only loads the last 50 MiB of a session's transcript, plus", " // one shared 32 MiB for all of its subagents' transcripts, and hides the rest behind", diff --git a/js/panel_tabs_main.js b/js/panel_tabs_main.js index 4bf489ee..71128756 100644 --- a/js/panel_tabs_main.js +++ b/js/panel_tabs_main.js @@ -58,7 +58,7 @@ try { return _app.getPath("userData"); } catch (e) { return null; } } // Same as every other config consumer (js/diff_views_main.js, extra_settings_main.js, - // growthbook_overrides.js, patches/add_feature_cowork_glow.nim): the one-time + // growthbook_overrides.js): the one-time // claude-desktop-bin.* -> claude-desktop-extra.* rename migration is installed by // the custom-themes patch and same-anchor prefix injections stack in reverse, so // it may not have run yet when we get here. Nudging it before every path @@ -163,7 +163,7 @@ return { ok: true, path: p }; } - // Exact-origin allowlist, same posture as the diff-views and cowork-glow + // Exact-origin allowlist, same posture as the diff-views and Extra settings // sender checks: comparing parsed origins instead of testing whether the raw // URL STRING contains "claude.ai"/"claude.com" anywhere. A substring test // would also pass a lookalike host such as "https://evil.example/?next= diff --git a/js/transcript_limits_main.js b/js/transcript_limits_main.js index e8995b37..fdf00faf 100644 --- a/js/transcript_limits_main.js +++ b/js/transcript_limits_main.js @@ -18,7 +18,7 @@ * It owns: * - the opt-in pref `transcriptLimits` (default OFF) plus two optional numbers, * `transcriptLimitsMainMiB` / `transcriptLimitsSubagentMiB`, hand-edited in - * the .jsonc like `coworkGlowOpacity` (no GUI field); + * in the .jsonc (no GUI field); * - the derivation of the two cache ceilings from those numbers (below); * - globalThis.__cdbTranscriptLimits, read once per session manager when it is * constructed (patches/community/add_feature_transcript_limits.nim); @@ -72,7 +72,7 @@ var DEFAULT_MAIN_MIB = 256; var DEFAULT_SUBAGENT_MIB = 192; // Both directions are allowed: a weak machine may want LESS than Anthropic's - // numbers. Outside this range a number is clamped, as coworkGlowOpacity is. + // numbers. Outside this range a number is clamped. var MIN_MIB = 8; var MAX_MIB = 1024; var MIB = 1048576; @@ -138,8 +138,7 @@ var jsonc = readFileJson(pathFor(JSONC_NAME)); var json = readFileJson(pathFor(JSON_NAME)); var en = pick(jsonc, json, PREF_KEY, isBool); - // Same as coworkGlowOpacity (patches/community/add_feature_cowork_glow.nim): - // a number, or a string parseFloat can read (a quoted "512" is an easy slip in + // A number, or a string parseFloat can read (a quoted "512" is an easy slip in // hand-edited JSON), is clamped into range; anything else is skipped, so the // next source (.jsonc, then .json, then the default) answers instead. Nothing // here warns - the effective numbers are what the row and the startup log show. @@ -289,7 +288,7 @@ return { ok: true, path: p }; } - // Exact-origin allowlist, same posture as the diff-views, cowork-glow and + // Exact-origin allowlist, same posture as the diff-views, Extra settings and // quick-open sender checks: comparing parsed origins instead of testing whether // the raw URL STRING contains "claude.ai" anywhere, which would also pass a // lookalike such as "https://claude.ai.evil.example". diff --git a/js/window_controls_main.js b/js/window_controls_main.js index d334a3c4..a76bc06d 100644 --- a/js/window_controls_main.js +++ b/js/window_controls_main.js @@ -130,7 +130,7 @@ } // Exact-origin allowlist, same posture as the panel-tabs, diff-views and - // cowork-glow sender checks: comparing parsed origins instead of testing + // Extra settings sender checks: comparing parsed origins instead of testing // whether the raw URL STRING contains "claude.ai"/"claude.com" anywhere. A // substring test would also pass a lookalike host such as // "https://evil.example/?next=claude.ai" or "https://claude.ai.evil.example" diff --git a/patches/community/add_feature_allow_non_anthropic_models.nim b/patches/community/add_feature_allow_non_anthropic_models.nim index eb383493..f16abb7c 100644 --- a/patches/community/add_feature_allow_non_anthropic_models.nim +++ b/patches/community/add_feature_allow_non_anthropic_models.nim @@ -181,7 +181,7 @@ proc apply*(input: string): string = # Basename order puts this patch before add_growthbook_overrides, so on a # pristine stage the directive is usually still there; when an earlier patch # has already displaced it, the prepend is the normal case, not an error. - # Same shape as add_feature_cowork_glow / add_feature_theme_picker. + # Same shape as add_feature_theme_picker. let strictPrefix = "\"use strict\";" if result.startsWith(strictPrefix): result = strictPrefix & MODULE_JS & "\n;\n" & result[strictPrefix.len .. ^1] diff --git a/patches/community/add_feature_cowork_glow.nim b/patches/community/add_feature_cowork_glow.nim deleted file mode 100644 index b7a90b90..00000000 --- a/patches/community/add_feature_cowork_glow.nim +++ /dev/null @@ -1,203 +0,0 @@ -# @patch-target: app.asar.contents/.vite/build/index.js -# @patch-type: nim -# -# Let the pulsing Cowork glow be held still from Settings -> Extra -> Features, -# for laptops and machines without much graphics power. -# -# The Cowork hero carries an element with class `.cowork-hero-glow`, and the -# remote claude.ai stylesheet gives it: -# -# .cowork-hero-glow { animation: 3.2s ease-in-out infinite cowork-hero-glow-pulse } -# @keyframes cowork-hero-glow-pulse { 0%,to { opacity:.55 } 50% { opacity:1 } } -# -# `infinite` is the part that matters. The animation never ends, so for as long as -# a Cowork view is open the compositor is handed work every frame and the display -# pipeline never idles - on a laptop that is battery spent on decoration. An -# opacity animation is cheap while it is GPU-composited, but this is Linux: our -# own launcher falls back to `--disable-gpu-compositing` on software/llvmpipe -# setups and to `--disable-gpu` outright when the GPU is blocklisted, and in those -# configurations the glow is redrawn on the CPU for every one of those frames. -# -# Upstream's only escape hatch is `prefers-reduced-motion: reduce`, an OS-wide -# setting that flattens every other animation in the app along with it; this gives -# the glow its own switch instead. -# -# No frame-time or power figure is claimed: the rationale is the mechanism above - -# an unending animation plus our software-rendering fallbacks - not a benchmark. -# -# The class carries nothing but the animation - the gradient and blur come from -# utilities on the same element - so `animation: none` alone would leave the glow -# parked at its own opacity, i.e. brighter than the pulse's average rather than -# calmer. "calm" therefore pins BOTH: no animation, and a fixed opacity (default -# .55, the dim end of upstream's own range). -# -# Applied with webContents.insertCSS so no page-side script is needed, and -# removed/re-inserted on change so the switch takes effect in every open window -# without a restart. globalThis.__cdbCoworkGlow is the API the Extra settings -# page reaches through its ipcMain handlers (mirroring globalThis.__cdbThemes). -# -# NOTE: `animate-[conway-pulse-glow_2s_ease-in-out_infinite]` also exists in the -# same stylesheet and looks related, but `@keyframes conway-pulse-glow` is not -# defined anywhere in the shipped CSS, so that utility animates nothing and is -# deliberately NOT targeted. -# -# Break risk: LOW for the injection - same "use strict" anchor as -# fix_cowork_font, no regex against minified app code. The selector belongs to -# the remote claude.ai SPA, so a deploy can rename it; the CSS then simply -# matches nothing and the glow keeps pulsing as it does today. - -import std/[os, strutils] - -const INJECTION = - """;(function(){ -if(typeof process==="undefined"||!process.versions||!process.versions.electron)return; -var _e=require("electron"),_app=_e.app,_fs=require("fs"),_path=require("path"); -function __cdbG_log(m){try{(globalThis.__cdbDiag||function(){})("[cowork-glow] "+m)}catch(e){}} - -var __cdbG_DEFAULT_OPACITY=0.55; -var __cdbG_mode="pulse",__cdbG_opacity=__cdbG_DEFAULT_OPACITY; -// wc -> key returned by insertCSS, so it can be removed again on change. -var __cdbG_keys=new WeakMap(); -var __cdbG_tracked=[]; - -function __cdbG_strip(s){ -return s.replace(/("(?:[^"\\]|\\.)*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g,function(m,q){return q?q:""}) - .replace(/,(\s*[}\]])/g,"$1"); -} -function __cdbG_read(p){ -try{var r=_fs.readFileSync(p,"utf8");return JSON.parse(__cdbG_strip(r))}catch(e){return null} -} -// New name first, legacy name as a fallback: the one-time -// claude-desktop-bin -> -extra migration lives in the custom-themes patch -// (globalThis.__cdbCfgMigrate) and same-anchor prefix injections stack in -// reverse, so it may not have run yet when this does. Reading both makes the -// order moot instead of losing a pre-rename config on the first launch. -function __cdbG_pick(d,newName,oldName){ -var v=__cdbG_read(_path.join(d,newName)); -return v!==null?v:__cdbG_read(_path.join(d,oldName)); -} -// .json then .jsonc, .jsonc wins - the same precedence the theme engine uses. -function __cdbG_cfg(){ -try{(globalThis.__cdbCfgMigrate||function(){})()}catch(e){} -var d=_app.getPath("userData"),out={}; -var a=__cdbG_pick(d,"claude-desktop-extra.json","claude-desktop-bin.json"); -var b=__cdbG_pick(d,"claude-desktop-extra.jsonc","claude-desktop-bin.jsonc"); -var k; -if(a&&typeof a==="object")for(k in a)out[k]=a[k]; -if(b&&typeof b==="object")for(k in b)out[k]=b[k]; -return out; -} -function __cdbG_clampOpacity(v){ -var n=typeof v==="number"?v:parseFloat(v); -if(!isFinite(n))return __cdbG_DEFAULT_OPACITY; -if(n<0)n=0;if(n>1)n=1; -return n; -} -function __cdbG_css(){ -return ".cowork-hero-glow{animation:none!important;opacity:"+__cdbG_opacity+"!important}"; -} -function __cdbG_isClaude(wc){ -try{var u=wc.getURL()||"";return u.indexOf("claude.ai")!==-1||u.indexOf("claude.com")!==-1}catch(e){return false} -} -function __cdbG_clear(wc){ -var k=__cdbG_keys.get(wc); -if(k===undefined)return; -__cdbG_keys.delete(wc); -try{wc.removeInsertedCSS(k)}catch(e){} -} -// Returns whether this webContents was handled, so switching back to "pulse" -// reports the windows it cleared rather than a misleading zero. -function __cdbG_push(wc){ -if(!wc||wc.isDestroyed()||!__cdbG_isClaude(wc))return false; -__cdbG_clear(wc); -if(__cdbG_mode!=="calm")return true; -try{ -var p=wc.insertCSS(__cdbG_css()); -if(p&&typeof p.then==="function")p.then(function(k){__cdbG_keys.set(wc,k)}).catch(function(){}); -return true; -}catch(e){return false} -} -function __cdbG_pushAll(){ -var n=0; -for(var i=0;i<__cdbG_tracked.length;i++){ -var wc=__cdbG_tracked[i]; -try{if(wc.isDestroyed())continue}catch(e){continue} -if(__cdbG_push(wc))n++; -} -return n; -} - -// The Extra settings page reaches these through validated ipcMain handlers. -globalThis.__cdbCoworkGlow={ -version:1, -read:function(){return {mode:__cdbG_mode,opacity:__cdbG_opacity,defaultOpacity:__cdbG_DEFAULT_OPACITY}}, -set:function(mode){ -if(mode!=="pulse"&&mode!=="calm")return {ok:false,error:"mode must be \"pulse\" or \"calm\""}; -__cdbG_mode=mode; -var n=__cdbG_pushAll(); -__cdbG_log("mode="+mode+" applied to "+n+" window(s)"); -return {ok:true,mode:mode,windows:n}; -} -}; - -try{ -var c0=__cdbG_cfg(); -if(c0.coworkGlow==="calm")__cdbG_mode="calm"; -__cdbG_opacity=__cdbG_clampOpacity(c0.coworkGlowOpacity===undefined?__cdbG_DEFAULT_OPACITY:c0.coworkGlowOpacity); -__cdbG_log("startup mode="+__cdbG_mode+" opacity="+__cdbG_opacity); -}catch(e){} - -_app.on("web-contents-created",function(_ev,wc){ -if(__cdbG_tracked.indexOf(wc)<0)__cdbG_tracked.push(wc); -wc.on("destroyed",function(){ -var i=__cdbG_tracked.indexOf(wc); -if(i>=0)__cdbG_tracked.splice(i,1); -}); -// insertCSS applies to the current document only, so re-apply per navigation. -wc.on("dom-ready",function(){try{__cdbG_push(wc)}catch(e){}}); -}); -})();""" - -const MARKER = "__cdbCoworkGlow" - -proc apply*(input: string): string = - result = input - - # Idempotency: assert OUR injected end-state is present, not merely that some - # pre-patch pattern is gone (AGENTS.md rule 6). - if MARKER in result: - echo " [OK] cowork glow: injection already present (idempotent)" - return - - let strictPrefix = "\"use strict\";" - if result.startsWith(strictPrefix): - result = strictPrefix & INJECTION & result[strictPrefix.len .. ^1] - echo " [OK] cowork glow injected after \"use strict\"" - else: - result = INJECTION & result - echo " [OK] cowork glow prepended" - - if MARKER notin result: - echo " [FAIL] cowork glow: injection not present after patching" - quit(1) - -when isMainModule: - if paramCount() != 1: - echo "Usage: add_feature_cowork_glow " - quit(1) - let filePath = paramStr(1) - echo "=== Patch: add_feature_cowork_glow ===" - echo " Target: " & filePath - if not fileExists(filePath): - echo " [FAIL] File not found: " & filePath - quit(1) - let input = readFile(filePath) - let output = apply(input) - if output != input: - writeFile(filePath, output) - echo " [PASS] cowork glow applied" - else: - if MARKER notin output: - echo " [FAIL] No changes made and injection is absent" - quit(1) - echo " [OK] Already applied (no changes needed)" diff --git a/patches/core/add_feature_extra_settings.nim b/patches/core/add_feature_extra_settings.nim index 7289e37a..fffd774f 100644 --- a/patches/core/add_feature_extra_settings.nim +++ b/patches/core/add_feature_extra_settings.nim @@ -9,7 +9,7 @@ # the live page. This patch is the main-process half of that. # # * ipcMain handlers: cdb-extra:themes-list / :paths / :reveal / :diag, -# cdb-flags:catalog / :read / :set / :unset, cdb-glow:read / :set, +# cdb-flags:catalog / :read / :set / :unset, # cdb-deploy:read / :mode / :set / :apply / :raw / :save-raw, cdb-app:relaunch. # cdb-themes:apply and cdb-themes:active are NOT registered here - they are # owned by add_feature_theme_picker.nim and the page calls them directly. diff --git a/scripts/apply_patches.py b/scripts/apply_patches.py index a50bf5a4..9f61a716 100644 --- a/scripts/apply_patches.py +++ b/scripts/apply_patches.py @@ -33,7 +33,7 @@ # forgotten `git mv` changes what gets discovered, the build must fail rather # than quietly ship a release with a patch missing. Bump this when you add or # remove a patch. -EXPECTED_PATCH_COUNT = 61 +EXPECTED_PATCH_COUNT = 60 # Byte strings that only ever appear in a bundle one of our builds already # patched: every injected identifier starts with `__cdb`, and diff --git a/scripts/tests/community/test-transcript-limits-main.mjs b/scripts/tests/community/test-transcript-limits-main.mjs index 494f1a1a..b761672a 100644 --- a/scripts/tests/community/test-transcript-limits-main.mjs +++ b/scripts/tests/community/test-transcript-limits-main.mjs @@ -160,7 +160,7 @@ const snap = (o) => JSON.stringify(o); ok(load(dir).g.__cdbTranscriptLimits().mainBytes === 100 * MIB, "a fractional number is rounded to whole MiB"); rmSync(dir, { recursive: true, force: true }); } -// Same as coworkGlowOpacity: a number outside the range is CLAMPED into it, a value that +// A number outside the range is CLAMPED into it, a value that // is not a number falls back to the default, and nothing is warned about - the numbers in // effect (shown in the row and the startup log) are the feedback. for (const [label, val, want] of [["below the minimum", 7, 8], ["above the maximum", 5000, 1024], ["the old 4096 maximum", 4096, 1024], ["negative", -5, 8]]) { @@ -168,15 +168,15 @@ for (const [label, val, want] of [["below the minimum", 7, 8], ["above the maxim const m = load(dir); const L = m.g.__cdbTranscriptLimits(); await tick(); - ok(L.mainBytes === want * MIB, "a number outside the range (" + label + ") is clamped to " + want + " MiB, as coworkGlowOpacity is"); + ok(L.mainBytes === want * MIB, "a number outside the range (" + label + ") is clamped to " + want + " MiB"); ok(m.logs.every((l) => !/WARN/.test(l)), "...without a warning (" + label + ")"); rmSync(dir, { recursive: true, force: true }); } -// A quoted number is accepted, as coworkGlowOpacity accepts "0.5": an easy slip in hand-edited JSON. +// A quoted number is accepted: an easy slip in hand-edited JSON. for (const [label, val, want] of [["a quoted number", "100", 100], ["a quoted number above the range", "5000", 1024], ["a quoted number below the range", "2", 8]]) { const dir = profile({ [J]: { transcriptLimits: true, transcriptLimitsMainMiB: val } }); const L = load(dir).g.__cdbTranscriptLimits(); - ok(L.mainBytes === want * MIB, label + " is read like coworkGlowOpacity reads one (" + JSON.stringify(val) + " -> " + want + " MiB)"); + ok(L.mainBytes === want * MIB, label + " is read as a number (" + JSON.stringify(val) + " -> " + want + " MiB)"); rmSync(dir, { recursive: true, force: true }); } for (const [label, val] of [["text", "lots"], ["an empty string", ""], ["null", null], ["an object", {}], ["an array", []], ["a boolean", true]]) { @@ -285,8 +285,8 @@ for (const [label, heap] of [["getHeapStatistics() throws", "throw"], ["heap_siz } // --- 4b. a misspelled key is ignored, exactly as every other feature's config is ---------- -// The real-world case was "transcriptLimtisMainMiB": 20 (letters swapped). Like a misspelled -// coworkGlowOpacity it simply has no effect; the row and the startup log show the numbers +// The real-world case was "transcriptLimtisMainMiB": 20 (letters swapped). Like any other +// misspelled key it simply has no effect; the row and the startup log show the numbers // actually in effect, which is how it shows. { const dir = profile({ [J]: { transcriptLimits: true, transcriptLimtisMainMiB: 20 } }); diff --git a/scripts/tests/core/test-extra-settings-dom.mjs b/scripts/tests/core/test-extra-settings-dom.mjs index 3e80c3d4..b3a9eea6 100644 --- a/scripts/tests/core/test-extra-settings-dom.mjs +++ b/scripts/tests/core/test-extra-settings-dom.mjs @@ -325,8 +325,7 @@ async function featuresPanel(featuresItem) { ok(!!rs && rs.classList.contains("cdbx-hide"), "a hidden Restart now next to the switch: " + label); }); [ - "show the Code tab's side panels as tabs instead of a split layout", - "calm the Cowork glow" + "show the Code tab's side panels as tabs instead of a split layout" ].forEach(function (label) { const sw = panel.querySelector('.cdbx-switch[aria-label="' + label + '"]'); ok(!!sw, "renders the live row: " + label); @@ -775,16 +774,6 @@ async function featuresPanel(featuresItem) { featuresItem.click(); await sleep(200); - const glow = panel.querySelector(".cdbx-switch[aria-label='calm the Cowork glow']"); - ok(!!glow, "the Cowork glow switch renders in the Community Features panel"); - if (glow) { - ok(glow.getAttribute("aria-checked") === "false", "the glow switch reflects mode 'pulse'"); - ok(!glow.disabled, "the glow switch is enabled when the .jsonc does not set it"); - glow.click(); - await sleep(60); - ok(glow.getAttribute("aria-checked") === "true", "clicking the glow switch turns it on"); - } - // The theme picker: the only switch that is on with nothing on disk, because // the shortcut is how a fresh install finds the themes at all. const pickSel = ".cdbx-switch[aria-label='open the theme gallery with Ctrl+Shift+T']"; @@ -882,8 +871,8 @@ async function featuresPanel(featuresItem) { ok(visible().join(",") === "Shortcuts", "a note match works too: " + visible().join(",")); - await type("motion"); - ok(visible().join(",") === "Motion", "so does a section match: " + visible().join(",")); + await type("source control"); + ok(visible().join(",") === "Source control", "so does a section match: " + visible().join(",")); await type("no such feature"); ok(visible().length === 0, "a needle nothing matches hides every row"); @@ -1651,7 +1640,7 @@ async function flagsPanel(flagsItem) { ok(panel.querySelector(".cdbx-h1").textContent === "Anthropic Features", "the heading spells the full name out even though the nav row says Anthropic: " + panel.querySelector(".cdbx-h1").textContent); - ok(!panel.querySelector(".cdbx-switch[aria-label='calm the Cowork glow']"), + ok(!panel.querySelector(".cdbx-switch[aria-label=\"show the Code tab's side panels as tabs instead of a split layout\"]"), "our own switches stayed behind in Community Features"); // The restart notice speaks only for these flags, which is why it lives here. @@ -1902,8 +1891,8 @@ async function themesPanel(themesItem) { themesItem.click(); await sleep(80); ok(!!document.querySelector(".cdbx-sections"), "the Themes panel renders sections"); - ok(!document.querySelector(".cdbx-panel .cdbx-switch[aria-label='calm the Cowork glow']"), - "the Cowork glow switch is NOT in the Themes panel"); + ok(!document.querySelector(".cdbx-panel .cdbx-switch[aria-label=\"show the Code tab's side panels as tabs instead of a split layout\"]"), + "the panel tabs switch is NOT in the Themes panel"); const secs = sections(); ok(labels() === "Your themes:1 | Gaming:3 | Common:5 | More:1", @@ -2702,8 +2691,6 @@ window.cdbExtra = { window.__relaunchCalls++; return Promise.resolve({ ok: true }); }, - glowRead: stub({ ok: true, mode: "pulse", opacity: 0.55, defaultOpacity: 0.55, lockedByJsonc: null }), - glowSet: stub({ ok: true, mode: "calm", windows: 1, path: "/tmp/x.json" }), panelTabsRead: function () { return Promise.resolve(window.__panelTabsState); }, panelTabsSet: function (enabled) { window.__panelTabsCalls.push(enabled);