Skip to content

fix: harden existing-tab matching and document issue workflow#1108

Merged
chhoumann merged 2 commits intomasterfrom
780-bug-quickadd-opens-newly-created-file-in-two-separate-tabs-when-using-cmd+n-on-macos
Feb 22, 2026
Merged

fix: harden existing-tab matching and document issue workflow#1108
chhoumann merged 2 commits intomasterfrom
780-bug-quickadd-opens-newly-created-file-in-two-separate-tabs-when-using-cmd+n-on-macos

Conversation

@chhoumann
Copy link
Copy Markdown
Owner

@chhoumann chhoumann commented Feb 22, 2026

Summary

  • harden existing-tab detection by normalizing vault paths before comparison in openExistingFileTab
  • add regression tests for path normalization/equivalence edge cases
  • document dev-vault/devtools and evidence-first bug triage workflow in AGENTS.md
  • add explicit gh issue develop <issue-number> rule for issue-driven work

Context

Issue #780 was investigated on macOS and could not be reproduced in the current dev setup, but path-comparison hardening is kept as a low-risk guard against path-shape mismatches when reusing open tabs.

Validation

  • bun run test src/utilityObsidian.test.ts
  • bun run lint
  • bun run build

Open with Devin

Summary by CodeRabbit

  • Documentation

    • Expanded developer workflow, Obsidian workflows, and evidence-first bug triage; added GitHub issue resolution guidance.
  • Improvements

    • Improved file-path handling so equivalent paths are recognized regardless of separators, slashes, or case differences.
  • Tests

    • Added tests covering path normalization and equivalence across varied path formats.
  • Chores

    • Added environment configuration manifest for the project.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quickadd Ready Ready Preview Feb 22, 2026 9:26am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 22, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Added workflow and triage documentation to AGENTS.md; introduced vault-path utilities (normalizeVaultFilePath, areSameVaultFilePath), updated openExistingFileTab to use path equivalence, and added tests for path normalization and comparison. Also added a new .codex environment manifest.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md
Added extensive workflow and triage guidance: GitHub issue branch guideline, Dev workflow, Obsidian Dev Vault Workflow, Obsidian DevTools Workflow, and Evidence‑First Bug Triage content.
Vault Path Utilities & Tests
src/utilityObsidian.ts, src/utilityObsidian.test.ts
Added normalizeVaultFilePath(path: string) and areSameVaultFilePath(a,b) exports; updated openExistingFileTab() to consider normalized-path equivalence; added tests covering redundant separators, leading/trailing slashes, Windows-style separators, and path equivalence semantics.
Environment manifest
.codex/environments/environment.toml
Added project environment manifest with basic setup (version, name, setup.script).

Sequence Diagram(s)

(omitted — changes are documentation, small utility additions, and tests; no multi-component sequential flow requiring visualization)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

released

Poem

🐰 In vaults where slashes used to stray,
I hopped and cleaned the messy way.
Two little helpers, tidy and spry,
Now paths agree — no tears to cry. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: hardening existing-tab matching via vault path normalization and documenting issue workflow in AGENTS.md.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 780-bug-quickadd-opens-newly-created-file-in-two-separate-tabs-when-using-cmd+n-on-macos

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 22, 2026

Deploying quickadd with  Cloudflare Pages  Cloudflare Pages

Latest commit: ba1ab62
Status: ✅  Deploy successful!
Preview URL: https://6591702d.quickadd.pages.dev
Branch Preview URL: https://780-bug-quickadd-opens-newly.quickadd.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@chhoumann chhoumann merged commit 7b12d3b into master Feb 22, 2026
4 checks passed
@chhoumann chhoumann deleted the 780-bug-quickadd-opens-newly-created-file-in-two-separate-tabs-when-using-cmd+n-on-macos branch February 22, 2026 09:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/utilityObsidian.ts (2)

901-914: return inside iterateRootLeaves does not short-circuit iteration.

This is pre-existing behaviour and not introduced by this PR, but worth noting for context: when multiple leaves display the same file, leaf is overwritten on each match and the function ends up returning the last matching leaf rather than the first. If "first found" semantics are desired, the callback should throw or use an external flag, since Obsidian's iterateRootLeaves doesn't support early exit via a return value.

The view.file === file || areSameVaultFilePath(...) guard itself is correct and a clean improvement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utilityObsidian.ts` around lines 901 - 914, The iterateRootLeaves
callback uses a plain return to attempt early exit, but Obsidian's
app.workspace.iterateRootLeaves doesn't short-circuit on callback return so the
variable leaf (set inside the callback) can be overwritten and ends up as the
last match; modify the logic in the iterateRootLeaves loop (the callback passed
to app.workspace.iterateRootLeaves, which inspects WorkspaceLeaf and FileView)
to stop after the first match by either throwing a sentinel Error to break out
of iteration or by using an external boolean flag (e.g., found) checked at the
top of the callback to skip further processing once leaf is set; ensure the
matching condition (view.file === file || areSameVaultFilePath(file.path,
view.file.path)) remains and is used to set leaf exactly once.

881-884: Stub fidelity gap: normalizeVaultFilePath does defensive work compensating for minimal test stub.

Obsidian's normalizePath removes leading and trailing slashes and collapses consecutive // sequences. The test stub at tests/obsidian-stub.ts (line 511) only replaces backslashes with forward slashes. This means normalizeVaultFilePath lines 882–883 perform extra work in tests that would be redundant in production:

  • Line 882: .replace(/\/{2,}/g, "/") — collapsed by real normalizePath
  • Line 883: .replace(/^\/+/, "") — stripped by real normalizePath

The code is correct and tests pass, but they validate the extra steps rather than confirming round-trip behavior through the real API. Bring the stub into parity with Obsidian's contract so test coverage reflects production:

Proposed stub update
 export function normalizePath(p: string): string {
   if (typeof p !== 'string') return '' as unknown as string;
-  return p.replace(/\\/g, '/');
+  return p.replace(/\\/g, '/').replace(/\/{2,}/g, '/').replace(/^\/+|\/+$/g, '');
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utilityObsidian.ts` around lines 881 - 884, The test stub for Obsidian's
normalizePath is too minimal, forcing normalizeVaultFilePath to compensate;
update the stubbed normalizePath implementation used in tests to mirror
Obsidian's contract (convert backslashes to forward slashes, collapse
consecutive slashes into a single '/', and remove leading/trailing slashes) so
tests exercise the same behavior as production and no longer validate the
defensive .replace(/\/{2,}/g, "/") and .replace(/^\/+/, "") steps in
normalizeVaultFilePath.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AGENTS.md`:
- Around line 60-71: Replace the three hard-coded machine-specific absolute
paths shown in the doc (the lines beginning "Dev vault root path:", "QuickAdd
plugin path:", and the symlink path string) with configurable placeholders or
instructions to use environment/config variables (e.g. <YOUR_DEV_VAULT_ROOT>,
<YOUR_QUICKADD_PLUGIN_PATH>, <YOUR_SYMLINK_PATH>) and add a short note that
contributors must set these values for their environment; update the examples
that reference running/building (e.g. "bun run dev", "obsidian plugin:reload
vault=dev id=quickadd") to indicate they assume those placeholders are
configured, and remove/neutralize any explicit author-specific absolute paths so
the AGENTS.md content is machine-agnostic.

In `@src/utilityObsidian.test.ts`:
- Around line 92-96: The test "normalizes windows path separators" currently
uses a double-escaped backslash literal ("notes\\\\capture.md") which represents
notes\\capture.md at runtime; add an additional assertion that passes a
single-backslash Windows path literal ("notes\\capture.md") to
normalizeVaultFilePath and expect "notes/capture.md", and update the test
description (or add a separate it block) to clearly state it covers
single-backslash Windows separators; keep or retain the existing double-escape
case if you want to also cover already-escaped strings.

---

Nitpick comments:
In `@src/utilityObsidian.ts`:
- Around line 901-914: The iterateRootLeaves callback uses a plain return to
attempt early exit, but Obsidian's app.workspace.iterateRootLeaves doesn't
short-circuit on callback return so the variable leaf (set inside the callback)
can be overwritten and ends up as the last match; modify the logic in the
iterateRootLeaves loop (the callback passed to app.workspace.iterateRootLeaves,
which inspects WorkspaceLeaf and FileView) to stop after the first match by
either throwing a sentinel Error to break out of iteration or by using an
external boolean flag (e.g., found) checked at the top of the callback to skip
further processing once leaf is set; ensure the matching condition (view.file
=== file || areSameVaultFilePath(file.path, view.file.path)) remains and is used
to set leaf exactly once.
- Around line 881-884: The test stub for Obsidian's normalizePath is too
minimal, forcing normalizeVaultFilePath to compensate; update the stubbed
normalizePath implementation used in tests to mirror Obsidian's contract
(convert backslashes to forward slashes, collapse consecutive slashes into a
single '/', and remove leading/trailing slashes) so tests exercise the same
behavior as production and no longer validate the defensive .replace(/\/{2,}/g,
"/") and .replace(/^\/+/, "") steps in normalizeVaultFilePath.

Comment thread AGENTS.md
Comment on lines +60 to +71
- Always target the `dev` vault when using the Obsidian CLI by passing
`vault=dev` on every command.
- Dev vault root path: `/Users/christian/Developer/dev_vault/dev/`.
- QuickAdd plugin path in the vault:
`/Users/christian/Developer/dev_vault/dev/.obsidian/plugins/quickadd`.
- Run `bun run dev` in this repository to generate/update `main.js` for
development.
- Reload QuickAdd after build/deploy with:
`obsidian plugin:reload vault=dev id=quickadd`.
- In this setup, the vault plugin `main.js` is symlinked to
`/Users/christian/Developer/quickadd/main.js`, so rebuilding updates
the active plugin code directly.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hard-coded machine-specific absolute paths will mislead other contributors and AI agents.

Lines 62–71 embed paths that only exist on the author's machine:

  • Dev vault root path: /Users/christian/Developer/dev_vault/dev/
  • QuickAdd plugin path: /Users/christian/Developer/dev_vault/dev/.obsidian/plugins/quickadd
  • Symlink path: /Users/christian/Developer/quickadd/main.js

Any agent or developer following these instructions on a different machine will either fail silently or write to non-existent locations. Consider replacing the hard-coded values with placeholder tokens (e.g. <YOUR_DEV_VAULT_ROOT>) or directing contributors to set a local config variable, and adding a note that these values are machine-specific and must be configured per-environment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 60 - 71, Replace the three hard-coded
machine-specific absolute paths shown in the doc (the lines beginning "Dev vault
root path:", "QuickAdd plugin path:", and the symlink path string) with
configurable placeholders or instructions to use environment/config variables
(e.g. <YOUR_DEV_VAULT_ROOT>, <YOUR_QUICKADD_PLUGIN_PATH>, <YOUR_SYMLINK_PATH>)
and add a short note that contributors must set these values for their
environment; update the examples that reference running/building (e.g. "bun run
dev", "obsidian plugin:reload vault=dev id=quickadd") to indicate they assume
those placeholders are configured, and remove/neutralize any explicit
author-specific absolute paths so the AGENTS.md content is machine-agnostic.

Comment on lines +92 to +96
it("normalizes windows path separators", () => {
expect(normalizeVaultFilePath("notes\\\\capture.md")).toBe(
"notes/capture.md",
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test uses double backslash; the typical Windows path has a single backslash.

"notes\\\\capture.md" in source is notes\\capture.md at runtime (two backslashes). A real Windows path separator would be one backslash — "notes\\capture.md" in source → notes\capture.md at runtime. The test description "normalizes windows path separators" is therefore slightly misleading. Consider adding the single-backslash case as well:

🛠️ Suggested test additions
 it("normalizes windows path separators", () => {
+  expect(normalizeVaultFilePath("notes\\capture.md")).toBe(
+    "notes/capture.md",
+  );
   expect(normalizeVaultFilePath("notes\\\\capture.md")).toBe(
     "notes/capture.md",
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("normalizes windows path separators", () => {
expect(normalizeVaultFilePath("notes\\\\capture.md")).toBe(
"notes/capture.md",
);
});
it("normalizes windows path separators", () => {
expect(normalizeVaultFilePath("notes\\capture.md")).toBe(
"notes/capture.md",
);
expect(normalizeVaultFilePath("notes\\\\capture.md")).toBe(
"notes/capture.md",
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utilityObsidian.test.ts` around lines 92 - 96, The test "normalizes
windows path separators" currently uses a double-escaped backslash literal
("notes\\\\capture.md") which represents notes\\capture.md at runtime; add an
additional assertion that passes a single-backslash Windows path literal
("notes\\capture.md") to normalizeVaultFilePath and expect "notes/capture.md",
and update the test description (or add a separate it block) to clearly state it
covers single-backslash Windows separators; keep or retain the existing
double-escape case if you want to also cover already-escaped strings.

github-actions Bot pushed a commit that referenced this pull request Mar 5, 2026
# [2.12.0](2.11.0...2.12.0) (2026-03-05)

### Bug Fixes

* **capture:** preserve canvas tab indentation on configured writes ([#1125](#1125)) ([0a1578e](0a1578e))
* disallow capture targets with .base extension ([cb39ed4](cb39ed4))
* **field-suggestions:** opt-in inline values from fenced code blocks ([#1128](#1128)) ([8597905](8597905))
* **gui:** preserve modal edit position during settings reload ([#1132](#1132)) ([11bda19](11bda19))
* **gui:** reduce ai settings modal reload churn ([#1134](#1134)) ([ae0f7a1](ae0f7a1))
* **gui:** reduce choice builder reload churn ([#1136](#1136)) ([818272a](818272a))
* **gui:** reduce macro settings modal reload churn ([#1135](#1135)) ([a1a6271](a1a6271))
* harden existing-tab matching and document issue workflow ([#1108](#1108)) ([7b12d3b](7b12d3b))
* make template path resolution deterministic ([3297d54](3297d54))
* normalize capture title for non-markdown targets ([964d672](964d672))
* preserve capture-format spacing for insert-at-end ([#1119](#1119)) ([8bb8ed4](8bb8ed4))
* preserve explicit capture target file extensions ([57e43ff](57e43ff))
* preserve insert-at-end order for non-newline captures ([#1120](#1120)) ([e7cbbf2](e7cbbf2))
* resolve template file-name paths without duplicate default folder ([7bfd41b](7bfd41b))
* resolve vault-relative template paths using root folders ([81216de](81216de))

### Features

* add AI request logging API and reduce assistant log noise ([#1110](#1110)) ([2c36800](2c36800))
* automate docs version snapshot during release ([#1111](#1111)) ([1571846](1571846))
* **capture:** fully support capture into canvas cards ([#1124](#1124)) ([a53f889](a53f889))
* **cli:** add native QuickAdd Obsidian CLI handlers ([#1129](#1129)) ([8102d47](8102d47))
* **format:** support mapped VALUE suggester display text ([#1127](#1127)) ([b8ec56c](b8ec56c))
* **macro:** add editor cursor navigation commands ([101d5f6](101d5f6))
* support .base template files for template choices ([11e6490](11e6490))

### Reverts

* **gui:** remove modal reload refactor series ([#1137](#1137)) ([3ba1a73](3ba1a73)), closes [#1136](#1136) [#1135](#1135) [#1134](#1134) [#1133](#1133) [#1132](#1132)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 5, 2026

🎉 This PR is included in version 2.12.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] QuickAdd opens newly created file in two separate tabs when using Cmd+N on macOS

1 participant