[GH-ISSUE #7432] [Bug] Rendered Markdown tables strip edge spaces from tensor names #4438

Closed
opened 2026-10-31 00:49:48 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7432

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

node scripts/rea.mjs inspect-model-artifact "$WORK/model.safetensors" --format md succeeds, but rendering its output with the repository's locked VitePress renderer changes the tensor name " leading " to "leading" and "trailing " to "trailing". A separate tensor actually named "leading" becomes indistinguishable from the first. The same command with --format json retains the exact names; raw Markdown retains the spaces.

Reproduction

From the stated source commit with Node 24.22.1 on PATH and locked dependencies installed, run the following from the repository root. A fresh checkout needs npm ci && npm run build:cached first; verification used the supplied locked dependencies and ran npm run build:cached. The paired CLI/renderer comparison below was run once.

export WORK="$(mktemp -d)"
export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config"
export XDG_DATA_HOME="$WORK/data" XDG_CACHE_HOME="$WORK/cache"
export XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/tmp"
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME" "$TMPDIR"
node --input-type=module <<'JS'
import { writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { createMarkdownRenderer, disposeMdItInstance } from 'vitepress';
import { parseFragment } from 'parse5';
const work = process.env.WORK;
const names = ['  leading ', 'leading', 'trailing  ', 'plain'];
const header = Buffer.from(JSON.stringify(Object.fromEntries(names.map((name, i) => [name, { dtype: 'F32', shape: [1], data_offsets: [i * 4, i * 4 + 4] }]))));
const length = Buffer.alloc(8);
length.writeBigUInt64LE(BigInt(header.length));
writeFileSync(`${work}/model.safetensors`, Buffer.concat([length, header, Buffer.alloc(names.length * 4)]));
const outputs = {};
for (const format of ['md', 'json']) {
  const result = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-model-artifact', `${work}/model.safetensors`, '--format', format], { env: { ...process.env, REA_LOG_LEVEL: 'silent' }, encoding: 'utf8', timeout: 30000 });
  console.log(`${format}: exit=${result.status}, stderr=${JSON.stringify(result.stderr)}`);
  if (result.status !== 0) throw new Error(result.stderr || String(result.error));
  outputs[format] = result.stdout;
}
const renderer = await createMarkdownRenderer(work, { linkify: false });
const text = node => node.nodeName === '#text' ? node.value : (node.childNodes ?? []).map(text).join('');
const rows = [];
(function visit(node) {
  if (node.tagName === 'tr') rows.push(node);
  else (node.childNodes ?? []).forEach(visit);
})(parseFragment(renderer.render(outputs.md)));
const cells = rows.map(row => (row.childNodes ?? []).filter(node => node.tagName === 'td').map(text));
const tensorRows = cells.filter(row => row.length === 14 && /^\d+$/.test(row[0]));
console.log(JSON.stringify({ expected: JSON.parse(outputs.json).normalized_result.tensors.map(tensor => tensor.name), rendered: tensorRows.map(row => row[1]) }, null, 2));
disposeMdItInstance();
JS

Expected behavior

Rendered tensor-name cells should retain the exact names returned by JSON, including edge spaces. At the stated commit, docs/cli.md:965-973 promises literal source text in table headers and cells, and tests/boundary/cli/markdownLiteralCells.test.ts already uses VitePress and parse5 to compare rendered public CLI cell text with source values.

Impact and scope

Distinct tensor identifiers collapse to the same displayed name despite successful exit status. This is P2: the observed failure is bounded to rendered table text, and the executed JSON command preserves the exact identifiers as a practical workaround. The plain-name control is unchanged. Stored Evidence loss was not observed. Other whitespace characters, table headers, other renderers and browser layout were not tested.

REA version

Exact node scripts/rea.mjs --version output: 6.8.0. Source commit: c79bd41b5d6d65b7b2a9b6e0b58d376fc36910d6.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, arm64; lockfile-pinned VitePress 1.6.4 and parse5. No external analysis engine was used.

Target and artifact context

An inline-generated safetensors file with four F32 tensors, each shape [1], four nonoverlapping four-byte data ranges, and names " leading ", "leading", "trailing " and "plain".

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The independently executed comparison printed:

md: exit=0, stderr=""
json: exit=0, stderr=""
{
  "expected": ["  leading ", "leading", "plain", "trailing  "],
  "rendered": ["leading", "leading", "plain", "trailing"]
}

The raw first tensor row contains | 0 | leading |, retaining the name's spaces before rendering. Evidence provenance: observed through the built public CLI and locked renderer; the source explanation below is separate reasoning.

Analysis or suspected cause

Source-derived: src/cli/cliResultOutput.ts:489-509 formats and writes Markdown through REA's formatter. src/cli/cliOutputFormatter.ts:61-67 escapes syntax without encoding edge spaces, and renderTable at lines 82-109 surrounds the resulting strings with ordinary table padding. The renderer consumes edge spaces as table layout. The helper also serves table headers, but that broader case was not executed.

Additional context

Open issue #7325 concerns brace-delimited provider JSON disappearing through VitePress's attributes extension; escaping braces has a different closure criterion. Closed issue #6755 concerns pipe delimiters truncating cells, and closed issue #5525 concerns nested objects becoming [object Object]. Merged PR #7073 preserves inline Markdown syntax, but edge spaces still fail on the verified current main. Proposed restoration check: the same comparison should return identical expected and rendered name arrays, while the plain control and JSON names remain unchanged. The first affected release is unknown.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A successful public CLI report renders two distinct tensor identifiers as the same name, and strips another identifier's trailing spaces in the executed four-tensor fixture.
  • Workaround status: available
  • Workaround basis: The paired --format json command retained every exact tensor name; the raw Markdown also retained its spaces.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7432 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area CLI ## Observed behavior `node scripts/rea.mjs inspect-model-artifact "$WORK/model.safetensors" --format md` succeeds, but rendering its output with the repository's locked VitePress renderer changes the tensor name `" leading "` to `"leading"` and `"trailing "` to `"trailing"`. A separate tensor actually named `"leading"` becomes indistinguishable from the first. The same command with `--format json` retains the exact names; raw Markdown retains the spaces. ## Reproduction From the stated source commit with Node 24.22.1 on PATH and locked dependencies installed, run the following from the repository root. A fresh checkout needs `npm ci && npm run build:cached` first; verification used the supplied locked dependencies and ran `npm run build:cached`. The paired CLI/renderer comparison below was run once. ```sh export WORK="$(mktemp -d)" export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config" export XDG_DATA_HOME="$WORK/data" XDG_CACHE_HOME="$WORK/cache" export XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/tmp" mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME" "$TMPDIR" node --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { createMarkdownRenderer, disposeMdItInstance } from 'vitepress'; import { parseFragment } from 'parse5'; const work = process.env.WORK; const names = [' leading ', 'leading', 'trailing ', 'plain']; const header = Buffer.from(JSON.stringify(Object.fromEntries(names.map((name, i) => [name, { dtype: 'F32', shape: [1], data_offsets: [i * 4, i * 4 + 4] }])))); const length = Buffer.alloc(8); length.writeBigUInt64LE(BigInt(header.length)); writeFileSync(`${work}/model.safetensors`, Buffer.concat([length, header, Buffer.alloc(names.length * 4)])); const outputs = {}; for (const format of ['md', 'json']) { const result = spawnSync(process.execPath, ['scripts/rea.mjs', 'inspect-model-artifact', `${work}/model.safetensors`, '--format', format], { env: { ...process.env, REA_LOG_LEVEL: 'silent' }, encoding: 'utf8', timeout: 30000 }); console.log(`${format}: exit=${result.status}, stderr=${JSON.stringify(result.stderr)}`); if (result.status !== 0) throw new Error(result.stderr || String(result.error)); outputs[format] = result.stdout; } const renderer = await createMarkdownRenderer(work, { linkify: false }); const text = node => node.nodeName === '#text' ? node.value : (node.childNodes ?? []).map(text).join(''); const rows = []; (function visit(node) { if (node.tagName === 'tr') rows.push(node); else (node.childNodes ?? []).forEach(visit); })(parseFragment(renderer.render(outputs.md))); const cells = rows.map(row => (row.childNodes ?? []).filter(node => node.tagName === 'td').map(text)); const tensorRows = cells.filter(row => row.length === 14 && /^\d+$/.test(row[0])); console.log(JSON.stringify({ expected: JSON.parse(outputs.json).normalized_result.tensors.map(tensor => tensor.name), rendered: tensorRows.map(row => row[1]) }, null, 2)); disposeMdItInstance(); JS ``` ## Expected behavior Rendered tensor-name cells should retain the exact names returned by JSON, including edge spaces. At the stated commit, `docs/cli.md:965-973` promises literal source text in table headers and cells, and `tests/boundary/cli/markdownLiteralCells.test.ts` already uses VitePress and parse5 to compare rendered public CLI cell text with source values. ## Impact and scope Distinct tensor identifiers collapse to the same displayed name despite successful exit status. This is P2: the observed failure is bounded to rendered table text, and the executed JSON command preserves the exact identifiers as a practical workaround. The plain-name control is unchanged. Stored Evidence loss was not observed. Other whitespace characters, table headers, other renderers and browser layout were not tested. ## REA version Exact `node scripts/rea.mjs --version` output: `6.8.0`. Source commit: `c79bd41b5d6d65b7b2a9b6e0b58d376fc36910d6`. ## Runtime and platform Node.js v24.22.1; macOS 27.0.1, arm64; lockfile-pinned VitePress 1.6.4 and parse5. No external analysis engine was used. ## Target and artifact context An inline-generated safetensors file with four F32 tensors, each shape `[1]`, four nonoverlapping four-byte data ranges, and names `" leading "`, `"leading"`, `"trailing "` and `"plain"`. ## Interface CLI ## Earliest failure stage Transport or result projection ## Relevant output or evidence The independently executed comparison printed: ```text md: exit=0, stderr="" json: exit=0, stderr="" ``` ```json { "expected": [" leading ", "leading", "plain", "trailing "], "rendered": ["leading", "leading", "plain", "trailing"] } ``` The raw first tensor row contains `| 0 | leading |`, retaining the name's spaces before rendering. Evidence provenance: observed through the built public CLI and locked renderer; the source explanation below is separate reasoning. ## Analysis or suspected cause Source-derived: `src/cli/cliResultOutput.ts:489-509` formats and writes Markdown through REA's formatter. `src/cli/cliOutputFormatter.ts:61-67` escapes syntax without encoding edge spaces, and `renderTable` at lines 82-109 surrounds the resulting strings with ordinary table padding. The renderer consumes edge spaces as table layout. The helper also serves table headers, but that broader case was not executed. ## Additional context Open [issue #7325](https://github.com/morluto/rea/issues/7325) concerns brace-delimited provider JSON disappearing through VitePress's attributes extension; escaping braces has a different closure criterion. Closed [issue #6755](https://github.com/morluto/rea/issues/6755) concerns pipe delimiters truncating cells, and closed [issue #5525](https://github.com/morluto/rea/issues/5525) concerns nested objects becoming `[object Object]`. Merged [PR #7073](https://github.com/morluto/rea/pull/7073) preserves inline Markdown syntax, but edge spaces still fail on the verified current main. Proposed restoration check: the same comparison should return identical expected and rendered name arrays, while the plain control and JSON names remain unchanged. The first affected release is unknown. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A successful public CLI report renders two distinct tensor identifiers as the same name, and strips another identifier's trailing spaces in the executed four-tensor fixture. - Workaround status: available - Workaround basis: The paired --format json command retained every exact tensor name; the raw Markdown also retained its spaces.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4438
No description provided.