@tangle-network/ui 11.16.0 → 11.16.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c59ed11: `CommandPreview` prints a failed command's text once when the producer persisted it as both the output and the error.
8
+
9
+ It also reads a JSON string as a `{ stdout, stderr, exitCode }` envelope only when every key is an envelope key, so a command whose own output is JSON with a `stdout` field is shown as printed, and an `error` status turns the exit badge red even when the retained exit code is 0.
10
+
3
11
  ## 11.16.0
4
12
 
5
13
  ### Minor Changes
@@ -67,6 +67,20 @@ function fromObject(obj) {
67
67
  exitCode: finiteNumber(obj.exitCode ?? obj.exit_code ?? obj.code)
68
68
  };
69
69
  }
70
+ /** The keys a serialized command result carries, and nothing else. */
71
+ const ENVELOPE_KEYS = /* @__PURE__ */ new Set([
72
+ "stdout",
73
+ "stderr",
74
+ "output",
75
+ "exitCode",
76
+ "exit_code",
77
+ "code",
78
+ "signal",
79
+ "durationMs",
80
+ "duration_ms",
81
+ "timedOut",
82
+ "timed_out"
83
+ ]);
70
84
  /** A string that is a serialized `{ stdout, stderr, exitCode }` record. */
71
85
  function parseStructured(text) {
72
86
  const trimmed = text.trimStart();
@@ -76,6 +90,7 @@ function parseStructured(text) {
76
90
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
77
91
  const obj = parsed;
78
92
  if (!("stdout" in obj) && !("stderr" in obj)) return void 0;
93
+ if (!Object.keys(obj).every((key) => ENVELOPE_KEYS.has(key))) return;
79
94
  return fromObject(obj);
80
95
  } catch {
81
96
  return;
@@ -91,7 +106,7 @@ function ExitBadge({ exitCode, status }) {
91
106
  "aria-hidden": "true"
92
107
  }), "running"]
93
108
  });
94
- const failed = exitCode !== void 0 ? exitCode !== 0 : status === "error";
109
+ const failed = status === "error" || exitCode !== void 0 && exitCode !== 0;
95
110
  const label = exitCode !== void 0 ? `exit ${exitCode}` : failed ? "error" : null;
96
111
  if (label === null) return null;
97
112
  return /* @__PURE__ */ jsx("span", {
@@ -113,7 +128,8 @@ const CommandPreview = memo(({ part, defaultExpanded = false, className }) => {
113
128
  const command = commandOf(part.state.input);
114
129
  const { status } = part.state;
115
130
  const output = extractCommandOutput(part.state.output);
116
- const errorText = part.state.error;
131
+ const normalize = (text) => text.replace(/\s+/g, " ").trim();
132
+ const errorText = part.state.error !== void 0 && output.stdout.length > 0 && normalize(part.state.error) === normalize(output.stdout) ? void 0 : part.state.error;
117
133
  const hasBody = output.stdout.length > 0 || output.stderr.length > 0 || errorText !== void 0 && errorText.length > 0;
118
134
  const promptLine = /* @__PURE__ */ jsxs(Fragment$1, { children: [
119
135
  /* @__PURE__ */ jsx("span", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.16.0",
3
+ "version": "11.16.1",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -79,6 +79,21 @@ function fromObject(obj: Record<string, unknown>): CommandOutput {
79
79
  };
80
80
  }
81
81
 
82
+ /** The keys a serialized command result carries, and nothing else. */
83
+ const ENVELOPE_KEYS = new Set([
84
+ "stdout",
85
+ "stderr",
86
+ "output",
87
+ "exitCode",
88
+ "exit_code",
89
+ "code",
90
+ "signal",
91
+ "durationMs",
92
+ "duration_ms",
93
+ "timedOut",
94
+ "timed_out",
95
+ ]);
96
+
82
97
  /** A string that is a serialized `{ stdout, stderr, exitCode }` record. */
83
98
  function parseStructured(text: string): CommandOutput | undefined {
84
99
  const trimmed = text.trimStart();
@@ -89,7 +104,13 @@ function parseStructured(text: string): CommandOutput | undefined {
89
104
  return undefined;
90
105
  }
91
106
  const obj = parsed as Record<string, unknown>;
107
+ // Only an unambiguous envelope: a stream key, and nothing but envelope keys.
108
+ // A command whose own stdout is JSON that happens to have a `stdout` field
109
+ // (`{"stdout":"v","result":"ok"}`) is shown as the text it printed.
92
110
  if (!("stdout" in obj) && !("stderr" in obj)) return undefined;
111
+ if (!Object.keys(obj).every((key) => ENVELOPE_KEYS.has(key))) {
112
+ return undefined;
113
+ }
93
114
  return fromObject(obj);
94
115
  } catch {
95
116
  return undefined;
@@ -113,7 +134,9 @@ function ExitBadge({
113
134
  </span>
114
135
  );
115
136
  }
116
- const failed = exitCode !== undefined ? exitCode !== 0 : status === "error";
137
+ // The tool's own error status wins: a runner can fail after the shell exited
138
+ // 0, and a green "exit 0" beside that error would contradict it.
139
+ const failed = status === "error" || (exitCode !== undefined && exitCode !== 0);
117
140
  const label =
118
141
  exitCode !== undefined ? `exit ${exitCode}` : failed ? "error" : null;
119
142
  if (label === null) return null;
@@ -146,7 +169,16 @@ export const CommandPreview = memo(
146
169
  const command = commandOf(part.state.input);
147
170
  const { status } = part.state;
148
171
  const output = extractCommandOutput(part.state.output);
149
- const errorText = part.state.error;
172
+ // A failed call often persists the same text twice — as its output and as
173
+ // its error. Print it once. Only an exact repeat (ignoring whitespace) is
174
+ // folded: an error that merely CONTAINS the output, or the reverse, is a
175
+ // distinct message and keeps its own region.
176
+ const normalize = (text: string) => text.replace(/\s+/g, " ").trim();
177
+ const errorRepeats =
178
+ part.state.error !== undefined &&
179
+ output.stdout.length > 0 &&
180
+ normalize(part.state.error) === normalize(output.stdout);
181
+ const errorText = errorRepeats ? undefined : part.state.error;
150
182
  const hasBody =
151
183
  output.stdout.length > 0 ||
152
184
  output.stderr.length > 0 ||