Commit graph

971 commits

Author SHA1 Message Date
Zero Liu
8658282aa9
chore(lint): enable 4 type-aware quick-win rules and fix violations (#2424)
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.
2026-05-13 21:57:33 -07:00
Zero Liu
f901343583
chore(eslint): enable @typescript-eslint/no-floating-promises and fix violations (#2437)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:45:35 -07:00
Zero Liu
df6f455662
chore(eslint): enable @typescript-eslint/no-unsafe-argument and fix violations (#2440)
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>
2026-05-13 21:40:26 -07:00
Zero Liu
8499b85a1b
chore(eslint): enable no-unnecessary-type-assertion and no-misused-promises (#2441)
* 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>
2026-05-13 21:27:35 -07:00
Logan Yang
3b8af4d341
ci(release): publish artifact attestations for release assets (#2431)
Add a step that signs main.js, manifest.json, and styles.css with the
workflow's OIDC identity and publishes the attestation to Sigstore's
public transparency log via actions/attest-build-provenance@v2.

Why: Obsidian's plugin review tooling flags release assets without an
attestation as a soft warning. Attestations cryptographically prove the
asset was built by this workflow on this commit, defending against an
attacker who gains release-asset-replace permissions and silently swaps
in a compromised main.js. Verification post-release:

  gh attestation verify main.js --owner logancyang --repo logancyang/obsidian-copilot

Placement: the new step runs AFTER the prerelease manifest swap and
BEFORE gh release create. That way the attested manifest.json is
exactly the bytes that get uploaded — for stable releases that's
the committed manifest.json; for prereleases it's the in-runner
copy of manifest-beta.json.

Permissions: adds attestations: write and id-token: write to the job.
The id-token: write permission is what allows the workflow to request
an OIDC token from GitHub; the attest action uses that token as proof
of identity when signing. No secrets to manage.

Free for public repos.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:09:14 -07:00
Logan Yang
bb5a166477
release: v3.3.0 (#2430)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:25:18 -07:00
Logan Yang
28cfff08ef
chore(release): make prerelease tooling stop poisoning master's manifest.json (#2429)
* chore(release): make prerelease tooling stop poisoning master's manifest.json

Background: Obsidian's community plugin store reads manifest.json from master
to decide which GitHub Release to serve installers. The prerelease agent's
npm version bump was rewriting manifest.json with a prerelease version, which
broke plugin installs until hotfix #2428 reverted master's manifest.json to
the stable version.

This PR fixes the underlying tooling so it can't recur.

Changes:

- version-bump.mjs now branches on whether npm_package_version is a prerelease
  (contains a hyphen). Stable: writes to manifest.json + versions.json. Prerelease:
  writes to manifest-beta.json + versions.json, leaving manifest.json untouched.
  Seeds manifest-beta.json from manifest.json when it doesn't exist yet.
  On a stable bump that finds a stale manifest-beta.json, removes it (git rm)
  because the new stable supersedes the in-flight beta. Stages the right files
  itself; package.json no longer needs the explicit git-add step.

- package.json: simplifies the "version" lifecycle script to just
  "node version-bump.mjs" since the script now stages files itself.

- release.yml:
  * "Verify version matches manifest" step now selects the file based on
    is_prerelease (manifest.json for stable, manifest-beta.json for prerelease).
    For stable runs nothing changes.
  * Adds a "Prepare release-asset manifest" step before the release upload that
    copies manifest-beta.json over manifest.json IN THE RUNNER only when the
    release is a prerelease. This makes the uploaded manifest.json asset carry
    the prerelease version so testers sideloading the assets get a consistent
    manifest. The committed master manifest.json is never touched by the
    workflow (the workflow doesn't push back).

- .claude/agents/release.md: adds a Step 0 pre-flight assertion that master's
  manifest.json.version equals the latest non-prerelease GitHub Release tag.
  Catches drift loudly before doing any work. Notes the manifest-beta.json
  auto-removal in the rules.

- .claude/agents/prerelease.md: documents the manifest.json / manifest-beta.json
  split. Adds the same Step 0 drift assertion. Updates the git add step to
  stage manifest-beta.json instead of manifest.json. Adds an explicit guard
  rule that manifest.json must not change during a prerelease bump.

Verified locally by exercising version-bump.mjs in a throwaway repo:
- Stable 3.2.8 -> 3.2.9: writes manifest.json + versions.json, no beta side
- Prerelease 3.2.8 -> 3.2.9-beta.0: creates manifest-beta.json, manifest.json untouched
- Prerelease iteration 3.2.9-beta.0 -> 3.2.9-beta.1: updates manifest-beta.json,
  manifest.json untouched
- Stable supersedes beta 3.2.9-beta.1 -> 3.2.9: writes manifest.json,
  git-rm's manifest-beta.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release): address Codex review on PR #2429

1. release.yml: when verifying a prerelease, also assert that the committed
   manifest.json on the merge commit still equals the latest non-prerelease
   GitHub Release tag. This catches an old-style version bump or hand edit
   that accidentally modified master's manifest.json — without this guard,
   the verify step only inspected manifest-beta.json so a poisoned
   manifest.json could merge unnoticed and break the Obsidian plugin store.

2. version-bump.mjs: don't fail stable bumps when manifest-beta.json exists
   on disk but is untracked (or has local modifications). Check git
   trackedness first; use `git rm -f` only when tracked, fall back to a
   plain unlink for working-tree-only files. Logs which path it took.

Verified both scenarios in a throwaway repo:
- Untracked manifest-beta.json + stable bump: unlinked, no git rm error
- Tracked manifest-beta.json + stable bump: staged deletion via git rm -f

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release): use /releases/latest API for stable-drift guard

gh release list defaults to 30 results, so a cluster of 30+ prereleases
since the last stable would make the prerelease drift guard return empty
and fail valid prerelease publishes. Switch to GitHub's /releases/latest
endpoint instead, which by design returns only the most-recent
non-prerelease, non-draft release in a single call regardless of how
many prereleases have accumulated.

Applied the same fix to the Step 0 drift check in both the release agent
and prerelease agent doc for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:53:48 -07:00
Logan Yang
fdb338110e
fix(manifest): restore stable version to 3.2.8, split beta into manifest-beta.json (#2428)
URGENT FIX. The prerelease agent's npm version run on PR #2427 left master's
manifest.json with version "3.2.9-beta.0", but Obsidian's community plugin
store reads manifest.json on master to determine which GitHub Release to
serve to new installers. With the stable release at 3.2.8 and master's
manifest pointing at a prerelease tag, plugin installs and updates were
breaking for users.

Fix:
- manifest.json on master: version back to "3.2.8" (the latest stable
  release). minAppVersion 1.7.2 and isDesktopOnly:false are preserved.
- manifest-beta.json (new): full manifest for 3.2.9-beta.0 so BRAT and
  similar beta-installer tools can locate the prerelease without us
  poisoning master's stable manifest.

The longer-term fix (version-bump.mjs + prerelease agent updates so
this can't recur) follows in a separate PR.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:03:22 -07:00
Logan Yang
20d98120e4
prerelease: v3.2.9-beta.0 (#2427) 2026-05-13 14:54:58 -07:00
Logan Yang
54d5c7bbfe
chore(release): add prerelease agent + workflow support, harden release agent pre-flight (#2426)
* chore(release): add prerelease agent + workflow support, harden release agent pre-flight

- release.yml: accept prerelease semver in PR titles (X.Y.Z-tag.N) and
  pass --prerelease to gh release create so prereleases are not offered
  as stable updates via Obsidian's plugin browser.
- .claude/agents/release.md: add a Step 0 pre-flight (clean tree, full
  project check, bundle size guard, manifest integrity, non-empty diff
  since last tag) and explicit rules against silently changing
  minAppVersion/isDesktopOnly or shipping with broken checks.
- .claude/agents/prerelease.md: new agent. Mirrors the stable release
  flow but bumps via npm version prepatch/prerelease with --preid, emits
  prerelease-shaped semver titles, and produces testing-focused release
  notes with explicit "What to Test" and "How to Install" sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release): make prerelease regex match full SemVer 2.0.0 grammar

Previous pattern '^[0-9]+\.[0-9]+\.[0-9]+-[a-zA-Z0-9.]+$' rejected hyphens
inside prerelease identifiers, so valid SemVer titles like '3.2.9-beta-hotfix.1'
would fall through to the reject branch and the merged PR would silently skip
publishing a GitHub Release.

New pattern '^[0-9]+\.[0-9]+\.[0-9]+-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$' allows:
- hyphens within identifiers (e.g. beta-hotfix)
- dot-separated multiple identifiers
- single identifier (e.g. 3.2.9-alpha)

And still rejects malformed cases: '3.2.9-', '3.2.9-.1', '3.2.9-x.', '3.2.9-x..y'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release): tighten prerelease title to require numeric counter

Previous pattern accepted any SemVer prerelease suffix, including bare
labels like '3.2.9-beta' or '3.2.9-feature' with no counter. That broadened
the workflow's release-trigger surface beyond the documented agent
contract of 'X.Y.Z-<tag>.<N>'.

New pattern '^[0-9]+\.[0-9]+\.[0-9]+-[0-9A-Za-z-]+\.[0-9]+$' requires
exactly one alphanumeric (hyphen-allowed) identifier followed by a
numeric counter, which is what 'npm version prepatch/prerelease --preid=...'
actually emits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:38:43 -07:00
Logan Yang
ca885b03ac
chore(manifest): bump minAppVersion to 1.7.2 and declare mobile support (#2425)
- minAppVersion 1.4.0 -> 1.7.2: matches the actual Obsidian APIs the code
  already calls (Workspace.revealLeaf needs 1.7.2; FileManager.processFrontMatter
  needs 1.4.4). Users on Obsidian < 1.7.2 were already hitting runtime errors
  on those code paths; stating an honest minimum prevents the install.
- Add isDesktopOnly: false: Chat, Vault QA, and Plus modes work on mobile
  (extensive Platform.isMobile guards already in place across dbOperations,
  vectorStoreManager, indexEventHandler, CopilotView, etc.).

Addresses two warnings from the Obsidian plugin preview check:
  * "Manifest is missing optional but recommended field: isDesktopOnly"
  * "'Workspace.revealLeaf' requires Obsidian v1.7.2, but minAppVersion is 1.4.0"
  * "'FileManager.processFrontMatter' requires Obsidian v1.4.4, but minAppVersion is 1.4.0"

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:43:35 -07:00
Zero Liu
602f6d6ac0
chore(eslint): enable @typescript-eslint/await-thenable and fix violations (#2423)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:14:04 -07:00
Zero Liu
cbe2ed771d
chore(eslint): enable no-restricted-imports; replace moment with luxon (#2422)
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.
2026-05-13 02:13:27 -07:00
Zero Liu
eca1b081d5
chore(eslint): enable depend/ban-dependencies and fix violations (#2421)
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>
2026-05-13 02:07:28 -07:00
Zero Liu
9dba5ff02b
chore(lint): enable import/no-nodejs-modules and import/no-extraneous-dependencies (#2420)
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.
2026-05-13 02:01:32 -07:00
Zero Liu
2df1be0b4f
chore(lint): enable no-unsanitized/method and property (#2418)
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>
2026-05-13 01:56:00 -07:00
Zero Liu
f7bcac3d86
chore(eslint): annotate disabled type-aware rules with violation counts (#2417)
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>
2026-05-13 01:52:09 -07:00
Zero Liu
a529387533
chore(lint): enable @microsoft/sdl/no-inner-html and fix violations (#2419)
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>
2026-05-13 01:43:25 -07:00
Zero Liu
08c14440dc
chore(lint): enable obsidianmd/rule-custom-message and fix violations (#2416)
* 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>
2026-05-13 01:39:35 -07:00
Zero Liu
f767d88038
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>
2026-05-13 01:26:48 -07:00
Zero Liu
c88b1e2e5b
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>
2026-05-13 01:21:25 -07:00
Zero Liu
710acc2436
chore(lint): enable obsidianmd/no-global-this and fix violations (#2413)
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>
2026-05-13 01:14:47 -07:00
Zero Liu
48794ce097
chore(eslint): enable obsidianmd/prefer-active-doc (#2411)
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>
2026-05-13 01:09:06 -07:00
Zero Liu
99b3b1e22a
chore(lint): enable obsidianmd/vault/iterate and fix violations (#2412)
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>
2026-05-13 01:05:38 -07:00
Zero Liu
17739cafa0
Enable basic obsidian eslint rules (#2410) 2026-05-13 00:49:49 -07:00
Logan Yang
bb013ef4bb
docs: add commit-signing setup; consolidate README ToC (#2409)
- CONTRIBUTING.md: new "Commit Signing" section with the 4-step SSH
  signing setup so future protection rules requiring signed commits
  can be turned on with team-ready instructions in place.
- README.md: remove the abbreviated ToC, promote the comprehensive
  one to the top, drop the self-referencing entry.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:19:00 -07:00
Zero Liu
15ac5844ed
fix(vault): respect user trash preference via FileManager.trashFile (W7/9) (#2405)
* fix(vault): respect user trash preference via FileManager.trashFile (W7/9)

Eighth of nine workspaces splitting #2397. Intentional behavior change:
file deletions now honor the user's Settings -> Files and links ->
Deleted files preference (system trash / .trash / permanent), instead
of unconditionally calling vault.delete(file, true).

- src/utils/vaultAdapterUtils.ts: add trashFile(app, file) helper that
  casts past the missing app.fileManager.trashFile typedef.
  createSyntheticTFile now uses Object.create(TFile.prototype) so
  instanceof TFile returns true.
- src/projects/ProjectFileManager.ts: constructor takes App instead
  of Vault; all vault.delete(file, true) -> trashFile(app, file).
  Project create rollback now trashes partial files (was permanent-
  delete before).
- src/projects/projectRegister.ts: constructor takes App.
- src/projects/projectMigration.ts: rollbackCreatedFile(app, ...),
  ensureProjectsMigratedIfNeeded(app), all delete sites use trashFile.
- src/main.ts: deleteChatHistory uses trashFile; ProjectRegister
  constructed with this.app.
- src/system-prompts/systemPromptManager.ts: prompt delete via trashFile.
- src/commands/customCommandManager.ts: command delete via trashFile.
- src/LLMProviders/projectManager.ts, src/components/Chat.tsx,
  src/components/chat-components/ProjectList.tsx: pass app (not vault)
  to ProjectFileManager.getInstance.
- __mocks__/obsidian.js: app.fileManager.trashFile jest mock.
- Tests updated to expect trashFile + App constructor arg.

W0, W1, and #2402 already merged. Behavior change requires manual
verification (test plan section 1 + 6).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(manifest): bump minAppVersion to 1.4.0

FileManager.trashFile (used by the vault deletion helper introduced in
W7/9) requires Obsidian 1.4+. Bumping minAppVersion ensures the plugin
store only offers this and future releases to compatible app versions;
users on older Obsidian builds stay on their current Copilot version
instead of receiving an update that would crash on delete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 23:06:49 -07:00
Zero Liu
97165937bb
chore(providers): adopt non-deprecated LangChain APIs + remaining type cleanup (W6/9) (#2404)
Seventh of nine workspaces splitting #2397. Picks up provider-file
changes W1 deferred — primarily LangChain public-API renames that
aren't pure type changes.

- src/LLMProviders/BedrockChatModel.ts: _getType() -> getType()
  (non-deprecated method, same behavior)
- src/LLMProviders/BedrockChatModel.test.ts: matching mock-message
  rename across all streaming-decode tests
- src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts: stringify
  non-string response.content via JSON.stringify instead of String()
  (avoids [object Object] when content is structured)

No semantic message-handling changes. All behavior preserved.

W0, W1, and #2402 already merged.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 23:00:56 -07:00
Zero Liu
858ce4c862
chore(styles): replace element.style with Tailwind + CSS vars (W5/9) (#2400)
* chore(styles): replace element.style with Tailwind + CSS vars (W5/9)

Sixth of nine workspaces splitting #2397. Replaces direct DOM
inline-style writes with Tailwind utility classes (addClass) and
CSS custom properties for dynamic values.

- src/components/modals/SourcesModal.tsx — addClass + tw-* classes
- src/components/modals/AddImageModal.tsx — input.style.display -> tw-hidden
- src/components/modals/CachePreviewModal.ts — modalEl.addClass tw-w/tw-max-w
- src/components/modals/project/AddProjectModal.tsx — !tw-max-h-[85vh]
- src/components/modals/project/context-manage-modal.tsx — tw-min-w-[50vw]
- src/components/quick-ask/QuickAskOverlay.tsx — body cursor/select via tw + CSS var
- src/components/ui/textarea.tsx — --copilot-autosize-height + tw-h-[var(...)]
- src/components/chat-components/ChatViewLayout.ts — removeProperty fix
- src/components/command-ui/draggable-modal.tsx — tw-left/top-[var(--copilot-drag-x/y,0px)]
- src/settings/SettingsPage.tsx — tw-select-text
- src/settings/v2/components/ModelEditDialog.tsx — tw-h-4/5
- src/system-prompts/SystemPromptAddModal.tsx — tw-h-4/5
- src/hooks/use-draggable.ts and use-resizable.ts: write
  --copilot-drag-x/y and --copilot-resize-cursor CSS variables instead
  of inline left/top/cursor; consumers reference them via Tailwind
  arbitrary-value classes

Behavior: visual-only changes; no logic changes. Reviewer should
walk through SCORECARD_WARNINGS_TEST_PLAN.md §3 (drag/resize, QuickAsk
positioning, draggable modal, typeahead positioning, inline pills).

W0 already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(styles): use Obsidian setCssProps API for CSS variable writes

Follow-up to the W5 commit. The Obsidian scorecard warning is
"Use the setCssProps function if the CSS properties need to change
dynamically", and the prior pass still wrote them via
element.style.setProperty / removeProperty. Routes the remaining 14
sites across 6 files through HTMLElement#setCssProps, which is the
Obsidian-sanctioned API for writing CSS custom properties.

- src/hooks/use-draggable.ts: --copilot-drag-x/y batched into one call
- src/hooks/use-resizable.ts: --copilot-resize-cursor set/clear
- src/components/quick-ask/QuickAskOverlay.tsx: same cursor pattern
- src/components/chat-components/ChatViewLayout.ts: --copilot-status-bar-clearance
- src/components/ui/textarea.tsx: --copilot-autosize-height
- src/utils/dom/dynamicStyleManager.ts: batches every set+clear into a single
  setCssProps call per invocation (CSSOM defines setProperty(name, "") to
  call removeProperty(name), so empty-string entries clear)

The single getPropertyValue read at src/components/CopilotView.tsx:112 is
a read, not a write, and is not flagged by the scorecard rule.

Verification: grep -rn "\.style\.setProperty\|\.style\.removeProperty" src
returns zero hits. npm run lint, npm run build, npm test (1962 tests) all
pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 22:50:05 -07:00
Zero Liu
8b01ef746e
Fix broken tests (#2408) 2026-05-12 22:28:12 -07:00
Zero Liu
6331631dd0
chore(popout): window-global API swaps — timers, globalThis, base64 (W3/9) (#2401)
Fourth of nine workspaces splitting #2397. Mechanical swaps to
window-namespaced globals for Obsidian popout window compatibility.
No semantic behavior change beyond "works correctly in a popout."

- setTimeout / clearTimeout / setInterval / clearInterval /
  requestAnimationFrame → window.* across all callsites
- globalThis → window (BedrockChatModel fetch.bind, scattered sites)
- atob / btoa → Buffer.from(..., 'base64') / Buffer.from(...).toString('base64')
  - src/encryptionService.ts (drops Node 'buffer' import; Buffer is global)
  - src/utils.ts safeFetch arrayBuffer() branch
  - src/LLMProviders/BedrockChatModel.ts decodeBase64ToUint8Array

Builds on top of #2398 (W0 — deps bump).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 22:24:17 -07:00
Zero Liu
c67c4fc200
fix(popout): document ownership and selection listener fixes (W4/9) (#2406)
* fix(popout): document ownership and selection listener fixes (W4/9)

Fifth of nine workspaces splitting #2397. Real popout-window
correctness fixes — semantic choices about which window owns a DOM
operation, plus selection-listener captured-document fixes.

- document → activeDocument where a generic active-window lookup is
  correct (SourcesModal, ChatViewLayout, ItemView/Notice mocks,
  TabContext, ChatInput keydown listener, CopilotView keyboard
  observer, TypeaheadMenuPortal, AddImageModal, and ?? document
  fallbacks across draggable-modal, menu-command-modal,
  CustomCommandChatModal, use-draggable, use-resizable, and
  QuickAskOverlay).
- document → root.ownerDocument in ChatSingleMessage's
  linkInlineCitations so DOM nodes end up in the citation's window;
  same pattern in the processMessage streaming block via
  contentRef.current.ownerDocument.
- New getEditorDocument(editor) helper in BasePillNode. createDOM and
  exportDOM now accept the LexicalEditor; pills create DOM in the
  editor's window (correct popout behavior). All 6 pill subclasses
  updated to match the new signature.
- main.ts: selectionListenerDocument captured at listener registration;
  remove uses the same document (fixes leak when activeDocument has
  drifted before unload).
- chatSelectionHighlightController.ts: getActiveViewOfType(MarkdownView)
  replaces the brittle activeLeaf type comparison.
- Test setup: (window as any).activeDocument = window.document in
  ChatSingleMessage.test; window.document.createElement in
  AgentPrompt.test Notice mock.

Behavior fix: chat in a popout window now creates DOM nodes in the
popout's document, not the focused window's document.

W0, W1, and #2402 already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(popout): prefer element.doc/.win over ownerDocument and activeDocument

Switch popout-window-sensitive call sites to Obsidian's `.doc` / `.win`
augmentations so DOM ops, listeners, portals, and positioning follow the
element's actual owner window. Recreate Lexical root on view migration to
rebind input handling after a leaf moves between windows. Document the
decision order in AGENTS.md and polyfill `Node.doc` / `Node.win` in
jest.setup.js so tests still run under jsdom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* improve image upload implementation

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 21:28:04 -07:00
Zero Liu
7619fb5578
chore(promises): explicit handling of floating promises (W2/9) (#2407)
Third of nine workspaces splitting #2397. Surfaces previously
swallowed promise rejections via .catch(logError) or `void` prefix.
No logic changes - only error-handling explicitness.

- ~67 floating promises now use void / .catch(logError)
- Promise-returning callbacks where void was expected are wrapped
- ConfirmModal accepts onConfirm/onCancel returning void | Promise<void>;
  rejections logged via logError
- Custom command runners wrap `result instanceof Promise` paths
- ChatUIState.replaceMessages is now async to match its delegate
- ChainManager initialize / createChainWithNewModel are explicitly
  voided with .catch(logError) at call sites
- dbOperations subscribeToSettingsChange uses void IIFE with try/catch

Behavior change: errors previously hidden by floating promises now
log via @/logger. No code paths changed.

W0, W1, and #2402 already merged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:42:22 -07:00
Zero Liu
e470b5c70c
chore(types): tighten types and replace TFile/TFolder casts (W1/9) (#2403) 2026-05-12 16:18:22 -07:00
Logan Yang
b7ad36624a
perf(deps): route Cohere & Mistral through OpenAI-compat to cut bundle by 1.8MB (#2402)
Replace ChatCohere / ChatMistralAI / CohereEmbeddings with ChatOpenAI /
OpenAIEmbeddings pointed at each provider's OpenAI-compatible endpoint:

- Cohere: https://api.cohere.ai/compatibility/v1
- Mistral: https://api.mistral.ai/v1 (already OpenAI-shaped)

Dropping @langchain/cohere, @langchain/mistralai, cohere-ai, and
@mistralai/mistralai also eliminates the @aws-sdk/* and @smithy/* chain
that cohere-ai dragged in for Bedrock auth (a path this plugin doesn't use).

Production bundle: 5.21MB -> 3.39MB.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:53:59 -07:00
Zero Liu
2fb1552a49
chore(deps): bump deps ahead of scorecard cleanup (W0/9) (#2398)
First of nine workspaces splitting #2397 into focused PRs (see
designdocs/todo/SCORECARD_WARNINGS.md, landing in a later workspace).
This PR is deps-only so the source-touching workspaces (types,
promise safety, popout compat, styles, trash semantics, brevilabs
rewrite, eslint rules) can each land independently.

Dependencies:
- @codemirror/state, @codemirror/view: peer deps for newer @lexical/* usage
- @langchain/classic: required by langchain ^1 split layout
- openai: direct consumer in upcoming provider work
- uuid + @types/uuid: replaces ad-hoc id generation
- zod: schema validation used in upcoming type tightening

DevDependencies:
- eslint-plugin-obsidianmd: installed now but not wired into .eslintrc
  until the final workspace (W9) once all flagged sites are cleaned up

No source changes; no behavior changes. npm run lint / build / test
all pass on this branch.
2026-05-12 14:11:53 -07:00
Logan Yang
8f35b396bc
chore(models): bump built-in models to latest (May 2026) (#2396)
OpenAI:
- gpt-5.4 -> gpt-5.5 (new flagship, released 2026-04-24)
- gpt-5-mini -> gpt-5.4-mini
- gpt-5-nano -> gpt-5.4-nano (enum only)

Anthropic:
- claude-opus-4-6 -> claude-opus-4-7 (Opus 4.6 now legacy)
- Add claude-haiku-4-5 (disabled by default)

Google:
- gemini-3.1-flash-lite-preview -> gemini-3.1-flash-lite (GA 2026-05-07)

xAI:
- grok-4-1-fast -> grok-4.3 (grok-4-1-fast retiring 2026-05-15)

OpenRouter mirrors updated for all of the above.

ModelParametersEditor: gate Extra-High reasoning effort on
startsWith("gpt-5.5") instead of "gpt-5.4" so the option follows
the new flagship and does not accidentally surface on gpt-5.4-mini
or gpt-5.4-nano.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:34:07 -07:00
Zero Liu
358b373953
chore: remove orphaned RemoveFromIndexModal (#2395)
The modal and its REMOVE_FILES_FROM_COPILOT_INDEX command were orphaned in
the vault search v3 migration (#1703): the addCommand registration was
removed but the modal file and its constant entries were left behind.
Nothing in the codebase has instantiated it since. Deleting it also clears
the risk-report finding about contentEl.createEl("style", ...), which
Obsidian disallows at runtime.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:45:15 -07:00
Wenzheng Jiang
38a23358cd
fix(miyo): send portable folder-prefixed path to related-notes endpoint (#2393)
findRelevantNotes and MiyoIndexBackend.getDocumentsByPath were sending the
local absolute filesystem path as Miyo's file_path. On remote-vault setups
the indexed path is the server's absolute path, so the scroll filter
matched zero points and the related-notes endpoint always returned 404.

Replace getMiyoAbsolutePath with getMiyoFilePath, which returns
"<FolderName>/<vaultRelativePath>" — the inverse of
getVaultRelativeMiyoPath. Miyo's resolveFileInput remaps this format to
the registered folder's canonical absolute path, so it works identically
for local and remote setups.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:52:04 +09:00
齐柯宇 (Qi Kery)
e826889c11
fix: use /responses for GitHub Copilot codex models (#2320)
* fix: use /responses for GitHub Copilot codex models
Route Copilot codex models away from /chat/completions to avoid 400
errors, and keep ping/model verification aligned with runtime routing.

* refactor: deduplicate shouldUseGitHubCopilotResponsesApi calls and restore rationale comments

  Cache the result in useCopilotResponses in both createModelInstance() and
  tryPing() instead of calling the helper multiple times per flow.

  Restore Reason comments removed in c96a9d8 that document non-obvious
  constraints: why ChatOpenAICompletions is used over ChatOpenAI, the
  delta.role fallback to avoid broken tool_call_chunks, defaultRole?: any
  type choice, streamUsage/baseURL SDK behavior, and the typeof Request
  guard for Obsidian mobile runtimes.
2026-05-10 17:06:26 -07:00
HT
baa2b954dc
fix: prevent </div> from being consumed by indented code blocks in think sections (#2343)
When thinking content ends with a 4-space-indented line (e.g. Gemma's
bullet-point reasoning), markdown's indented code block rule would consume
the immediately-following </div> closing tag and render it as literal
"&lt;/div&gt;" text. Fix by always trimming section content and appending
\n before the closing tag so it lands on its own unindented line.

Also adds integration tests covering non-streaming, streaming-complete,
and streaming-unclosed think block paths.

Co-authored-by: H <trulyshelton@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 17:02:55 -07:00
Arjun Pramanik
bfc7a4e017
docs(ai): unify AGENTS.md and CLAUDE.md (#2358) 2026-05-09 14:27:22 -07:00
Octopus
c7a1d36b3b
fix: remove 100ms IME timeout causing Enter key delay (fixes #2354) (#2367)
The previous implementation used compositionstart/compositionend events
with a 100ms timeout to track IME state. This caused Korean (Hangul)
IME users to experience a noticeable delay when pressing Enter to send:
the composition-ending Enter was blocked, and subsequent Enters within
100ms were also blocked, requiring users to hold the key or wait.

In Chromium/Electron, the keydown event for IME-confirming Enter fires
with isComposing=true (before compositionend), so the explicit ref-based
tracking is redundant. Relying directly on event.isComposing and the
legacy keyCode 229 is sufficient and matches the simpler approach already
used in follow-up-input.tsx.

Co-authored-by: octo-patch <octo-patch@github.com>
2026-05-09 14:15:25 -07:00
Emt-lin
506de5477b
feat: migrate project storage from data.json to vault files (#2324)
* feat: migrate project storage from data.json to vault files

Migrate project definitions and metadata from data.json to individual
markdown files in the vault's projects directory.

- Project file manager with YAML frontmatter parsing and persistence
- Migration logic from legacy data.json format with unsupported/ backup
- Project UI components (AddProjectModal, ProjectList, ProgressCard)
- Chat history integration for the new storage model
- Status sorting, cache-first ordering, and URL removal in processing
- Atomic cache operations to prevent transient disappearance on rename
- Awaited context cache cleanup on all delete/rename/folder-switch paths
- Raw-content-first frontmatter parsing to avoid stale metadataCache
- BOM/CRLF support in frontmatter read and write helpers
- Accessibility: aria-labels on all icon-only buttons

* fix: address codex review feedback

- Defer vault listener registration from constructor to initialize()
  to avoid premature cache mutations and frontmatter writes during
  initial vault load (before onLayoutReady).

- Coerce numeric YAML project IDs to strings so bare numeric values
  like `copilot-project-id: 123` are not silently dropped.
2026-05-09 12:21:04 -07:00
Wenzheng Jiang
94737297b7
fix(miyo): strip vault folder-name prefix from indexed paths (#2390)
Miyo returns indexed file paths prefixed with the owning folder name
(the vault name), but getVaultRelativeMiyoPath was stripping the
absolute filesystem path prefix instead. As a result, the "List
Indexed Files" command rendered every entry as a broken Obsidian link
(e.g. [[MyVault/notes/foo.md]] instead of [[notes/foo.md]]).

Switch the strip logic to match what Miyo actually returns. Files from
other vaults pass through unchanged so cross-vault search results stay
intact. Always return a forward-slash-normalized path so callers see a
consistent separator. Drop the now-unused getVaultBasePath helper.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:21:07 +09:00
Zero Liu
37bea9a65c
fix(ollama): route through safeFetch when enableCors is set (#2382)
Other OpenAI-compatible providers (LM Studio, OpenAI-format, SiliconFlow,
etc.) already pass a fetch override so requests can go through Obsidian's
requestUrl API. The Ollama branch was missing this wiring, so toggling
"Enable CORS" on an Ollama model had no effect and mobile (WKWebView)
requests to http:// Ollama hosts failed with "Load failed" due to CORS /
mixed-content blocking.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:48:45 -07:00
Wenzheng Jiang
95d23b2387
release: v3.2.8 (#2369)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 20:43:24 +09:00
Wenzheng Jiang
2e3d6ba0d2
feat(miyo): add toggle to search all indexed content (#2353)
* feat(miyo): add toggle to search all indexed content across folders

Allow users to opt into searching everything in Miyo by omitting the
folder_name from search requests. This is enabled by default so Copilot
searches all indexed content. When disabled, searches are scoped to the
current vault folder only. Only affects the search endpoint — other Miyo
APIs (parseDoc, listFolderFiles, etc.) still send folder_name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(miyo): default miyoSearchAll to false

Scope searches to the current vault folder by default, which is the
safer default. Users can opt into cross-folder search via the toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:15:57 +09:00
Wenzheng Jiang
1960d86a3a
release: v3.2.7 (#2352)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:31:09 +09:00
Wenzheng Jiang
d8890ada6d
refactor(miyo): rename folder_path request param to folder_name (#2349)
Miyo server now supports folder_name, which better reflects the
semantic that we send a vault name rather than a filesystem path.
Also removes folder_path from response interfaces as the server
no longer returns it.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:49:20 +09:00