@cortexkit/opencode-magic-context 0.27.2 → 0.27.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,13 +88,7 @@ The wizard auto-detects which harnesses you have (OpenCode, Pi, or both), adds t
88
88
 
89
89
  **Troubleshooting:** `npx @cortexkit/magic-context@latest doctor` auto-detects your harnesses, checks for conflicts (compaction, OMO hooks, DCP), verifies the plugin and TUI sidebar, runs an integrity check on the database, and fixes what it can. Add `--issue` to file a ready-to-submit bug report.
90
90
 
91
- ### Adding Magic Context to an existing project
92
-
93
- Magic Context is not limited to greenfield work. Run the setup wizard from the root of the project you already use with OpenCode or Pi, then restart the harness in that same directory so the plugin can attach to the existing project identity. From that point forward, new turns are captured automatically and the historian compartmentalizes the active session as it grows.
94
-
95
- Magic Context does **not** retroactively import or reprocess OpenCode or Pi sessions that happened before it was installed. Those past conversations are not backfilled into compartments. What builds up across an existing project from day one includes project memories, dreamer-maintained docs, key files, and other context captured after installation.
96
-
97
- If you want to seed knowledge from older work, add the important facts explicitly with `ctx_memory`, write a short project note in the repo, or keep working in the project and let the historian and dreamer promote recurring facts over time. Use `/ctx-recomp` when you need to rebuild Magic Context compartments from raw message history that Magic Context has captured in that session, such as after upgrading historian rules; it is not an importer for unrelated pre-install OpenCode or Pi sessions. The dashboard can also inspect the shared CortexKit database, memories, compartments, and dreamer runs without starting a coding session.
91
+ Works the same on a brand-new or a long-running project: install, restart the harness, and Magic Context captures context from that point forward. It does not backfill OpenCode or Pi sessions from before it was installed.
98
92
 
99
93
  <details>
100
94
  <summary><strong>Compatibility with other context-management plugins</strong></summary>
@@ -52,6 +52,7 @@ export declare const __test: {
52
52
  getPendingPi(sessionId: string): PendingPiTransformDecision | undefined;
53
53
  reset(): void;
54
54
  setWriterForTests(writer: TransformDecisionWriter | null): void;
55
+ setRetentionForTests(cap: number | null): void;
55
56
  writeRow(dbPath: string, row: TransformDecisionRow): void;
56
57
  findNewestPiAssistantEntryIdAfter: typeof findNewestPiAssistantEntryIdAfter;
57
58
  };
@@ -1 +1 @@
1
- {"version":3,"file":"transform-decision-log.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/transform-decision-log.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform-decision-log.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/transform-decision-log.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -151003,7 +151003,7 @@ function enforceSchemaFence(db, dbPath, latestSupportedVersion) {
151003
151003
  return true;
151004
151004
  }
151005
151005
  lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
151006
- log(`[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.`);
151006
+ log(`[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.`);
151007
151007
  return false;
151008
151008
  }
151009
151009
  function setSqlitePragmaConfig(config2) {
@@ -165579,8 +165579,13 @@ async function sendSchemaFenceWarning(client, directory, detail) {
165579
165579
  `newer build (OpenCode and Pi share one database). This build only supports`,
165580
165580
  `up to v${detail.supportedVersion}, so it has fail-closed to avoid corrupting the cache.`,
165581
165581
  "",
165582
- "Update Magic Context on this harness (or update OpenCode/Pi) to the latest",
165583
- "version, then restart. Your data is safe nothing is disabled permanently."
165582
+ "This usually means a pinned or stale plugin is sharing the database with a",
165583
+ "newer instance. Update or unpin Magic Context on this harness (or update",
165584
+ "OpenCode/Pi) to the latest version, then restart. The fastest fix is:",
165585
+ "",
165586
+ " npx @cortexkit/magic-context@latest doctor --force",
165587
+ "",
165588
+ "Your data is safe; nothing is disabled permanently."
165584
165589
  ].join(`
165585
165590
  `);
165586
165591
  try {
@@ -196697,6 +196702,7 @@ var pendingPiDecisionBySession = new Map;
196697
196702
  var lastBoundMessageIdBySession = new Map;
196698
196703
  var scheduledWriteTokensBySession = new Map;
196699
196704
  var writerOverrideForTests = null;
196705
+ var retentionOverrideForTests = null;
196700
196706
  function normalizeMaterializeReason(harness, reason, rematerialized) {
196701
196707
  const raw = typeof reason === "string" ? reason.trim() : "";
196702
196708
  if (raw.length > 0) {
@@ -196797,7 +196803,7 @@ function writeTransformDecisionRow(dbPath, row) {
196797
196803
  WHERE session_id = ? AND harness = ?
196798
196804
  ORDER BY ts_ms DESC, rowid DESC
196799
196805
  LIMIT ?
196800
- )`).run(row.sessionId, row.harness, row.sessionId, row.harness, TRANSFORM_DECISIONS_RETENTION);
196806
+ )`).run(row.sessionId, row.harness, row.sessionId, row.harness, retentionOverrideForTests ?? TRANSFORM_DECISIONS_RETENTION);
196801
196807
  } finally {
196802
196808
  closeQuietly(db);
196803
196809
  }
@@ -197206,6 +197212,7 @@ init_session_project_storage();
197206
197212
  init_storage_meta_persisted();
197207
197213
  await init_storage();
197208
197214
  init_logger();
197215
+ init_models_dev_cache();
197209
197216
 
197210
197217
  // src/hooks/magic-context/boundary-execution.ts
197211
197218
  var FORCE_MATERIALIZE_PERCENTAGE2 = 85;
@@ -201345,6 +201352,18 @@ function findLastAssistantModel2(messages) {
201345
201352
  }
201346
201353
  return null;
201347
201354
  }
201355
+ function findNewestUserModel(messages) {
201356
+ for (let i = messages.length - 1;i >= 0; i--) {
201357
+ const info = messages[i].info;
201358
+ if (info.role !== "user")
201359
+ continue;
201360
+ if (info.model?.providerID && info.model.modelID) {
201361
+ return { providerID: info.model.providerID, modelID: info.model.modelID };
201362
+ }
201363
+ return null;
201364
+ }
201365
+ return null;
201366
+ }
201348
201367
  function createTransform(deps) {
201349
201368
  const loadedSessions = new Set;
201350
201369
  const lastEmergencyNotificationCount = new Map;
@@ -201405,15 +201424,15 @@ function createTransform(deps) {
201405
201424
  const canRunCompartments = fullFeatureMode && historianRunnable && deps.client !== undefined && compartmentDirectory.length > 0;
201406
201425
  const fallbackModelId = deps.getFallbackModelId?.(sessionId);
201407
201426
  const tModelDetect = performance.now();
201427
+ const persistedUsageBeforeResets = loadPersistedUsage(db, sessionId);
201408
201428
  if (deps.liveModelBySession) {
201409
- const lastAssistantModel = findLastAssistantModel2(messages);
201410
- if (lastAssistantModel) {
201411
- const knownModel = deps.liveModelBySession.get(sessionId);
201412
- if (!knownModel) {
201413
- deps.liveModelBySession.set(sessionId, lastAssistantModel);
201414
- } else if (knownModel.providerID !== lastAssistantModel.providerID || knownModel.modelID !== lastAssistantModel.modelID) {
201415
- sessionLog(sessionId, `transform: model change detected (${knownModel.providerID}/${knownModel.modelID} -> ${lastAssistantModel.providerID}/${lastAssistantModel.modelID}), clearing stale context state`);
201416
- deps.liveModelBySession.set(sessionId, lastAssistantModel);
201429
+ const currentOutgoingModel = findNewestUserModel(messages) ?? deps.liveModelBySession.get(sessionId) ?? findLastAssistantModel2(messages);
201430
+ if (currentOutgoingModel) {
201431
+ deps.liveModelBySession.set(sessionId, currentOutgoingModel);
201432
+ const outgoingModelKey = resolveModelKey(currentOutgoingModel.providerID, currentOutgoingModel.modelID);
201433
+ const lastUsageModelKey = persistedUsageBeforeResets?.lastObservedModelKey ?? null;
201434
+ if (lastUsageModelKey != null && outgoingModelKey != null && lastUsageModelKey !== outgoingModelKey) {
201435
+ sessionLog(sessionId, `transform: model change since last usage (${lastUsageModelKey} -> ${outgoingModelKey}), clearing stale per-model state`);
201417
201436
  updateSessionMeta(db, sessionId, {
201418
201437
  lastContextPercentage: 0,
201419
201438
  lastInputTokens: 0,
@@ -201443,7 +201462,6 @@ function createTransform(deps) {
201443
201462
  const tFirstPass = performance.now();
201444
201463
  const isFirstTransformPassForSession = !loadedSessions.has(sessionId);
201445
201464
  loadedSessions.add(sessionId);
201446
- const persistedUsageBeforeFirstPassReset = loadPersistedUsage(db, sessionId);
201447
201465
  const historianFailureState = getHistorianFailureState(db, sessionId);
201448
201466
  if (isFirstTransformPassForSession && sessionMeta) {
201449
201467
  const persistedPct = sessionMeta.lastContextPercentage ?? 0;
@@ -201462,6 +201480,17 @@ function createTransform(deps) {
201462
201480
  let emergencyRecoveryArmed = false;
201463
201481
  if (fullFeatureMode) {
201464
201482
  try {
201483
+ const armModel = deps.liveModelBySession?.get(sessionId);
201484
+ const armModelKey = deps.getModelKey?.(sessionId);
201485
+ const armSnapshot = persistedUsageBeforeResets;
201486
+ const lastMeasuredInput = armSnapshot?.usage.inputTokens ?? sessionMeta?.lastInputTokens ?? 0;
201487
+ const lastMeasuredModelKey = armSnapshot?.lastObservedModelKey ?? null;
201488
+ const armCatalogLimit = armModel ? getSdkContextLimit(armModel.providerID, armModel.modelID) : undefined;
201489
+ if (!sessionMeta?.isSubagent && armModel && typeof armCatalogLimit === "number" && armCatalogLimit > 0 && lastMeasuredInput > armCatalogLimit && lastMeasuredModelKey != null && armModelKey != null && lastMeasuredModelKey !== armModelKey && !getOverflowState(db, sessionId).needsEmergencyRecovery) {
201490
+ sessionLog(sessionId, `transform: last input ${lastMeasuredInput} (model ${lastMeasuredModelKey}) exceeds new model ${armModelKey} catalog limit ${armCatalogLimit}; arming overflow recovery proactively for the shrinking switch`);
201491
+ recordOverflowDetected(db, sessionId, undefined, armModelKey);
201492
+ resetProtectedTailNoEligibleHead(db, sessionId);
201493
+ }
201465
201494
  const overflowState = getOverflowState(db, sessionId);
201466
201495
  emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
201467
201496
  if (contextUsageEarly.percentage < 80 && !overflowState.needsEmergencyRecovery) {
@@ -201498,7 +201527,7 @@ function createTransform(deps) {
201498
201527
  sessionID: sessionId
201499
201528
  }) : undefined;
201500
201529
  const currentModelKeyForBoundary = deps.getModelKey?.(sessionId);
201501
- const persistedUsageFreshForBoundary = persistedUsageBeforeFirstPassReset && Date.now() - persistedUsageBeforeFirstPassReset.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeFirstPassReset.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || persistedUsageBeforeFirstPassReset.lastObservedModelKey === currentModelKeyForBoundary) && (resolvedContextLimit === undefined || persistedUsageBeforeFirstPassReset.lastUsageContextLimit === 0 || persistedUsageBeforeFirstPassReset.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeFirstPassReset.usage : null;
201530
+ const persistedUsageFreshForBoundary = persistedUsageBeforeResets && Date.now() - persistedUsageBeforeResets.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeResets.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || persistedUsageBeforeResets.lastObservedModelKey === currentModelKeyForBoundary) && (resolvedContextLimit === undefined || persistedUsageBeforeResets.lastUsageContextLimit === 0 || persistedUsageBeforeResets.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeResets.usage : null;
201502
201531
  const boundaryUsageForProtectedTail = persistedUsageFreshForBoundary ?? contextUsageEarly;
201503
201532
  const boundaryUsageSource = persistedUsageFreshForBoundary ? "persisted" : "live";
201504
201533
  const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens, resolvedContextLimit);
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAsLlE;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqDf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAwHf;AAkCD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC1C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAkFf"}
1
+ {"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAsLlE;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqDf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAwHf;AAkCD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC1C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAkFf"}
@@ -3,7 +3,7 @@ export declare function getTuiPreferencesFile(): string;
3
3
  export declare function readTuiPreferencesFile(): Promise<Record<string, unknown>>;
4
4
  export declare function readTuiPreferencesFileSync(): Record<string, unknown>;
5
5
  export declare const PLUGIN_KEY = "magic-context";
6
- export declare const DEFAULT_SLOT_ORDER = 200;
6
+ export declare const DEFAULT_SLOT_ORDER = 170;
7
7
  export interface MagicContextTuiPrefs {
8
8
  forceToTop: boolean;
9
9
  order: number;
@@ -0,0 +1,31 @@
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
+ * The pick is WHITE-BIASED off the accent's relative luminance: white for any
13
+ * accent in the dark half (luminance < 0.5), black only for genuinely light
14
+ * accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
15
+ * flips ordinary mid-tone accents to black: a typical orange/amber sidebar
16
+ * accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
17
+ * picks black even though white at ~3.7:1 is perfectly legible for a short bold
18
+ * label. That looks heavy and clashes with the sibling status badges, so we
19
+ * prefer white across the whole dark half and only fall to black once the accent
20
+ * is actually light (pale/pastel/near-white), where white would be unreadable.
21
+ *
22
+ * `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
23
+ * minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
24
+ * independent of the native color class.
25
+ */
26
+ export declare function readableTextColorOn(bg: {
27
+ r: number;
28
+ g: number;
29
+ b: number;
30
+ }): string;
31
+ //# sourceMappingURL=badge-contrast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAmBH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEnF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.27.2",
3
+ "version": "0.27.3",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@ import { parse, stringify } from "comment-json";
11
11
  // Cross-plugin convention (anthropic-auth / aft / magic-context all mirror it):
12
12
  // - same file name + env override + lookup order,
13
13
  // - byte-identical `computeEffectiveOrder` so the three sort consistently,
14
- // - a coordinated default-order ladder (anthropic-auth 160, AFT 180, MC 200).
14
+ // - a coordinated default-order ladder (anthropic-auth 160, MC 170, AFT 180).
15
15
  //
16
16
  // MC uses `comment-json` (already a dep, Bun-safe) for the WRITE path — a full
17
17
  // parse → mutate-one-key → stringify round-trip that preserves comments and
@@ -64,7 +64,7 @@ export function readTuiPreferencesFileSync(): Record<string, unknown> {
64
64
  }
65
65
 
66
66
  export const PLUGIN_KEY = "magic-context";
67
- export const DEFAULT_SLOT_ORDER = 200;
67
+ export const DEFAULT_SLOT_ORDER = 170;
68
68
 
69
69
  export interface MagicContextTuiPrefs {
70
70
  forceToTop: boolean;
@@ -0,0 +1,45 @@
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
+ // Only accents pale enough that white fails the contrast bar go 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("mid-tone orange accent prefers white (white-bias, matches sibling badges)", () => {
18
+ // A typical orange/amber accent reads white ~4:1 and black ~5:1. A
19
+ // higher-contrast-wins pick would flip it to black (the #186 over-
20
+ // correction); the white bias keeps it white, clearing the bold-text bar.
21
+ expect(readableTextColorOn({ r: 0.69, g: 0.455, b: 0.188 })).toBe("#ffffff");
22
+ expect(readableTextColorOn({ r: 0.741, g: 0.482, b: 0.2 })).toBe("#ffffff");
23
+ });
24
+
25
+ test("pure green is treated as light (white fails the contrast bar)", () => {
26
+ // A saturated green is bright enough that white drops below the bar.
27
+ expect(readableTextColorOn({ r: 0, g: 1, b: 0 })).toBe("#000000");
28
+ });
29
+
30
+ test("pure blue is treated as dark (low luma weight)", () => {
31
+ // Blue contributes little to perceived brightness, so a saturated blue
32
+ // badge needs light text.
33
+ expect(readableTextColorOn({ r: 0, g: 0, b: 1 })).toBe("#ffffff");
34
+ });
35
+
36
+ test("does not depend on the (possibly transparent) background alpha", () => {
37
+ // The helper only reads r/g/b — the regression in #186 was using a
38
+ // background color whose alpha could be 0. Two accents with identical
39
+ // rgb resolve identically regardless of any alpha the caller might pass.
40
+ const a = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
41
+ const b = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
42
+ expect(a).toBe(b);
43
+ expect(a).toBe("#ffffff");
44
+ });
45
+ });
@@ -0,0 +1,46 @@
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
+ * The pick is WHITE-BIASED off the accent's relative luminance: white for any
13
+ * accent in the dark half (luminance < 0.5), black only for genuinely light
14
+ * accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
15
+ * flips ordinary mid-tone accents to black: a typical orange/amber sidebar
16
+ * accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
17
+ * picks black even though white at ~3.7:1 is perfectly legible for a short bold
18
+ * label. That looks heavy and clashes with the sibling status badges, so we
19
+ * prefer white across the whole dark half and only fall to black once the accent
20
+ * is actually light (pale/pastel/near-white), where white would be unreadable.
21
+ *
22
+ * `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
23
+ * minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
24
+ * independent of the native color class.
25
+ */
26
+
27
+ // Luminance midpoint: accents below this keep white text, accents at/above it
28
+ // (light/pastel/near-white) get black. White-biased relative to the strict
29
+ // equal-contrast crossover (~0.179) so saturated mid-tone accents stay white.
30
+ const LIGHT_ACCENT_LUMINANCE = 0.5;
31
+
32
+ function srgbChannelToLinear(c: number): number {
33
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
34
+ }
35
+
36
+ function relativeLuminance(bg: { r: number; g: number; b: number }): number {
37
+ return (
38
+ 0.2126 * srgbChannelToLinear(bg.r) +
39
+ 0.7152 * srgbChannelToLinear(bg.g) +
40
+ 0.0722 * srgbChannelToLinear(bg.b)
41
+ );
42
+ }
43
+
44
+ export function readableTextColorOn(bg: { r: number; g: number; b: number }): string {
45
+ return relativeLuminance(bg) < LIGHT_ACCENT_LUMINANCE ? "#ffffff" : "#000000";
46
+ }
@@ -2,6 +2,7 @@
2
2
  import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
3
3
  import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
4
4
  import packageJson from "../../../package.json"
5
+ import { readableTextColorOn } from '../badge-contrast';
5
6
  import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
6
7
  import { formatThresholdPercent } from "../../shared/format-threshold"
7
8
  import {
@@ -694,7 +695,7 @@ const SidebarContent = (props: {
694
695
  onMouseDown={() => props.controller.toggleCollapsed()}
695
696
  >
696
697
  <box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
697
- <text fg={props.theme.background}>
698
+ <text fg={readableTextColorOn(props.theme.accent)}>
698
699
  <b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
699
700
  </text>
700
701
  </box>