* feat(agent-mode): one-click "Report an Issue" flow with screenshot + frame log
Reporting an Agent Mode bug was high-friction: the diagnostic frame log
defaulted off (so it usually wasn't capturing when a problem hit), and there
was no in-product path to assemble a report.
- Default `agentMode.debugFullFrames` to ON for new installs so the frame log
is already capturing when a bug occurs. The existing sanitize migration
preserves an explicit prior choice (a user who turned it off stays off).
- Add a "Report an Issue" button to the Agent Mode control bar. It opens a
modal for a note, captures a screenshot of the chat surface (Electron
capturePage, popout-aware, degrades gracefully), bundles it with the current
frame log into a timestamped folder, reveals the folder, and opens a
prefilled GitHub issue.
- Optionally include the OpenCode log when that backend is active, and show an
in-flow privacy disclosure shown only when the user chooses to share.
Desktop-only; mobile and capture failures degrade to a report without a
screenshot. Adds unit tests for the new default, the preserve-explicit-choice
migration, the bundle assembler, and the opencode-log locator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): make opencode log attachment opt-in by default
The Report-issue flow bundles opencode's newest *global* log, which may
belong to an unrelated CLI/Desktop session for another project. Defaulting
the checkbox to checked could silently attach that log, exposing unrelated
prompts and tool output. Default it to unchecked so the user opts in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): cap report issue URL length and document the flow
- buildReportIssueUrl: truncate the prefilled body so the assembled URL
stays under Electron's ~2081-char openExternal limit on Windows. Without
this, a long note made openExternal reject silently while the success
notice still claimed the issue page opened. The full report is preserved
in report.md on disk, and the truncated body points the user there.
- Document the Report an Issue flow (bundle contents, drag-drop attach,
privacy note, opt-in OpenCode log) in docs/agent-mode-and-tools.md per
DOCS_GUIDE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): skip frame log in report when logging is disabled
If a user turns off "Log Full Agent Mode Frames" but a stale
acp-frames.ndjson still exists in the temp dir, the report assembler would
copy that old file into the bundle, leaking plaintext prompts/tool output
despite logging being opted out. Only bundle the frame log when
debugFullFrames is currently enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): file reports to public repo; clarify report UX and settings
Addresses review feedback on the Report an Issue flow:
- File user reports to the PUBLIC logancyang/obsidian-copilot repo (label
"bug"), never the private preview repo, which users can't see.
- Report modal: state plainly that the screenshot is of the Agent Mode chat
pane (not the whole screen), spell out what "Prepare report" does (save
files to a folder, open it, open a prefilled GitHub issue to attach them),
and replace the obscure muted disclaimer with a prominent warning callout.
- Advanced settings: split Agent Mode logging into its own "Agent Mode
debugging" section with plain-language copy (renamed to "Keep an Agent Mode
activity log" / "Agent Mode activity log file"), and clarify that the legacy
Debug Mode / Create Log File tools are for the regular chat, not Agent Mode.
- Update docs to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(agent-mode): move Report an Issue to the Advanced settings section
Per UX feedback, the entry point moves out of the agent chat control bar into
Settings → Advanced → Agent Mode debugging, alongside the activity-log
controls it relates to.
- Remove the bug-icon button and its wiring from AgentChatControls/AgentHome.
- Add a "Report an Issue" button to the Agent Mode debugging settings section.
- ReportIssueModal now resolves its screenshot target lazily via
resolveCaptureTarget(): the settings caller closes the Settings window and
reveals the agent pane first, so the screenshot is still the chat surface
(not the dialog). No agent pane open -> screenshot is skipped gracefully.
- Export ReportIssueModal from the agentMode barrel so host code can reach it
without crossing the ui boundary.
- Update docs to point at the new location.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): honor opencode env overrides when finding its log
OpencodeBackend spawns opencode with `{ ...process.env, ...envOverrides }`, so a
user who relocates opencode's data dir via XDG_DATA_HOME/HOME overrides has its
logs written there. The report's opencode-log lookup only consulted ambient
process.env/homedir, so it could attach an unrelated global log or miss the
active session's. Resolve the log path from the same merged env + HOME override.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): report uses active session backend; gate import on desktop
Two review fixes for the settings-based Report an Issue button:
- Read the backend from the active Agent Mode session, not the persisted
default. Switching tabs across backends changes the active session without
touching settings.agentMode.activeBackend, so the modal could hide/show the
OpenCode-log option for the wrong tab and name the wrong backend in the env
block.
- Check isDesktopRuntime() before importing the @/agentMode barrel. On mobile
the barrel evaluates Node-only modules; importing before the modal's own
desktop guard could reject during module load instead of showing the
desktop-only notice. Mirrors the existing frame-log buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mobile): keep Agent Mode off the emulated-mobile load path
`app.emulateMobile(true)` flips Platform.isMobile to true (stubbing Node's
built-ins to null) but leaves Platform.isDesktopApp true — so gating Agent Mode
on isDesktopApp still imported the @/agentMode barrel, whose module-scope
`promisify(execFile)` then crashed the whole plugin with
"Cannot read properties of null (reading 'promisify')". Add isDesktopRuntime()
(Platform.isDesktopApp && !Platform.isMobile) and gate every Agent Mode
load/registration on it so the barrel is never imported on a Node-less runtime.
Update the mobile-load smoke gate rule to enforce the new gate and add a unit test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(lint): ban Platform.isDesktopApp; route desktop gating through isDesktopRuntime()
Platform.isDesktopApp stays true under app.emulateMobile(true) (which stubs Node),
so it doesn't keep desktop-only/Node code off the emulated-mobile path. Add a
no-restricted-properties ESLint rule banning it (the canonical check in
src/utils/desktopRuntime.ts is exempt via an inline disable), and migrate all 14
existing usages — web viewer, encryption/keychain, Miyo discovery, web-tab and
@-mention chat hooks, and main.ts's web-viewer gates — to isDesktopRuntime().
Behavior is unchanged on real desktop and mobile; only emulateMobile now faithfully
hides these desktop-only features.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up hardening to #2569 (preview issue #135). Moving the managed opencode
binary out of the vault made `~/.obsidian-copilot` our first OS-level install
namespace; this locks down its shape before GA.
1. Single source of truth: new `copilotAppDataDir(homeDir)` + `COPILOT_APP_DIR_NAME`
in `src/utils/appPaths.ts` define the `~/.obsidian-copilot` root exactly once,
with the rationale (not `~/.copilot` — collides with the GitHub Copilot CLI;
matches `~/.opencode`/`~/.miyo` convention) and the "shared across all vaults,
never destructively prune" invariant. `opencodeManagedDataDir` now composes
`<root>/opencode`. Future managed runtimes compose under the same root.
2. Guard `getDataDir()` against an unusable home dir (empty / filesystem root),
and wrap the install-time `mkdir` so an unwritable root (sandboxed/confined
HOME on Linux Flatpak/Snap, locked-down accounts) fails with an actionable
message naming the path, instead of a confusing downstream spawn error.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(lint): enforce no-global-app via no-restricted-globals; fix all violations
Flip on `no-restricted-globals` with an `app` entry (error) for production
`src/**/*.{ts,tsx}`, exempting test files (they consume the `window.app` mock).
Sweep the whole codebase to 0 violations by threading `app` explicitly:
useApp() in React, this.app / this.chainManager.app in classes, params in plain
modules, a seed pattern for no-arg getInstance() singletons, setters for eager
module-level singletons, and factory functions for app-using built-in tools.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(agent): pass full mock app to initializeBuiltinTools
The initializer's app branch dereferences app.vault.getRoot() via
registerFileTreeTool, so passing the bare vault crashed setup before any
tools registered. Pass the full mock app instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(agent-mode): auto-detect agent binaries under Node version managers
Auto-detect now finds opencode/codex-acp/claude binaries installed under
nvm, fnm, n, Volta, asdf, and `npm i -g` (against an nvm-managed Node) —
not just Homebrew and /usr/local/bin. macOS GUI launches inherit a sparse
launchd PATH that omits every version-manager bin dir, so a new shared
resolver (`nodeToolBinDirs`) enumerates them and feeds both detect-time
(`which`/`where`) and spawn-time (`#!/usr/bin/env node`) PATH so a binary
that detects also spawns. Windows now augments PATH too and prefers `.exe`
over unspawnable `.cmd` shims; the Claude resolver shares the same dir
discovery; and the not-found hint lists the directories actually searched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix codex comments
* fix failing tests
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(react): centralize React root creation via createPluginRoot helper
Every standalone React root in the plugin must wrap its tree in
AppContext.Provider so descendants can rely on useApp() — PR #2466 fixed
one missing wrap (QuickAskOverlay) but the contract was implicit and any
new createRoot callsite could silently re-introduce the same crash.
Introduce a createPluginRoot(container, app) helper that injects the
provider automatically, migrate all 17 createRoot callsites to use it,
and add a Jest guardrail that fails if any non-helper file imports
createRoot from react-dom/client.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(lint): replace createRoot Jest guardrail with ESLint rule
Switch the createPluginRoot enforcement from a Jest test (walks src/
and greps imports) to an ESLint no-restricted-syntax rule. Same
invariant, faster feedback (in-editor instead of on test run), and one
fewer test file to maintain. The helper file is exempted via the
config block's `ignores`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(lint): fix no-array-index-key and other React lint warnings
Re-enable @eslint-react/no-array-index-key, replace index keys with stable
ids where possible (capability enum, action label, file paths, computed
coords), and add per-line eslint-disable with rationale where index is
intentional (diff parts, append-only steps, parsed markdown, duplicate-
tolerant context badges).
Also addresses adjacent React lint warnings: useMemo for context provider
values, stable module-scope defaults for empty array props, lazy useState
initializers, and explicit type="button" on non-submit buttons. Surface
no-direct-set-state-in-use-effect as a warning for follow-up triage.
* fix: dedupe selected image files
* chore(eslint): enable no-explicit-any; fix ~395 violations
Switches @typescript-eslint/no-explicit-any from "off" to "error" and
replaces explicit `any` with proper types or `unknown` + narrowing
across ~100 source files and 15 test files. Eleven eslint-disable
comments remain: one for a BaseLanguageModel prototype patch and ten
for Orama<any> API surfaces where typed alternatives poison Orama's
internal inference to `never`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(scripts): cast chainContext to ChainManager in printPromptDebugEntry
The earlier `as any` → `as unknown` rewrite left buildAgentPromptDebugReport's
chainManager argument typed as `unknown`, which CI's `tsc -noEmit` (without
`--skipLibCheck`) flagged. Restore the cast through the real ChainManager type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(eslint): fix remaining no-explicit-any and unnecessary-type-assertion errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(eslint): drop redundant no-explicit-any rule
Already set to "error" by typescript-eslint/recommendedTypeChecked via
obsidianmd's recommended config.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(styles): drop dead CSS, reduce !important, fix duplicate selectors
Removed orphaned chains (.remove-note, .chat-icon-button/.submit-button/.chain-select-button etc., .model-settings-table) that haven't had JSX consumers since PRs #1055 and #1074. Cuts !important from 23 to 14, eliminates two duplicate-selector lint warnings (.message-content pre merged; the dead .submit-button svg duplicate removed), and moves the live .message-content wrapper styles inline so the surviving rule no longer needs !important.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(eslint): whitelist Obsidian's 'clickable-icon' for tailwindcss/no-custom-classname
The eslint-plugin-tailwindcss `no-custom-classname` rule treats classes mentioned anywhere in the configured cssFiles as known. Removing the dead `.chat-icon-button.clickable-icon` selector dropped `clickable-icon` from that scanned set, which then failed lint in `button.tsx` and `dialog.tsx` where the Obsidian-provided utility is still used. Whitelist it explicitly so the lint rule no longer relies on a CSS-file mention to recognize it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces `any`-typed mock objects in 26 test files with structured
types using `jest.Mock`, casts requireMock results to typed shapes,
and introduces `internals()` helpers for private-method access. No
production code changes; eliminates 487 of 679 no-unsafe-call
violations in preparation for enabling the rule.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a test-only override enforcing @typescript-eslint/no-unsafe-assignment
on **/*.test.{ts,tsx} and fixes all 378 violations across 44 test files.
Production code (~499 violations) remains a follow-up PR — the rule stays
"off" globally, with the violation count comment updated to reflect that.
Common fix patterns applied:
- Typed accessor helpers (asInternal) for private-method access via
`as unknown as Shape`, replacing `(instance as any).method(...)`.
- Inline `as <Type>` on mock indexing (mock.calls[i][j]), JSON.parse,
and jest.requireMock results.
- mockTFile()/mockTFolder() from @/__tests__/mockObsidian replacing
`Object.create(TFile.prototype)` (also avoids obsidianmd/no-tfile-tfolder-cast).
- `as unknown as RealType` for mock-object casts where `as any` previously
masked the assignment.
Enables @typescript-eslint/no-unsafe-member-access globally. Tests and 41
heavy source files (>5 violations each) are exempted via per-file overrides
with current violation counts annotated for follow-up PRs. Fixes 124
violations across 51 source files using narrow type assertions, instanceof
Error guards, and proper structural types instead of blanket any casts.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enabled in the TS-only override block. Disabled for test files since
jest assertion patterns (`expect(mock.method).toHaveBeenCalled()`)
reference methods unbound by design and have no clean workaround.
Fixed the single real source violation in colorOpacityPlugin by
calling addUtilities through the plugin API object instead of
destructuring it.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the rule into the TS-only config block (the plugin is only registered
for .ts/.tsx). Fixes 187 violations across 74 files using narrow `as` casts,
explicit return-type annotations on callbacks, typed intermediate variables,
and tightening parameter types from `any` to `unknown` where it didn't break
callers. Also replaces the `@ts-ignore`'d `Electron.SafeStorage` reference
with a local `SafeStorage` interface in `encryptionService.ts`. No prompt
content modified; no `eslint-disable` comments added.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The four obsidianmd typed rules (no-view-references-in-plugin,
no-unsupported-api, prefer-file-manager-trash-file, prefer-instanceof)
are already gated to **/*.ts(x) by the plugin's hybridRecommendedConfig,
so disabling them on non-TS files is a no-op. Only no-plugin-as-component
and @typescript-eslint/no-deprecated actually leak onto non-TS files.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turns on no-unsafe-enum-comparison, no-base-to-string,
no-redundant-type-constituents, and restrict-template-expressions.
Fixes all 24 violations across 13 source files.
Removes the `off` override and fixes all 368 violations via type casts,
widened parameter types (unknown / string|null|undefined for functions
that defensively handle non-string inputs), and concrete types in place
of `any` for Lexical nodes, mock objects, and option handlers.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(eslint): enable no-unnecessary-type-assertion and no-misused-promises
Auto-fixed all 40 no-unnecessary-type-assertion violations via `eslint --fix`.
For no-misused-promises, configured `checksVoidReturn` with two relaxations
(documented inline):
- `attributes: false` — `onClick={async () => ...}` is the standard React pattern
- `inheritedMethods: false` — Obsidian's Plugin.onload/onunload are async
Fixed the remaining 10 violations by hand:
- Wrapped async subscribers/listeners with `void` (projectManager, useChatFileDrop,
main.ts, webViewerServiceSelection)
- Widened ConfirmModal subclass callback types to `() => void | Promise<void>`
- Widened MobileCardDropdownAction.onClick to match parent prop types
- Made SettingsMainV2 handleReset sync (it never awaited anything)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(eslint): move no-misused-promises to TS-only config block
The @typescript-eslint plugin is registered only for .ts/.tsx files via
obsidianmd's recommendedTypeChecked. Enabling the rule in the
JS+TS-shared block caused CI to fail with "could not find plugin
@typescript-eslint" because the plugin isn't loaded for .js/.mjs files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the `no-restricted-imports: off` override so the obsidianmd recommended
rule applies (bans axios/node-fetch/moment/etc.). Replaces the two `moment(...)`
call sites in `src/utils.ts` with `luxon`'s `DateTime` (already a dep) and adds
unit tests covering local/UTC formatting, zero-padding, immutability, and the
invalid-input fallback. Integration tests get a per-file override since they
need `node-fetch` to polyfill jsdom fetch. `no-restricted-globals` stays
disabled — banning the global `app` will land in a separate PR.
Removes the "off" overrides for depend/ban-dependencies, configures the
package.json check with an allowed-list for deps we deliberately keep
(crypto-js, lodash.debounce, eslint-plugin-react, lint-staged, npm-run-all),
and drops three unused direct deps: axios, builtin-modules, and node-fetch
(plus @types/node-fetch). The integration-test fetch polyfills are no longer
needed in Node 18+ jsdom.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the deferred "off" overrides from eslint.config.mjs so the
obsidianmd recommended config's "error" severity applies. Scopes
import/no-nodejs-modules off for test setup, mocks, and Node-context
build scripts (e.g. jest.setup.js polyfills TextEncoder/TextDecoder
from node:util). import/no-extraneous-dependencies is now enabled
everywhere; declared js-yaml, dotenv, and @jest/globals as explicit
devDependencies so transitive uses pass.
Fixes the one real violation in src/utils.ts by migrating two moment
call sites (formatDateTime, stringToFormattedDateTime) to Luxon, which
is already in dependencies. Adds 10 unit tests covering UTC/local
formatting, zero-padding, round-tripping, and invalid-input fallback.
Remove the explicit "off" overrides in eslint.config.mjs so the rules
from obsidianmd.configs.recommended apply. Add a scoped disable for the
sole violation in scripts/printPromptDebug.js, where the dynamic import
targets a controlled path under os.tmpdir() produced by esbuild.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Group the 14 disabled @typescript-eslint rules into Heavy / Medium / Quick-win
buckets with inline violation counts measured against src/**/*.{ts,tsx}. Makes
it obvious which rules (e.g. restrict-template-expressions at 1, no-base-to-string
at 7) are cheap follow-ups vs. the no-unsafe-* family that dominates the block.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the `off` override so the rule (set to `error` by obsidianmd's
recommended config) is enforced. Fixes the one production violation in
markdown-preview by using `replaceChildren()` instead of clearing via
`innerHTML`. Rewrites test fixtures to use `DOMParser` and mock impls
to use `textContent` so the rule is on for tests too.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(lint): enable obsidianmd/rule-custom-message and fix violations
Turn on the obsidianmd/rule-custom-message ESLint rule (which wraps no-console
to enforce logInfo/logWarn/logError over console.log per AGENTS.md). Swap
console.log → logInfo across LLMProviders/ and search/, and delete console.log
noise from test files.
src/logger.ts, src/chainFactory.ts (circular import with constants), and
scripts/** are exempted via narrow file overrides.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(lint): enable obsidianmd/no-unsupported-api (#2414)
Removes the deferred "off" override so the rule runs at the recommended
severity on .ts/.tsx. With manifest.minAppVersion=1.4.0 and obsidian@1.2.5
(no @since tags) the rule fires on nothing today — it acts as a guard for
future obsidian type-stub bumps.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(lint): enable obsidianmd/object-assign rule (#2415)
Follow-up to #2410. The rule only flags `Object.assign(<ident-containing-default>, <non-object-literal>)` — the Obsidian anti-pattern of mutating DEFAULT_SETTINGS. No call sites in this repo match, so enabling produces zero new errors.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract ChainType into src/chainType.ts to break import cycle
constants.ts → chainFactory.ts → logger.ts → settings/model.ts → constants.ts
formed a runtime cycle because chainFactory.ts (a heavy LangChain module) re-
exported the ChainType enum that constants.ts needed at module load. Move the
enum into a tiny standalone file, update all 26 importers, and restore
logInfo in chainFactory.ts. The eslint override for chainFactory.ts is no
longer needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to #2410. The rule only flags `Object.assign(<ident-containing-default>, <non-object-literal>)` — the Obsidian anti-pattern of mutating DEFAULT_SETTINGS. No call sites in this repo match, so enabling produces zero new errors.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the deferred "off" override so the rule runs at the recommended
severity on .ts/.tsx. With manifest.minAppVersion=1.4.0 and obsidian@1.2.5
(no @since tags) the rule fires on nothing today — it acts as a guard for
future obsidian type-stub bumps.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the deferred `"off"` override and rewrites the 55 violations
(all in test/mocks/Node-script files) to use `window.*` instead of
`global.*`. Production code was already cleaned up in #2401.
- 18 test files + __mocks__/obsidian.js + jest.setup.js: mechanical
`global.X` → `window.X` (Jest runs under jsdom, so window === globalThis)
- scripts/printPromptDebugEntry.ts: inline eslint-disable — Node-only
debug script with no window available
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flip the rule from "off" to "error" to catch accidental references to
the global `document` (which always points at the main window even when
the user is interacting with a popout).
The only flagged site was a `document` field on the `RelevantNoteEntry`
type — not a DOM reference, but the rule pattern-matches the identifier.
Rename it to `note` so `entry.note.path/title` reads naturally next to
`entry.metadata.*`, and remove the suppression.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace `app.vault.getFiles().find(f => f.path === id)` lookups in the
Project Context Manage modal with `getAbstractFileByPath` + `instanceof
TFile` narrowing — direct hash lookup vs full-vault scan.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>