Skip to content

Commit 8e086aa

Browse files
Fix invisible sidebar badge on transparent themes (#186); point schema-fence message at doctor --force (#185)
#186: the sidebar header badge drew its label with fg={theme.background} on a theme.accent background. Themes that set background:"none" to respect terminal transparency resolve theme.background to RGBA(0,0,0,0), so the badge text rendered fully transparent and vanished, making transparent themes unusable. Derive the text color from the badge's own (always-opaque) accent background via a luminance-based black/white pick (readableTextColorOn), so the label stays readable regardless of background transparency. Pure, unit-tested helper. #185: the schema-fence message (DB on disk is a newer schema than this binary supports) said only "upgrade Magic Context/OpenCode/Pi". The common cause is a pinned or stale plugin sharing the shared context.db with a newer instance, and the concrete remedy is 'doctor --force' (which unpins the config entry AND clears the stale npm plugin cache). Updated the shared storage log line, the Desktop fence warning, and Pi's fence warning to name the likely cause and the exact command. Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
1 parent b394fa3 commit 8e086aa

6 files changed

Lines changed: 72 additions & 5 deletions

File tree

packages/pi-plugin/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
468468
if (!db) {
469469
warn(
470470
`Magic Context (pi) storage unavailable at ${dbPath} (cache schema is newer than this binary supports). ` +
471-
"Plugin will not register hooks; upgrade/restart Pi/OpenCode/Magic Context to recover.",
471+
"A pinned or stale plugin is likely sharing this database with a newer instance. " +
472+
"Plugin will not register hooks; run 'npx @cortexkit/magic-context@latest doctor --force' " +
473+
"(or update Pi/OpenCode) and restart to recover.",
472474
);
473475
return;
474476
}

packages/plugin/src/features/magic-context/storage-db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export function enforceSchemaFence(
296296
}
297297
lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
298298
log(
299-
`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). Upgrade Magic Context/OpenCode/Pi before writing to this cache.`,
299+
`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). A pinned or stale plugin is likely sharing this database with a newer instance; update or unpin Magic Context with 'npx @cortexkit/magic-context@latest doctor --force', then restart.`,
300300
);
301301
return false;
302302
}

packages/plugin/src/plugin/conflict-warning-hook.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,13 @@ export async function sendSchemaFenceWarning(
524524
`newer build (OpenCode and Pi share one database). This build only supports`,
525525
`up to v${detail.supportedVersion}, so it has fail-closed to avoid corrupting the cache.`,
526526
"",
527-
"Update Magic Context on this harness (or update OpenCode/Pi) to the latest",
528-
"version, then restart. Your data is safe — nothing is disabled permanently.",
527+
"This usually means a pinned or stale plugin is sharing the database with a",
528+
"newer instance. Update or unpin Magic Context on this harness (or update",
529+
"OpenCode/Pi) to the latest version, then restart. The fastest fix is:",
530+
"",
531+
" npx @cortexkit/magic-context@latest doctor --force",
532+
"",
533+
"Your data is safe; nothing is disabled permanently.",
529534
].join("\n");
530535

531536
try {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { readableTextColorOn } from "./badge-contrast";
3+
4+
describe("readableTextColorOn", () => {
5+
test("dark accent gets white text", () => {
6+
// A typical dark accent (deep blue/purple) should read as white.
7+
expect(readableTextColorOn({ r: 0.1, g: 0.1, b: 0.3 })).toBe("#ffffff");
8+
expect(readableTextColorOn({ r: 0, g: 0, b: 0 })).toBe("#ffffff");
9+
});
10+
11+
test("light accent gets black text", () => {
12+
// Light/pastel accents should read as black.
13+
expect(readableTextColorOn({ r: 0.9, g: 0.9, b: 0.7 })).toBe("#000000");
14+
expect(readableTextColorOn({ r: 1, g: 1, b: 1 })).toBe("#000000");
15+
});
16+
17+
test("pure green is treated as light (high luma weight)", () => {
18+
// Green dominates perceived brightness, so a saturated green badge needs
19+
// dark text.
20+
expect(readableTextColorOn({ r: 0, g: 1, b: 0 })).toBe("#000000");
21+
});
22+
23+
test("pure blue is treated as dark (low luma weight)", () => {
24+
// Blue contributes little to perceived brightness, so a saturated blue
25+
// badge needs light text.
26+
expect(readableTextColorOn({ r: 0, g: 0, b: 1 })).toBe("#ffffff");
27+
});
28+
29+
test("does not depend on the (possibly transparent) background alpha", () => {
30+
// The helper only reads r/g/b — the regression in #186 was using a
31+
// background color whose alpha could be 0. Two accents with identical
32+
// rgb resolve identically regardless of any alpha the caller might pass.
33+
const a = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
34+
const b = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
35+
expect(a).toBe(b);
36+
expect(a).toBe("#ffffff");
37+
});
38+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Pick a readable text color (black or white) for text drawn ON TOP of a given
3+
* background color.
4+
*
5+
* The sidebar header badge previously drew its label with `fg={theme.background}`
6+
* on a `theme.accent` background. That breaks for themes that set
7+
* `background: "none"` (transparent) to respect terminal transparency: the
8+
* resolved background is `RGBA(0,0,0,0)`, so the badge text renders fully
9+
* transparent and disappears (issue #186). The badge background (`accent`) is
10+
* always opaque, so deriving the text color from it is transparency-proof.
11+
*
12+
* `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
13+
* minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
14+
* independent of the native color class.
15+
*/
16+
export function readableTextColorOn(bg: { r: number; g: number; b: number }): string {
17+
// Perceptual brightness (ITU-R BT.601 luma weights). Channels are 0..1, so
18+
// the result is 0..1; >= 0.5 is a "light" background that needs dark text.
19+
const brightness = 0.299 * bg.r + 0.587 * bg.g + 0.114 * bg.b;
20+
return brightness >= 0.5 ? "#000000" : "#ffffff";
21+
}

packages/plugin/src/tui/slots/sidebar-content.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
33
import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
44
import packageJson from "../../../package.json"
5+
import { readableTextColorOn } from '../badge-contrast';
56
import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
67
import { formatThresholdPercent } from "../../shared/format-threshold"
78
import {
@@ -694,7 +695,7 @@ const SidebarContent = (props: {
694695
onMouseDown={() => props.controller.toggleCollapsed()}
695696
>
696697
<box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
697-
<text fg={props.theme.background}>
698+
<text fg={readableTextColorOn(props.theme.accent)}>
698699
<b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
699700
</text>
700701
</box>

0 commit comments

Comments
 (0)