Releases: MokuDev/docurip
Releases · MokuDev/docurip
Release list
docurip v0.6.1
v0.6.1 (2026-07-09)
Added
- Include patterns & path prefix filter: new
includePatterns(regex list) andpathPrefixfields onCrawlConfigallow whitelisting URLs during a crawl. When any include constraint is set, only URLs matching at least one include pattern or the path prefix are enqueued. Exclude patterns still override includes. UI fields added to New Crawl view; backend validation rejects malformed regex before the crawl starts. - Keyboard shortcuts:
Ctrl/Cmd+Nopens New Crawl (or Active Crawl when running),Ctrl/Cmd+Ffocuses the search input,Escapecloses the topmost modal or Live Console. Shortcuts suppress inside text inputs (except Escape). NewuseKeyboardShortcutshook andEscapeStackcontext for coordinating Escape across nested modals. - Desktop notifications: system notifications fire when a crawl completes or fails. Gated by a new
notificationsEnabledsetting (default on) with a toggle in Settings → Notifications. Usestauri-plugin-notificationwith permission request on first use.
Fixed
- ResultTree focusedIndex out of range: keyboard-focused index could exceed the visible node count after filtering or collapsing a folder, causing undefined access. Now clamped via
useEffectwhenevervisibleNodesshrinks. - Duplicate desktop notifications: if the backend emitted multiple terminal events for the same job (e.g. race between completed/failed), the notification fired more than once. A ref-backed
Set<string>now deduplicates per jobId. - Notification plugin errors unhandled:
sendNotificationwas not wrapped in try/catch — if the plugin threw after permission was granted, the rejection propagated as unhandled. Now caught and logged. - Whitespace-only pattern lines sent to backend: include/exclude/selector textareas split on newlines but only filtered with
.filter(Boolean), so a line of spaces passed through as a non-empty (invalid) regex. Lines are now.trim()'d before filtering. - Keyboard shortcuts case-sensitive:
Ctrl+N/Ctrl+Fmatchede.key === 'n'/'f'literally, so they failed with CapsLock on. Now normalized via.toLowerCase(). - ResultTree rowProps invalidating memoization:
rowProps={{}}created a fresh object every render, defeating react-window's row memoization. Hoisted to a module-level constant. headlessStrategycast toany: select onChange usedas anyto bypass TypeScript, hiding potential type mismatches. Now uses the properCrawlConfig['headlessStrategy']union type.pathPrefixsubmitted with query/fragment: user-entered path prefixes containing?or#would never match (the backend compares againsturl.path()only). Now normalized on both frontend submit and backendstart_crawl: trim whitespace, strip query/fragment, enforce leading/.listReftyped asanywith wrong API calls: ResultTree useduseRef<any>and react-window v1'sscrollToItemmethod. Replaced with react-window v2'suseListRefhook (properly typedListImperativeAPI) andscrollToRowAPI. Fixes the only TypeScript error in the codebase.
docurip v0.6.0
v0.6.0 (2026-07-08)
Added
- Dark / Light / System theme toggle: the app was previously dark-only with no theme infrastructure. Semantic Tailwind tokens (
deepVoid,surface,abyssal,ghost,smooth,secondary,charcoal) now resolve through CSS variables that flip based on a.dark/.lightclass on<html>, so existing component styling didn't need to change. NewThemeProvider/useTheme()hook persists the preference via a dedicatedset_themecommand;systemmode tracksprefers-color-schemelive. Quick-access toggle in the top status bar; full picker in Settings → Appearance.
Fixed
- Unreadable button text in light mode:
text-deepVoidwas reused as guaranteed-dark label text onaccentGreen/amberbuttons (Save, Start Crawl, dashboard CTA); sincedeepVoidnow flips to near-white in light mode, those labels went nearly invisible. Switched to a fixed dark color for button labels instead of a theme-aware token. - Low-contrast secondary text/badges in light mode: darkened the light-mode text scale so
charcoal(the most-muted token, used widely for hints and status badges) clears WCAG AA contrast against the near-white backgrounds. - Theme-save race:
useTheme'ssetThemepreviously did its ownget_settings→update_settingsround trip, which could race with the Settings page's own save/reset flow and silently clobber either the theme or unrelated field edits (the backend does a full overwrite, not a merge). Replaced with a dedicatedset_themecommand that only touches thethemekey; Settings now reads the live theme from context at save time instead of a locally-mirrored copy, and persistence failures now surface a toast instead of failing silently.
docurip v0.5.3
v0.5.3 (2026-07-08)
Fixed
- Auto-Updater: Autoupdater fixed.
docurip v0.5.2
v0.5.2 (2026-07-04)
Added
- HTML export format: Export crawled documentation as styled HTML files (individual or merged). Uses pulldown-cmark to convert Markdown to HTML with embedded CSS styling. Added
HtmlFilesandMergedHtmlvariants toExportFormat. - Virtualized ResultTree: Tree view in ResultBrowser now uses react-window for efficient rendering of large result sets. Only visible nodes are rendered, improving performance with thousands of pages.
- Lazy-loaded MarkdownPreview: MarkdownPreview component is now code-split and loaded on demand, reducing initial bundle size by ~31KB.
v0.5.1 (2026-07-01)
Added
- Advanced Markdown cleaning pipeline: Comprehensive pre- and post-processing for cleaner output:
- Pre-processing: strips
<script>and<style>tags with content, removes empty<a href="#"></a>links before HTML-to-Markdown conversion - Extended boilerplate detection: filters cookie banners ("we use cookies"), newsletter signups ("Subscribe to our newsletter"), and copy-code buttons in addition to existing UI elements
- Post-processing: collapses excessive blank lines (3+ → 2), removes empty link syntax
[text](), and strips broken image references![alt]()
- Pre-processing: strips
docurip v0.5.0
v0.5.0 (2026-06-28)
Added
- PDF/EPUB import: Import PDF and EPUB files into Markdown with automatic image extraction. New Import view with drag & drop file picker. Backend modules
importer/pdf.rsandimporter/epub.rshandle extraction viapdf_extractandepubcrates. - JSON export format: Export crawled documentation as structured JSON files (individual or merged). Each entry includes
title,url,content, andmetafields. AddedJsonFilesandMergedJsonvariants toExportFormat. - Text cleaner for imports: Configurable pipeline that strips headers, footers, page numbers, footnotes, and boilerplate from imported PDFs/EPUBs. Uses cross-page frequency analysis for header/footer detection, sequential number detection for page numbers, and zone-restricted pattern matching. Toggle in Import UI, enabled by default.
- Tauri native drag & drop: Import view uses Tauri's native file drop event instead of HTML5 drag & drop for more reliable file handling.
- Auto content extraction in crawler: When no CSS selectors are configured, the crawler now tries common content selectors (
main,article,[role="main"],#content,.content, etc.) before falling back to the full HTML body. Prevents nav, sidebar, and footer content from polluting the Markdown output. - Markdown deduplication: Post-processing step in
HtmlToMarkdownremoves duplicate text blocks (>80 chars) that appear multiple times in converted output, eliminating repeated content from pages with duplicated DOM structures.
Fixed
- JSON export title extraction: Now detects both ATX-style (
# Heading) and setext-style (Heading\n===) Markdown headings. Previously only ATX headings were detected, causing filenames likegetting-startedto appear as the title instead of the actual heading text. - Clean text toggle not working: Fixed click area (onClick was only on the small track, not the label) and stale closure in drag & drop handler (useEffect captured initial state; now uses useRef to always read current value).
- TextCleaner ineffective on real PDFs: Pre-trims excessive blank lines from
pdf_extractoutput, uses non-blank-line-aware zone indexing, expanded detection zones to 5 lines, added sequential page number detection across pages, and caps effective zone at half page height to avoid false positives on short pages. - UI boilerplate in crawled output: Strips "Copy page", "Open markdown", "Edit page" text that leaks from documentation site UI buttons into the Markdown/JSON output.
- TOC navigation polluting output: Detects and removes anchor-link table-of-contents sections (lists of
[Section](#anchor)links), including full-path variants like/docs/page#section. - Trailing heading stubs: Removes repeated heading-only blocks (no content between them) that appear at the end of pages from sidebar/mobile navigation elements. Handles both ATX (
## Heading) and setext (Heading\n----------) formats, plus short fragments like "💡Tip" interspersed between stubs.
docurip v0.4.2
v0.4.2 (2026-06-28)
Added
- "Active Crawl" navigation item: New sidebar entry appears when a crawl is running, allowing access to crawl controls (pause/cancel) from any view. Dashboard remains accessible during active crawls.
- Default settings update:
default_page_limitincreased from 50 to 1000,request_delayreduced from 1000ms to 750ms (25% faster).
Fixed
activeJobIdstracking bug: Fixed stale "RUNNING" badge issue —activeJobIdsnow only updates onjobStatusChangedevents. Jobs are correctly removed from active set when status iscompleted,failed, orcancelled. Previously, all non-status events incorrectly added the jobId to the active set.NewCrawlViewstate loss: Active crawl state is now persisted insessionStorageand restored on mount — switching between Dashboard and Active Crawl no longer loses the Live Monitor or controls. Paused jobs are now correctly restored (previously onlyrunning/queuedwere handled).- Duplicate sidebar entries: "New Crawl" and "Active Crawl" are now mutually exclusive — only one appears at a time based on whether a crawl is running.
- LiveConsole close button removed: Removed non-functional "X" close button, leaving only "Clear" and "Minimize" controls.
- Vitest test suite expanded: 11 frontend unit tests now pass (useToasts: 6 tests for push, dismiss, auto-dismiss, error persistence, multiple types, correct removal; useCrawlEvents: 5 tests for initial state, event emission, active job tracking, 500-event cap, and non-status event isolation).
- LiveConsole close button removed: Removed the "X" close button from the LiveConsole header — only "Clear" and "Minimize" remain, as closing the console while a crawl is active was confusing and rarely useful.
v0.4.1 (2026-06-28)
Added
- Queue backpressure:
MAX_QUEUE_SIZE = 50_000limit with warning event when queue reaches capacity — prevents unbounded memory growth during aggressive crawls. - Vitest test suite: 8 passing frontend unit tests covering
useToasts(push, dismiss, auto-dismiss, error persistence) anduseCrawlEvents(event handling, active job tracking, 500-event cap). Includesvitest.config.ts,src/test/setup.ts, and test files for both hooks.
Fixed
- Phosphor icon
titleprop removed:titleattribute is not supported by@phosphor-icons/react— removed from all icon instances inLiveConsole.tsxto fix TypeScript build error.
docurip v0.4.0
v0.4.0 (2026-06-28)
Added
- Full-text search in ResultBrowser: backend-powered content search via
search_job_results— reads.mdfiles from disk with relevance scoring and preview snippets. Triggered at 3+ characters with 300ms debounce. - ErrorKind icons in LiveConsole: error events now display visual indicators for error type — red disk icon for
Disk, orange cloud forNetwork, file-x forParse, stop sign forRobotsBlocked, and generic warning forUnknown.
Changed
job.resultskeeps only metadata in RAM:PageMetanow stores URL, title, HTTP status, and link count — Markdown content no longer sits on the heap. Eliminates O(n·content) RAM growth during large crawls.- Persist throttling:
persist_jobis no longer invoked after every page; it runs at most every 50 pages or 10 seconds, whichever comes first. - Introduced
JobStatus::Cancelled: cancelled jobs now report statusCancelledinstead ofFailed. ErrorKindfor typed error classification:CrawlEvent::Errornow carrieskind: ErrorKind(Network/Disk/Unknown), laying the groundwork for differentiated error display in the frontend.
Fixed
- Blocking
tokio::select!in crawl loop:select! { _ = persist_interval.tick() => true, else => false }blocked each loop iteration for up to 10 seconds because_ = futurealways matches andelsenever fires. Replaced with a non-blockingInstant::elapsed()check — resolves a ~7× throughput regression.
v0.3.4
v0.3.4 (2026-06-27)
Added
- MIME-Type validation for asset downloads:
HttpFetcher::fetch_byteschecksContent-Typeagainst an allow-list (images, fonts, CSS, JS, JSON, PDF, audio/video, octet-stream) and rejectstext/html/application/xhtml+xmlso that error pages or login redirects served at asset URLs no longer get persisted as broken images/stylesheets. - SSRF check on the start URL:
validate_crawl_inputnow runscrawler::ssrf::is_private_targeton the submitted URL whenssrf_protectionis enabled, returning an actionable error before the crawl is even spawned. Previously SSRF was only enforced on follow-up links during the crawl. useUpdater.errorrendered in the update banner: the update banner now shows the captured error message and switches the action button label from "Install & Restart" to "Retry" when a previous install attempt failed.
Changed
- User-Agent unified to
Docurip/0.3.3inHttpFetcher(fetcher/http.rs) and the defaultAppSettings.user_agent(settings/config.rs); previously both still advertisedDocurip/0.3.1. - Dashboard stats polling throttled: stats refresh every 3 s while at least one crawl is active, otherwise every 4th tick (~12 s). Job list and recent exports continue to poll every 3 s. Keeps the live-stats UX from v0.3.2 during crawls while reducing idle backend load.
- NewCrawl logs migrated to
useRef: log entries are appended into a ref-backed array (mutated in place, capped at 500); alogTickcounter drives re-renders. Avoids per-append array-copy that grew quadratic on long crawls. walk_diris nowpubinexport.rsandcommands::export_job_zipcallsexport::zip_directoryinstead of its own inlineadd_dir_to_ziprecursion. Eliminates ~25 lines of duplicated ZIP-walking logic.- Error classification uses typed downcasts:
crawler::orchestrator::is_disk_errornow walks theanyhow::Errorchain looking forstd::io::Errorand matches onErrorKind::PermissionDenied/StorageFull/ReadOnlyFilesystem. The original substring-based logic is kept asis_disk_error_strand used as a fallback only when noio::Erroris present in the chain.HttpFetcher::is_transient_errornow downcasts toreqwest::Errorand usesis_timeout()/is_connect()/is_request(). Substring matching remains as a fallback for non-reqwest errors.
Fixed
- Headless feature build:
tab.close()now passes the requiredfire_unload: boolargument (tab.close(false)) socargo check --features headlesssucceeds against headless_chrome 1.x. Previously failed to compile in the headless build.
Tests
- 2 new tests for
is_disk_error: classifiesio::ErrorKind::PermissionDenied/StorageFull/ReadOnlyFilesystemvia cause chain; falls back to string matching when noio::Erroris present. - 1 new test for
is_allowed_asset_mimecovering images, fonts, CSS, JS, JSON, PDF, audio/video, octet-stream, charset suffixes, empty content-type, and rejection oftext/html/application/xhtml+xml.
v0.3.3
v0.3.3 (2026-06-14)
Added
- Window size setting: new dropdown in Settings → Window with 5 presets (1280×900 Compact, 1600×1000 Standard, 1920×1080 Full HD, 2560×1440 QHD, 3840×2160 UHD/4K). Selection applies immediately — the window resizes and centers on the current monitor without restart. Persisted across sessions via
tauri-plugin-store. On startup, the saved size is applied before the window becomes visible. Oversized selections (e.g. UHD on a 1080p display) are clamped to the available monitor dimensions with a toast notice. Minimum window size constraint of 1280×900 enforced viatauri.conf.json.
Fixed
- Dashboard stats showing zero:
DashboardStatsstruct was missing#[serde(rename_all = "camelCase")], so the backend sentpages_savedbut the frontend expectedpagesSaved— all fields wereundefinedand fell back to0. - Recent Exports always empty:
list_recent_exportsscanned the nonexistentapp_data_dir/exports/directory. Exports are actually written to{outputDir}/zip/. Rewrote the function to accept a list of job output dirs and scan each{dir}/zip/subfolder.list_exportscommand now collects unique output dirs from all active + persisted jobs.
Changed
- Removed unused
Managerimport fromcommands.rs.
v0.3.2 (2026-06-14)
Added
- Auto-organized output folders: every crawl now creates three subfolders under the global output directory:
{outputDir}/{targetName}/main/for crawled content,{outputDir}/{targetName}/zip/for exported ZIPs, and{outputDir}/{targetName}/formats/for format exports (MD files, PDF files, merged variants). Folders are created automatically when a crawl starts — no manual setup required. - targetName extraction: the subfolder name is derived from the crawl URL's domain (e.g.
docs.example.com), keeping results organized by site. - Simplified ExportModal: export destination is now fully automatic — the format picker is all you need. ZIP exports land in the job's
zip/subfolder; all other formats (Markdown files, PDF files, merged MD, merged PDF) land in theformats/subfolder. No more manual folder picker step. - "Open folder" opens main/ subfolder: History and ResultBrowser "Open output folder" buttons now open the
main/subfolder directly, showing the crawled content instead of the parent directory. - Live dashboard stats: dashboard stats (pages saved, total size, crawl velocity, fail rate) now update in real-time during active crawls, not just after completion.
- Animated stat counters: stat cards use a smooth count-up animation with ease-out cubic interpolation when values change.
Changed
- Dashboard stats cache removed: the 30-second cache TTL that served stale zeros during active crawls has been eliminated. Stats are computed fresh on every 3-second poll.
collect_all_jobsnow async: uses.read().awaitinstead oftry_read()on bothactive_jobsandpersisted_jobsRwLocks, so active jobs are no longer silently skipped when the orchestrator holds a write lock.- Crawl velocity includes active jobs: velocity is now computed from wall-clock time (
Utc::now() - start_time) for running jobs, falling back toend_time - start_timefor completed jobs. - Total size includes active job output:
total_size_bytesnow sums output directories for all jobs (active + completed), not just completed ones. compute_velocityextracted: velocity logic moved to a dedicated function that handles both running and completed jobs.- Output directory setting moved to Settings only: the per-crawl output directory picker in New Crawl has been removed. The output directory is configured once in Settings and applies to all crawls, reducing configuration friction and ensuring a consistent folder structure.
- Export commands use subfolder structure:
export_jobandexport_job_zipnow read crawled content from{outputDir}/main/and write ZIPs to{outputDir}/zip/.export_job_v2auto-derives the destination to{outputDir}/formats/when no explicit destination is provided, with thedestinationparameter becoming optional. resolve_output_dirsimplified: generates{baseDir}/{domain}(no date/id suffix), matching the cleaner subfolder structure.
v0.3.1 (2026-06-14)
Added
- Tests: 3 regression tests for URL-to-path query-string stripping in
writer/fs.rs(verifies?queryand#fragmentare stripped from filenames) - Logs memory cap:
NewCrawl.tsxnow caps log entries at 500, preventing unbounded memory growth during long crawls - Search debounce:
ResultSearch.tsxdebounces input by 200ms to reduce re-rendering of ResultTree and MarkdownPreview during typing - Dashboard error logging: empty
catchblocks inDashboard.tsxnow emitconsole.warnwith context for debugging
Fixed
- Startup crash:
AppState::init()calledHandle::current().block_on()before Tauri starts its Tokio runtime, causing an immediate panic; reverted to synchronousstd::fsfor the one-time startup load - prefillUrl re-trigger: removed the
if (prev.url) return prevguard inNewCrawl.tsxso quick-start URLs are always applied, even after the user has manually edited the URL field
v0.3.0 (2026-06-14)
Added
- Domain filtering (
stay_within_domain): new config option (defaulttrue) that restricts the crawler to links within the same domain as the start URL; implemented inorchestrator.rsURL queue with checkbox in New Crawl and Settings views - robots.txt enforcement (
respect_robots_txt): newrobots.rsmodule that fetches and parsesrobots.txtfrom the target site, honoringUser-agent,Disallow,Allow, andCrawl-delaydirectives; enforced as the first URL-level filter in the crawl loop - SSRF protection (
ssrf_protection): newssrf.rsmodule that detects and blocks requests to private/internal IP addresses (loopback, link-local, RFC 1918, IPv6 ULA) via IP-literal checks, known-local hostname patterns, and DNS resolution; configurable per-crawl with checkbox UI - 10 unit tests for SSRF detection covering IPv4 private ranges, IPv6 loopback/ULA, localhost variants,
.localTLD, and public-host passthrough
Security
- XSS prevention in MarkdownPreview: added DOMPurify sanitization after Markdown-to-HTML rendering and after search-query highlighting; restricted allowed tags/attributes and blocked
javascript:URIs in links - CSP hardened: removed
'unsafe-inline'fromscript-src; setwithGlobalTauri: falseso the Content Security Policy now actually blocks injected inline scripts
Changed
useCrawlEvents: migrated fromwindow.__TAURI__.event.listenback to typedlisten()from@tauri-apps/api/eventwith proper async cleanup and TypeScript-safe event payloads- Async disk I/O:
save_job_to_disk,load_job_from_disk,load_all_jobs, anddelete_job_from_diskinstate.rsnow usetokio::fsinstead of blockingstd::fs, preventing the crawl runtime from stalling on disk operations;AppState::init()bridges the sync/async boundary viaHandle::current().block_on() persist_jobandremove_persisted_jobnow.awaitthe underlying async disk operations- System stats caching:
system.rsnow uses aLazyLock<Mutex<System>>singleton instead of creating a newsysinfo::System::new_all()on every 2-second poll, reducing allocation overhead and improving accuracy of CPU readings - Asset download size limit:
fetch_bytesnow checks thecontent-lengthheader before consuming the response body, rejecting assets larger than 50 MB to prevent memory exhaustion from adversarial or oversized downloads - Dashboard polling merged: three separate
useEffectintervals (jobs 3s, stats 3s, exports 5s) inDashboard.tsxconsolidated into a single 3s interval - Parallel asset downloads:
orchestrator.rsasset-download loop replaced withtokio::task::JoinSetso all assets per page download concurrently instead of sequentially - Shared StatusBadge: extracted
StatusIconandStatusBadgeintosrc/components/StatusBadge.tsx;Dashboard.tsx,History.tsx, andNewCrawl.tsxnow import the shared component instead of duplicating local versions - LiveConsole event processing: fixed event loss by switching from
events[events.length - 1]to index-based tracking withlastProcessedIdxref, ensuring all events between renders are processed - History loading flicker:
loadJobsnow accepts ashowSpinnerparameter; background polls skipsetLoading(true)so the spinner only appears on initial load
Fixed
- UTF-8 panic in search preview:
extract_previewnow useschar_safe_start/char_safe_endhelpers to find valid UTF-8 character boundaries before slicing, preventing panics on non-ASCII content (e.g. German umlauts, CJK characters) - Panic-safe CSS selector: replaced
unwrap()on hardcodeda[href]selector inDomParserwithexpect()carrying an explanatory message - ZIP export path error:
export_job_zipnow propagates an explicit error whenoutput_dirhas no parent directory instead of silently falling back viaunwrap_or - Silent polling failure in New Crawl:
get_jobpolling now tracks consecutive errors via a ref; after 3 consecutive failures the interval is cleared and the job status is set tofailedso the UI reflects the broken state instead of freezing silently - Invalid exclude patterns ignored silently:
validate_crawl_inputnow validates each exclude pattern before the crawl starts and returns an actionable error if any pattern is malformed;Orchestrator::newpropagates the error via?instead of discarding it with.ok() stay_within_domainwas defined but never enforced:CrawlConfig.stay_within_domainexisted in the struct since v0.2.x but had no filtering logic — links to ex...