From 00ab2b9915425e16c220b9d107f6fdabc9ea8c7f Mon Sep 17 00:00:00 2001 From: salmonumbrella <182032677+salmonumbrella@users.noreply.github.com> Date: Sat, 28 Feb 2026 11:19:19 -0800 Subject: [PATCH 1/3] fix: normalize TODO+ARCHIVED prefix on Cmd/Ctrl+Enter When pressing Cmd/Ctrl+Enter on an ARCHIVED block, Roam prepends TODO to get TODO+ARCHIVED. This collapses that into just TODO. Also reads block text from Roam's data layer instead of the textarea DOM value, which can lag behind after API updates. Closes #4 --- src/index.ts | 30 ++++++++++++++++++++---- src/utils/normalizeTodoArchivedPrefix.ts | 7 ++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/utils/normalizeTodoArchivedPrefix.ts diff --git a/src/index.ts b/src/index.ts index e4cd138..e2a3487 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ import extractRef from "roamjs-components/util/extractRef"; import extractTag from "roamjs-components/util/extractTag"; import getChildrenLengthByParentUid from "roamjs-components/queries/getChildrenLengthByParentUid"; import initializeTodont, { TODONT_MODES } from "./utils/todont"; +import normalizeTodoArchivedPrefix from "./utils/normalizeTodoArchivedPrefix"; export default runExtension(async ({ extensionAPI }) => { const toggleTodont = initializeTodont(); @@ -128,6 +129,9 @@ export default runExtension(async ({ extensionAPI }) => { } const text = extensionAPI.settings.get("append-text") as string; let value = oldValue; + // Roam's Cmd/Ctrl+Enter prepends TODO for non-checkbox blocks. + // If the block starts with ARCHIVED, collapse TODO+ARCHIVED into TODO. + value = normalizeTodoArchivedPrefix(value); if (text) { const formattedText = ` ${text .replace(new RegExp("\\^", "g"), "\\^") @@ -352,10 +356,28 @@ export default runExtension(async ({ extensionAPI }) => { if (target.tagName === "TEXTAREA") { const textArea = target as HTMLTextAreaElement; const { blockUid } = getUids(textArea); - if (textArea.value.startsWith("{{[[DONE]]}}")) { - onDone(blockUid, textArea.value); - } else if (textArea.value.startsWith("{{[[TODO]]}}")) { - onTodo(blockUid, textArea.value); + // Read from Roam's data layer — the textarea DOM value may lag + // behind after a recent API update (e.g. toggling to ARCHIVED). + const blockText = + getTextByBlockUid(blockUid) || textArea.value; + if (blockText.startsWith("{{[[ARCHIVED]]}}")) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + const withoutArchived = blockText.replace( + /^\{\{\[\[ARCHIVED\]\]\}}\s*/, + "", + ); + const normalized = withoutArchived + ? `{{[[TODO]]}} ${withoutArchived}` + : "{{[[TODO]]}}"; + if (normalized !== blockText) { + updateBlock({ uid: blockUid, text: normalized }); + } + } else if (blockText.startsWith("{{[[DONE]]}}")) { + onDone(blockUid, blockText); + } else if (blockText.startsWith("{{[[TODO]]}}")) { + onTodo(blockUid, blockText); } return; } diff --git a/src/utils/normalizeTodoArchivedPrefix.ts b/src/utils/normalizeTodoArchivedPrefix.ts new file mode 100644 index 0000000..27896b1 --- /dev/null +++ b/src/utils/normalizeTodoArchivedPrefix.ts @@ -0,0 +1,7 @@ +const TODO_ARCHIVED_PREFIX_REGEX = + /^(\{\{\[\[TODO\]\]\}})\s*\{\{\[\[ARCHIVED\]\]\}}/; + +const normalizeTodoArchivedPrefix = (value: string): string => + value.replace(TODO_ARCHIVED_PREFIX_REGEX, "$1"); + +export default normalizeTodoArchivedPrefix; From 930f34a9fac233d04a9a6da70cb261fc40a6d32e Mon Sep 17 00:00:00 2001 From: salmonumbrella <182032677+salmonumbrella@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:24:14 -0700 Subject: [PATCH 2/3] fix: align single block todo toggle callbacks --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index e2a3487..4ab16f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -375,9 +375,9 @@ export default runExtension(async ({ extensionAPI }) => { updateBlock({ uid: blockUid, text: normalized }); } } else if (blockText.startsWith("{{[[DONE]]}}")) { - onDone(blockUid, blockText); - } else if (blockText.startsWith("{{[[TODO]]}}")) { onTodo(blockUid, blockText); + } else if (blockText.startsWith("{{[[TODO]]}}")) { + onDone(blockUid, blockText); } return; } From 60359650dc27754eeaf7260c94f5553d702e4a0c Mon Sep 17 00:00:00 2001 From: salmonumbrella <182032677+salmonumbrella@users.noreply.github.com> Date: Tue, 31 Mar 2026 05:07:20 -0700 Subject: [PATCH 3/3] fix: revert TODO/DONE toggle to read textarea value synchronously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single-block handler was reading getTextByBlockUid (pre-toggle data layer) and using inverted routing. This caused a race with Roam's own toggle — onDone/onTodo received text with the wrong prefix, leading to duplicated append text instead of clean toggling. Reverting to textArea.value (which Roam updates synchronously on Ctrl+Enter) with standard routing (DONE→onDone, TODO→onTodo) fixes the regression. The ARCHIVED handler still uses getTextByBlockUid since textarea lags for that specific case. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4ab16f3..9aee33c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -356,8 +356,8 @@ export default runExtension(async ({ extensionAPI }) => { if (target.tagName === "TEXTAREA") { const textArea = target as HTMLTextAreaElement; const { blockUid } = getUids(textArea); - // Read from Roam's data layer — the textarea DOM value may lag - // behind after a recent API update (e.g. toggling to ARCHIVED). + // Check data layer for ARCHIVED state — the textarea DOM value + // may lag behind after a recent API update. const blockText = getTextByBlockUid(blockUid) || textArea.value; if (blockText.startsWith("{{[[ARCHIVED]]}}")) { @@ -374,10 +374,10 @@ export default runExtension(async ({ extensionAPI }) => { if (normalized !== blockText) { updateBlock({ uid: blockUid, text: normalized }); } - } else if (blockText.startsWith("{{[[DONE]]}}")) { - onTodo(blockUid, blockText); - } else if (blockText.startsWith("{{[[TODO]]}}")) { - onDone(blockUid, blockText); + } else if (textArea.value.startsWith("{{[[DONE]]}}")) { + onDone(blockUid, textArea.value); + } else if (textArea.value.startsWith("{{[[TODO]]}}")) { + onTodo(blockUid, textArea.value); } return; }