From 7e807c106eb4fa13a6b6bcfc75a0c227cb5b27a4 Mon Sep 17 00:00:00 2001 From: Ben Phillips Date: Mon, 29 Jun 2026 14:45:33 +0100 Subject: [PATCH] Flatten multi-line note content into single-line next actions Note inbox items keep their full raw file content as the item's original text. When processed without editing the action field, that multi-line content was written verbatim as a next action, embedding newlines and breaking the one-action-per-line invariant. The bug only appeared alongside a (source) link because both originate from note-type items. Collapse each resolved next action to a single line at the point all write paths read from, so the invariant holds everywhere. Claude-Session: https://claude.ai/code/session_01L6PM2wwRPN7BDUDcB3N6kw --- src/inbox-item-persistence.ts | 12 +++++++++++- tests/inbox-item-persistence.test.ts | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/inbox-item-persistence.ts b/src/inbox-item-persistence.ts index 44b2f87..bd65e47 100644 --- a/src/inbox-item-persistence.ts +++ b/src/inbox-item-persistence.ts @@ -21,6 +21,14 @@ const ACTIONS_REQUIRING_SPHERES: readonly string[] = [ "someday-file", ]; +function flattenToSingleLine(text: string): string { + return text + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .join(" "); +} + export class InboxItemPersistenceService { constructor( private readonly writer: FileWriter, @@ -59,7 +67,9 @@ export class InboxItemPersistenceService { finalNextActions = [item.original]; } - return finalNextActions; + // Each next action must occupy a single line in the destination file, so + // collapse any multi-line content (e.g. a captured note used as the action). + return finalNextActions.map(flattenToSingleLine); } private validateFinalNextActions(item: EditableItem, finalNextActions: string[]): void { diff --git a/tests/inbox-item-persistence.test.ts b/tests/inbox-item-persistence.test.ts index bd4fe47..56a37fd 100644 --- a/tests/inbox-item-persistence.test.ts +++ b/tests/inbox-item-persistence.test.ts @@ -115,6 +115,22 @@ describe("InboxItemPersistenceService", () => { expect(writerMocks.addToNextActionsFile).not.toHaveBeenCalled(); }); + it("flattens multi-line note content into a single-line next action", async () => { + const item: EditableItem = { + original: "Call dentist\nabout the crown that fell out", + isAIProcessed: false, + selectedAction: "next-actions-file", + selectedSpheres: ["personal"], + sourceNoteLink: "[[Captured note|source]]", + }; + + await service.persist(item); + + expect(writerMocks.addToNextActionsFile).toHaveBeenCalledTimes(1); + const actionsArg = writerMocks.addToNextActionsFile.mock.calls[0][0]; + expect(actionsArg).toEqual(["Call dentist about the crown that fell out"]); + }); + it("skips persistence work for trash items", async () => { const item: EditableItem = { original: "Duplicate",