[GH-ISSUE #6242] [Bug] Script export guide omits destination-dependent case isolation #3797

Closed
opened 2026-10-31 00:44:08 +00:00 by bsakaguchi · 3 comments
Owner

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

Before submitting

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

Area

Documentation

Observed behavior

The guide groups case collisions with other isolated layouts without explaining that case-distinct URL paths remain canonical until an actual destination write collides. In an independently executed export-web-scripts CLI export, the first spelling remained canonical and the later spelling was isolated. The manifest explained this intentional rule; the guide did not explain the retained canonical spelling. This report concerns documentation accuracy, not a new exporter behavior regression.

Reproduction

At the source revision listed under REA version, with its locked dependencies already installed, save the following test as tests/acceptance/caseCollisionDocumentation.test.ts. It uses a source-owned capture fixture; no browser or network service runs. I ran this content once under a temporary test filename and then deleted the test. The two CLI exports each succeeded once: one case-distinct pair and one exact-path control.

import { spawnSync } from 'node:child_process';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { expect, it } from 'vitest';
import { scriptScenarioFixture } from '../fixtures/webScriptCapture.js';

it('compares case-distinct export paths with exact-path collisions', async () => {
  const root = await mkdtemp(join(process.env.XDG_CACHE_HOME!, 'case-doc-'));
  try {
    for (const [name, urls] of [
      ['case', ['https://fixture.test/Case.js', 'https://fixture.test/case.js']],
      ['exact', ['https://fixture.test/same.js', 'https://fixture.test/same.js']],
    ] as const) {
      const sources = urls.map((url, index) => ({
        url, bytes: Buffer.from(`export const marker = ${index};\n`),
      }));
      const capture = join(root, `${name}.json`);
      const output = join(root, name);
      await writeFile(capture, JSON.stringify(scriptScenarioFixture(sources)));
      const run = spawnSync(process.execPath, [
        resolve('scripts/rea.mjs'), 'export-web-scripts', capture, output, '--json',
      ], { encoding: 'utf8', env: process.env, timeout: 60000, maxBuffer: 1024 * 1024 });
      expect(run.status, run.stderr).toBe(0);
      const manifest = JSON.parse(await readFile(join(output, 'manifest.json'), 'utf8'));
      const rows = manifest.scripts.map(({ content }: any) => ({
        layout: content.layout, relative_path: content.relative_path,
        layout_reason: content.layout_reason,
      }));
      console.log(JSON.stringify({ name, rows, limitation: manifest.limitations.find((text: string) => text.includes('case-distinct')) }));
      expect(rows[0].layout).toBe(name === 'case' ? 'url-path' : 'isolated');
      if (name === 'case') expect(rows[0].layout_reason).toBeNull();
      else expect(rows[1].layout).toBe('isolated');
      for (const [index, script] of manifest.scripts.entries()) {
        expect(await readFile(join(manifest.analysis_input.input_path, script.content.relative_path))).toEqual(sources[index].bytes);
      }
    }
  } finally {
    await rm(root, { recursive: true, force: true });
  }
}, 120000);

Run from the checkout root with isolated configuration directories:

(
  RUN_ROOT=$(mktemp -d)
  export HOME="$RUN_ROOT/home" DSH_HOME="$RUN_ROOT/dsh"
  export XDG_CONFIG_HOME="$RUN_ROOT/config" XDG_CACHE_HOME="$RUN_ROOT/cache"
  mkdir -p "$HOME" "$DSH_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
  npm run build:cached
  node node_modules/vitest/vitest.mjs run tests/acceptance/caseCollisionDocumentation.test.ts --maxWorkers=1
)

Compare the printed manifest rows with the guide's Sources and mappings passage. The test passes because it records the intentional runtime behavior; the defect is the guide's missing explanation of that behavior.

Expected behavior

The guide should explain the intended distinction between preplanned exact-path/version isolation and destination-dependent case isolation. Case-distinct spellings retain canonical URL paths when the destination supports them; when a later spelling encounters a destination collision, that record is isolated and an already written canonical spelling can remain. This is a documentation correction. The intentional exporter behavior should remain supported.

The intended rule is stated in the persisted manifest limitation and pinned by the case-distinct planning test.

Impact and scope

P3 wording/missing-guidance defect: a reader cannot predict the retained canonical path from the guide's generalized collision bullet. No data loss, byte mismatch, failed export, or blocked workflow was observed. Inspecting the manifest's limitation and per-record layout fields is an available workaround. One case-distinct pair and one exact-path control were run; frequency outside that bounded sample is NOT_ENOUGH_INFO. No security boundary was crossed.

REA version

Source commit 4a4bbab3c3 (main)

Runtime and platform

Node v26.12.0; macOS 27.0.1; arm64. The repository pins Node 24.18.0; that version was not run. The destination rejected the second case spelling; filesystem type was not independently identified.

Target and artifact context

Synthetic saved browser-scenario capture with retained JavaScript response bytes from two URLs at the same fixture origin. No browser was launched, URLs were not fetched, and scripts were not executed.

Interface

CLI

Earliest failure stage

Unknown

Relevant output or evidence

The guide's Sources and mappings bullet gives the generalized case-collision isolation description. The temporary test read the actual persisted manifests and verified all exported bytes. Selected observed rows (origin/digest components omitted only in this display):

case input:
  Case.js -> origins/<origin-hash>/Case.js; layout=url-path; layout_reason=null
  case.js -> isolated/source-2-<source-hash>.js; layout=isolated
  reason: Destination filesystem cannot store both <case.js path> and <Case.js path>; inventory retains both logical names.
exact-path control:
  same.js version 1 -> isolated; reason=Captured paths collide by content version or file/directory prefix.
  same.js version 2 -> isolated; reason=Captured paths collide by content version or file/directory prefix.
CLI exit status: 0 for each input
Focused test: 1 passed; exported bytes matched all four input byte sequences

The manifest's limitation explicitly says case-distinct spellings retain relative layouts and isolation occurs only when the destination cannot store both. Case-sensitive publication is source-derived only, not executed here; MCP and downstream module resolution were not tested.

Typed outcome or recovery expected

No response

Analysis or suspected cause

Source-derived: exact spelling planning preserves case-distinct candidates, and publication fallback isolates only the record whose destination write collides. The guide omits that distinction. The report does not request changing this intentional behavior.

Additional context

Related: #1490, whose closure confirms intended destination-dependent behavior, and #1513, which merged that behavior and the manifest explanation without updating this guide's generalized bullet. These concern the earlier behavior fix; this report concerns remaining documentation. Nine keyword searches covered open and closed issues/PRs; no matching documentation report was found in returned results. Fetched main matched the tested source revision, with no newer relevant commits. No known-good documentation revision was established.

Originally created by @coygeek on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6242 ### 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 Documentation ### Observed behavior The guide groups case collisions with other isolated layouts without explaining that case-distinct URL paths remain canonical until an actual destination write collides. In an independently executed `export-web-scripts` CLI export, the first spelling remained canonical and the later spelling was isolated. The manifest explained this intentional rule; the guide did not explain the retained canonical spelling. This report concerns documentation accuracy, not a new exporter behavior regression. ### Reproduction At the source revision listed under REA version, with its locked dependencies already installed, save the following test as `tests/acceptance/caseCollisionDocumentation.test.ts`. It uses a source-owned capture fixture; no browser or network service runs. I ran this content once under a temporary test filename and then deleted the test. The two CLI exports each succeeded once: one case-distinct pair and one exact-path control. ```ts import { spawnSync } from 'node:child_process'; import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { expect, it } from 'vitest'; import { scriptScenarioFixture } from '../fixtures/webScriptCapture.js'; it('compares case-distinct export paths with exact-path collisions', async () => { const root = await mkdtemp(join(process.env.XDG_CACHE_HOME!, 'case-doc-')); try { for (const [name, urls] of [ ['case', ['https://fixture.test/Case.js', 'https://fixture.test/case.js']], ['exact', ['https://fixture.test/same.js', 'https://fixture.test/same.js']], ] as const) { const sources = urls.map((url, index) => ({ url, bytes: Buffer.from(`export const marker = ${index};\n`), })); const capture = join(root, `${name}.json`); const output = join(root, name); await writeFile(capture, JSON.stringify(scriptScenarioFixture(sources))); const run = spawnSync(process.execPath, [ resolve('scripts/rea.mjs'), 'export-web-scripts', capture, output, '--json', ], { encoding: 'utf8', env: process.env, timeout: 60000, maxBuffer: 1024 * 1024 }); expect(run.status, run.stderr).toBe(0); const manifest = JSON.parse(await readFile(join(output, 'manifest.json'), 'utf8')); const rows = manifest.scripts.map(({ content }: any) => ({ layout: content.layout, relative_path: content.relative_path, layout_reason: content.layout_reason, })); console.log(JSON.stringify({ name, rows, limitation: manifest.limitations.find((text: string) => text.includes('case-distinct')) })); expect(rows[0].layout).toBe(name === 'case' ? 'url-path' : 'isolated'); if (name === 'case') expect(rows[0].layout_reason).toBeNull(); else expect(rows[1].layout).toBe('isolated'); for (const [index, script] of manifest.scripts.entries()) { expect(await readFile(join(manifest.analysis_input.input_path, script.content.relative_path))).toEqual(sources[index].bytes); } } } finally { await rm(root, { recursive: true, force: true }); } }, 120000); ``` Run from the checkout root with isolated configuration directories: ```sh ( RUN_ROOT=$(mktemp -d) export HOME="$RUN_ROOT/home" DSH_HOME="$RUN_ROOT/dsh" export XDG_CONFIG_HOME="$RUN_ROOT/config" XDG_CACHE_HOME="$RUN_ROOT/cache" mkdir -p "$HOME" "$DSH_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" npm run build:cached node node_modules/vitest/vitest.mjs run tests/acceptance/caseCollisionDocumentation.test.ts --maxWorkers=1 ) ``` Compare the printed manifest rows with the guide's Sources and mappings passage. The test passes because it records the intentional runtime behavior; the defect is the guide's missing explanation of that behavior. ### Expected behavior The guide should explain the intended distinction between preplanned exact-path/version isolation and destination-dependent case isolation. Case-distinct spellings retain canonical URL paths when the destination supports them; when a later spelling encounters a destination collision, that record is isolated and an already written canonical spelling can remain. This is a documentation correction. The intentional exporter behavior should remain supported. The intended rule is stated in the [persisted manifest limitation](https://github.com/morluto/rea/blob/4a4bbab3c337701febeb6b532ace49041c4876e0/src/browser/assets/PublishWebScripts.ts#L28) and pinned by the [case-distinct planning test](https://github.com/morluto/rea/blob/4a4bbab3c337701febeb6b532ace49041c4876e0/src/domain/webScriptExportPlan.test.ts#L104). ### Impact and scope P3 wording/missing-guidance defect: a reader cannot predict the retained canonical path from the guide's generalized collision bullet. No data loss, byte mismatch, failed export, or blocked workflow was observed. Inspecting the manifest's limitation and per-record layout fields is an available workaround. One case-distinct pair and one exact-path control were run; frequency outside that bounded sample is NOT_ENOUGH_INFO. No security boundary was crossed. ### REA version Source commit 4a4bbab3c337701febeb6b532ace49041c4876e0 (main) ### Runtime and platform Node v26.12.0; macOS 27.0.1; arm64. The repository pins Node 24.18.0; that version was not run. The destination rejected the second case spelling; filesystem type was not independently identified. ### Target and artifact context Synthetic saved browser-scenario capture with retained JavaScript response bytes from two URLs at the same fixture origin. No browser was launched, URLs were not fetched, and scripts were not executed. ### Interface CLI ### Earliest failure stage Unknown ### Relevant output or evidence The [guide's Sources and mappings bullet](https://github.com/morluto/rea/blob/4a4bbab3c337701febeb6b532ace49041c4876e0/docs/website-script-export.md#L62) gives the generalized case-collision isolation description. The temporary test read the actual persisted manifests and verified all exported bytes. Selected observed rows (origin/digest components omitted only in this display): ```text case input: Case.js -> origins/<origin-hash>/Case.js; layout=url-path; layout_reason=null case.js -> isolated/source-2-<source-hash>.js; layout=isolated reason: Destination filesystem cannot store both <case.js path> and <Case.js path>; inventory retains both logical names. exact-path control: same.js version 1 -> isolated; reason=Captured paths collide by content version or file/directory prefix. same.js version 2 -> isolated; reason=Captured paths collide by content version or file/directory prefix. CLI exit status: 0 for each input Focused test: 1 passed; exported bytes matched all four input byte sequences ``` The manifest's limitation explicitly says case-distinct spellings retain relative layouts and isolation occurs only when the destination cannot store both. Case-sensitive publication is source-derived only, not executed here; MCP and downstream module resolution were not tested. ### Typed outcome or recovery expected _No response_ ### Analysis or suspected cause Source-derived: [exact spelling planning](https://github.com/morluto/rea/blob/4a4bbab3c337701febeb6b532ace49041c4876e0/src/domain/webScriptExportPlan.ts#L106) preserves case-distinct candidates, and [publication fallback](https://github.com/morluto/rea/blob/4a4bbab3c337701febeb6b532ace49041c4876e0/src/browser/assets/PublishWebScripts.ts#L184) isolates only the record whose destination write collides. The guide omits that distinction. The report does not request changing this intentional behavior. ### Additional context Related: #1490, whose closure confirms intended destination-dependent behavior, and #1513, which merged that behavior and the manifest explanation without updating this guide's generalized bullet. These concern the earlier behavior fix; this report concerns remaining documentation. Nine keyword searches covered open and closed issues/PRs; no matching documentation report was found in returned results. Fetched main matched the tested source revision, with no newer relevant commits. No known-good documentation revision was established.
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

确认这是文档准确性问题:manifest 的 limitation 与 #1513 的实现都明确了"大小写不同的路径在目标支持时保持 canonical、仅在实际目的写冲突时 isolate",但指南的 Sources and mappings 泛化表述让读者无法预测 Case.js 会保持 url-path 布局。建议按你提供的 fixture 加一张对照表:case-distinct 对(canonical url-path + 冲突后 isolated)与 exact-path 控制对(双 isolated),并明确 trigger 是"目标文件系统实际写冲突"而非"源捕获时预判"——这样读者和自动化测试都能对齐 manifest 的 limitation 文案。

<!-- gh-comment-id:6332677306 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认这是文档准确性问题:manifest 的 limitation 与 #1513 的实现都明确了"大小写不同的路径在目标支持时保持 canonical、仅在实际目的写冲突时 isolate",但指南的 Sources and mappings 泛化表述让读者无法预测 Case.js 会保持 url-path 布局。建议按你提供的 fixture 加一张对照表:case-distinct 对(canonical url-path + 冲突后 isolated)与 exact-path 控制对(双 isolated),并明确 trigger 是"目标文件系统实际写冲突"而非"源捕获时预判"——这样读者和自动化测试都能对齐 manifest 的 limitation 文案。
Author
Owner

@SahinurDEV commented on GitHub (Oct 22, 2026):

Hi! I'd like to take this one. I'll update the "Sources and mappings" bullet in docs/website-script-export.md to separate planned exact-path/prefix isolation from destination-dependent case isolation, matching the manifest limitation and webScriptExportPlan.ts. Docs-only, no behavior change. I'll open a PR shortly.

<!-- gh-comment-id:6333987440 --> @SahinurDEV commented on GitHub (Oct 22, 2026): Hi! I'd like to take this one. I'll update the "Sources and mappings" bullet in `docs/website-script-export.md` to separate planned exact-path/prefix isolation from destination-dependent case isolation, matching the manifest limitation and `webScriptExportPlan.ts`. Docs-only, no behavior change. I'll open a PR shortly.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Fixed on main in github.com/morluto/rea@80bc0f5ede. Guide now separates preplanned exact-path/version isolation from destination-dependent case isolation, explains that an earlier canonical spelling can remain, and directs readers to per-record layout fields.

Verified against the full issue and current source. The published tree also retains passing docs:check and docs:build receipts. This closes the documentation/help/CI-configuration scope; no new provider runtime verification is claimed.

<!-- gh-comment-id:6376062565 --> @morluto commented on GitHub (Oct 23, 2026): Fixed on main in https://github.com/morluto/rea/commit/80bc0f5ede815a5d9e1a5e97ba20dea5ea7831e3. Guide now separates preplanned exact-path/version isolation from destination-dependent case isolation, explains that an earlier canonical spelling can remain, and directs readers to per-record layout fields. Verified against the full issue and current source. The published tree also retains passing docs:check and docs:build receipts. This closes the documentation/help/CI-configuration scope; no new provider runtime verification is claimed.
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#3797
No description provided.