Replaces the single long-Notice failure UX with a kind-aware dispatcher
so users get actionable buttons instead of a wall of text.
- CliProcessError extends Error with a typed CliErrorDetails payload
(reason / pid / elapsed / idle / bytes / tails / exitCode / signal /
timeoutMs / idleTimeoutMs). All five runCli failure paths now throw
the typed error; message format is preserved so existing classifier
regex and tests still match.
- classifyGenerationError short-circuits on details.reason for the
deterministic cases (wall/idle-timeout → timeout, spawn/startup →
config, streams-unavailable → unknown). exit-nonzero falls through
so stderr-derived auth/rate-limit hints still classify correctly.
Duck-typed check avoids cli ↔ generation-job-manager circular import.
- New error-ui.ts dispatches by ErrorKind:
timeout (with details) → TimeoutDiagnosticsModal showing cmd, pid,
elapsed, idle, bytes, redacted stderr/stdout tail, plus copy /
open-settings / close buttons.
timeout (no details, e.g. API streaming) → actionable Notice.
auth/config → Notice + "Open Settings" CTA.
rate-limit → Notice + "Copy details".
schema → Notice + "Copy raw output" CTA.
unknown → unchanged legacy short Notice.
- main.ts handleGenerationError delegates to dispatcher; new
openPluginSettings() helper uses Obsidian's app.setting.openTabById.
- 21 i18n strings (zh + en) for all new UI labels and the modal copy.
- styles.css adds error notice + diagnostics modal styling.
- 7 new test assertions cover typed details on every failure path and
classifyGenerationError respecting structured reasons.
Change-Id: I8af46d375d92ba26d14180ffbeb6c3a6dedd89f5
Add eslint v9 + @typescript-eslint/parser + eslint-plugin-obsidianmd as
a dedicated `npm run lint:obsidian` track, separate from the existing
biome lint. Apply autofixes from the recommended config and resolve the
follow-on type/test issues.
- prefer-create-el: createEl('div'/'span') -> createDiv/createSpan
- prefer-active-window-timers: setTimeout/clearTimeout -> activeWindow.*
+ retype timer holders from ReturnType<typeof setTimeout> to number
- prefer-active-doc: drop globalThis.fetch in streaming
- no-useless-catch: drop no-op rethrow wrapper
- no-restricted-globals: streaming.ts is exempted because requestUrl
does not support streaming responses
- @typescript-eslint/no-unsafe-*: disabled (out of scope for an Obsidian
plugin lint pass; tsc + biome already cover type safety)
- tests: polyfill globalThis.activeWindow so unit tests still run in Node
Change-Id: I43cc652e29a66d490e2834e940843c39b211b80b
UX change:
- The "open Parallel Reader" command now toggles the right sidebar's
collapsed state instead of detaching the leaf. The tab and its content
are preserved across hide/show, matching standard Obsidian behaviour
for File Explorer and friends. Detaching the tab on every second
press destroyed cached cards and forced re-generation.
Behaviour:
- No leaf yet → create + reveal
- Leaf exists, right sidebar collapsed → reveal (expands sidebar)
- Leaf exists, right sidebar expanded → collapse(); tab is preserved
- No right sidebar (mobile / unusual layout) → reveal as fallback
Tests:
- New tests/view-render.test.js (18 cases) covering the component layer
that was previously a gap. The 1.0.12 → 1.0.13 sidebar regression
could not have been caught by any existing pure-function test; this
file fills that gap by mocking app.workspace + view spies and running
real runForFile / refreshViewAfterCache* / toggleParallelView.
- shouldRender matrix (4 cases): silentView × sourceFile state — the
exact regression introduced in PR2 where guards were over-applied.
- toggleParallelView (4 cases): no-leaf opens, expanded → collapse,
collapsed → reveal, no-rightSplit fallback.
- refreshViewAfterCacheDelete + Clear (4 cases): matching file vs
different file vs no-view, plus clear-all always renderEmpty.
- runForFile outcome enum mapping (6 cases): already-running early
return, already-running from catch, cancelled, generic error,
regenerate-confirm cancellation, skipEditConfirm bypass — these
drive batch statistics and were untested before.
- view.deleteCard / updateCard cardPersistFailed path (3 cases):
exposes ParallelReaderView via test-exports for instantiation;
mocks cacheReplaceCards returning false to verify the failure
notice path lands and the public method returns false.
src/test-exports.ts: export ParallelReaderView for component tests.
tests/catalog.json: register view-render.test.js under component.
Change-Id: I439b5fca07b0928e6fe44d9025c2421c71c6cb28
Two regressions reported after 1.0.12 was installed:
1. Hotkey-triggered Generate did not update the sidebar. PR2 added
`viewIsShowingFile(view, file)` guards to renderLoading/loadFor/
renderError in runForFile to keep batch (silentView) jobs from
disturbing a panel showing a different note. The guard was applied
to all paths, breaking the interactive case: when the sidebar had
never been opened (sourceFile=null) or was showing a different note,
every render call was skipped and the sidebar stayed blank while
generation completed in the background.
Fix: compute `shouldRender = !silentView || viewIsShowingFile(...)`
once. Non-silent (user pressed Generate on this file) renders
unconditionally; silentView keeps the existing "only if it's already
this file" behaviour.
2. The open-view command had no toggle behaviour. Pressing the hotkey a
second time silently re-opened the same panel.
Fix: if a parallel-reader leaf already exists, detach it; otherwise
ensureView + sync.
Change-Id: I1db3d70806d8d07965d18bf3717023de027ac577
Findings from the original review (P2):
- streaming: flush unterminated final SSE event at EOF (some providers
close the stream without a trailing blank line, dropping the last delta)
- cache-manager: validate entry shape on load — drop entries where
cards is not an array, anchor is not a string, or bullets is not an
array. Tolerates missing optional fields (treated as cache miss).
- view: card edit/delete now check cacheReplaceCards return and surface
failures via a localized Notice instead of pretending success
- main: clear-current / clear-all / file-menu-clear refresh open view
via renderEmpty so stale UI does not display deleted data
- prompt + settings-tab: card count is normalized via a single helper
used by buildPrompts and onChange. Prompt and fingerprint stay in sync.
UI value is written back to the textbox after clamping.
- generation-job-manager: global concurrency limit (default 3) with a
cancellable wait queue. Race-safe slot accounting via a reserved
counter so resolved-but-not-yet-set waiters are visible to fast-path
start() callers.
- runForFile: now returns RunForFileResult; accepts preloadedContent +
silentView + skipEditConfirm options used by batch (and reflected on
the PluginHost interface).
- batch: avoid double file read, do not steal UI focus, classify
results correctly (generated / cached / already-running / empty / ...)
Follow-up from the 1.0.11 review:
- generationFingerprint: codex backend excludes settings.model from the
hash. Codex ignores --model; previously editing model spuriously
invalidated all codex cache.
Codex review pass:
- generation-job-manager: race fix — releaseSlot's resolve microtask
and a synchronous start() fast-path could briefly overshoot
maxConcurrent. Reserve the slot synchronously inside the wrapped
resolve to close the window.
- cache-manager: anchor type validation in addition to bullets.
NOTE: Codex backend users will see a one-time "stale cache" banner on
existing notes due to the fingerprint change; regenerate to refresh.
Change-Id: I7721c7dfe51dea3f51b0215764f721523c2f6806
P1:
- cli: pin codex --sandbox read-only (defeat prompt injection)
- settings: clear apiKey/apiHeaders on provider preset switch
- main/view: render error state when LLM returns empty cards
- schema/cli: do not log raw model output; UI errors expose length only
P2:
- cli: pass --model to Claude Code when settings.model set
- settings-tab: add CLI timeout input + normalizeCliTimeoutMs (min 1s)
- view: export source link uses [[path|basename]]
- view: export failures surface a localized Notice
P3:
- schema: cardUntitled fallback now respects uiLanguage
- generation-job-manager: error codes localized via main.ts
- batch: settled guard on promptForBatchFolder modal
- README: drop stale e2e validator install note
Codex review pass:
- provider-parsers: forward settings to normalizeCardsPayload
- generation-job-manager: classifyGenerationError regex covers new EN/zh
schema-error messages
- cli: do NOT pass --model to codex (DEFAULT_SETTINGS.model is a Claude
name and would break codex; codex relies on its own config)
Tests: cli (codex sandbox + claude --model + classify zh/en), settings
(applyApiProviderPreset isolation + normalizeCliTimeoutMs), schema
(language-aware fallback title), test-exports (new symbols).
Change-Id: I9f4c21f9299e1a9bf166f69f712859854482fe92
Tests now load src/test-exports.ts via esbuild buildSync instead of
importing __test from the production main.js. This removes ~60 internal
symbols and all test-only module code from the shipped plugin bundle.
Change-Id: I2caf5232f6894c7514215e1751f2e268b74d7dc4
Replaces the 64-line __test manual export block in main.ts with a
single import from src/test-exports.ts. Also removes 30+ imports that
were only needed for __test re-export. main.ts reduced from 788 to
693 lines.
Change-Id: I029f127faf053135d04dba17b8c39c8c39ca8540
Split runForFile into focused methods: handleGenerationError for the
catch block and streamProgressFor for streaming callback creation.
Reduces runForFile from 91 to ~55 lines with clearer separation.
Change-Id: Icb0195092f25ab640f8227834a96519d14cab8df
Test repairTruncatedCardsJson with whitespace variations, escaped quotes,
braces in string values, all-truncated cards, and empty arrays.
Add collectJsonObjectCandidates tests for empty input, nested objects,
braces in strings, and unclosed braces.
Change-Id: I82392bff6eaf457934dbbe63e7c5788ea6a44e19
Move the inline Modal dialog for batch folder selection into a reusable
promptForBatchFolder function. Removes Modal import from main.ts.
main.ts now 775 lines.
Change-Id: I7a5c62b7df95635dcbe0ac814007838ff83eded4
Move resolveCardAnchors to src/cards.ts and confirmRegenerateEditedCards
to src/modal.ts to reduce main.ts from 835 to 796 lines (under 800 target).
Change-Id: If38ed4e45c61658b7cc52a6283f8e0e24458761e
The regenerate confirmation modal's onClose callback fired after button
click handlers, calling resolve() a second time. While Promise.resolve
is idempotent, this was confusing. Added explicit settlement tracking
so the promise resolves exactly once regardless of how the modal closes.
Change-Id: Ief4cbb78f15fe220a20adaf8343d744ffa8c1653
- Add repairTruncatedCardsJson to recover complete cards when output
is cut off mid-card (e.g. token limit reached)
- Log raw LLM response to console on parse failure for debugging
- Make error panel text selectable and add "copy error" button
Change-Id: I4e76121138888234d42f84f3695cbbfd6beba886
- Fix CI to run all 4 test files (was missing direct-modules.test.js)
- Remove unused ESLint dependencies (eslint, @typescript-eslint/*, eslint-plugin-obsidianmd)
- Enable noImplicitReturns in tsconfig.json for stricter type safety
- Make applyApiProviderPreset return new object instead of mutating input
- Add per-file error handling in batch processing loop with error count in summary
- Narrow all catch clause types to unknown with proper instanceof narrowing
Change-Id: I76975dbd5ebdb5645d18c5858c8a52fd6da3405e
The catch callback no longer contains any await expressions
after the previous refactor removed await on sync view methods.
Change-Id: I054c50fe209aba0e925bd1838896228bce686841
- Remove unnecessary TFile cast (instanceof already narrows)
- Remove await on non-Promise view methods (loadFor, renderLoading, renderError)
- Remove async from CacheManager.touch() (no await expression)
- Replace this-aliasing with captured locals in batch modal
- Use CSS class instead of inline style for modal input width
- Fix sentence case in settings UI text
Change-Id: I2a222c6429eb7e4d69761cca8369956fef510054
- Remove `parallel-reader-` prefix from command IDs (Obsidian auto-namespaces)
- Remove default hotkeys to avoid conflicts with user keybindings
- Replace `as TFile` casts with `instanceof TFile` checks
- Make onunload sync, remove detachLeavesOfType (preserves leaf positions)
- Use `vault.configDir` instead of hardcoded `.obsidian`
- Replace `window.confirm` with Obsidian Modal dialog
- Fix unhandled promises with `void` or `await`
- Remove unnecessary `async` from methods without `await`
- Use `new Setting().setHeading()` instead of raw HTML headings
- Fix promise-returning callbacks in event listeners
- Fix error type narrowing with `instanceof Error`
- Apply sentence case to UI text labels
Change-Id: I6e850d8a13b458d5cf1ad91227bf1772636b56b9
Register parallel-reader-batch-generate command. It prompts the user for a
folder path via a Modal, finds all .md files in that folder (non-recursive),
skips files whose cache is still valid, and runs runForFile() sequentially
on the rest, showing per-file progress notices. Add i18n keys for the new
UI strings in both zh and en.
https://claude.ai/code/session_016QvEfqw6YZ3RjwBHrJ4w8S
Add CmEditorView, ObsidianEditorWithCm, ObsidianMenuItem, and ObsidianMenu
interfaces to src/types.ts. Replace (editor as any).cm and (it: any) casts
in main.ts with these typed wrappers, eliminating all any casts in the
scroll-sync and file-menu code paths.
https://claude.ai/code/session_016QvEfqw6YZ3RjwBHrJ4w8S
Move all cache persistence methods and state (_cacheSaveTimer, _cacheDirty)
into a new CacheManager class in src/cache-manager.ts. Plugin delegates to
it via thin wrappers that preserve the existing method names tested by the
test suite. main.ts reduced from 826 to 713 lines.
https://claude.ai/code/session_016QvEfqw6YZ3RjwBHrJ4w8S
Add back Codex CLI alongside Claude Code CLI and API backends.
All three backends are now available in the settings dropdown.
Change-Id: If93e0e93b724bf0dc18308d54f8d39b47c55deb4
Add back Claude Code CLI as a backend option alongside API. Claude
Code's `-p --output-format json` is a documented interface that works
well with prompt mode. API remains the default.
Change-Id: I048936a7d59e6768952e0cb007382f75413d1ba0
Remove Claude Code CLI and Codex CLI backends — they depend on
unstable CLI flags and macOS Keychain ACLs. API backend is now the
only path, default provider is Anthropic.
Replace English README with Chinese version focused on API usage.
Add .agent/ to .gitignore.
Change-Id: I7b2488d9bca23e753cc3d461f5f7bc39052dcbef
Implement SSE streaming for API backends that support it (OpenAI Chat
Completions and Anthropic Messages). During generation the view shows
a live streaming preview with accumulated text. Adds a streaming toggle
in settings (enabled by default). Non-streaming-capable formats (Gemini,
OpenAI Responses) and CLI backends continue using the existing path.
Change-Id: I3361a5ca5aec3e25a7d16e4fb585185f6fc0695e
Switch from incremental strict flags to strict: true. Fix property
initializers with definite assignment assertions and inline defaults,
and annotate all catch clauses with unknown type.
Change-Id: I805047eebea7dc5414ff7001aca96eac5a099d02
Turn on noImplicitAny in tsconfig.json. Add explicit type annotations
to all 137 previously untyped parameters across main.ts, view.ts,
modal.ts, settings-tab.ts, providers.ts, cli.ts, anchor.ts, scroll.ts,
ui-helpers.ts, vault.ts, and settings.ts.
Change-Id: Icc5a69ab57113e9f6dc08dd97e3a8fa5e7e24937
Install @biomejs/biome with lint and format config.
Add npm lint/lint:fix scripts. Auto-fix formatting across all source
files. Update tsconfig lib to ES2022 for Object.hasOwn support.
Change-Id: I13e3ba2f106f7e3d03349080b7ed515d427d24a1
Turn on strictNullChecks, strictFunctionTypes, strictBindCallApply,
and noImplicitThis in tsconfig.json. Fix 13 resulting type errors
across main.ts, anchor.ts, providers.ts, schema.ts, prompt.ts.
Change-Id: Ib747b9c0cb809d99a7b3d3d7b177ee5918a74dfd