@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 +1 -7
- package/dist/features/magic-context/transform-decision-log.d.ts +1 -0
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.js +43 -14
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/dist/shared/tui-preferences.d.ts +1 -1
- package/dist/tui/badge-contrast.d.ts +31 -0
- package/dist/tui/badge-contrast.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/shared/tui-preferences.ts +2 -2
- package/src/tui/badge-contrast.test.ts +45 -0
- package/src/tui/badge-contrast.ts +46 -0
- package/src/tui/slots/sidebar-content.tsx +2 -1
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
|
-
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAI/C,MAAM,MAAM,wBAAwB,GAAG,UAAU,GAAG,IAAI,CAAC;AACzD,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,MAAM,MAAM,0BAA0B,GAChC,aAAa,GACb,cAAc,GACd,sBAAsB,GACtB,UAAU,GACV,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,iBAAiB,GACjB,eAAe,GACf,mBAAmB,CAAC;AAE1B,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,0BAA0B,CAAC;IACrC,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,0BAA0B,GAAG,IAAI,CAAC;IACrD,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,oBAAqB,SAAQ,wBAAwB;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,wBAAwB,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,0BAA2B,SAAQ,wBAAwB;IACjE,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;CACjD;AAED,KAAK,uBAAuB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,KAAK,IAAI,CAAC;
|
|
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":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAIxE,OAAO,EACH,KAAK,eAAe,EAYvB,MAAM,sCAAsC,CAAC;
|
|
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}).
|
|
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
|
-
"
|
|
165583
|
-
"
|
|
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
|
|
201410
|
-
if (
|
|
201411
|
-
|
|
201412
|
-
|
|
201413
|
-
|
|
201414
|
-
|
|
201415
|
-
sessionLog(sessionId, `transform: model change
|
|
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 =
|
|
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,
|
|
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 =
|
|
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
|
@@ -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,
|
|
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 =
|
|
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.
|
|
698
|
+
<text fg={readableTextColorOn(props.theme.accent)}>
|
|
698
699
|
<b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
|
|
699
700
|
</text>
|
|
700
701
|
</box>
|