From 47287c09a62dcbd2a171fe77f6d868e94379a67c Mon Sep 17 00:00:00 2001 From: huangsen Date: Wed, 30 Sep 2026 17:38:46 +0800 Subject: [PATCH] fix(codex): simplify status panel layout for narrow cards --- integrations/codex/README.md | 5 +- integrations/codex/README_ZH.md | 5 +- .../plugins/reme/.codex-plugin/plugin.json | 2 +- .../codex/plugins/reme/ui/status.html | 150 +++++++++--------- integrations/codex/status-app/src/status.html | 127 ++++++++------- integrations/codex/status-app/src/status.js | 17 +- .../test_codex_plugin_status_ui.py | 28 +++- 7 files changed, 189 insertions(+), 145 deletions(-) diff --git a/integrations/codex/README.md b/integrations/codex/README.md index 955e741f..f6e7dbda 100644 --- a/integrations/codex/README.md +++ b/integrations/codex/README.md @@ -173,10 +173,11 @@ already sent to ReMe; a timeout also does not prove that server-side processing Click **ReMe status** to open a dedicated panel from MCP settings. Connection checks and memory status share this one entry. Four tabs organize the panel: **Overview**, **Auto Memory**, -**Memory Consolidation**, and **Components**. They show service health and the memory flow; +**Consolidation**, and **Components**. They show service health and the pending queue; capture settings, queued turns/sessions, recall and recent activity; Dream scheduling and results; and component memory estimates plus process RSS. Click **Refresh** to check again; -service details are collapsed in Components until needed. The initial view uses the opening call's result without +connection details, recent activity, usage notes, and raw service details expand when needed. Narrow cards use a +single content column and two rows of tabs. The initial view uses the opening call's result without a second request. The panel follows the host theme and the saved `language` setting. You can also ask Codex to call `reme_status` in a conversation. Hosts without an App view receive diff --git a/integrations/codex/README_ZH.md b/integrations/codex/README_ZH.md index dbaca032..49712240 100644 --- a/integrations/codex/README_ZH.md +++ b/integrations/codex/README_ZH.md @@ -164,9 +164,10 @@ ReMe-Example-7319 的评审时间和校验词是什么? ## 6. 查看状态和调整配置 点击 **ReMe status**,从 MCP 设置打开独立状态面板。连接检查和记忆状态共用这一个入口, -面板分为 **总览、自动记忆、记忆整理、组件** 四个页签,分别展示连接健康与记忆流程, +面板分为 **总览、自动记忆、记忆整理、组件** 四个页签,分别展示连接健康与待提交队列, 记录开关、批次和队列、召回及最近活动,Dream 计划和结果,以及组件内存估算与进程 RSS。 -点击 **刷新** 可重新检查,组件页的服务详情默认折叠。首次展示直接使用打开时返回的结果,不重复请求;面板适配宿主主题及保存的 `language`。 +点击 **刷新** 可重新检查;连接详情、最近活动、使用说明和原始服务详情按需展开。 +窄卡片使用单列内容和两行页签。首次展示直接使用打开时返回的结果,不重复请求;面板适配宿主主题及保存的 `language`。 也可以在对话中让 Codex 调用 `reme_status`;不展示 App 的宿主仍会收到文字结果。 打开或刷新面板不会记录或整理记忆。服务不可达时仍可查看本地队列与活动。 diff --git a/integrations/codex/plugins/reme/.codex-plugin/plugin.json b/integrations/codex/plugins/reme/.codex-plugin/plugin.json index 04f45d7e..422952d0 100644 --- a/integrations/codex/plugins/reme/.codex-plugin/plugin.json +++ b/integrations/codex/plugins/reme/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "reme", - "version": "0.2.1", + "version": "0.2.2", "description": "Automatic memory, focused recall, runtime status, and consolidation through ReMe MCP.", "author": { "name": "EconML team of Alibaba Tongyi Lab", diff --git a/integrations/codex/plugins/reme/ui/status.html b/integrations/codex/plugins/reme/ui/status.html index 7b89acd3..eeef06a7 100644 --- a/integrations/codex/plugins/reme/ui/status.html +++ b/integrations/codex/plugins/reme/ui/status.html @@ -11,31 +11,27 @@ :root[data-theme="dark"] { color-scheme: dark; --bg: #0b0d12; --surface: #12151d; --panel: #181c26; --ink: #f3f5f7; --muted: #979dab; --line: #292e3b; --blue: #79a8ff; --green: #63d4a3; --green-bg: #25382f; --amber: #f2be5c; --amber-bg: #3c3526; --red: #ff7b86; --red-bg: #40262c; } :root[data-theme="light"] { color-scheme: light; } * { box-sizing: border-box; } - body { margin: 0; padding: 28px; background: var(--color-background-primary, var(--bg)); color: var(--color-text-primary, var(--ink)); font: 14px/1.5 system-ui, -apple-system, sans-serif; } - main { max-width: 1100px; margin: auto; } - header { display: flex; align-items: start; justify-content: space-between; gap: 20px; margin-bottom: 22px; } - .brand { display: flex; gap: 12px; align-items: center; } - .logo { display: grid; place-items: center; width: 44px; height: 44px; flex: 0 0 44px; border-radius: 13px; background: linear-gradient(145deg, #678ff7, #725de3); color: #fff; font-size: 26px; } - .eyebrow { color: var(--blue); font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; } - h1 { font-size: 25px; letter-spacing: -.5px; margin: 0; font-weight: 650; } - h2 { font-size: 14px; margin: 0 0 12px; font-weight: 600; } + body { margin: 0; padding: 24px; background: var(--color-background-primary, var(--bg)); color: var(--color-text-primary, var(--ink)); font: 14px/1.6 system-ui, -apple-system, sans-serif; } + main { max-width: 920px; margin: auto; overflow-wrap: anywhere; } + header { display: flex; align-items: start; justify-content: space-between; gap: 20px; margin-bottom: 24px; } + header > div { min-width: 0; } + h1 { font-size: 22px; letter-spacing: -.4px; margin: 0; font-weight: 650; } + h2 { font-size: 15px; margin: 0 0 20px; font-weight: 600; } p { margin: 6px 0; } .muted, small { color: var(--muted); } - .label { font-size: 12px; color: var(--muted); } + .label { font-size: 13px; color: var(--muted); } .endpoint { overflow-wrap: anywhere; font-size: 12px; font-family: ui-monospace, monospace; } - .actions { display: flex; gap: 10px; align-items: center; } button { background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 8px 14px; font: inherit; cursor: pointer; white-space: nowrap; } button:hover:not(:disabled) { border-color: var(--blue); } button:focus-visible, summary:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; } button:disabled { opacity: .55; cursor: wait; } - .tabs { display: flex; gap: 4px; overflow-x: auto; border-bottom: 1px solid var(--line); margin-bottom: 20px; } - .tabs button { color: var(--muted); background: transparent; border: 0; border-radius: 0; border-bottom: 2px solid transparent; padding: 11px 14px; font-weight: 600; } - .tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--blue); } - .grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; } - .card { background: linear-gradient(150deg, var(--panel), var(--surface)); border: 1px solid var(--line); border-radius: 12px; padding: 18px; min-width: 0; } - .wide { grid-column: span 2; } + .tabs { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; padding: 5px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; margin-bottom: 24px; } + .tabs button { min-width: 0; color: var(--muted); background: transparent; border-color: transparent; padding: 10px 8px; font-weight: 600; white-space: normal; line-height: 1.4; } + .tabs button[aria-selected="true"] { color: var(--ink); background: var(--surface); border-color: var(--line); box-shadow: 0 1px 3px #0000000a; } + .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; } + .card { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 24px; min-width: 0; } .full { grid-column: 1 / -1; } - .value { font-size: 25px; line-height: 1.3; font-weight: 700; margin-top: 8px; } + .value { font-size: 26px; line-height: 1.35; font-weight: 650; margin-top: 8px; } .compact { font-size: 16px; overflow-wrap: anywhere; } .pill { display: inline-block; color: var(--green); background: var(--green-bg); border-radius: 99px; padding: 4px 10px; font-size: 12px; font-weight: 600; } [data-tone="good"] { color: var(--green); } @@ -43,32 +39,42 @@ .pill[data-tone="neutral"] { color: var(--muted); background: var(--bg); } .pill[data-tone="warn"] { color: var(--amber); background: var(--amber-bg); } .pill[data-tone="bad"] { color: var(--red); background: var(--red-bg); } - .flow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } - .step { background: var(--surface); border: 1px solid var(--line); padding: 10px; border-radius: 8px; flex: 1 1 100px; } - .arrow { color: var(--muted); } + .section-heading { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; } + .section-heading h2 { margin: 0; } + .metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } + .properties { margin: 0; } + .properties > div { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; padding: 14px 0; border-bottom: 1px solid var(--line); } + .properties > div:first-child { padding-top: 0; } + .properties > div:last-child { padding-bottom: 0; border: 0; } + .properties dt { color: var(--muted); } + .properties dd { margin: 0; text-align: right; font-weight: 600; min-width: 0; } .rows { padding: 0; margin: 0; list-style: none; } - .rows li { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--line); } + .rows li { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; padding: 16px 0; border-bottom: 1px solid var(--line); } .rows li:last-child { border: 0; padding-bottom: 0; } .rows code { display: block; color: var(--muted); font-size: 11px; } - time { color: var(--muted); font-size: 12px; white-space: nowrap; } - .component-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } - .component { border: 1px solid var(--line); background: var(--surface); padding: 14px; border-radius: 9px; min-width: 0; } + time { color: var(--muted); font-size: 12px; flex-shrink: 0; max-width: 45%; } + .component-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; } + .component { border: 1px solid var(--line); background: var(--panel); padding: 18px; border-radius: 9px; min-width: 0; } .component strong { display: block; overflow-wrap: anywhere; font-size: 13px; } - details { margin-top: 18px; } + details { margin-top: 20px; } + details.card { margin-top: 0; } + details[open] > summary { margin-bottom: 16px; } + .card > summary { color: var(--ink); font-weight: 600; font-size: 15px; } summary { cursor: pointer; color: var(--muted); } pre { white-space: pre-wrap; overflow-wrap: anywhere; background: var(--surface); padding: 14px; border: 1px solid var(--line); border-radius: 8px; font-size: 12px; max-height: 280px; overflow: auto; } - .note { margin: 18px 0 0; font-size: 12px; color: var(--muted); } + .note { margin: 20px 0 0; font-size: 13px; color: var(--muted); } #error { border: 1px solid var(--red); color: var(--red); padding: 12px; border-radius: 8px; margin-bottom: 16px; } [hidden] { display: none !important; } - @media (max-width: 850px) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } - @media (max-width: 540px) { body { padding: 18px; } .grid, .component-grid { grid-template-columns: 1fr; } .wide { grid-column: 1 / -1; } .card { padding: 16px; } header { flex-wrap: wrap; gap: 12px; } h1 { font-size: 23px; } .rows li { flex-direction: column; gap: 2px; } } + @media (max-width: 700px) { .tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (max-width: 640px) { body { padding: 16px; } .grid, .component-grid { grid-template-columns: 1fr; } .card { padding: 20px; } header { gap: 12px; } .rows li { flex-direction: column; gap: 6px; } time { max-width: 100%; } } + @media (max-width: 380px) { .metrics { grid-template-columns: 1fr; gap: 16px; } .metrics > div { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; } .metrics .value { margin: 0; } }
-
Codex memory

ReMe status

Waiting for status…

-
+

ReMe status

Waiting for status…

+
Opening the connection and memory overview…
@@ -76,49 +82,62 @@
-
ReMe service
-
Runtime phase
-
Queued turns
-
Queued sessions
-

Memory automation

1 · Prompt
Automatic recall
→
2 · Response
Turn capture
→
3 · Workspace
Memory files
-

Connection

+
+

ReMe service

+

+
Connection details

+
+
+
Queued turns
+
Queued sessions
+
-

Connection health does not establish Hook trust. Review ReMe in Codex Hooks settings.

-

This view uses saved settings. Save changes in MCP settings, then refresh here.

+
About this view +

Connection health does not establish Hook trust. Review ReMe in Codex Hooks settings.

+

This view uses saved settings. Save changes in MCP settings, then refresh here.

+
diff --git a/integrations/codex/status-app/src/status.js b/integrations/codex/status-app/src/status.js index 5f73d214..64bfe408 100644 --- a/integrations/codex/status-app/src/status.js +++ b/integrations/codex/status-app/src/status.js @@ -10,10 +10,10 @@ let hasSnapshot = false; let refreshing = false; const words = { en: { - overview: "Overview", autoMemory: "Auto Memory", consolidation: "Memory Consolidation", components: "Components", - service: "ReMe service", runtime: "Runtime phase", queuedTurns: "Queued turns", queuedSessions: "Queued sessions", - automation: "Memory automation", prompt: "1 · Prompt", response: "2 · Response", workspace: "3 · Workspace", - capture: "Turn capture", files: "Memory files", interval: "Batch interval", timezone: "Timezone", lastResult: "Last result", + overview: "Overview", autoMemory: "Auto Memory", consolidation: "Consolidation", components: "Components", + service: "ReMe service", queuedTurns: "Queued turns", queuedSessions: "Queued sessions", + connectionDetails: "Connection details", viewHelp: "About this view", + interval: "Batch interval", timezone: "Timezone", lastResult: "Last result", healthy: "Healthy", unhealthy: "Unhealthy", unknown: "Unknown", processMemory: "Process memory (RSS)", componentMemory: "Estimated component memory", noComponents: "No component memory details returned.", componentHelp: "Memory estimates from ReMe status. Component-level health is not included in the MCP response.", @@ -41,9 +41,9 @@ const words = { }, zh: { overview: "总览", autoMemory: "自动记忆", consolidation: "记忆整理", components: "组件", - service: "ReMe 服务", runtime: "运行状态", queuedTurns: "待提交轮数", queuedSessions: "待提交会话", - automation: "记忆流程", prompt: "1 · 提问", response: "2 · 回复", workspace: "3 · 工作区", - capture: "记录完成轮次", files: "记忆文件", interval: "批次轮数", timezone: "时区", lastResult: "最近结果", + service: "ReMe 服务", queuedTurns: "待提交轮数", queuedSessions: "待提交会话", + connectionDetails: "连接详情", viewHelp: "使用说明", + interval: "批次轮数", timezone: "时区", lastResult: "最近结果", healthy: "健康", unhealthy: "异常", unknown: "未知", processMemory: "进程内存(RSS)", componentMemory: "组件内存估算", noComponents: "暂无组件内存信息。", componentHelp: "内存为 ReMe status 返回的估算值;MCP 响应不包含组件级健康信息。", @@ -107,7 +107,6 @@ function render(result) { const summary = typeof health.answer === "string" ? health.answer.trim().match(/(?:^|-\s*)(unhealthy|healthy)$/) : null; text("service-health", t(!health.reachable ? "offline" : summary?.[1] ?? "unknown")); $("service-health").dataset.tone = !health.reachable || summary?.[1] === "unhealthy" ? "bad" : summary ? "good" : "neutral"; - text("runtime", t(data.auto_dream.phase)); text("overview-queue", data.auto_memory.queued_turns); text("overview-sessions", data.auto_memory.queued_sessions); text("health", health.reachable ? (typeof health.answer === "string" ? health.answer : JSON.stringify(health.answer)) : health.error); @@ -212,7 +211,7 @@ $("refresh").addEventListener("click", async () => { async function connect() { if (window.parent === window) { text("loading", t("noHost")); return; } const host = await transport.request("ui/initialize", { - protocolVersion: "2026-01-26", appInfo: { name: "reme-status", version: "0.2.1" }, + protocolVersion: "2026-01-26", appInfo: { name: "reme-status", version: "0.2.2" }, appCapabilities: { availableDisplayModes: ["fullscreen"] }, }); if (host.protocolVersion !== "2026-01-26") throw new Error("Unsupported UI protocol"); diff --git a/tests/integration/test_codex_plugin_status_ui.py b/tests/integration/test_codex_plugin_status_ui.py index ff027894..a172f0df 100644 --- a/tests/integration/test_codex_plugin_status_ui.py +++ b/tests/integration/test_codex_plugin_status_ui.py @@ -22,7 +22,7 @@ def snapshot(language): return { "checked_at": 1790757300, "language": language, - "plugin_version": "0.2.1", + "plugin_version": "0.2.2", "mcpUrl": "http://localhost:2333/mcp", "service": { "health_check": {"reachable": True, "answer": "ReMe v0.4.1.13 - healthy"}, @@ -105,6 +105,9 @@ def test_initial_result_refresh_offline_failure_theme_and_untrusted_text(languag assert "healthy" in frame.locator("#health").inner_text() assert "23:00" in frame.locator("#next").inner_text() assert frame.locator("#activity li").count() == 2 + assert not frame.locator("#connection-details").get_attribute("open") + assert not frame.locator("#activity-details").get_attribute("open") + assert not frame.locator("#view-help").get_attribute("open") assert not page.evaluate( "window.calls.some(m => m.method === 'tools/call')", ) # Opening reuses the supplied result. @@ -119,9 +122,18 @@ def test_initial_result_refresh_offline_failure_theme_and_untrusted_text(languag assert frame.locator("#component-total").inner_text() == "1.25 MiB" frame.locator("#tab-components").press("Home") assert frame.locator("#panel-overview").is_visible() - page.set_viewport_size({"width": 420, "height": 1250}) - page.evaluate("document.querySelector('iframe').style.width = '100%'") - assert frame.locator("body").evaluate("node => node.scrollWidth <= window.innerWidth") + for width in (320, 480, 640, 940): + page.set_viewport_size({"width": width + 16, "height": 1250}) + page.evaluate("width => document.querySelector('iframe').style.width = `${width}px`", width) + for name in ("overview", "memory", "dream", "components"): + frame.locator(f"#tab-{name}").click() + assert frame.locator("body").evaluate("node => node.scrollWidth <= window.innerWidth") + assert frame.locator(f"#tab-{name}").evaluate( + "node => node.scrollWidth <= node.clientWidth && node.scrollHeight <= node.clientHeight", + ) + frame.locator("#tab-overview").click() + if width == 480: + page.screenshot(path=str(tmp_path / f"status-test-host-{theme}-narrow.png"), full_page=True) offline = snapshot(language) offline["service"]["health_check"] = {"reachable": False, "error": "ConnectionError"} @@ -131,6 +143,7 @@ def test_initial_result_refresh_offline_failure_theme_and_untrusted_text(languag page.evaluate("value => window.result = {structuredContent:value}", offline) frame.locator("#refresh").click() playwright.expect(frame.locator("#connection-state")).to_have_attribute("data-tone", "bad") + frame.locator("#connection-details summary").click() assert frame.locator("#endpoint").inner_text() == offline["mcpUrl"] assert frame.locator("#endpoint img").count() == 0 assert "3" in frame.locator("#queue").inner_text() @@ -139,17 +152,20 @@ def test_initial_result_refresh_offline_failure_theme_and_untrusted_text(languag assert frame.locator("#error").is_hidden() # Offline is a useful status result, not a broken panel. frame.locator("#tab-memory").click() + frame.locator("#activity-details summary").click() + assert frame.locator("#activity li").first.is_visible() page.evaluate("window.failRefresh = true") frame.locator("#refresh").click() frame.locator("#error").wait_for(state="visible") assert "3" in frame.locator("#queue").inner_text() # Failed refresh keeps the last snapshot. assert frame.locator("#refresh").is_enabled() assert frame.locator("#panel-memory").is_visible() # Refresh preserves navigation. + assert frame.locator("#activity li").first.is_visible() # Refresh preserves expanded details. frame.locator("#tab-components").click() assert frame.locator("#rss").inner_text() == "—" assert frame.locator("#components .component").count() == 0 - assert not frame.locator("details").get_attribute("open") - frame.locator("summary").click() + assert not frame.locator("#service-details").get_attribute("open") + frame.locator("#service-details summary").click() assert "ConnectionError" in frame.locator("#details").inner_text() unhealthy = snapshot(language) unhealthy["service"]["health_check"]["answer"] = "ReMe v0.4.1.13 - unhealthy"