Commit Graph
417 Commits
Author SHA1 Message Date
amitwh f63a678c88 fix(security): strengthen ReDoS denylist in search-in-files
Follow-up to the push-sweep review: the previous UNSAFE_REGEX used
JS bitwise OR (|) between regex literals, accidentally OR-ing the
RegExp objects instead of combining alternatives into one pattern.

Replace with a single RegExp that catches:
- nested quantifiers (a+)+, [a-z]*+
- class + quantifier
- dot-quantifier followed by dot-quantifier
- lookahead / lookbehind (?=, ?!)
- backrefs \1..\9
- alternation + quantifier

Also add MAX_REGEX_LENGTH=200 hard cap. The denylist is still
defense-in-depth — the length, files-traversed, and per-file-byte caps
are the primary defense.
2026-07-23 09:39:08 +05:30
amitwh 5e76d77ece fix(security): harden search-in-files against DoS + path traversal
Address findings from automated security review:

1. Validate rootPath via validatePath/isPathAccessible before recursing,
   matching the pattern used by read-file/write-file.
2. Cap query length at 1024 chars to bound regex compilation cost.
3. Reject regexes matching classic ReDoS shapes (nested quantifiers
   and similar) before invoking RegExp.
4. Use realpathSync to resolve symlinks and verify containment under
   rootPath; drop symlinks that point outside the search root.
5. Cap total files traversed at 10000, in addition to existing
   1000-result and 2MB-per-file caps.

Add regression tests for each guard.
2026-07-23 09:34:57 +05:30
amitwh f43e34ca2b fix(ipc): re-type wrapper signatures to match handler return shapes
Many IPC handlers were returning useful data but the wrappers typed
'void', silently dropping the response. Re-type all affected wrappers:

- file.write: returns { path } (resolved after security validation)
- file.delete/ensureDir/exists/isDirectory/copy/move: full wrappers
  (previously only on window.electronAPI.file.*)
- crash.read: typed as Array<{...}> so callers can trust the shape
- crash.delete: returns boolean
- gitStage: returns { files, error? } so callers can detect failure
- gitCommit: returns { summary } | { error }
- updater.check/getState: returns { state }
2026-07-23 09:34:57 +05:30
amitwh 6da2ba7cc7 fix(ipc): crash modal reads wrapper, search wired to real handler
CrashReportModal crashed on dumps.map because ipc.crash.read returns
{ ok, data } and the consumer was assigning the wrapper to state. Now
unwraps .data and falls back to [] on error.

ipc.file.search was miswired to ipc.file.pickFile — clicking 'Search'
popped a file picker instead of searching. Add real 'search-in-files'
handler in src/main/files/search-in-files.js with regex support,
case-sensitivity toggle, .git/node_modules/dist skip, 2MB file cap, and
1000-result limit. Wire preload bridge + TS declaration + allowlist.

Also:
- Drop dead ipc.file.open wrapper that was miswired to pickFile
- Rename FileEntry field 'modified' to 'modifiedAt' (number) so the
  IPC payload matches the declared type
- Update CrashReportModal tests to use the safeCall envelope shape
2026-07-23 09:34:57 +05:30
amitwh f1f8a16a79 fix: list-directory returns flat array, drop defensive fallback
The list-directory handler returned { path, entries } while the
renderer typed result.data as FileEntry[]; the file-store had to fall
back to raw.entries via Array.isArray. Extract the entries builder into
src/main/files/list-directory.js so it can be unit-tested, return a
plain array, and skip entries that can't be stat'd (broken symlinks,
permission errors) instead of throwing.

Also tighten jest config so dist/*.snap electron-builder artifacts are
not matched as test suites.
2026-07-23 09:34:57 +05:30
amitwh 772a791d9a fix(git): git-status IPC returns a flat array, not wrapped object
GitOperations.getStatus returns { files: [...] } but the renderer's
ipc.file.gitStatus type declares Array<...> and calls result.data.map().
The mismatch made GitStatusPanel.tsx throw 'n.map is not a function'
on mount, which blanked the entire React tree.

Unwrap result.files in the IPC handler so it returns the array the
renderer expects. Add tests covering the success, empty, and non-git
branches so this regression cannot recur.

Refs: blank-screen-on-md-open
2026-07-23 09:34:57 +05:30
amitwh ec3d53ea7e chore: lint cleanup + 5.1.0 changelog entry
- Drop unused imports/vars (withEpubEmbedFontArgs, monoPdfHeaderDir, _e)
- Append CHANGELOG entry summarising the parity work and the
  Markdown Converter React rename
2026-07-23 09:34:57 +05:30
amitwh 50c6369348 test(e2e): verify monospace font embeds into PDF/DOCX/EPUB/HTML 2026-07-23 09:34:57 +05:30
amitwh 81d38160d8 build(identity): rename to Markdown Converter React + defaults
- package.json: name -> markdown-converter-react; productName -> Markdown
  Converter React; appId -> com.concreteinfo.markdownconverter.react;
  linux.executableName -> markdown-converter-react
- Window title: 'Markdown Converter — React Dev' in dev mode
- Migration defaults: monospaceFont, monospaceLigatures, appVariant
- Bumped fields in v5OnlyFields so v4->v5 detection covers new keys
2026-07-23 09:34:57 +05:30
amitwh 28ef350f39 feat(ipc): expose monospace settings through preload bridge
- Add 'get-monospace-settings' + 'set-monospace-settings' to allowlist
- Expose electronAPI.monospace.{getSettings,saveSettings}
- Extend ElectronAPI TypeScript interface with the monospace namespace
2026-07-23 09:34:57 +05:30
amitwh d29f19b1f5 feat(renderer): body-class monospace toggle + CSS tokens
- useMonospaceClasses hook calls electronAPI.monospace.getSettings()
  and toggles body.mono-{jetbrains-mono,fira-code} and
  body.mono-ligatures-{on,off} classes
- Add --font-mono-active and --font-mono-feature tokens in globals.css
- Apply to code/pre/kbd/samp so all code rendering picks up the active
  monospace font + ligature settings
2026-07-23 09:34:57 +05:30
amitwh b2ad8b8326 fix(security): address supply-chain + resource-leak findings
- PdfFontHeader: use mkdtempSync for exclusive temp dir; caller unlinks
  after pandoc consumes (cleanup wired into exportWithPandoc callback)
- download-tools: pin FiraCode to immutable release v6.2 with SHA-256
  digests verified before atomic rename; refuse download on mismatch
- Vendor missing FiraCode-Bold.ttf + JetBrainsMono-Regular.ttf
2026-07-23 09:34:57 +05:30
amitwh d2f3118bfe feat(main): wire monospace embedders into PDF/DOCX/EPUB/HTML exports
- Add getActiveMonospaceContext() helper that reads settings + resolves TTF
- PDF: build fontspec header and pass --include-in-header to xelatex
- DOCX: post-process zip to embed TTF and add fontTable.xml + rels
- EPUB: prepend --epub-embed-font + patch manifest
- HTML: embed woff2 base64 into built-in marked exporter CSS
- Register get-monospace-settings / set-monospace-settings IPC at startup
2026-07-23 09:34:57 +05:30
amitwh 4cb38cd861 build(monospace): bundle JetBrains Mono + Fira Code TTFs, asarUnpack
- Copy TTFs and LICENSE files from master v4.5.0
- Add assets/fonts/** to electron-builder files + asarUnpack
- Extend download-tools.js with downloadFiraCode() parallel to downloadPandoc()
2026-07-23 09:34:57 +05:30
amitwh 65dfdfb307 feat(monospace): add get/set IPC handlers with safe validation 2026-07-23 09:34:57 +05:30
amitwh f1c3aaa0ef feat(monospace): build CSS with embedded woff2 base64 2026-07-23 09:34:57 +05:30
amitwh 0c37a8ca2d feat(monospace): EPUB embed-font helper + manifest patcher 2026-07-23 09:34:57 +05:30
amitwh 992c6b72d6 feat(monospace): embed TTF into pandoc-generated DOCX 2026-07-23 09:34:57 +05:30
amitwh 001c9463e3 feat(monospace): add xelatex fontspec header builder 2026-07-23 09:34:57 +05:30
amitwh 3602bc35a7 feat(monospace): add path resolver for bundled TTF assets 2026-07-23 09:34:57 +05:30
amitwh 14b5a38a5a feat(monospace): add settings schema with safe defaults 2026-07-23 09:34:57 +05:30
amitwh 33a61e65ef docs(plan): monospace font embedding + naming implementation plan 2026-07-23 09:34:57 +05:30
amitwh 857fd8a75a docs(spec): monospace font embedding + naming differentiation design
Port master's v4.5.0 monospace font embedding feature (JetBrains Mono +
Fira Code TTF assets, embedded into PDF/DOCX/EPUB/HTML exports) and
rename branch identity to Markdown Converter React so dev build coexists
with the installed markdown-converter deb without appId/productName
collisions.

Amit Haridas
2026-07-23 09:34:57 +05:30
amitwh b8d26c8d78 chore(repo-map): add auto-generated structural map
Generated with ~/.claude-shared/scripts/repo-map.sh (universal-ctags).
Signatures-only map of classes/functions/methods/interfaces/enums.

Amit Haridas
2026-07-18 07:23:30 +05:30
amitwh bb4e874809 docs(claude-md): add tailored CLAUDE.md for react-electron branch
Documents the React 19 + TypeScript + Tailwind/shadcn rewrite: dual
Vite+Electron dev workflow, modular main process, Zustand stores,
typed IPC via preload whitelist, two-stage build pipeline, and
dual test runners (Jest main, Vitest renderer).

Amit Haridas
claude-md-2026-06-19-react-electron
2026-06-19 23:18:22 +05:30
amitwh 36422a9ab3 feat: complete master feature parity with interactive PDF editing, Reveal.js export dialog, Large File Mode, and scoped CSS 2026-06-15 01:22:17 +05:30
amitwh 7eb90d467a feat: wire batch media converter and document compare, remove coming-soon stubs
- batch.showConverter: open BatchMediaConverterDialog for image/audio/video
  types (uses existing universal-convert-batch handler with ImageMagick/FFmpeg)
- tools.documentCompare: line-by-line diff of two open tabs with confirmation
  dialog, shows diff count in toast and full diff in console
- No more 'coming soon' toasts for any menu item
2026-06-14 01:51:43 +05:30
amitwh 809c266e54 fix: serialize git status/stage/commit/log results for IPC transfer
simple-git returns objects with Map/Set fields that cannot be cloned
over Electron IPC. Flatten all git operation results into plain
JSON-serializable objects to fix 'An object could not be cloned' error.
2026-06-14 01:43:29 +05:30
amitwh 2e3f4dda0f fix: add missing rendererReady call and fix temporal dead zone crash
- App.tsx: call electronAPI.file.rendererReady() on mount so main process
  opens pendingFile (files passed via CLI or file associations)
- FindReplaceBar: move updateMatchCount declaration before executeCommand
  to fix 'Cannot access d before initialization' crash in minified build
  (executeCommand referenced updateMatchCount via closure before its
  const declaration in the same scope)
2026-06-14 01:25:05 +05:30
amitwh 21a00a53a4 fix: resolve 12 critical runtime failures, security issue, and dead code
CRITICAL fixes:
- GitStatusPanel: use ipc.file.gitStage/gitCommit instead of non-existent top-level methods
- HeaderFooterDialog: use send/on channels instead of non-existent headerFooter namespace
- CodeMirrorEditor: use invoke('save-pasted-image') instead of non-existent savePastedImage
- ipc.ts: map to correct preload namespaces (git.status, git.stage, git.commit)
- Add pick-folder/pick-file to ALLOWED_SEND_CHANNELS whitelist
- Add git convenience namespace to preload (git.status/stage/commit/log/diff)

HIGH fixes:
- FindReplaceBar: replace broken match count stub with regex-based counter
- PDF export window: disable nodeIntegration, enable contextIsolation + sandbox
- Git handler: accept rootPath argument from renderer
- Add git-diff IPC handler + GitOperations.diff()

MEDIUM fixes:
- Remove 4 dead functions: checkPandocAvailable, safeExecFile, runPandoc, openPDFFile, exportWithPandocPDF
- Remove stale ODT header/footer console.log stub
- Rewrite electron.d.ts to match actual preload API shape
2026-06-13 19:52:51 +05:30
amitwh f2398e6e1a feat: enhance WelcomeDialog, export dialogs, GitStatusPanel, add CommandPalette, FindReplaceBar, and new converter dialogs
- WelcomeDialog: add quick start, feature showcase, keyboard shortcuts, recent files, version display
- ExportDocxDialog/ExportHtmlDialog/ExportPdfDialog: add header/footer, paper size, margin controls
- GitStatusPanel: full staging/unstaging, discard, commit workflow
- CommandPalette: fuzzy command search with keyboard navigation
- FindReplaceBar: find/replace with regex, case, whole-word options
- New dialogs: BatchMediaConverterDialog, HeaderFooterDialog, PdfEditorDialog, UniversalConverterDialog
- Tests: comprehensive coverage for all new/updated components
2026-06-13 19:34:42 +05:30
amitwh 6b564a4569 style: run prettier formatter over src and tests 2026-06-11 20:46:00 +05:30
amitwh 2389d4f297 feat: add writing analytics dashboard and daily word goal tracking 2026-06-11 20:44:16 +05:30
amitwh 50f4f62575 fix: handle list-directory return shape {entries} in openFolder/loadChildren
The main process list-directory handler returns {path, entries} but
the file store was calling .map() directly on result.data. Since
arrays have a .entries iterator method, the nullish coalescing
fallback didn't work. Now properly extracts the entries array with
Array.isArray check.
2026-06-11 07:49:42 +05:30
amitwh 9ef5317d71 fix: add show-document-compare to preload receive channel whitelist
The IPC channel was missing from ALLOWED_RECEIVE_CHANNELS in preload.js,
causing the useBridgeNativeMenu listener to be silently blocked.
2026-06-11 07:37:48 +05:30
amitwh cf6b6817b9 fix: wire orphaned IPC channels, fix data-loss bug, print preview events, sidebar navigation
- Fix file.clearRecent sending IPC to main process instead of clearing openTabs
- Wire show-batch-converter, show-document-compare, open-header-footer-dialog IPC channels
- Add print preview event listeners (mc:print-preview, mc:print-preview-styled) in App.tsx
- Fix sidebar hidden bridge buttons toggling sidebar closed after scroll
- Update sidebar menu labels to match actual sections (Files, Outline, Git)
- Fix ipc.print to be an object with show/doPrint methods (was bare function)
- Update callers: ExportPdfDialog, pdf-export, PrintPreview
- Add 7 regression tests for all fixes
- Clean 2 obsolete snapshots
- Build Linux packages (deb, AppImage, snap) — 549 tests passing
2026-06-11 07:15:33 +05:30
amitwh e25a5e1d75 fix(migration): normalize legacy theme values under v5 marker
Two related bugs surfaced during end-to-end verification:

1. The 'isAlreadyV5' short-circuit in both the main-side and renderer-side
   v4-to-v5 transforms returned the persisted object as-is when a
   migration.version=5 marker was present. A previously-shipped v4 build
   had written theme: 'ayu-light' (and similar) under the v5 marker;
   the transform trusted the marker and passed the invalid theme through,
   which then failed the renderer's zod schema on every launch and
   reset user settings to defaults.

   Fix: in both transforms, when isAlreadyV5 matches, normalize theme
   against the v5 enum (light/dark/system) and validate the full result
   with the v5 schema before returning. Out-of-range values are replaced
   with the v5 default ('system').

2. The renderer's settings-store onRehydrateStorage callback called
   useSettingsStore.setState() to reset the store on validation failure.
   At that moment the store is still being constructed, and setState
   could hit a TDZ ReferenceError (the one we already wrapped in a
   try/catch in v5.0.0, which only hid the symptom).

   Fix: return the normalized state object from onRehydrateStorage
   instead. Zustand's persist middleware applies the returned value
   *after* construction completes, so there is no TDZ.

Tests: 334 vitest + 208 jest = 542 passing.
E2E: 12/12 verify-features.mjs steps green; no console errors.

Amit Haridas
2026-06-08 07:45:06 +05:30
amitwh c5d4b113bd fix(polish): address 3 review blockers + 1 security issue
1. ThemeSettings: change 'auto' radio value to 'system' to match
   the v5 settingsSchema enum. Storing 'auto' silently wipes all
   settings on next launch.
2. UpdateBanner: render an 'available' branch so users see a CTA
   when a new version is detected (the store already had this state,
   but the banner was silent). Added a test.
3. feed-config: remove the unused resolveFeedUrl / FEEDS exports.
   feedConfigFor is the actual implementation used by the IPC
   handler. Updated tests to match.
4. main/index.js: tighten app:open-external regex to https:// only.
2026-06-08 07:35:54 +05:30
Amit Haridas 1715e26e5f chore: E2E additions to verify-features.mjs (note: .claude/ is gitignored, see script content in working tree) 2026-06-08 07:23:11 +05:30
Amit Haridas 81484a8d33 test(e2e): verify first-run wizard and update banner in live app
- verify-features.mjs: dismiss FirstRunWizard if visible before clicking
  toolbar (wizard overlay blocked previous toolbar clicks); write a
  firstRun=true settings.json to test wizard visibility; emit
  'updater:status' from main via Playwright IPC.
- package.json: dev:electron now passes --no-sandbox to match the
  .claude/skills/run-desktop driver scripts.
2026-06-08 07:23:01 +05:30
amitwh 6df1389cc8 fix(updater): dispatch setFeedURL by provider instead of raw url 2026-06-08 07:11:28 +05:30
amitwh aeadde5f40 fix(migration): handle already-v5 settings and ensure 'failed' branch writes v5 marker 2026-06-08 07:02:45 +05:30
amitwh 74dde8d6d1 docs: v5.1.0 changelog + Distribution & Updates section 2026-06-08 06:49:12 +05:30
amitwh cd57f34c36 ci(release): publish latest.yml on all platforms, mirror to ConcreteInfo 2026-06-08 06:48:00 +05:30
amitwh 4da701deb5 ci: run on react-electron branch and ensure both jest and vitest run 2026-06-08 06:46:11 +05:30
amitwh 41c68fcf82 feat(renderer): auto-check for updates 5s after launch when enabled 2026-06-08 06:45:16 +05:30
amitwh 9b899b5205 feat(renderer): mount UpdateBanner, FirstRunWizard, and CrashReportModal 2026-06-08 06:43:40 +05:30
amitwh 691b00a2b8 feat(settings): add Updates section with channel radio and Check now 2026-06-08 06:40:51 +05:30
amitwh ade6c115b8 feat(renderer): add CrashReportModal listing local dumps 2026-06-08 06:38:08 +05:30
amitwh cc33e6c7a8 feat(renderer): add FirstRunWizard with theme/channel/template steps 2026-06-08 06:34:40 +05:30