Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d852c29
feat(capture): add targeted canvas node capture
chhoumann Feb 25, 2026
70ec2e6
chore(capture): apply review polish
chhoumann Feb 25, 2026
908011d
fix(capture): abort .canvas capture without node id
chhoumann Feb 25, 2026
66965a0
fix(capture): prevent concurrent canvas overwrite
chhoumann Feb 25, 2026
74fec55
fix(capture): allow vault picker and clear stale canvas node ids
chhoumann Feb 25, 2026
01d80d6
codex: address PR review feedback (#1124)
chhoumann Feb 25, 2026
2eee632
codex: fix PR #1124 CI regressions
chhoumann Feb 25, 2026
de8e841
codex: avoid duplicate capture path formatting (#1124)
chhoumann Feb 25, 2026
e5a287e
codex: fix capture success notice placement (#1124)
chhoumann Feb 25, 2026
49c2eae
codex: guard canvas file-card cursor fallback (#1124)
chhoumann Feb 25, 2026
9f69be8
codex: restrict picker to capturable canvas nodes (#1124)
chhoumann Feb 25, 2026
94e9a28
codex: remove redundant canvas destination source-path reset (#1124)
chhoumann Feb 25, 2026
cef9a59
codex: fix picker button disable typing (#1124)
chhoumann Feb 26, 2026
a63cce7
codex: address PR review feedback (#1124)
chhoumann Feb 26, 2026
e5bb166
codex: address PR review feedback (#1124)
chhoumann Feb 26, 2026
3e8d167
codex: fix canvas capture correctness regressions (#1124)
chhoumann Feb 26, 2026
1aea168
codex: harden stale canvas node config fallback (#1124)
chhoumann Feb 26, 2026
577dea5
codex: add canvas file-card regressions and remove dead helpers (#1124)
chhoumann Feb 26, 2026
4bb7627
fix(capture): abort invalid insert-after and align canvas link behavior
chhoumann Feb 27, 2026
7885014
test(capture): assert choice abort notices are always visible
chhoumann Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions docs/docs/Choices/CaptureChoice.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ If you have a tag called `#people`, and you type `#people` in the _Capture To_ f
- _Create file if it doesn't exist_ will do as the name implies - you can also create the file from a template, if you specify the template (the input box will appear below the setting).
- _Task_ will format your captured text as a task.
- _Use editor selection as default value_ controls whether the current editor selection is used as `{{VALUE}}`. Choose **Follow global setting**, **Use selection**, or **Ignore selection** (global default lives in Settings > Input). This does not affect `{{SELECTED}}`.
- _Write to bottom of file_ will put whatever you enter at the bottom of the file.
- _Write position_ controls where Capture writes: top, bottom, after line, and active-file cursor modes.
- _Append link_ will append a link to the file you have open in the file you're capturing to. You can choose between three modes:
- **Enabled (requires active file)** – keeps the legacy behavior and throws an error if no note is focused
- **Enabled (requires active file)** – keeps the legacy behavior and throws an error if no note is focused (except Canvas-triggered capture, where link insertion is skipped)
- **Enabled (skip if no active file)** – inserts the link when possible and silently drops `{{LINKCURRENT}}` if nothing is open
- **Disabled** – never append a link

Expand All @@ -60,6 +60,78 @@ If you have a tag called `#people`, and you type `#people` in the _Capture To_ f
- **End of line** - Places the link at the end of the current line
- **New line** - Places the link on a new line below the cursor

## Canvas Capture Notes

QuickAdd supports two Canvas capture workflows:

- Capture to one selected card in the active Canvas view
- Capture to a specific card in a specific `.canvas` file

### 1) Capture to selected card in active Canvas

This mode is enabled when **Capture to active file** is on and the active leaf
is a Canvas.

Supported card targets:

- Text cards
- File cards that point to markdown files

### 2) Capture to specific card in specific `.canvas` file

This mode is enabled when **Capture to active file** is off, the capture path
resolves to a `.canvas` file, and **Target canvas node** is set.

When the capture path is a `.canvas` file, QuickAdd shows a node picker that
helps you choose a node id directly from that board.

### Write position support in Canvas

- Text cards support: **Top of file**, **Bottom of file**, **After line...**
- File cards (markdown targets) support: **Top of file**, **Bottom of file**, **After line...**
- Canvas does not support cursor-based modes: **At cursor**, **New line above cursor**, **New line below cursor**

If **Capture to active file** is enabled and you leave the default write
position at **At cursor**, capture will abort in Canvas until you switch to a
supported mode.

Canvas capture requires exactly one selected card in selected-card mode. If the
selection is missing, multi-select, or unsupported, QuickAdd aborts with a
notice instead of writing to the wrong place.

When append-link is set to **Enabled (requires active file)** and capture runs
from a Canvas card without a focused Markdown editor, the capture still writes
and link insertion is skipped.

A dedicated Canvas walkthrough page will return in a future update.

### Canvas Capture FAQ

**Why did my capture abort in Canvas?**

Most often one of these is true:

- No card is selected
- More than one card is selected
- The selected card type is unsupported
- The selected write mode is cursor-based

**Can I target a specific card in a Canvas file?**

Yes. Set capture path to a `.canvas` file and choose a **Target canvas node**.

**Does "At cursor" work in Canvas cards?**

No. Use top, bottom, or insert-after placement.

**Can I capture to a file card that points to a Canvas file?**

No. File-card capture supports markdown targets only.

**Can I still create new Canvas files from templates?**

Yes. Template choices support `.canvas` templates.

## Insert after

Insert After will allow you to insert the text after some line with the specified text.
Expand Down
6 changes: 3 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ export const DATE_VARIABLE_REGEX = new RegExp(
);
export const LINK_TO_CURRENT_FILE_REGEX = new RegExp(/{{LINKCURRENT}}/i);
export const FILE_NAME_OF_CURRENT_FILE_REGEX = new RegExp(/{{FILENAMECURRENT}}/i);
export const MARKDOWN_FILE_EXTENSION_REGEX = new RegExp(/\.md$/);
export const CANVAS_FILE_EXTENSION_REGEX = new RegExp(/\.canvas$/);
export const BASE_FILE_EXTENSION_REGEX = new RegExp(/\.base$/);
export const MARKDOWN_FILE_EXTENSION_REGEX = new RegExp(/\.md$/i);
export const CANVAS_FILE_EXTENSION_REGEX = new RegExp(/\.canvas$/i);
export const BASE_FILE_EXTENSION_REGEX = new RegExp(/\.base$/i);
export const JAVASCRIPT_FILE_EXTENSION_REGEX = new RegExp(/\.js$/);
export const MACRO_REGEX = new RegExp(/{{MACRO:([^\n\r}]*)}}/i);
export const TEMPLATE_REGEX = new RegExp(
Expand Down
29 changes: 24 additions & 5 deletions src/engine/CaptureChoiceEngine.notice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import { CaptureChoiceEngine } from "./CaptureChoiceEngine";
import type { IChoiceExecutor } from "../IChoiceExecutor";
import type ICaptureChoice from "../types/choices/ICaptureChoice";
import { MacroAbortError } from "../errors/MacroAbortError";
import { ChoiceAbortError } from "../errors/ChoiceAbortError";
import { settingsStore } from "../settingsStore";

const defaultSettingsState = structuredClone(settingsStore.getState());
Expand Down Expand Up @@ -153,7 +154,7 @@ const createCaptureChoice = (): ICaptureChoice => ({
},
});

const createEngine = (abortMessage: string) => {
const createEngine = (abortError: Error) => {
const app = {
vault: {
adapter: {
Expand Down Expand Up @@ -185,7 +186,7 @@ const createEngine = (abortMessage: string) => {
);

(engine as any).getFormattedPathToCaptureTo = vi.fn(async () => {
throw new MacroAbortError(abortMessage);
throw abortError;
});

return engine;
Expand All @@ -202,7 +203,7 @@ describe("CaptureChoiceEngine cancellation notices", () => {
...settingsStore.getState(),
showInputCancellationNotification: true,
});
const engine = createEngine("Input cancelled by user");
const engine = createEngine(new MacroAbortError("Input cancelled by user"));

await engine.run();

Expand All @@ -218,7 +219,7 @@ describe("CaptureChoiceEngine cancellation notices", () => {
showInputCancellationNotification: false,
});

const engine = createEngine("Input cancelled by user");
const engine = createEngine(new MacroAbortError("Input cancelled by user"));

await engine.run();

Expand All @@ -231,7 +232,7 @@ describe("CaptureChoiceEngine cancellation notices", () => {
showInputCancellationNotification: false,
});

const engine = createEngine("Target file missing");
const engine = createEngine(new MacroAbortError("Target file missing"));

await engine.run();

Expand All @@ -241,6 +242,24 @@ describe("CaptureChoiceEngine cancellation notices", () => {
);
});

it("shows notices for choice abort errors even when input cancellation notifications are disabled", async () => {
settingsStore.setState({
...settingsStore.getState(),
showInputCancellationNotification: false,
});

const engine = createEngine(
new ChoiceAbortError("Insert-after target not found: '# Missing'."),
);

await engine.run();

expect(noticeClass.instances).toHaveLength(1);
expect(noticeClass.instances[0]?.message).toContain(
"Capture execution aborted: Insert-after target not found: '# Missing'.",
);
});

it("shows a notice when the target file is missing and create is disabled", async () => {
settingsStore.setState({
...settingsStore.getState(),
Expand Down
Loading