15 KiB
Obsidian Plugin Code Review Fixes Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Fix all required issues from the Nov 16, 2025 ObsidianReviewBot code review to unblock plugin submission approval.
Architecture: Systematic file-by-file fixes addressing: sentence case UI text, async/await cleanup, eslint directive removal, require() to ES6 import conversion, and promise handling improvements.
Tech Stack: TypeScript, Obsidian API, ESLint
Task 1: Fix Sentence Case in main.ts
Files:
- Modify:
src/main.ts:45
Step 1: Fix ribbon icon tooltip
Change line 45 from:
this.addRibbonIcon('server', 'Toggle MCP Server', async () => {
To:
this.addRibbonIcon('server', 'Toggle MCP server', async () => {
Step 2: Fix onunload promise issue (lines 96-98)
Change from:
async onunload() {
await this.stopServer();
}
To:
onunload() {
void this.stopServer();
}
Step 3: Verify build
Run: npm run build
Expected: No errors
Step 4: Commit
git add src/main.ts
git commit -m "fix: sentence case and onunload promise in main.ts"
Task 2: Fix Sentence Case in settings.ts
Files:
- Modify:
src/settings.ts:209,319
Step 1: Fix authentication section header (line 209)
Change from:
authSummary.setText('Authentication & Configuration');
To:
authSummary.setText('Authentication & configuration');
Step 2: Fix notifications section header (line 319)
Change from:
notifSummary.setText('UI Notifications');
To:
notifSummary.setText('UI notifications');
Step 3: Verify build
Run: npm run build
Expected: No errors
Step 4: Commit
git add src/settings.ts
git commit -m "fix: sentence case for section headers in settings.ts"
Task 3: Fix mcp-server.ts Issues
Files:
- Modify:
src/server/mcp-server.ts:57,70,77-79,117
Step 1: Remove async from handleInitialize (line 57)
Change from:
private async handleInitialize(_params: JSONRPCParams): Promise<InitializeResult> {
To:
private handleInitialize(_params: JSONRPCParams): InitializeResult {
Step 2: Remove async from handleListTools (line 70)
Change from:
private async handleListTools(): Promise<ListToolsResult> {
To:
private handleListTools(): ListToolsResult {
Step 3: Update handleRequest callers (lines 41-43)
Since handleInitialize and handleListTools are no longer async, remove the await:
Change from:
case 'initialize':
return this.createSuccessResponse(request.id, await this.handleInitialize(request.params ?? {}));
case 'tools/list':
return this.createSuccessResponse(request.id, await this.handleListTools());
To:
case 'initialize':
return this.createSuccessResponse(request.id, this.handleInitialize(request.params ?? {}));
case 'tools/list':
return this.createSuccessResponse(request.id, this.handleListTools());
Step 4: Remove eslint-disable and fix any type (lines 77-79)
Change from:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and need runtime validation
const paramsObj = params as { name: string; arguments: any };
To:
const paramsObj = params as { name: string; arguments: Record<string, unknown> };
Step 5: Fix promise rejection to use Error (line 117)
Change from:
reject(error);
To:
reject(error instanceof Error ? error : new Error(String(error)));
Step 6: Verify build
Run: npm run build
Expected: No errors
Step 7: Commit
git add src/server/mcp-server.ts
git commit -m "fix: async/await, eslint directive, and promise rejection in mcp-server.ts"
Task 4: Fix routes.ts Promise Issue
Files:
- Modify:
src/server/routes.ts:10-19
Step 1: Wrap async handler to handle void context
Change from:
app.post('/mcp', async (req: Request, res: Response) => {
try {
const request = req.body as JSONRPCRequest;
const response = await handleRequest(request);
res.json(response);
} catch (error) {
console.error('MCP request error:', error);
res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Internal server error'));
}
});
To:
app.post('/mcp', (req: Request, res: Response) => {
void (async () => {
try {
const request = req.body as JSONRPCRequest;
const response = await handleRequest(request);
res.json(response);
} catch (error) {
console.error('MCP request error:', error);
res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Internal server error'));
}
})();
});
Step 2: Verify build
Run: npm run build
Expected: No errors
Step 3: Commit
git add src/server/routes.ts
git commit -m "fix: wrap async handler with void for proper promise handling"
Task 5: Fix tools/index.ts ESLint Directive
Files:
- Modify:
src/tools/index.ts:477-478
Step 1: Remove eslint-disable and fix type
Change from:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and require runtime validation
async callTool(name: string, args: any): Promise<CallToolResult> {
To:
async callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult> {
Step 2: Verify build
Run: npm run build
Expected: No errors
Step 3: Commit
git add src/tools/index.ts
git commit -m "fix: remove eslint-disable directive in tools/index.ts"
Task 6: Fix vault-tools.ts Async Methods
Files:
- Modify:
src/tools/vault-tools.ts:18,63,310,498,925
Step 1: Remove async from getVaultInfo (line 18)
Change from:
async getVaultInfo(): Promise<CallToolResult> {
To:
getVaultInfo(): CallToolResult {
Step 2: Remove async from listNotes (line 63)
Change from:
async listNotes(path?: string): Promise<CallToolResult> {
To:
listNotes(path?: string): CallToolResult {
Step 3: Remove async from createFileMetadataWithFrontmatter (line 310)
Change from:
private async createFileMetadataWithFrontmatter(
To:
private createFileMetadataWithFrontmatter(
Also update the return type from Promise<FileMetadataWithFrontmatter> to FileMetadataWithFrontmatter.
Step 4: Remove async from exists (line 498)
Change from:
async exists(path: string): Promise<CallToolResult> {
To:
exists(path: string): CallToolResult {
Step 5: Remove async from resolveWikilink (line 925)
Change from:
async resolveWikilink(sourcePath: string, linkText: string): Promise<CallToolResult> {
To:
resolveWikilink(sourcePath: string, linkText: string): CallToolResult {
Step 6: Update callers if any use await on these methods
Search for any await this.getVaultInfo(), await this.listNotes(), await this.exists(), await this.resolveWikilink(), await this.createFileMetadataWithFrontmatter() and remove the await keyword.
Step 7: Verify build
Run: npm run build
Expected: No errors
Step 8: Commit
git add src/tools/vault-tools.ts
git commit -m "fix: remove async from methods without await in vault-tools.ts"
Task 7: Fix notifications.ts ESLint Directives
Files:
- Modify:
src/ui/notifications.ts:10-11,78-79,145-146,179
Step 1: Fix interface args type (lines 10-11)
Change from:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
args: any;
To:
args: Record<string, unknown>;
Step 2: Fix showToolCall parameter type (lines 78-79)
Change from:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
showToolCall(toolName: string, args: any, duration?: number): void {
To:
showToolCall(toolName: string, args: Record<string, unknown>, duration?: number): void {
Step 3: Fix formatArgs parameter type (lines 145-146)
Change from:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tool arguments come from JSON-RPC and can be any valid JSON structure
private formatArgs(args: any): string {
To:
private formatArgs(args: Record<string, unknown>): string {
Step 4: Fix unused 'e' variable (line 179)
Change from:
} catch (e) {
To:
} catch {
Step 5: Verify build
Run: npm run build
Expected: No errors
Step 6: Commit
git add src/ui/notifications.ts
git commit -m "fix: remove eslint directives and unused catch variable in notifications.ts"
Task 8: Fix crypto-adapter.ts Require Import
Files:
- Modify:
src/utils/crypto-adapter.ts:18-34
Step 1: Replace require with dynamic approach
The challenge here is that require() is used for synchronous access. We need to restructure to use a lazy initialization pattern.
Change the entire Node.js section from:
// Node.js environment (15+) - uses Web Crypto API standard
if (typeof global !== 'undefined') {
try {
// Using require() is necessary for synchronous crypto access in Obsidian desktop plugins
// ES6 dynamic imports would create race conditions as crypto must be available synchronously
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Synchronous Node.js crypto API access required
const nodeCrypto = require('crypto') as typeof import('crypto');
if (nodeCrypto?.webcrypto) {
return nodeCrypto.webcrypto as unknown as Crypto;
}
} catch {
// Crypto module not available or failed to load
}
}
To (using globalThis.crypto which is available in Node 19+ and Electron):
// Node.js/Electron environment - globalThis.crypto available in modern runtimes
if (typeof globalThis !== 'undefined' && globalThis.crypto) {
return globalThis.crypto;
}
Step 2: Verify build
Run: npm run build
Expected: No errors
Step 3: Commit
git add src/utils/crypto-adapter.ts
git commit -m "fix: use globalThis.crypto instead of require('crypto')"
Task 9: Fix encryption-utils.ts Require Import
Files:
- Modify:
src/utils/encryption-utils.ts:8-18
Step 1: Restructure electron import
Since Electron's safeStorage must be accessed synchronously at module load time, and ES6 dynamic imports are async, we need to use a different approach. In Obsidian plugins running in Electron, we can access electron through the window object.
Change from:
// Safely import safeStorage - may not be available in all environments
let safeStorage: ElectronSafeStorage | null = null;
try {
// Using require() is necessary for synchronous access to Electron's safeStorage API in Obsidian desktop plugins
// ES6 dynamic imports would create race conditions as this module must be available synchronously
// eslint-disable-next-line @typescript-eslint/no-var-requires -- Synchronous Electron API access required for Obsidian plugin
const electron = require('electron') as typeof import('electron');
safeStorage = electron.safeStorage || null;
} catch (error) {
console.warn('Electron safeStorage not available, API keys will be stored in plaintext');
}
To:
// Safely import safeStorage - may not be available in all environments
let safeStorage: ElectronSafeStorage | null = null;
try {
// Access electron through the global window object in Obsidian's Electron environment
// This avoids require() while still getting synchronous access
const electronRemote = (window as Window & { require?: (module: string) => typeof import('electron') }).require;
if (electronRemote) {
const electron = electronRemote('electron');
safeStorage = electron.safeStorage || null;
}
} catch {
console.warn('Electron safeStorage not available, API keys will be stored in plaintext');
}
Step 2: Verify build
Run: npm run build
Expected: No errors
Step 3: Commit
git add src/utils/encryption-utils.ts
git commit -m "fix: use window.require pattern instead of bare require for electron"
Task 10: Fix link-utils.ts Async Method
Files:
- Modify:
src/utils/link-utils.ts:448
Step 1: Remove async from validateLinks
Change from:
static async validateLinks(
vault: IVaultAdapter,
metadata: IMetadataCacheAdapter,
content: string,
sourcePath: string
): Promise<LinkValidationResult> {
To:
static validateLinks(
vault: IVaultAdapter,
metadata: IMetadataCacheAdapter,
content: string,
sourcePath: string
): LinkValidationResult {
Step 2: Update any callers that await this method
Search for await LinkUtils.validateLinks or await this.validateLinks and remove the await.
Step 3: Verify build
Run: npm run build
Expected: No errors
Step 4: Commit
git add src/utils/link-utils.ts
git commit -m "fix: remove async from validateLinks method"
Task 11: Final Build and Test
Step 1: Run full build
Run: npm run build
Expected: No errors
Step 2: Run tests
Run: npm test
Expected: All tests pass
Step 3: Commit any remaining changes
git status
# If any uncommitted changes:
git add -A
git commit -m "fix: final cleanup for code review issues"
Optional Tasks (if time permits)
Optional Task A: Fix Unused Error Variables
Files:
src/tools/vault-tools.ts:289,359,393,445,715src/utils/encryption-utils.ts:16src/utils/frontmatter-utils.ts:76,329,358src/utils/search-utils.ts:117,326src/utils/waypoint-utils.ts:103
For each occurrence, change catch (error) { or catch (e) { or catch (decompressError) { to just catch {.
Optional Task B: Use FileManager.trashFile()
Files:
- Modify:
src/adapters/vault-adapter.ts:46-48 - Modify:
src/adapters/interfaces.ts(update IVaultAdapter interface)
This requires passing the App or FileManager to the VaultAdapter, which is a larger refactor.
Summary Checklist
- Task 1: main.ts sentence case + onunload
- Task 2: settings.ts sentence case
- Task 3: mcp-server.ts async/eslint/promise fixes
- Task 4: routes.ts promise handling
- Task 5: tools/index.ts eslint directive
- Task 6: vault-tools.ts async methods
- Task 7: notifications.ts eslint directives
- Task 8: crypto-adapter.ts require import
- Task 9: encryption-utils.ts require import
- Task 10: link-utils.ts async method
- Task 11: Final build and test