Release WV (v0.51.641): dark-mode disabled-plugin badge contrast + active-first sort (#4848, closes #4713)

Release WV (v0.51.641): dark-mode disabled-plugin badge contrast + active-first sort (#4848, closes #4713)
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-06-24 20:20:46 -07:00
commit ff110458fd
4 files changed
+111 -1

No files matched your search

+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.641] — 2026-06-25 — Release WV (disabled plugins read as disabled in dark mode, active ones sort first)
### Fixed
- **In dark mode, disabled plugins no longer show a green "enabled-looking" badge, and active plugins sort to the top of the Plugins list.** The disabled-badge style lost a CSS specificity contest to the dark provider-badge rule, so a disabled plugin's badge rendered the same green as an enabled one — indistinguishable at a glance. A dark-mode-specific rule now renders the disabled badge in the muted surface color. The Plugins list also sorts active/enabled plugins above inactive ones (a stable partition that never reorders within a bucket), with the sort bucket always matching the badge actually shown. Thanks @rodboev. (#4848, closes #4713)
## [v0.51.640] — 2026-06-25 — Release WU (faster profile switching — Claude Code transcript parses are cached)
### Fixed
+27 -1
View File
@@ -8253,6 +8253,32 @@ async function handlePluginEnableToggle(pluginKey, checked){
}
}
function _pluginActivationState(plugin){
const activation=(plugin&&typeof plugin.activation==='string')
? plugin.activation
: (plugin&&plugin.enabled===false ? 'disabled' : 'enabled');
// Mirror _buildPluginCard's isProviderActive precedence: an explicit
// is_active_provider===true overrides the activation string so the sort
// bucket always matches the badge.
if(plugin&&plugin.is_active_provider===true) return 'provider';
if(activation==='exclusive'||activation==='provider'){
if(plugin&&plugin.is_active_provider===false) return 'disabled';
return 'provider';
}
if(activation==='enabled') return 'enabled';
return 'disabled';
}
function _partitionPluginsActiveFirst(plugins){
const active=[];
const inactive=[];
for(const p of plugins){
if(_pluginActivationState(p)==='disabled') inactive.push(p);
else active.push(p);
}
return active.concat(inactive);
}
async function loadPluginsPanel(){
const list=$('pluginsList');
const empty=$('pluginsEmpty');
@@ -8271,7 +8297,7 @@ async function loadPluginsPanel(){
}
if(empty) empty.style.display='none';
list.style.display='';
for(const plugin of plugins){
for(const plugin of _partitionPluginsActiveFirst(plugins)){
list.appendChild(_buildPluginCard(plugin));
}
}catch(e){
+1
View File
@@ -4654,6 +4654,7 @@ main.main > #mainPlugin{display:none;}
.plugin-card-header{cursor:default;}
.plugin-card-header:hover{background:transparent;}
.plugin-card-badge-disabled{background:var(--surface);color:var(--muted);}
:root.dark .provider-card-badge.plugin-card-badge-disabled{background:var(--surface);color:var(--muted);}
.plugin-hook-list{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
.plugin-hook-badge{display:inline-flex;align-items:center;border:1px solid var(--border2);background:var(--code-bg);color:var(--text);border-radius:999px;padding:3px 8px;font-size:11px;font-family:var(--font-mono);}
.plugin-hook-empty{font-size:12px;color:var(--muted);font-style:italic;}
@@ -0,0 +1,77 @@
"""Regression checks for Issue #4713: plugin badge and sort-order alignment.
Focus:
- _pluginActivationState must check is_active_provider===true before the
activation string branch so the sort bucket always matches the badge shown
by _buildPluginCard (Greptile P2 finding on PR #4848).
- _buildPluginCard must show the active-provider badge when is_active_provider
is true, regardless of the activation string.
"""
import re
from pathlib import Path
PANELS_JS = (Path(__file__).parent.parent / "static" / "panels.js").read_text(encoding="utf-8")
def _function_block(src: str, name: str) -> str:
marker = re.search(rf"(^|\n)(?:async\s+)?function\s+{re.escape(name)}\(", src)
assert marker is not None, f"{name}() not found"
start = marker.start()
next_marker = re.search(r"\n(?:function\s+\w+\(|async\s+function\s+\w+\()", src[start + 1:])
end = start + 1 + next_marker.start() if next_marker else len(src)
return src[start:end]
def test_activation_state_checks_is_active_provider_before_activation_string():
"""is_active_provider===true must be checked before the activation branch."""
block = _function_block(PANELS_JS, "_pluginActivationState")
pos_early = block.find("is_active_provider===true")
pos_branch = block.find("activation==='exclusive'")
assert pos_early != -1, "_pluginActivationState must check is_active_provider===true"
assert pos_branch != -1, "_pluginActivationState must have the exclusive|provider branch"
assert pos_early < pos_branch, (
"is_active_provider===true check must precede the activation==='exclusive' branch"
)
def test_activation_state_early_guard_returns_provider():
"""The early is_active_provider===true guard must return 'provider'."""
block = _function_block(PANELS_JS, "_pluginActivationState")
match = re.search(r"is_active_provider===true\)?\s*return\s*'provider'", block)
assert match is not None, (
"_pluginActivationState must return 'provider' when is_active_provider===true"
)
def test_build_plugin_card_uses_is_active_provider_boolean():
"""_buildPluginCard must read plugin.is_active_provider when it's a boolean."""
block = _function_block(PANELS_JS, "_buildPluginCard")
assert "is_active_provider" in block, (
"_buildPluginCard must reference plugin.is_active_provider"
)
def test_activation_state_disabled_plugin_with_active_provider_sorts_active():
"""Ensure badge and sort agree: is_active_provider===true wins over activation='disabled'.
Static code check verifying the fix structure rather than a runtime
execution, since panels.js runs in a browser context.
"""
block = _function_block(PANELS_JS, "_pluginActivationState")
lines = block.splitlines()
early_return_line = None
branch_line = None
for i, line in enumerate(lines):
if "is_active_provider===true" in line and "return 'provider'" in line:
early_return_line = i
if early_return_line is None and "is_active_provider===true" in line:
if i + 1 < len(lines) and "return 'provider'" in lines[i + 1]:
early_return_line = i
if "activation==='exclusive'" in line:
branch_line = i
assert early_return_line is not None, "early is_active_provider===true return not found"
assert branch_line is not None, "exclusive|provider branch not found"
assert early_return_line < branch_line, (
"is_active_provider===true return must precede the exclusive|provider branch"
)