You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/10/16 03:31:13 UTC

(maka) branch rust-preview updated: fix(tui): highlight the composer border for pending approvals

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch rust-preview
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/rust-preview by this push:
     new 2b705af7e2 fix(tui): highlight the composer border for pending approvals
2b705af7e2 is described below

commit 2b705af7e2f8fff58dc7bc8520aea9d26468bccb
Author: Wang <m4...@gmail.com>
AuthorDate: Fri Oct 16 11:31:06 2026 +0800

    fix(tui): highlight the composer border for pending approvals
    
    Keep focused and unfocused borders amber until approval is settled, taking precedence over execution animation. Derive the hint from current review and Plan state.
    
    Generated-by: Codex
---
 plugins/agent/agent-screen/conversation-view.ts    |  6 +++++
 plugins/agent/composer/component.ts                |  3 ++-
 plugins/shared/contracts.ts                        |  2 ++
 plugins/shared/view.ts                             |  5 ++--
 products/maka/bundle/tests/composer.test.mjs       | 30 ++++++++++++++++++++++
 .../maka/bundle/tests/product-composition.test.mjs | 19 ++++++++++++++
 products/maka/cli/notices.json                     |  8 +++---
 .../integration/terminal/bundled_product/chat.rs   |  1 +
 .../terminal/bundled_product/chat/health.rs        |  8 +++++-
 9 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/plugins/agent/agent-screen/conversation-view.ts b/plugins/agent/agent-screen/conversation-view.ts
index 31f09af589..e18bbb21cc 100644
--- a/plugins/agent/agent-screen/conversation-view.ts
+++ b/plugins/agent/agent-screen/conversation-view.ts
@@ -29,6 +29,7 @@ import { draftReady } from '../../shared/drafts.js';
 
 import {
   canExecute,
+  reviewTarget,
   selectedBackend,
   catalogPending,
   conversationRoute,
@@ -257,6 +258,11 @@ export function conversationView<N extends Node>(
     'composer',
     'composer',
     {
+      approvalPending:
+        state.plan.page?.current.proposal?.status === 'pending_approval' ||
+        ['service_approval', 'permissions', 'client_capability'].includes(
+          reviewTarget(state)?.kind ?? '',
+        ),
       backend: canExecute(state) ? selectedBackend(state) : null,
       context: state.selected?.id ?? null,
       dataSource: state.selected?.dataSource ?? null,
diff --git a/plugins/agent/composer/component.ts b/plugins/agent/composer/component.ts
index f28cb21b85..778693222f 100644
--- a/plugins/agent/composer/component.ts
+++ b/plugins/agent/composer/component.ts
@@ -478,7 +478,8 @@ export function composer<N extends Node>(
       {
         row: target === 'tui',
         border: target === 'tui',
-        busy: props.running,
+        borderColor: props.approvalPending ? props.theme.warning : undefined,
+        busy: props.running && !props.approvalPending,
         gap: target === 'tui' ? 0 : 12,
         terminal: { shrink: true },
         style: { width: fill },
diff --git a/plugins/shared/contracts.ts b/plugins/shared/contracts.ts
index 259eeb704b..f62c0f1461 100644
--- a/plugins/shared/contracts.ts
+++ b/plugins/shared/contracts.ts
@@ -333,6 +333,7 @@ export interface ComposerSent {
   attachments: string[];
 }
 export interface ComposerProps {
+  approvalPending?: boolean;
   backend: ComposerBackend | null;
   sendLabel?: string;
   dataSource: AgentDataSource | null;
@@ -1166,6 +1167,7 @@ export const composerContract = component(
   'maka.agent.Composer',
   object(
     {
+      approvalPending: { type: 'boolean' },
       backend: composerBackend,
       sendLabel: string,
       context: { anyOf: [{ type: 'string', minLength: 1, maxLength: 512 }, { type: 'null' }] },
diff --git a/plugins/shared/view.ts b/plugins/shared/view.ts
index 9e9ed63de6..e235c0ae13 100644
--- a/plugins/shared/view.ts
+++ b/plugins/shared/view.ts
@@ -56,6 +56,7 @@ interface BoxOptions {
   autoFocus?: boolean;
   wheel?: { up: string; down: string };
   border?: boolean;
+  borderColor?: number;
   busy?: boolean;
   shortcuts?: Shortcut[];
   focusShortcuts?: FocusShortcut[];
@@ -435,8 +436,8 @@ function terminalPaint(theme: Theme): Painter<TerminalNode> {
         border: options.border
           ? {
               breathing: options.busy === true && theme.motion,
-              color: color(options.busy ? theme.accent : theme.border),
-              focused_color: color(theme.accent),
+              color: color(options.borderColor ?? (options.busy ? theme.accent : theme.border)),
+              focused_color: color(options.borderColor ?? theme.accent),
               style: theme.ascii ? 'ascii' : 'rounded',
             }
           : null,
diff --git a/products/maka/bundle/tests/composer.test.mjs b/products/maka/bundle/tests/composer.test.mjs
index 1e72bf48c7..f1e7ffcf27 100644
--- a/products/maka/bundle/tests/composer.test.mjs
+++ b/products/maka/bundle/tests/composer.test.mjs
@@ -502,3 +502,33 @@ for (const target of ['tui', 'desktop']) {
     );
   });
 }
+
+test('pending approval keeps the entire composer border amber through focus and execution, then restores it', async () => {
+  const c = await client('tui');
+  const find = (node) =>
+    node?.key === 'composer-main'
+      ? node
+      : node && typeof node === 'object'
+        ? Object.values(node)
+            .flatMap((value) => (Array.isArray(value) ? value : [value]))
+            .map(find)
+            .find(Boolean)
+        : undefined;
+  for (const running of [false, true]) {
+    c.props.running = running;
+    c.props.approvalPending = true;
+    c.update();
+    assert.equal(validProps(c.props), true, JSON.stringify(validProps.errors));
+    const border = find(c.out.frame.view).border;
+    assert.equal(border.color, theme.terminal.warning);
+    assert.equal(border.focused_color, theme.terminal.warning);
+    assert.equal(border.breathing, false);
+    c.props.approvalPending = false;
+    c.update();
+    assert.equal(
+      find(c.out.frame.view).border.color,
+      running ? theme.terminal.accent : theme.terminal.border,
+    );
+    assert.equal(find(c.out.frame.view).border.focused_color, theme.terminal.accent);
+  }
+});
diff --git a/products/maka/bundle/tests/product-composition.test.mjs b/products/maka/bundle/tests/product-composition.test.mjs
index ad2f09971f..8b5295cc6d 100644
--- a/products/maka/bundle/tests/product-composition.test.mjs
+++ b/products/maka/bundle/tests/product-composition.test.mjs
@@ -380,6 +380,10 @@ test('inline approval Escape returns to the retained task and composer', async (
     permissions: { access: 'workspace_write', requestPermissions: true },
     workspace: { path: '/project', directoryIdentity: 'a'.repeat(64), identity: 'workspace-a' },
   };
+  out.frame.state.interactions = {
+    revision: 1,
+    entries: [{ requestId: 'request-a', kind: 'permissions' }],
+  };
   out.frame.state.dialog = 'approval';
   out = module.component.update(out.frame.state, {}, values);
   const nodes = (node) =>
@@ -412,4 +416,19 @@ test('inline approval Escape returns to the retained task and composer', async (
     'closing approval preserves the native draft owner',
   );
   assert.equal(after.props.command, composer.props.command);
+  assert.equal(after.props.approvalPending, true, 'closing a review does not settle its approval');
+  for (const kind of ['service_approval', 'permissions', 'client_capability', 'question', 'form']) {
+    out.frame.state.interactions.entries[0].kind = kind;
+    out = module.component.update(out.frame.state, {}, values);
+    assert.equal(
+      nodes(out.frame.view).find((n) => n.key === 'composer').props.approvalPending,
+      !['question', 'form'].includes(kind),
+    );
+  }
+  out.frame.state.interactions.entries = [];
+  out = module.component.update(out.frame.state, {}, values);
+  assert.equal(
+    nodes(out.frame.view).find((n) => n.key === 'composer').props.approvalPending,
+    false,
+  );
 });
diff --git a/products/maka/cli/notices.json b/products/maka/cli/notices.json
index 4359f72e20..079545af91 100644
--- a/products/maka/cli/notices.json
+++ b/products/maka/cli/notices.json
@@ -47,7 +47,7 @@
     "plugins/agent/agent-screen/checklist-view.ts": "e2976d6408df0407af4c26c9221a143761197f800f7a4f620639dad80091192f",
     "plugins/agent/agent-screen/component-events.ts": "b9d4112af44db1162008c795db667e995a050a6147726556edfb162e22b0e8bf",
     "plugins/agent/agent-screen/composer-status.ts": "ff8380142e1b247d508b5be10d2089b353019de8a705169703fd53f3008b2db9",
-    "plugins/agent/agent-screen/conversation-view.ts": "999c8f15dd889aa72af576a192dc70e99505fd5446fd18304daa167d1069b6b2",
+    "plugins/agent/agent-screen/conversation-view.ts": "256e40c6ccd78f11502c77a851f9b8764cb3692acc0d28f227d4d6b45839b518",
     "plugins/agent/agent-screen/desktop.ts": "9cd44cb6a5225feead1aa87fc4e2d7935b994022fa38175342330119e72c4660",
     "plugins/agent/agent-screen/events.ts": "efb229195679d01d5a12c0229938f1b1cb4955a8e08033e12fdc95398ce303a4",
     "plugins/agent/agent-screen/frame.ts": "973876ad92fe856235052f68785ac173aab6608c71ca64c67a295d00f0a73a36",
@@ -83,7 +83,7 @@
     "plugins/agent/composer-footer/locale.ts": "eaf74e85cac91778c904d2ada3bbba5c975e1671cf5191061d1e27ab7e177989",
     "plugins/agent/composer-footer/package.ts": "256408914147346b046d9447869acdf5d778f5822fe3b392b1ece8f805a10399",
     "plugins/agent/composer-footer/tui.ts": "a42a739ba6a216de189ffee699a259f72a1a236ff8d96210a688ee29287939fb",
-    "plugins/agent/composer/component.ts": "6e5dfa27ff9d06e9946847e19c3e65e2cec3ea069e0294e068bc3e6d29d9ce85",
+    "plugins/agent/composer/component.ts": "a27b23b923ff771e82c934f103a2b7778a14185eab3c94da7f761bef46e3b8fa",
     "plugins/agent/composer/desktop.ts": "781fa9950b60606fd3301d3efc55af43dc95e8585b8a4b8ef4d967e278a8b653",
     "plugins/agent/composer/locale.ts": "291a6de52d51617c4d5802524063ea546b7aba3fda826faf4d76fc8e2cc40bb9",
     "plugins/agent/composer/package.ts": "d996adc3bfed1e72b4cbdfaf28525c942d38f8a3ff53910d6d76edd7e16a36c7",
@@ -315,7 +315,7 @@
     "plugins/shared/attachments.ts": "e3e49eb56eb7dc28dc70db66dbc57062be502d42c1ed502b6eeae23eb6985bf9",
     "plugins/shared/bookmark.ts": "d16b12b7aa9fd900271d6d8469c45d40e9295c8bd949adbcc7280f76e1d79dfc",
     "plugins/shared/components.ts": "eac3ccc60365ac7fd31031f487a40c40f729e7184bb693ff736e9ded9027759c",
-    "plugins/shared/contracts.ts": "79a8fa058c6f9425f8b466851724f145fbee783530dfb8b3e0240d111b5ac212",
+    "plugins/shared/contracts.ts": "c94edfec7da14afd3368092363463864feabd96aa161b8945b972d6c3253dc50",
     "plugins/shared/conversation-search.ts": "5355d6002832ec057511eea9be24f5a910bf43971e10c15e336bc83bde1f03ec",
     "plugins/shared/deployment-document.ts": "7e1af7e2b599dffc1eefa0cb19f31d75b2e1f38e20f62989628a631731d711ac",
     "plugins/shared/deployment-import.ts": "6515214d9c8a8c27e2810932923e2b8368762fb7c06126ac1b1132996cd9f89b",
@@ -349,7 +349,7 @@
     "plugins/shared/tui-locale.ts": "5fbc39f3c8cfccfa5122dbaf0ba05db869a19796adb8a663adc8ffbc4a6c66e6",
     "plugins/shared/tui-messages.ts": "08f7f3858ac6f9027f52f82da74af5942fb09ecc06fe89012cbe4ec62addf950",
     "plugins/shared/value.ts": "9b1b52b05ca45a4e37fbab6454810fca2a474c8532941e25c15ab897aaa2d607",
-    "plugins/shared/view.ts": "0c5057fa30153fcef747167672483721a632c0a44a197c023f2f1df3429bb606",
+    "plugins/shared/view.ts": "b94144fbcf3bf991529bcf51527133d70930486e762f9db96b208cce17408769",
     "plugins/shared/welcome.ts": "a5aa03797bcfb5c95e04f5c1ba1676cd59c1f85f6313ae6fca97cb69d699ce41",
     "plugins/skills/api/Cargo.toml": "ddca1ffbbee64efc3051e61b3d1aacaaa7bacfd18be967ab167efa29fde5756d",
     "plugins/skills/core/Cargo.toml": "2fdb7ac50558436fca5392d20847d2bf9f48708eb86dfb9f3395bff704e2fc7c",
diff --git a/products/maka/cli/tests/integration/terminal/bundled_product/chat.rs b/products/maka/cli/tests/integration/terminal/bundled_product/chat.rs
index f7b2e44394..e5ca11498d 100644
--- a/products/maka/cli/tests/integration/terminal/bundled_product/chat.rs
+++ b/products/maka/cli/tests/integration/terminal/bundled_product/chat.rs
@@ -233,6 +233,7 @@ fn evidence(tui: &Pty, name: &str) {
             tui.screen.snapshot().unwrap().screen,
         )
         .unwrap();
+        std::fs::write(Path::new(&path).join(format!("{name}.ansi")), &tui.output).unwrap();
         std::fs::write(Path::new(&path).join("terminal.ansi"), &tui.output).unwrap();
     }
 }
diff --git a/products/maka/cli/tests/integration/terminal/bundled_product/chat/health.rs b/products/maka/cli/tests/integration/terminal/bundled_product/chat/health.rs
index ea460d6780..8b67afa7bc 100644
--- a/products/maka/cli/tests/integration/terminal/bundled_product/chat/health.rs
+++ b/products/maka/cli/tests/integration/terminal/bundled_product/chat/health.rs
@@ -137,6 +137,7 @@ fn default_product_health_checks_process_and_independent_endpoint() {
         json!({"ref":reference,"url":url}),
     );
     tui.wait_for("Approval needed");
+    evidence(&tui, "composer-approval");
     click(&mut tui, "Approval needed");
     tui.wait_for("Allow health check access to");
     click(&mut tui, "Deny this call");
@@ -148,7 +149,12 @@ fn default_product_health_checks_process_and_independent_endpoint() {
         std::io::ErrorKind::WouldBlock
     );
     click(&mut tui, "Close");
-    tui.wait_until(|s| !s.contains("Allow health check access to") && s.contains("Message…"));
+    tui.wait_until(|s| {
+        !s.contains("Allow health check access to")
+            && s.contains("Message…")
+            && !s.contains("Approval needed")
+    });
+    evidence(&tui, "composer-settled");
     code_mode::tool_response(
         &mut socket,
         "BackgroundTaskHealth",


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org