Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ independent review, blockers, and whether a later phase is authorized.
| CL-01 | `feat/cl-01-conformance-harness` | `c2113ca47b8a05c5a5f90679e4eaa640ca2c6a66` | `22d608c82d82e2746c0cef9cd761db19a8e465ee` | [#1320](https://github.com/lidge-jun/opencodex/pull/1320) | MERGED TO `dev` at `4bb249b756abd468c675d2d92fffe4da95ad3e2a` |
| CL-02 | `feat/cl-02-evidence-ledger` | `4bb249b756abd468c675d2d92fffe4da95ad3e2a` | NOT RECORDED | [#1333](https://github.com/lidge-jun/opencodex/pull/1333) | MERGED TO `dev` at `025c37916225dd685d9217e5b40190600f06d278`; POST-MERGE HARDENING [#1343](https://github.com/lidge-jun/opencodex/pull/1343) MERGED at `eee2dab4d1bbacefce56057adad51d734f346702`; FINAL CLOSURE GATE [#1348](https://github.com/lidge-jun/opencodex/pull/1348) |
| CL-03 | `feat/cl-03-live-route-probes` | `4f746d13799888ea0a8c7a111aa2ad61c2126ea0` | `003f7402f49bfe8dd710a7beba52f717051bfadf` | [#1352](https://github.com/lidge-jun/opencodex/pull/1352) | MERGED TO `dev` at `68c71a4e9cdf882d812f09fd94783a28749db629`; ACCEPTED/CLOSED |
| CL-04 | `feat/cl-04-lab-read-surfaces` | `68c71a4e9cdf882d812f09fd94783a28749db629` | NOT RECORDED | DRAFT PENDING | IMPLEMENTATION IN PROGRESS (not accepted) |
| CL-04 | `feat/cl-04-lab-read-surfaces` | `68c71a4e9cdf882d812f09fd94783a28749db629` | NOT RECORDED | [#1378](https://github.com/lidge-jun/opencodex/pull/1378) | MERGED TO `dev` at `d517161aeaa3a974ad3c0360ff0c97b03b4c4520` |
| CL-05 | `feat/cl-05-compatibility-matrix-ui` | `d517161aeaa3a974ad3c0360ff0c97b03b4c4520` | `2a159b8b7` (Models tab placement) | [#1384](https://github.com/lidge-jun/opencodex/pull/1384) | IMPLEMENTATION IN PROGRESS (not accepted) |

The CL-01 starting SHA is the exact CL-00 tip recorded when CL-01 began. Its
moving base-ref name is not a substitute for that historical SHA.
Expand Down Expand Up @@ -158,7 +159,8 @@ Claims cannot produce `PROBED`/`VERIFIED`.
- CL-01: **MERGED** via #1320 at `4bb249b756abd468c675d2d92fffe4da95ad3e2a`.
- CL-02: **MERGED** via #1333 at `025c37916225dd685d9217e5b40190600f06d278`; post-merge hardening #1343 is also **MERGED** at `eee2dab4d1bbacefce56057adad51d734f346702`; final closure is tracked in #1348.
- CL-03: **ACCEPTED/CLOSED** via [#1352](https://github.com/lidge-jun/opencodex/pull/1352), merged to `dev` at `68c71a4e9cdf882d812f09fd94783a28749db629`.
- CL-04: **IMPLEMENTATION IN PROGRESS**, authorized from accepted CL-03 merge `68c71a4e9cdf882d812f09fd94783a28749db629`.
- CL-04: **MERGED** via #1378 at `d517161aeaa3a974ad3c0360ff0c97b03b4c4520`.
- CL-05: **IMPLEMENTATION IN PROGRESS**, authorized from CL-04 merge `d517161aeaa3a974ad3c0360ff0c97b03b4c4520`.

## CL-03 implementation log (2026-08-09)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CL-05 implementation record - Compatibility Matrix UI

## Programme position

- **Phase:** CL-05 (read-only GUI)
- **Starting `upstream/dev` SHA:** `d517161aeaa3a974ad3c0360ff0c97b03b4c4520` (merge #1378 / CL-04)
- **Branch:** `feat/cl-05-compatibility-matrix-ui`
- **PR:** DRAFT → `lidge-jun/opencodex:dev` ([#1384](https://github.com/lidge-jun/opencodex/pull/1384))
- **CL-06:** not started

## Scope delivered

### GUI (`gui/src/pages/`)

- **Models → Compatibility tab** at `#models/compatibility` (not a standalone sidebar page)
- Legacy `#lab` hash redirects to `#models/compatibility`
- `CompatibilityMatrix.tsx` - read-only verdict matrix over CL-04 management APIs
- `compatibility-matrix-api.ts` - paginated fetch helpers for `/api/lab/status`, `/api/lab/verdicts`, `/api/lab/subjects`, detail reads
- `compatibility-matrix-shared.ts` - DTO parsing, matrix grouping, filters
- `styles-compatibility-matrix.css` - scrollable matrix + detail pane
- i18n keys in all seven locales (`en`, `de`, `ko`, `zh`, `ru`, `ja`, `tr`)
- Component/layout tests under `gui/tests/compatibility-lab.test.tsx`
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

### Behaviour

- Projection status cards (subjects, verdicts, observations, events, built-at)
- Subject × evidence-layer matrix with per-suite verdict badges
- Server-side verdict filters (layer, verdict, subjectId, suiteId) with paginated "Load more"
- Verdict detail pane (subject, observations, contributing events, artifact metadata only)
- Empty/unavailable/incompatible projection states via existing data-surface contract
- Lazy mount with `active` / `pauseWhenHidden` gating inside Models workspace
- No probe execution, projection rebuild, or evidence mutation

## Validation (local)

- `bun x tsc --noEmit` - passed
- `bun test tests/lab-read-surfaces.test.ts tests/models-workspace-tabs.test.ts` - 33/33 passed
- `cd gui && bun test tests/compatibility-lab.test.tsx tests/models-workspace-panels.test.tsx` - 29/29 passed
- `bun run lint:gui && bun run doctor:gui && bun run build:gui && bun run privacy:scan` - passed
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Acceptance blockers

- Draft PR requires cross-platform CI green
- GUI screenshot in PR body (or `gui-screenshot-waived` label)
- Independent acceptance review not performed

## Out of scope (confirmed)

- CL-06 routing profile compatibility fields, CL-07 fabric, CL-08 shadow/automatic/public publish
- New management APIs or CLI changes (CL-04 read surfaces are sufficient)
- Probe execution, projection rebuild triggers, raw artifact download in GUI
11 changes: 7 additions & 4 deletions gui/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export function readPageFromHash(hash?: string): Page {
export const DASHBOARD_TAB_HASHES = ["dashboard/providers", "dashboard/models"] as const;

/**
* Models owns three tabs: the catalog, Combos, and Routing. The catalog is the bare
* `#models`, so it has no suffix entry here — same convention Dashboard uses for
* Overview and Logs uses for the log list.
* Models owns four tabs: the catalog, Combos, Routing, and Compatibility. The catalog
* is the bare `#models`, so it has no suffix entry here - same convention Dashboard
* uses for Overview and Logs uses for the log list.
*/
export const MODELS_TAB_HASHES = ["models/combos", "models/routing"] as const;
export const MODELS_TAB_HASHES = ["models/combos", "models/routing", "models/compatibility"] as const;

/**
* `#dashboard/update` is an action deep link, not a tab: the sidebar update button uses
Expand Down Expand Up @@ -132,6 +132,9 @@ export function resolveAppHashChange(rawHash: string): AppHashChangeAction {
if (rawHash === "routing" || rawHash.startsWith("routing/")) {
return { page: "models", replaceTo: "models/routing" };
}
if (rawHash === "lab" || rawHash.startsWith("lab/")) {
return { page: "models", replaceTo: "models/compatibility" };
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/*
* Legacy top-level integration pages.
Expand Down
49 changes: 49 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,11 @@ export const de: Record<TKey, string> = {
"prov.openaiApiSetup": "API-Schlüssel einrichten",
"models.tab.catalog": "Modelle",
"models.tab.combos": "Combos",
"models.tab.compatibility": "Kompatibilität",
"models.tab.routing": "Routing (beta)",
"models.tabsLabel": "Modell-Oberflächen",
"models.subtitle.combos": "Geordnete Modellgruppen, die unter einer id antworten. Failover probiert Ziele der Reihe nach, Round-Robin verteilt die Last.",
"models.subtitle.compatibility": "Schreibgeschützte Kompatibilitätsmatrix aus der Lab-Projektion.",
"models.subtitle.routing": "Policy-Profile, Dry-Run-Auswertung und quellenbasierte Routing-Analysen.",
"models.subtitle": "Steuere, welche Modelle Codex sieht — natives GPT-Passthrough und geroutete Anbieter, nach Anbieter gruppiert (Kopfzeile zum Einklappen anklicken). Ausgeblendete Modelle fehlen in Katalog und Auswahl, bleiben aber per genauer ID aufrufbar. Änderungen gelten bei der nächsten Codex-Runde — opencodex invalidiert Codex 5-Minuten-Modell-Cache, kein Neustart nötig.",
"models.nativeGroupLabel": "OpenAI nativ",
Expand Down Expand Up @@ -1908,4 +1910,51 @@ export const de: Record<TKey, string> = {
"sub.delegation.modelHint": "Das Modell, zu dem Codex zuerst greift, wenn es Arbeit übergibt. Oben steht, wen es überhaupt aufrufen darf; hier wählst du den Ersten davon.",
"dash.syncModelsHint": "Schreibt Codex' Modellkatalog anhand deiner verbundenen Provider neu.",
"dash.syncRun": "Jetzt synchronisieren",
"lab.title": "Kompatibilitäts-Labor",
"lab.subtitle": "Schreibgeschützte Kompatibilitätsmatrix aus der Lab-Projektion.",
"lab.loadFailed": "Kompatibilitäts-Lab-Daten konnten nicht geladen werden",
"lab.projectionUnavailable": "Lab-Projektion ist nicht verfügbar. Führen Sie zuerst Konformitäts- oder Live-Probes aus.",
"lab.projectionIncompatible": "Lab-Projektionsschema ist inkompatibel. Projektion neu aufbauen.",
"lab.statusTitle": "Projektionsstatus",
"lab.matrixTitle": "Kompatibilitätsmatrix",
"lab.verdictsTitle": "Urteilsdatensätze",
"lab.filter.layer": "Evidenzschicht",
"lab.filter.verdict": "Urteil",
"lab.filter.subject": "Subject-ID",
"lab.filter.all": "Alle",
"lab.col.subject": "Subject",
"lab.col.layer": "Schicht",
"lab.col.suite": "Suite",
"lab.col.verdict": "Urteil",
"lab.col.asOf": "Stand",
"lab.col.protocol": "Protokollkonformität",
"lab.col.live": "Live-Route-Kompatibilität",
"lab.col.task": "Aufgabenwirksamkeit",
"lab.empty": "Noch keine Kompatibilitätsurteile in der Projektion.",
"lab.subjectKind": "Art",
"lab.observationCount": "Beobachtungen",
"lab.eventCount": "Ereignisse",
"lab.verdictCount": "Urteile",
"lab.subjectCount": "Subjects",
"lab.builtAt": "Erstellt",
"lab.loading": "Kompatibilitätsevidenz wird geladen…",
"lab.loadMore": "Load more",
"lab.detailTitle": "Verdict detail",
"lab.detailClose": "Close",
"lab.detailSubject": "Subject",
"lab.detailObservations": "Observations",
"lab.detailEvents": "Contributing events",
"lab.detailArtifacts": "Artifact metadata",
"lab.detailLoadFailed": "Could not load verdict detail",
"lab.refresh": "Aktualisieren",
"lab.verdict.UNKNOWN": "Unbekannt",
"lab.verdict.CLAIMED": "Behauptet",
"lab.verdict.PROBED": "Geprüft",
"lab.verdict.VERIFIED": "Verifiziert",
"lab.verdict.DEGRADED": "Eingeschränkt",
"lab.verdict.BLOCKED": "Blockiert",
"lab.verdict.UNSUPPORTED": "Nicht unterstützt",
"lab.layer.protocol_conformance": "Protokollkonformität",
"lab.layer.live_route_compatibility": "Live-Route-Kompatibilität",
"lab.layer.task_effectiveness": "Aufgabenwirksamkeit",
};
51 changes: 51 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,11 @@ export const en = {
// models
"models.tab.catalog": "Models",
"models.tab.combos": "Combos",
"models.tab.compatibility": "Compatibility",
"models.tab.routing": "Routing (beta)",
"models.tabsLabel": "Model surfaces",
"models.subtitle.combos": "Ordered groups of models that answer as one id. Failover tries targets in order; round-robin spreads the load.",
"models.subtitle.compatibility": "Read-only compatibility verdict matrix from lab projection evidence.",
"models.subtitle.routing": "Policy profiles, dry-run evaluation, and source-backed routing analytics.",
"models.subtitle": "Toggle which models Codex sees — native GPT passthrough and routed providers, grouped by provider (click a header to collapse). Hidden models stay off the catalog + model picker but remain directly callable by exact id. Changes apply on the next Codex turn — opencodex invalidates Codex's 5-min model cache so no restart is needed.",
"models.nativeGroupLabel": "OpenAI native",
Expand Down Expand Up @@ -1936,6 +1938,55 @@ export const en = {
"claudeDesktop.effort.supported": "effort",
"claudeDesktop.effort.displayOnly": "effort (display only)",

// compatibility lab (CL-05)
"lab.title": "Compatibility Lab",
"lab.subtitle": "Read-only compatibility verdict matrix from lab projection evidence.",
"lab.loadFailed": "Could not load compatibility lab data",
"lab.projectionUnavailable": "Lab projection is not available. Run conformance or live probes first.",
"lab.projectionIncompatible": "Lab projection schema is incompatible. Rebuild the projection.",
"lab.statusTitle": "Projection status",
"lab.matrixTitle": "Compatibility matrix",
"lab.verdictsTitle": "Verdict records",
"lab.filter.layer": "Evidence layer",
"lab.filter.verdict": "Verdict",
"lab.filter.subject": "Subject ID",
"lab.filter.all": "All",
"lab.col.subject": "Subject",
"lab.col.layer": "Layer",
"lab.col.suite": "Suite",
"lab.col.verdict": "Verdict",
"lab.col.asOf": "As of",
"lab.col.protocol": "Protocol conformance",
"lab.col.live": "Live route compatibility",
"lab.col.task": "Task effectiveness",
"lab.empty": "No compatibility verdicts in the projection yet.",
"lab.subjectKind": "Kind",
"lab.observationCount": "Observations",
"lab.eventCount": "Events",
"lab.verdictCount": "Verdicts",
"lab.subjectCount": "Subjects",
"lab.builtAt": "Built",
"lab.loading": "Loading compatibility evidence…",
"lab.loadMore": "Load more",
"lab.detailTitle": "Verdict detail",
"lab.detailClose": "Close",
"lab.detailSubject": "Subject",
"lab.detailObservations": "Observations",
"lab.detailEvents": "Contributing events",
"lab.detailArtifacts": "Artifact metadata",
"lab.detailLoadFailed": "Could not load verdict detail",
"lab.refresh": "Refresh",
"lab.verdict.UNKNOWN": "Unknown",
"lab.verdict.CLAIMED": "Claimed",
"lab.verdict.PROBED": "Probed",
"lab.verdict.VERIFIED": "Verified",
"lab.verdict.DEGRADED": "Degraded",
"lab.verdict.BLOCKED": "Blocked",
"lab.verdict.UNSUPPORTED": "Unsupported",
"lab.layer.protocol_conformance": "Protocol conformance",
"lab.layer.live_route_compatibility": "Live route compatibility",
"lab.layer.task_effectiveness": "Task effectiveness",

} as const;

export type TKey = keyof typeof en;
49 changes: 49 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,11 @@ export const ja: Record<TKey, string> = {
// models
"models.tab.catalog": "モデル",
"models.tab.combos": "コンボ",
"models.tab.compatibility": "互換性",
"models.tab.routing": "ルーティング (beta)",
"models.tabsLabel": "モデルサーフェス",
"models.subtitle.combos": "複数のモデルを 1 つの id にまとめ、順に応答させます。failover は順番に試し、round-robin は負荷を分散します。",
"models.subtitle.compatibility": "ラボ投影証拠の読み取り専用互換性判定マトリクス。",
"models.subtitle.routing": "ポリシープロファイル、dry-run 評価、そして根拠の残るルーティング分析です。",
"models.subtitle": "Codex に表示するモデルを切り替えます — ネイティブ GPT パススルーとルーティングプロバイダー、プロバイダー別(ヘッダーをクリックで折りたたみ)。非表示モデルはカタログとピッカーから外れますが、正確な id での直接呼び出しは可能です。変更は次回の Codex ターンで適用 — opencodex は Codex の 5 分間モデルキャッシュを無効化するので再起動は不要です。",
"models.nativeGroupLabel": "OpenAI ネイティブ",
Expand Down Expand Up @@ -1929,4 +1931,51 @@ export const ja: Record<TKey, string> = {
"sub.delegation.modelHint": "Codex が作業を任せるとき、最初に呼ぶモデルです。上のおすすめが呼べる候補で、ここで選んだものがその中の第一候補になります。",
"dash.syncModelsHint": "接続済みのプロバイダーをもとに Codex のモデルカタログを書き直します。",
"dash.syncRun": "今すぐ同期",
"lab.title": "Compatibility Lab",
"lab.subtitle": "Read-only compatibility verdict matrix from lab projection evidence.",
"lab.loadFailed": "Could not load compatibility lab data",
"lab.projectionUnavailable": "Lab projection is not available. Run conformance or live probes first.",
"lab.projectionIncompatible": "Lab projection schema is incompatible. Rebuild the projection.",
"lab.statusTitle": "Projection status",
"lab.matrixTitle": "Compatibility matrix",
"lab.verdictsTitle": "Verdict records",
"lab.filter.layer": "Evidence layer",
"lab.filter.verdict": "Verdict",
"lab.filter.subject": "Subject ID",
"lab.filter.all": "All",
"lab.col.subject": "Subject",
"lab.col.layer": "Layer",
"lab.col.suite": "Suite",
"lab.col.verdict": "Verdict",
"lab.col.asOf": "As of",
"lab.col.protocol": "Protocol conformance",
"lab.col.live": "Live route compatibility",
"lab.col.task": "Task effectiveness",
"lab.empty": "No compatibility verdicts in the projection yet.",
"lab.subjectKind": "Kind",
"lab.observationCount": "Observations",
"lab.eventCount": "Events",
"lab.verdictCount": "Verdicts",
"lab.subjectCount": "Subjects",
"lab.builtAt": "Built",
"lab.loading": "Loading compatibility evidence…",
"lab.loadMore": "Load more",
"lab.detailTitle": "Verdict detail",
"lab.detailClose": "Close",
"lab.detailSubject": "Subject",
"lab.detailObservations": "Observations",
"lab.detailEvents": "Contributing events",
"lab.detailArtifacts": "Artifact metadata",
"lab.detailLoadFailed": "Could not load verdict detail",
"lab.refresh": "Refresh",
"lab.verdict.UNKNOWN": "Unknown",
"lab.verdict.CLAIMED": "Claimed",
"lab.verdict.PROBED": "Probed",
"lab.verdict.VERIFIED": "Verified",
"lab.verdict.DEGRADED": "Degraded",
"lab.verdict.BLOCKED": "Blocked",
"lab.verdict.UNSUPPORTED": "Unsupported",
"lab.layer.protocol_conformance": "Protocol conformance",
"lab.layer.live_route_compatibility": "Live route compatibility",
"lab.layer.task_effectiveness": "Task effectiveness",
Comment on lines +1934 to +1980

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Root cause: the new CL-05 "lab.*" i18n block was copied from en.ts without translation into five of the seven locale catalogs. gui/src/i18n/de.ts (Lines 1913-1959) shows the correct pattern — every "lab.*" key properly translated into German. The same block in the following five files still holds the exact English source strings, so Japanese, Korean, Russian, Turkish, and Chinese users of the new Compatibility Matrix tab will see English text for the entire feature (status card, filters, matrix headers, verdict badges, and detail pane):

  • gui/src/i18n/ja.ts#L1934-L1980: translate all keys from "lab.title" through "lab.layer.task_effectiveness" into Japanese.
  • gui/src/i18n/ko.ts#L1934-L1981: translate the same key range into Korean.
  • gui/src/i18n/ru.ts#L1936-L1982: translate the same key range into Russian.
  • gui/src/i18n/tr.ts#L1936-L1982: translate the same key range into Turkish.
  • gui/src/i18n/zh.ts#L1934-L1980: translate the same key range into Chinese.

Because Record<TKey, string> in each file only enforces key-set parity with en.ts, TypeScript compiles cleanly even with untranslated placeholder values, so this gap will not be caught until a non-German, non-English user opens the tab.

📍 Affects 5 files
  • gui/src/i18n/ja.ts#L1934-L1980 (this comment)
  • gui/src/i18n/ko.ts#L1934-L1981
  • gui/src/i18n/ru.ts#L1936-L1982
  • gui/src/i18n/tr.ts#L1936-L1982
  • gui/src/i18n/zh.ts#L1934-L1980
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/i18n/ja.ts` around lines 1934 - 1980, Translate every lab.* value
from lab.title through lab.layer.task_effectiveness, preserving all keys, in
gui/src/i18n/ja.ts lines 1934-1980, gui/src/i18n/ko.ts lines 1934-1981,
gui/src/i18n/ru.ts lines 1936-1982, gui/src/i18n/tr.ts lines 1936-1982, and
gui/src/i18n/zh.ts lines 1934-1980. Use Japanese, Korean, Russian, Turkish, and
Chinese respectively, including status, filters, matrix headers, verdict labels,
and detail-pane text.

};
Loading
Loading