fix(edit): skipped notebook bom sniffing

Skipped binary BOM detection for notebook-backed hashline reads so virtual cell text is serialized without a leading U+FEFF marker.
This commit is contained in:
roboomp
2026-06-30 05:58:01 +00:00
parent dd4eb68b37
commit 7ec00a5007
3 changed files with 38 additions and 3 deletions
@@ -28,6 +28,7 @@ import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
import { isInternalUrlPath } from "../../tools/path-utils";
import { enforcePlanModeWrite, resolvePlanPath, targetsLocalSandbox } from "../../tools/plan-mode-guard";
import { canonicalSnapshotKey } from "../file-snapshot-store";
import { isNotebookPath } from "../notebook";
import { readEditFileText, serializeEditFileText } from "../read-file";
import type { LspBatchRequest } from "../renderer";
@@ -123,8 +124,9 @@ export class HashlineFilesystem extends Filesystem {
return content;
}
async readBinary(relativePath: string): Promise<Uint8Array> {
async readBinary(relativePath: string): Promise<Uint8Array | undefined> {
const absolutePath = this.resolveAbsolute(relativePath);
if (isNotebookPath(absolutePath)) return undefined;
try {
return await fs.readFile(absolutePath);
} catch (error) {
@@ -128,6 +128,39 @@ describe("hashline executor", () => {
});
});
it("edits BOM-prefixed notebooks through the virtual cell text", async () => {
await withTempDir(async tempDir => {
const filePath = path.join(tempDir, "notebook.ipynb");
const notebook = {
cells: [
{
cell_type: "markdown",
metadata: { keep: true },
source: ["# Title\n"],
},
],
metadata: {},
nbformat: 4,
nbformat_minor: 5,
};
await Bun.write(
filePath,
new Uint8Array([0xef, 0xbb, 0xbf, ...new TextEncoder().encode(JSON.stringify(notebook))]),
);
const session = makeHashlineSession(tempDir);
const editableText = "# %% [markdown] cell:0\n# Title\n";
const sourceTag = recordFullSnapshot(getFileReadCache(session), filePath, editableText);
const input = `${header("notebook.ipynb", sourceTag)}\n${sameLineRange(tag(2, "# Title"))}\n${repl("# Updated")}\n`;
await executeHashlineSingle(hashlineExecuteOptions(tempDir, input, undefined, session));
const updated = await Bun.file(filePath).json();
expect(updated.cells).toHaveLength(1);
expect(updated.cells[0].source).toEqual(["# Updated\n"]);
expect(updated.cells[0].metadata).toEqual({ keep: true });
});
});
it("emits an actionable no-op diagnostic when the payload matches the file byte-for-byte", async () => {
await withTempDir(async tempDir => {
const filePath = path.join(tempDir, "a.ts");
+2 -2
View File
@@ -65,8 +65,8 @@ export abstract class Filesystem {
/** Read the file's full text content. Throw on missing file. */
abstract readText(path: string): Promise<string>;
/** Read the file's raw bytes when text decoding may hide leading bytes such as a UTF-8 BOM. */
readBinary?(path: string): Promise<Uint8Array>;
/** Read raw bytes for backends whose text is a direct decode of persisted bytes. */
readBinary?(path: string): Promise<Uint8Array | undefined>;
/** Validate that `path` is writable before a prepared batch starts committing. */
async preflightWrite(_path: string, _options?: PreflightWriteOptions): Promise<void> {}