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/22 11:39:23 UTC

(maka) 01/17: fix(terminal): keep portable helpers free of native runtime

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

commit 0d4d899f5dfd5164e2214fa4cd96709c56842ec4
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Thu Oct 22 18:25:50 2026 +0800

    fix(terminal): keep portable helpers free of native runtime
    
    Keep message carriers independent of the NSH runtime and render tool activity labels inside the reactive UI boundary. Preserve the Node validators and strengthen exact message and Unicode summary assertions.
    
    Source review passed. Runtime Node and formatter gates are reserved for root.
---
 products/maka/terminal-shell/app/TurnPanel.tsx     |  9 +++++--
 .../maka/terminal-shell/app/interaction-form.ts    |  2 +-
 products/maka/terminal-shell/app/text-value.ts     | 29 ++++++++++++++++++++++
 products/maka/terminal-shell/app/text.ts           | 12 +++------
 products/maka/terminal-shell/app/toolActivity.ts   | 10 ++------
 scripts/rust/terminal-interaction-form.test.mjs    | 15 ++++++++---
 scripts/rust/terminal-tool-activity.test.mjs       | 18 ++++++++++++++
 7 files changed, 71 insertions(+), 24 deletions(-)

diff --git a/products/maka/terminal-shell/app/TurnPanel.tsx b/products/maka/terminal-shell/app/TurnPanel.tsx
index 34a09ef26b..ad666299ad 100644
--- a/products/maka/terminal-shell/app/TurnPanel.tsx
+++ b/products/maka/terminal-shell/app/TurnPanel.tsx
@@ -18,7 +18,7 @@
  */
 
 import { createText, errorText } from './text';
-import { tr } from './locale';
+import { displayText, tr } from './locale';
 import { cleanup, computed, effect, inputAction, signal, Text, View } from 'nsh';
 import type { YNode } from 'nsh';
 import { Button } from 'nsh/base';
@@ -425,7 +425,12 @@ export function TurnPanel(props: {
     const activity = props.activity();
     if (activity.kind === 'waiting') return tr('Waiting for your response');
     const tool = props.toolActivity();
-    if (tool) return toolActivitySummary(tool);
+    if (tool)
+      return toolActivitySummary(
+        tool,
+        tool.phase === 'preparing' ? tr('Preparing tool') : tr('Running tool'),
+        tool.title ? displayText(tool.title) : undefined,
+      );
     if (
       activity.kind === 'thinking' &&
       activity.rowId &&
diff --git a/products/maka/terminal-shell/app/interaction-form.ts b/products/maka/terminal-shell/app/interaction-form.ts
index 1622a10b35..2af90c3a17 100644
--- a/products/maka/terminal-shell/app/interaction-form.ts
+++ b/products/maka/terminal-shell/app/interaction-form.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { message as uiMessage, type UiText } from './text';
+import { message as uiMessage, type UiText } from './text-value.ts';
 import type {
   FormField,
   FormValue,
diff --git a/products/maka/terminal-shell/app/text-value.ts b/products/maka/terminal-shell/app/text-value.ts
new file mode 100644
index 0000000000..a5abb6da55
--- /dev/null
+++ b/products/maka/terminal-shell/app/text-value.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { MessageKey } from './locale';
+
+export type UiTextValues = Readonly<Record<string, UiText | number>>;
+export type UiText = string | { readonly key: MessageKey; readonly values?: UiTextValues };
+
+/** Authored interface text keeps its identity; external text remains opaque. */
+export const message = (key: MessageKey, values?: UiTextValues): Exclude<UiText, string> => ({
+  key,
+  values,
+});
diff --git a/products/maka/terminal-shell/app/text.ts b/products/maka/terminal-shell/app/text.ts
index bba65c2e2b..2b1f1e871d 100644
--- a/products/maka/terminal-shell/app/text.ts
+++ b/products/maka/terminal-shell/app/text.ts
@@ -19,15 +19,9 @@
 
 import { signal } from 'nsh';
 import { tr, type MessageKey } from './locale';
+import { message, type UiText, type UiTextValues } from './text-value';
 
-type Values = Readonly<Record<string, UiText | number>>;
-export type UiText = string | { readonly key: MessageKey; readonly values?: Values };
-
-/** Authored interface text keeps its identity; external text remains opaque. */
-export const message = (key: MessageKey, values?: Values): Exclude<UiText, string> => ({
-  key,
-  values,
-});
+export { message, type UiText } from './text-value';
 
 export function renderText(value: UiText): string {
   if (typeof value === 'string') return value;
@@ -53,7 +47,7 @@ export function createText() {
 
 export class UiError extends Error {
   readonly text: UiText;
-  constructor(key: MessageKey, values?: Values) {
+  constructor(key: MessageKey, values?: UiTextValues) {
     const text = message(key, values);
     super(renderText(text));
     this.text = text;
diff --git a/products/maka/terminal-shell/app/toolActivity.ts b/products/maka/terminal-shell/app/toolActivity.ts
index 70a16b6046..3c8b753db1 100644
--- a/products/maka/terminal-shell/app/toolActivity.ts
+++ b/products/maka/terminal-shell/app/toolActivity.ts
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { displayText, tr } from './locale';
 import type { DisplayText } from '../../../../packages/plugin-sdk/src/index.js';
 
 // Display-only source observations. No preview is parsed or used to authorize a tool.
@@ -143,14 +142,9 @@ export function latestToolActivity(
   return latest;
 }
 
-export function toolActivitySummary(tool: ToolActivity): string {
+export function toolActivitySummary(tool: ToolActivity, phaseLabel: string, title?: string): string {
   const detail = tool.detail.replace(/\s+/g, ' ').trim();
-  return [
-    tool.phase === 'preparing' ? tr('Preparing tool') : tr('Running tool'),
-    tool.name,
-    tool.title ? snippet(displayText(tool.title)) : undefined,
-    detail,
-  ]
+  return [phaseLabel, tool.name, title ? snippet(title) : undefined, detail]
     .filter(Boolean)
     .join(' · ');
 }
diff --git a/scripts/rust/terminal-interaction-form.test.mjs b/scripts/rust/terminal-interaction-form.test.mjs
index 9787b1a8aa..a29927bea9 100644
--- a/scripts/rust/terminal-interaction-form.test.mjs
+++ b/scripts/rust/terminal-interaction-form.test.mjs
@@ -33,8 +33,8 @@ const string = (name, extra = {}) => ({
   ...extra,
 });
 
-// Exercise the same pure validator imported by the actual terminal InteractionPanel.
-// Node 24 strips its type-only imports; no copied schema or component stub is used.
+// Exercise the same pure validator and message carriers imported by the actual
+// terminal InteractionPanel. Node strips types; no native module or UI is emulated.
 test('terminal forms preserve complete multibyte defaults and aggregate answers', () => {
   const original = `原文😀${'甲😀'.repeat(1500)}完整结尾`;
   assert.ok([...original].length > 2048);
@@ -85,17 +85,24 @@ test('terminal forms preserve authored limits, omission, and malformed-value rej
   const bounded = string('value', { minLength: 2, maxLength: 3, default: '甲😀' });
   const valid = formSubmission([bounded], initialForm([bounded]));
   assert.equal(valid.answer.values.value, '甲😀', 'authored character limits count Unicode points');
-  for (const value of ['a', '甲😀ab']) {
+  for (const [value, key, count] of [
+    ['a', 'Use at least {count} characters.', 2],
+    ['甲😀ab', 'Use at most {count} characters.', 3],
+  ]) {
     const result = formSubmission([bounded], [{ included: true, value, hidden: false }]);
     assert.equal(result.answer, null);
     assert.ok(
       result.problems[0],
       'an explicit authored minimum or maximum must remain authoritative',
     );
+    assert.deepEqual(result.problems[0], { key, values: { count } });
   }
   const missing = formSubmission([bounded], [{ included: false, value: '甲😀', hidden: false }]);
   assert.equal(missing.answer, null);
-  assert.equal(missing.problems[0], 'This field is required.');
+  assert.deepEqual(missing.problems[0], {
+    key: 'This field is required.',
+    values: undefined,
+  });
 
   for (const [field, value] of [
     [string('value', { format: 'date' }), '2025-02-29'],
diff --git a/scripts/rust/terminal-tool-activity.test.mjs b/scripts/rust/terminal-tool-activity.test.mjs
index 2805d3df8c..cc874e0a5f 100644
--- a/scripts/rust/terminal-tool-activity.test.mjs
+++ b/scripts/rust/terminal-tool-activity.test.mjs
@@ -22,6 +22,7 @@ import test from 'node:test';
 import {
   updateToolActivity,
   latestToolActivity,
+  toolActivitySummary,
 } from '../../products/maka/terminal-shell/app/toolActivity.ts';
 
 import {
@@ -31,6 +32,23 @@ import {
 } from '../../products/maka/terminal-shell/app/history.ts';
 import { thinkingSummary } from '../../products/maka/terminal-shell/app/thinkingSummary.ts';
 
+test('tool summaries use UI-rendered labels while retaining the tool source details', () => {
+  const tool = {
+    phase: 'preparing',
+    name: '搜索源代码',
+    title: { fallback: 'Opaque fallback' },
+    detail: ' 搜索\n  原文😀 ',
+  };
+  assert.equal(
+    toolActivitySummary(tool, '正在准备工具', '查找定义'),
+    '正在准备工具 · 搜索源代码 · 查找定义 · 搜索 原文😀',
+  );
+  assert.equal(
+    toolActivitySummary({ ...tool, phase: 'running' }, 'Running tool'),
+    'Running tool · 搜索源代码 · 搜索 原文😀',
+  );
+});
+
 test('thinking summary retains the last nonempty paragraph and its Unicode text', () => {
   for (const [source, expected] of [
     ['', ''],


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