Commit graph

167 commits

Author SHA1 Message Date
wujunchen
714b6a368b fix: harden CLI backend smoke checks
Change-Id: I7b4388095412b155c056c42661cdf481bd601e4c
2026-05-09 11:49:23 +08:00
wujunchen
d00075779e ci: make release upload idempotent
Change-Id: I826fcc302c0ea20877c66045e1e264efa7dbb3d5
2026-05-09 11:16:59 +08:00
wujunchen
04428950de chore: prepare 1.0.19 release
Change-Id: I86c01c3d6a2c13a193636ac08ba562d54a2e9a20
2026-05-09 10:48:36 +08:00
wujunchen
07faeddab5 fix Obsidian plugin review scan
Change-Id: I43416145eca8ee04468522c350fdcebeb772ab6e
2026-05-08 22:57:31 +08:00
fancivez
a57743e7e2 chore: bump version to 1.0.17
Change-Id: I4c5eaa3d25ed804ad95d8d53b100ed0186eee79d
2026-05-05 21:29:38 +08:00
fancivez
d738fc120a feat(error-ui): structured CliProcessError + ErrorKind-driven UI dispatch
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
2026-05-05 20:19:57 +08:00
fancivez
89464608aa feat(cli): enrich CLI timeout diagnostics + idle timeout + debug logging
The CLI provider previously surfaced only "CLI timed out (Xms)" on hang,
discarding accumulated stderr/stdout, pid, and timing — leaving users
unable to tell network hangs from auth failures from a slow model.

- runCli timeout/idle errors now include pid, elapsed, idle duration,
  utf-8 byte counts, plus stderr/stdout tails (with secret redaction
  for Bearer tokens, sk-* keys, and Authorization-style headers).
- Add optional cliIdleTimeoutMs (default 0/disabled): kill the CLI if
  no output for N ms, independent of the wall-clock cliTimeoutMs.
- Add optional debugLogging: console hooks at spawn/ok/fail with pid
  and byte counts for live diagnosis.
- Settled-guard on noteActivity/armIdleTimer prevents stale timers
  after first-wins settle (cancel / wall / idle / child-error / close).
- New unit coverage: rich diagnostic suffix, idle timeout firing, idle
  reset on heartbeat, secret redaction, idle normalization edge cases.

Change-Id: I9191f8d348ef43ff9605dc5169f0951f5af85d26
2026-05-05 17:33:25 +08:00
wujunchen
65cb81730a chore: bump version to 1.0.16
Change-Id: Ic4e1e043af1256b8a9de7d0dac47160643724649
2026-05-03 10:46:43 +08:00
wujunchen
7d5bfffecf chore: bump minAppVersion to 1.7.2 to match revealLeaf API requirement
Workspace.revealLeaf was added in Obsidian 1.7.2 but the plugin has
been declaring minAppVersion 1.4.0 since 1.0.0. Update the manifest
and pin 1.0.15 in versions.json so the install gate reflects reality.
Older release entries in versions.json remain at 1.4.0 to avoid
retroactively changing their published compat declaration.

Change-Id: I1fa8c02770b4e88f9f1e1b31835adf4d4af5d718
2026-05-03 10:45:16 +08:00
wujunchen
33371cadd6 chore: integrate obsidianmd eslint plugin and fix recommended rules
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
2026-05-03 10:45:10 +08:00
fancivez
83dc9b2874 chore: bump version to 1.0.15
Change-Id: Idcbf67cc7a2ee535a8d06719b8205ea4b1aa3931
2026-05-02 15:54:43 +08:00
fancivez
e4b2ae3e22 refactor(settings): collapsible sections + smart auto-expand
The settings tab listed ~23 entries on first paint (API mode), most of
which the typical user never touches. Restructured into 5 sections,
of which only the first two are expanded by default.

Sections (default visibility):
- Quick setup (open):     backend / provider preset / credential / model / test
- Reading output (open):  prompt language / card range / export folder / UI lang
- Advanced connection:    api format / base URL / auth type / headers /
                          max tokens / streaming + timeout merged
- Advanced prompt:        max input chars / custom system prompt
- Cache & maintenance:    max entries / clear-all

First-paint count drops from ~23 to ~9 rows.

Smart auto-expand: Advanced connection opens automatically when the
user has departed from preset defaults — custom-* provider, custom
headers, non-default base URL, non-auto auth type, streaming=false,
non-default max tokens, or api format mismatching preset. Same idea
for Advanced prompt (custom prompt or non-default max input chars)
and CLI advanced (non-default cli timeout). Old users find their
configured values without manual digging.

Other simplifications:
- API key + env var fallback combined into one row. The env var input
  lives behind a tiny details/summary disclosure and is built with
  raw DOM (label + input) instead of a nested `new Setting` to avoid
  Obsidian theme CSS conflicts on .setting-item.
- Provider preset row gains an inline read-only summary of the
  derived API format and base URL, replacing the standalone rows
  for those two fields under built-in presets.
- Streaming toggle and streaming timeout are now one row; the
  timeout input is wrapped in its own div and toggled visible only
  when streaming is on.

No data.json changes — every settings key keeps its existing name and
default value. Existing installs upgrade with zero migration.

styles.css: add scoped classes for the collapsible sections, env-var
inline row, timeout inline input, and a generic .parallel-reader-hidden.
i18n: 7 new keys × zh+en for section headings + summary fallbacks.

Codex review pass:
- shouldOpenAdvancedConnection now also reacts to streaming=false /
  non-default apiMaxTokens / apiFormat ≠ preset.format.
- streaming + timeout merged row hides a dedicated wrapper instead of
  inputEl.parentElement (which on some themes shares the controlEl
  with the toggle).
- env-var fallback no longer nests `new Setting` inside a controlEl.
- "(no base url)" placeholder in the preset summary is now i18n'd.

Change-Id: I8ef11c56a89121df13a630b1c724652e78b4069f
2026-05-02 15:54:43 +08:00
fancivez
97db81b5c8 chore: bump version to 1.0.14
Change-Id: Id0f8fc86c95ec2ab5ad841dcfe60c1af71b1407b
2026-05-02 15:38:41 +08:00
fancivez
a5d9d74b54 feat: toggle right sidebar instead of detaching tab + component tests
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
2026-05-02 15:38:41 +08:00
fancivez
8e055cce50 chore: bump version to 1.0.13
Change-Id: I56e56f0f6fd3a440d26249b5baf845c65db15785
2026-05-02 14:10:49 +08:00
fancivez
d058348f6f fix: regressions in 1.0.12 — sidebar render guard + open-view toggle
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
2026-05-02 14:10:49 +08:00
fancivez
a283cb588a chore: bump version to 1.0.12
Change-Id: I25e04737ba455e13c9809f517444df430c582c56
2026-05-02 13:52:53 +08:00
fancivez
76d2e8050a fix: address 10 P2 behavioral and concurrency findings
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
2026-05-02 13:52:49 +08:00
fancivez
d1f36d52a0 chore: bump version to 1.0.11
Change-Id: I1a24c7e05011ff320eccfd5e917ec3793fa3164e
2026-05-02 13:11:27 +08:00
fancivez
c10c641949 docs: trim e2e gate section in readme
Drop the self-contained/no-Python historical note and the contributor-
focused tests/catalog.json paragraph; keep the gate command + TEST_LIVE=1
hint. Public README does not need internal test classification details.

Change-Id: Ic7a2ed45af0ebeb3be227aed03079ce258ed30c2
2026-05-02 12:51:19 +08:00
fancivez
01ec934d28 fix: address 13 review findings (security, UX, i18n)
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
2026-05-02 12:50:35 +08:00
fancivez
dc03345dc7 chore: bump version to 1.0.10
Change-Id: I2ba1c339f13e4e40fcaa5c85e911b51b97f19640
2026-04-30 15:02:06 +08:00
fancivez
418f733d14 ci(e2e): enforce self-contained release gate
Change-Id: I4a7385a861981223edf5b4a3f1861b349ef520bf
2026-04-30 14:45:56 +08:00
fancivez
15e81cbcb6 docs: expand readme badges and language switcher
Change-Id: Ia547e146cca71f0daf04c896fd14f892940ed707
2026-04-29 18:16:43 +08:00
wujunchen
310c81a073 fix: add cancel action to batch prompt
Change-Id: Ic3922d55d776d369959eff4e5c811c5d3434db2b
2026-04-29 14:13:47 +08:00
wujunchen
57c1c0e124 docs: document e2e contract gate
Change-Id: I940bac18e567c55a7cd57e1ccf37203f871c6aec
2026-04-29 14:09:55 +08:00
wujunchen
970a7bc364 chore: add e2e contract gate
Change-Id: Idf5c36b17a5b816e354d07f1e28ff4011e54f32e
2026-04-29 14:08:38 +08:00
wujunchen
3bdd6d67c6 fix: localize batch prompt confirmation
Change-Id: I13b546785c5984a9d0cb5abaffa843f9ad7655d0
2026-04-29 13:58:10 +08:00
wujunchen
2bf4e86451 fix: confirm before overwriting exports
Change-Id: I922fca90e46a8982686a3449b00ed793e924097b
2026-04-29 13:56:36 +08:00
wujunchen
5304ed3a48 fix: localize remaining action labels
Change-Id: I4ac6db14c4aede9a106e8023effac3c99d3c037a
2026-04-29 13:54:53 +08:00
wujunchen
2d39a91aed test: split architecture guard tests
Change-Id: Ibde4ae79f202ae0e26658ab47bbcf5b52ef9effe
2026-04-29 13:53:19 +08:00
wujunchen
39ddd4289e fix: count batch generation failures
Change-Id: Ie6b29cb3efc22fe72250e9c63c1e82c71b24a356
2026-04-29 13:51:33 +08:00
fancivez
ceea8d6ff0 chore: bump version to 1.0.9
Change-Id: I5bd7ac74eb9101ffeeb99ef24518f68e91b9c101
2026-04-27 20:41:14 +08:00
fancivez
dae5175b6c fix: add void operator to all floating promises in callbacks
Satisfies @typescript-eslint/no-floating-promises for fire-and-forget
async calls (runForFile, copyToClipboard, scrollEditorToLine, etc.)

Change-Id: I758cc8e1ae49deacd7e8527abcda90c0e3d4fe1c
2026-04-27 20:39:14 +08:00
wujunchen
f8bf0f99ca chore: bump version to 1.0.8
Change-Id: I0eedef0d4ad19f84f189f8c95a4a1f69b6d09eb4
2026-04-27 20:27:52 +08:00
wujunchen
0801b644f6 fix: address CR findings — bump script, test runner, temp cleanup
- bump-version.mjs: reject duplicate versions (use --force to override),
  auto git-add manifest.json + versions.json for npm version hook
- run-tests.mjs: use execFileSync to handle paths with spaces
- test-setup.js: clean up esbuild temp directory on process exit

Change-Id: I44410b550d09487dd7f5e30a8f9385a2c500252a
2026-04-27 20:19:53 +08:00
wujunchen
199ab73188 refactor: split main.test.js into focused test files
Reduce main.test.js from 560 to 70 lines by keeping only architecture
guards and export smoke tests. Moved provider error tests, Anthropic
tool-use parsing, and cancellationNoticeKey tests to providers.test.js.
Settings, cache, and other tests were already covered in their
dedicated test files.

Change-Id: I8f0f3c576dec7fcf5a4c6e0c2b6071601940349d
2026-04-27 19:59:46 +08:00
wujunchen
f5f974fd74 build: remove __test barrel from production bundle
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
2026-04-27 19:57:28 +08:00
wujunchen
0bf5ab12b1 lint: extend Biome coverage to tests/ and scripts/
Add tests/**/*.js and scripts/**/*.mjs to biome.json includes and
update npm lint scripts to cover these directories. Auto-fixed all
formatting issues in 26 files.

Change-Id: I8c2763cb9683616a8abc1484d91bf8af79bb590e
2026-04-27 19:53:35 +08:00
wujunchen
b26b241c7f refactor: centralize obsidian mock into tests/obsidian-mock.js
Extracts the duplicated Module._load obsidian stub from main.test.js,
generation-job-manager.test.js, and test-setup.js into a single shared
file. All three now require('./obsidian-mock') instead of defining
their own copy.

Change-Id: I8ac38f779299ac250963357eee9f7824e13c7d33
2026-04-27 19:52:34 +08:00
wujunchen
e10cbbbe9c build: add version bump script
npm run version <x.y.z> atomically updates manifest.json and
versions.json. Replaces the manual three-file version dance.

Change-Id: Ib894147f654b284d8f4c1df95bd8ff5da80d5dcc
2026-04-27 19:51:11 +08:00
wujunchen
e5f5af43ca ci: add tag-triggered release workflow
On tag push (e.g. 1.0.7), builds from source, runs typecheck + tests,
then creates a GitHub Release with main.js, main.js.map, manifest.json,
and styles.css attached. This replaces the need to commit main.js to
the repository.

Change-Id: I8ccbdddaa45267d2738cc43a4e7fccd1d35346ab
2026-04-27 19:50:17 +08:00
wujunchen
f6ce405249 build: remove main.js from git tracking
main.js is now a build artifact produced by esbuild, not tracked in
git. The release workflow (next commit) will build and attach it to
GitHub Releases. Also removes the check-build-artifacts script that
enforced main.js stay in sync.

Change-Id: I35f7dede09c5ac3c2d788c11248e2ed4931a95bc
2026-04-27 19:49:45 +08:00
wujunchen
098fee763c build: enable linked source maps for production builds
Generates main.js.map alongside main.js so error stack traces in the
Obsidian developer console point to original source locations.

Change-Id: I3f63196bfa421c2ac69aee371e4c294d42ee629c
2026-04-27 19:48:26 +08:00
wujunchen
f9e33d4c5d build: align tsconfig target and esbuild target to ES2022
Both were ES2020 while lib was already ES2022, creating a mismatch
that could allow using APIs esbuild wouldn't polyfill. Obsidian 1.4+
runs Electron 25+ which fully supports ES2022.

Change-Id: I732538280146c6a91cf52cc52ef124f6a364dc5e
2026-04-27 19:47:41 +08:00
wujunchen
552dd41109 chore: bump version to 1.0.7
Change-Id: Id06610a41ee7664e29f0b3f2894e2270b36649b7
2026-04-27 19:08:13 +08:00
wujunchen
df48dc2cc4 fix: remove unsupported --max-tokens flag from claude CLI args
The Claude Code CLI doesn't support --max-tokens. This caused the CLI
backend to always fail with exit code 1. Also adds args validation tests
for both claude and codex CLI backends.

Change-Id: I57c5cc672ed08de291e3941511ce68ba56e5b4ff
2026-04-27 19:07:53 +08:00
wujunchen
ea5f440ca5 chore: bump version to 1.0.6
Change-Id: Icc2aa02d42c7ffe32c1b6cc5ae8b2ea995460a3f
2026-04-27 17:21:07 +08:00
wujunchen
1fff3cb343 fix: address CR findings — test runner, CI dedup, error cause
- Create scripts/run-tests.mjs to auto-discover test files, replacing
  the 900-char inline test command in package.json (P3-1)
- Add test:only script for CI to avoid duplicate build+typecheck (P2-1)
- Preserve original SyntaxError as cause in responseJson (P2-3)

Change-Id: I51d0c8c51d3b0b648ed5ff4ea282bda070b0d457
2026-04-27 16:04:03 +08:00
wujunchen
986143b05b refactor: split settings-tab renderBackendSection into CLI/API methods
Extracts renderCliBackendSettings() and renderApiBackendSettings() from
the 144-line renderBackendSection(). The parent method is now a 5-line
dispatcher. CLI settings (14 lines) and API settings (128 lines) are
cleanly separated.

Change-Id: Ic866ba78d3b572f5f564760b248460584b0819c5
2026-04-27 15:45:25 +08:00