Skip to content

Copilot/turn repo into obsidian plugin#10

Open
carlymariec wants to merge 2 commits into
mainfrom
copilot/turn-repo-into-obsidian-plugin
Open

Copilot/turn repo into obsidian plugin#10
carlymariec wants to merge 2 commits into
mainfrom
copilot/turn-repo-into-obsidian-plugin

Conversation

@carlymariec
Copy link
Copy Markdown
Owner

No description provided.

Copilot AI review requested due to automatic review settings May 27, 2026 17:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@carlymariec
Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts in this pull request

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request transitions the repository into an Obsidian community plugin, adding configuration files, a build script, and compiled plugin code. The code review identifies several critical issues and areas for improvement. Notably, the TypeScript source file src/main.ts is missing from the commit, and the node_modules/ directory was accidentally committed. Additionally, there are major logic discrepancies in main.js where the code attempts to parse metadata using fragile regex on the document body instead of leveraging Obsidian's robust metadataCache to read from the YAML frontmatter. Other recommendations include adding proper error handling to async commands, enabling sourcemaps for development, and catching potential esbuild context errors.

Comment thread README.md

## Repository Contents

- `src/main.ts` – plugin source code
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The source file src/main.ts is referenced here and in build.js, but it has not been committed to the repository in this pull request. Only the compiled main.js is present. Please ensure src/main.ts is added and tracked in git so that others can contribute and build the plugin from source.

Comment thread main.js
Comment on lines +303 to +324
async generateEvidenceMatrix() {
const evidenceFiles = this.getMarkdownFilesInFolder("Evidence");
const rows = [];
for (const file of evidenceFiles.sort((a, b) => a.basename.localeCompare(b.basename))) {
const content = await this.app.vault.cachedRead(file);
const evidence = `[[${file.basename}]]`;
const facts = this.extractLinkedSection(content, "Related Facts").join(", ");
const issues = this.extractLinkedSection(content, "Related Issues").join(", ");
const people = this.extractLinkedSection(content, "Related People").join(", ");
const cases = this.extractLinkedSection(content, "Related Cases").join(", ");
rows.push(`| ${evidence} | ${facts} | ${issues} | ${people} | ${cases} | |`);
}
const matrix = [
"# Evidence Matrix",
"",
"| Evidence | Fact(s) Related | Issue(s) Related | Person(s) Related | Case(s) Related | Notes |",
"|----------|------------------|------------------|-------------------|-----------------|-------|",
...rows,
""
].join("\n");
await this.upsertFile("Matrix/evidence_matrix.md", matrix);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

There is a major discrepancy between the Evidence Template.md and the generateEvidenceMatrix implementation. The template defines relationships like RelatedFacts, RelatedPeople, RelatedCases, and RelatedIssues in the YAML frontmatter, but generateEvidenceMatrix attempts to extract them from markdown headers (e.g., ## Related Facts) in the body using extractLinkedSection. Since these headers do not exist in the template, the matrix columns will always be empty.

You should update this to read the relationships directly from the frontmatter using Obsidian's metadataCache.

  async generateEvidenceMatrix() {
    const evidenceFiles = this.getMarkdownFilesInFolder("Evidence");
    const rows = [];
    for (const file of evidenceFiles.sort((a, b) => a.basename.localeCompare(b.basename))) {
      const cache = this.app.metadataCache.getFileCache(file);
      const frontmatter = cache?.frontmatter;
      const evidence = "[[" + file.basename + "]]";
      const facts = this.extractLinksFromFrontmatter(frontmatter, "RelatedFacts").map((l) => "[[" + l + "]]").join(", ");
      const issues = this.extractLinksFromFrontmatter(frontmatter, "RelatedIssues").map((l) => "[[" + l + "]]").join(", ");
      const people = this.extractLinksFromFrontmatter(frontmatter, "RelatedPeople").map((l) => "[[" + l + "]]").join(", ");
      const cases = this.extractLinksFromFrontmatter(frontmatter, "RelatedCases").map((l) => "[[" + l + "]]").join(", ");
      rows.push("| " + evidence + " | " + facts + " | " + issues + " | " + people + " | " + cases + " | |");
    }
    const matrix = [
      "# Evidence Matrix",
      "",
      "| Evidence | Fact(s) Related | Issue(s) Related | Person(s) Related | Case(s) Related | Notes |",
      "|----------|------------------|------------------|-------------------|-----------------|-------|",
      ...rows,
      ""
    ].join("\n");
    await this.upsertFile("Matrix/evidence_matrix.md", matrix);
  }

Comment thread main.js
Comment on lines +337 to +347
async collectChronologyEntries(folder, type) {
const files = this.getMarkdownFilesInFolder(folder);
const entries = [];
for (const file of files) {
const content = await this.app.vault.cachedRead(file);
const date = content.match(/\*\*Date\/Time:\*\*\s*([^\n\r]+)/)?.[1]?.trim() ?? "";
const title = content.match(/^#\s*(.+)$/m)?.[1]?.trim() ?? file.basename;
entries.push({ date, type, link: `[[${file.basename}]]`, note: title });
}
return entries;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The templates define DateTime in the YAML frontmatter, but collectChronologyEntries attempts to parse **Date/Time:** from the body content using regex. This will fail to find any dates. Using Obsidian's metadataCache is much faster and 100% reliable.

Suggested change
async collectChronologyEntries(folder, type) {
const files = this.getMarkdownFilesInFolder(folder);
const entries = [];
for (const file of files) {
const content = await this.app.vault.cachedRead(file);
const date = content.match(/\*\*Date\/Time:\*\*\s*([^\n\r]+)/)?.[1]?.trim() ?? "";
const title = content.match(/^#\s*(.+)$/m)?.[1]?.trim() ?? file.basename;
entries.push({ date, type, link: `[[${file.basename}]]`, note: title });
}
return entries;
}
async collectChronologyEntries(folder, type) {
const files = this.getMarkdownFilesInFolder(folder);
const entries = [];
for (const file of files) {
const cache = this.app.metadataCache.getFileCache(file);
const date = String(cache?.frontmatter?.DateTime ?? "");
const title = cache?.headings?.find((h) => h.level === 1)?.heading ?? file.basename;
entries.push({ date, type, link: "[[" + file.basename + "]]", note: title });
}
return entries;
}

Comment thread main.js
Comment on lines +348 to +353
extractLinkedSection(content, section) {
const rgx = new RegExp(`## ${section}[\\n\\r]+((?:- \\[\\[.*?\\]\\][\\n\\r]+)*)`);
const match = content.match(rgx);
if (!match) return [];
return [...match[1].matchAll(/\[\[(.*?)\]\]/g)].map((item) => item[1]);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Since relationships are defined in the frontmatter rather than markdown headers, we can replace extractLinkedSection with a helper that parses wikilinks from frontmatter fields (supporting both string and array formats).

Suggested change
extractLinkedSection(content, section) {
const rgx = new RegExp(`## ${section}[\\n\\r]+((?:- \\[\\[.*?\\]\\][\\n\\r]+)*)`);
const match = content.match(rgx);
if (!match) return [];
return [...match[1].matchAll(/\[\[(.*?)\]\]/g)].map((item) => item[1]);
}
extractLinksFromFrontmatter(frontmatter, key) {
const value = frontmatter?.[key];
if (!value) return [];
const str = Array.isArray(value) ? value.join(" ") : String(value);
return [...str.matchAll(/\[\[(.*?)\]\]/g)].map((m) => m[1]);
}

Comment thread .gitignore
@@ -0,0 +1,2 @@
node_modules/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

It appears that the node_modules/ directory has been committed to the repository, despite being added to .gitignore. Committing node_modules/ significantly bloats the repository size and is a bad practice. You should untrack these files using git rm -r --cached node_modules and commit the deletion.

Comment thread build.js
format: "cjs",
platform: "browser",
target: "es2020",
sourcemap: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For Obsidian plugins, enabling sourcemaps during development is extremely helpful for debugging in the Obsidian developer console. You can conditionally enable inline sourcemaps when in watch mode.

Suggested change
sourcemap: false,
sourcemap: isWatch ? "inline" : false,

Comment thread build.js
Comment on lines +16 to +20
if (isWatch) {
esbuild.context(buildOptions).then((ctx) => ctx.watch());
} else {
esbuild.build(buildOptions).catch(() => process.exit(1));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If esbuild.context fails (for example, if there is a configuration error or the entry point is missing), the promise rejection will go unhandled. It is safer to add a .catch() block to handle errors in watch mode as well.

Suggested change
if (isWatch) {
esbuild.context(buildOptions).then((ctx) => ctx.watch());
} else {
esbuild.build(buildOptions).catch(() => process.exit(1));
}
if (isWatch) {
esbuild.context(buildOptions).then((ctx) => ctx.watch()).catch(() => process.exit(1));
} else {
esbuild.build(buildOptions).catch(() => process.exit(1));
}

Comment thread main.js
Comment on lines +223 to +264
async onload() {
this.addCommand({
id: "initialize-casemap-database",
name: "Initialize CaseMap database structure",
callback: async () => {
await this.initializeStructure();
new import_obsidian.Notice("CaseMap database structure initialized.");
}
});
this.addCommand({
id: "generate-casemap-indexes",
name: "Generate CaseMap indexes",
callback: async () => {
await this.generateIndexes();
new import_obsidian.Notice("CaseMap indexes updated.");
}
});
this.addCommand({
id: "generate-master-chronology",
name: "Generate master chronology",
callback: async () => {
await this.generateChronology();
new import_obsidian.Notice("Master chronology updated.");
}
});
this.addCommand({
id: "generate-evidence-matrix",
name: "Generate evidence matrix",
callback: async () => {
await this.generateEvidenceMatrix();
new import_obsidian.Notice("Evidence matrix updated.");
}
});
this.addCommand({
id: "generate-fileclass-index",
name: "Generate FileClass field index",
callback: async () => {
await this.generateFileClassIndex();
new import_obsidian.Notice("FileClass field index updated.");
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Async command callbacks should be wrapped in try/catch blocks. If any of these operations fail (e.g., due to file system permissions, locked files, or invalid metadata), the error will go unhandled, and the user will not be notified of the failure. Showing an error Notice on failure improves the user experience.

  async onload() {
    const runSafe = async (fn, successMsg, errorMsg) => {
      try {
        await fn();
        new import_obsidian.Notice(successMsg);
      } catch (error) {
        console.error(error);
        new import_obsidian.Notice(errorMsg);
      }
    };

    this.addCommand({
      id: "initialize-casemap-database",
      name: "Initialize CaseMap database structure",
      callback: () => runSafe(
        () => this.initializeStructure(),
        "CaseMap database structure initialized.",
        "Failed to initialize CaseMap database structure."
      )
    });
    this.addCommand({
      id: "generate-casemap-indexes",
      name: "Generate CaseMap indexes",
      callback: () => runSafe(
        () => this.generateIndexes(),
        "CaseMap indexes updated.",
        "Failed to update CaseMap indexes."
      )
    });
    this.addCommand({
      id: "generate-master-chronology",
      name: "Generate master chronology",
      callback: () => runSafe(
        () => this.generateChronology(),
        "Master chronology updated.",
        "Failed to update master chronology."
      )
    });
    this.addCommand({
      id: "generate-evidence-matrix",
      name: "Generate evidence matrix",
      callback: () => runSafe(
        () => this.generateEvidenceMatrix(),
        "Evidence matrix updated.",
        "Failed to update evidence matrix."
      )
    });
    this.addCommand({
      id: "generate-fileclass-index",
      name: "Generate FileClass field index",
      callback: () => runSafe(
        () => this.generateFileClassIndex(),
        "FileClass field index updated.",
        "Failed to update FileClass field index."
      )
    });
  }

Comment thread main.js
Comment on lines +325 to +336
async generateFileClassIndex() {
const rows = ["# FileClass Field Index", "", "| FileClass | Fields |", "|-----------|--------|"];
const fileClassFiles = this.getMarkdownFilesInFolder("FileClasses").filter((file) => file.name.endsWith(".fileclass.md")).sort((a, b) => a.basename.localeCompare(b.basename));
for (const file of fileClassFiles) {
const content = await this.app.vault.cachedRead(file);
const fileClass = content.match(/fileClass:\s*([^\n\r]+)/)?.[1]?.trim() ?? file.basename;
const fields = [...content.matchAll(/-\s*([A-Za-z0-9_]+)/g)].map((match) => match[1]).join(", ");
rows.push(`| ${fileClass} | ${fields} |`);
}
rows.push("");
await this.upsertFile("FileClasses/_fileclass_index.md", rows.join("\n"));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using raw regex matching on the file content to parse YAML frontmatter is fragile and can easily break if the file contains lists or other matching patterns in the body. Since Obsidian already parses and caches YAML frontmatter, you can use this.app.metadataCache.getFileCache(file) to retrieve the fileClass and fields array directly and safely.

  async generateFileClassIndex() {
    const rows = ["# FileClass Field Index", "", "| FileClass | Fields |", "|-----------|--------|"];
    const fileClassFiles = this.getMarkdownFilesInFolder("FileClasses").filter((file) => file.name.endsWith(".fileclass.md")).sort((a, b) => a.basename.localeCompare(b.basename));
    for (const file of fileClassFiles) {
      const cache = this.app.metadataCache.getFileCache(file);
      const fileClass = cache?.frontmatter?.fileClass ?? file.basename;
      const fieldsArray = cache?.frontmatter?.fields;
      const fields = Array.isArray(fieldsArray) ? fieldsArray.join(", ") : "";
      rows.push("| " + fileClass + " | " + fields + " |");
    }
    rows.push("");
    await this.upsertFile("FileClasses/_fileclass_index.md", rows.join("\n"));
  }

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants