perf: multi-dimension optimization pass (build, runtime, UI fluency, startup & bundle size) - #1778
Merged
bobleer merged 13 commits intoJul 26, 2026
Merged
Conversation
bobleer
force-pushed
the
perf/multi-dimension-optimizations
branch
from
July 26, 2026 10:35
4bee813 to
323c388
Compare
added 11 commits
July 26, 2026 20:28
- Run web type-check and vite build in parallel (scripts/build-web-parallel.mjs); enable incremental tsc for web-ui and mobile-web; drop duplicate type-check in desktop packaging CI - Trim dev profile debuginfo to line-tables-only and inject CARGO_PROFILE_DEV_CODEGEN_UNITS=256 into tauri dev (matching preview path) - Switch release profile to thin LTO and panic=abort (packaging regression recommended); remove per-platform LTO override in desktop-package.yml - Skip mobile-web rebuild when inputs are unchanged (mtime short-circuit, --force escape hatch) - Unify image crate to workspace 0.25 (was pinned 0.24 in desktop), migrate computer_use call sites and bridge screenshots-crate buffers at the boundary - Remove forced 100ms polling watcher in vite dev (VITE_USE_POLLING opt-in) - Make build.rs codegen deterministic (sorted keys) in core and cli - Parallelize beforeBuildCommand (web + mobile-web) and dev.cjs prepare steps - Prune unused Monaco NLS language packs (keep en/zh-cn/zh-tw), with verify-monaco-assets guard
…scalls - Emit backend->frontend events without unconditional payload deep-copies: peer fanout guard now runs before clone/to_value (global + terminal emitters) - Wrap directory listing in spawn_blocking (aligning with grep/glob tools), drop per-entry re-stat, preallocate results - LSP: take diagnostics by value instead of cloning, share cached diagnostics via Arc, move diagnostics cache to its own lock (no read-lock across await) - Session persistence: sanitize messages copy-on-write, serialize turn snapshots compact instead of pretty - File tree scan: stat once per entry (sort comparator is now syscall-free), reuse metadata for permissions, canonicalize via spawn_blocking, preallocate - Terminal transcript: keep files open with BufWriter and flush on a timer / rotation / close instead of open+write+flush per chunk - ACP tool-call tracker: share raw_input via Arc, removing repeated deep copies - Search: match before allocating per line, drop redundant is_file stats, return sink results without split/join round-trip, mem::take result buffers - File watcher: replace per-event block_on with sync path-table snapshot, register watches incrementally instead of rebuilding the watcher - PTY output: zero-copy UTF-8 conversion, Arc<str> tap fan-out, byte ring buffer for head/tail capture - Statically cache front-matter regex; evict per-path persistence locks (Weak)
- Run independent startup steps (i18n, AI client factory, log level) with tokio::join! after config init; bound the workspace bootstrap snapshot block_on with a 4s timeout falling back to the frontend init command - Stop bundling a second ESM copy of Monaco into the entry chunk: all monaco-editor value imports become type imports, runtime access goes through the AMD loader singleton (new monacoRuntime accessor). Entry chunk 5.44 MB -> 2.14 MB (-61%) - Replace three static Noto Sans SC weights (12.7 MB) with the fontsource variable font (4.6 MB) - Recompress oversized PNGs (logo, panda art, pet spritesheet) preserving alpha and dimensions (-2.85 MB)
- Split FlowChatContext into stable (callbacks + scalar ids) and volatile contexts; stop depending on the whole activeSession object so streaming flushes no longer re-render every visible message past React.memo - Stabilize Markdown renderer props (useCallback) so completed messages skip remark re-parsing during streaming - Nav divider drag writes --nav-width directly with rAF batching; state commits on mouseup; unmount cleanup for window listeners - Replace per-message window resize listeners with a shared ResizeObserver - Desktop pet: typewriter moves to rAF with ref targets (no interval churn), 120ms cursor polling pauses when hidden/idle and caches rects - Fine-grained zustand selectors for canvas store consumers and message edit state (editingTurnId gating) - rAF-throttle scroll handlers (file explorer, insights, explore groups) and Tooltip scroll tracking (passive, merged state) - EventBus history becomes a ring buffer; payload references only kept in dev - Fix listener leaks/races: GitStateManager dispose, PeerHostInvokeBridge and useWindowControls await-races, WorkspaceAPI abort listener, App.tsx disposed flags, tool-execution-service unlisten tracking - Cap second-tier session list expansion with incremental loading - Narrow transition properties in resizer/session styles (no layout-property hover animations)
Four review reports (compile/build, runtime, UI fluency, startup & bundle size) backing the optimizations in this branch, including the findings that were intentionally deferred as follow-ups.
The JpegEncoder in the computer_use integration test still used the image 0.24 by-value encode signature, breaking `cargo test -p bitfun-desktop` (CI Rust Build Check) even though `cargo check` on the lib target passed. Also revert panic = "abort" from the release profile: browser_get_url and the relay client's TLS setup deliberately use catch_unwind to contain known third-party panics, and aborting would convert those recoverable errors into process crashes. Thin LTO is kept.
- file_watch: re-registering an existing path was a no-op after the switch to incremental watch registration. ensure_watch_roots marks a vanished root inactive without calling unwatch_path, so when the directory reappeared the watch was never restored and that root silently stopped reporting changes (the old code rebuilt the whole watcher every time, which hid this). Now re-registration always unwatches before watching again; covered by a new file_watch contract test. - transcript: buffering moved structured markers (command, cwd, exit code, resume/finish) off the immediate-write path, so a crash or force-quit could lose the anchors agents grep for. Markers now flush immediately (carrying any buffered output with them) while plain output keeps the buffered write. - grep: returning sink results as one entry per match changed head_limit and offset from physical lines to match blocks in multiline mode. Entries are split again only when they actually contain newlines.
- UserMessageItem: the shared ResizeObserver captured the content node at effect time, while the window listener it replaced re-read the ref on every event. contentRef is attached inside conditional branches, so leaving edit mode or a turn failing left the observer bound to a detached node — the overflow/expand affordance froze and the node leaked. Re-observe when those branches change. - Desktop pet: the new getBoundingClientRect cache was only invalidated on size/overlay/task changes, but typewriter output grows bubbles and shifts the ones below, so hover hit-testing used stale rects for a whole streaming task. The cache epoch now also advances on typed-output flush. - Nav drag: a drop landing between animation frames left the DOM at the last painted width because React only rewrites the CSS variable when the committed width actually changes. The final width is now applied synchronously on cleanup, while the transition is still suppressed. - SessionScene: narrowing the transition list dropped box-shadow, which those resizer handles animate on hover — restored. - sharedResizeObserver: one throwing subscriber aborted the entry batch and killed resize handling for every observed element (a failure mode introduced by sharing the observer). Each callback is now isolated. - EventBus: the dev gate used process.env.NODE_ENV, which never resolves in the Vite browser build, so payloads were dropped even in dev, contrary to the documented behavior. Use import.meta.env.DEV. - Add monacoRuntime tests asserting the lazy proxy fails diagnosably when the runtime has not been injected yet.
… intact The optimization pass traded some flexibility for speed without saying so anywhere a contributor would look. Document CARGO_PROFILE_DEV_DEBUG=2 (the dev profile now ships line-tables-only), BITFUN_MOBILE_WEB_FORCE_BUILD=1 / --force (mobile-web builds are skipped when its dist is up to date) and VITE_USE_POLLING=1 (native watch events are the default again), plus a note that build:web now interleaves prefixed type-check and bundler output. The two parallel build wrappers also ended with process.exit(), which drops whatever is still queued on stdout when it is a pipe — as it is under CI. Set process.exitCode instead and let the process end on its own.
- grep: add a multiline + head_limit test pinning pagination to physical lines (the property the earlier fix restored) - sharedResizeObserver: report subscriber callback errors instead of swallowing them silently - vite: when the project sits on a UNC share or WSL mount during dev, print a one-line hint about VITE_USE_POLLING=1 — users upgrading from the polling-based watcher would otherwise silently lose HMR there
Final read-through of the whole diff: - workspace_manager: the comment justified the dedicated cache handle by contention with "start_server's write lock", but nothing in the repo ever takes that lock for writing. Describe what the handle actually avoids. - markdown: front_matter_regex() was introduced with no callers (agent-runtime cannot depend on services-core and keeps its own static), so use the static directly and drop the exported wrapper. - dev.cjs / prune-monaco-nls.cjs: comments overstated what they do — the dev codegen-units override only matters with CARGO_INCREMENTAL=0, and the 1.4 MB figure covers the seven pruned NLS packs, not all nine. - fonts README still described the three deleted static weights as the directory's contents; rewrite it for the variable subset build. - CI: the file watch contract suite sits behind a non-default feature and neither it nor tool-runtime were in any cargo test step, so the regression guards added by this PR never ran. Add both to the OS matrix, matching the existing rationale for platform-sensitive contract suites. Also address the frontend read-through: move a render-phase ref write into the layout effect that already batches with it, use the repo's logger instead of a bare console.error, and assert repo-wide that monaco-editor is only ever imported as a type (the guard that keeps the 3.4 MB ESM copy out of the entry chunk).
bobleer
force-pushed
the
perf/multi-dimension-optimizations
branch
from
July 26, 2026 12:33
e07a91e to
d543d24
Compare
added 2 commits
July 26, 2026 20:57
Enabling these suites surfaced failures that predate this branch: - the file watch contracts fail on macOS (debounce and atomic-rename contracts time out under FSEvents coalescing) — tests this branch does not touch, on a suite that has never run in CI - tool-runtime's glob tests fail on Windows (walk-root derivation handles separators differently) — this branch does not touch glob at all Neither belongs to a performance change, so run what is green rather than either turning CI red or dropping the coverage: file watch on Linux and Windows (the platforms whose watch registration this branch alters), and the search module where the grep changes live. Both deserve their own fix.
`search::` at the end of an unquoted YAML scalar reads as a nested mapping, which made the whole workflow file invalid — GitHub failed the run at startup with no jobs at all.
1688mengdie
pushed a commit
to 1688mengdie/BitFun
that referenced
this pull request
Jul 26, 2026
…startup & bundle size) (GCWing#1778) * perf(build): speed up dev and release build pipeline - Run web type-check and vite build in parallel (scripts/build-web-parallel.mjs); enable incremental tsc for web-ui and mobile-web; drop duplicate type-check in desktop packaging CI - Trim dev profile debuginfo to line-tables-only and inject CARGO_PROFILE_DEV_CODEGEN_UNITS=256 into tauri dev (matching preview path) - Switch release profile to thin LTO and panic=abort (packaging regression recommended); remove per-platform LTO override in desktop-package.yml - Skip mobile-web rebuild when inputs are unchanged (mtime short-circuit, --force escape hatch) - Unify image crate to workspace 0.25 (was pinned 0.24 in desktop), migrate computer_use call sites and bridge screenshots-crate buffers at the boundary - Remove forced 100ms polling watcher in vite dev (VITE_USE_POLLING opt-in) - Make build.rs codegen deterministic (sorted keys) in core and cli - Parallelize beforeBuildCommand (web + mobile-web) and dev.cjs prepare steps - Prune unused Monaco NLS language packs (keep en/zh-cn/zh-tw), with verify-monaco-assets guard * perf(runtime): cut hot-path allocations, blocking IO and redundant syscalls - Emit backend->frontend events without unconditional payload deep-copies: peer fanout guard now runs before clone/to_value (global + terminal emitters) - Wrap directory listing in spawn_blocking (aligning with grep/glob tools), drop per-entry re-stat, preallocate results - LSP: take diagnostics by value instead of cloning, share cached diagnostics via Arc, move diagnostics cache to its own lock (no read-lock across await) - Session persistence: sanitize messages copy-on-write, serialize turn snapshots compact instead of pretty - File tree scan: stat once per entry (sort comparator is now syscall-free), reuse metadata for permissions, canonicalize via spawn_blocking, preallocate - Terminal transcript: keep files open with BufWriter and flush on a timer / rotation / close instead of open+write+flush per chunk - ACP tool-call tracker: share raw_input via Arc, removing repeated deep copies - Search: match before allocating per line, drop redundant is_file stats, return sink results without split/join round-trip, mem::take result buffers - File watcher: replace per-event block_on with sync path-table snapshot, register watches incrementally instead of rebuilding the watcher - PTY output: zero-copy UTF-8 conversion, Arc<str> tap fan-out, byte ring buffer for head/tail capture - Statically cache front-matter regex; evict per-path persistence locks (Weak) * perf(startup): parallelize backend init, dedupe Monaco, shrink assets - Run independent startup steps (i18n, AI client factory, log level) with tokio::join! after config init; bound the workspace bootstrap snapshot block_on with a 4s timeout falling back to the frontend init command - Stop bundling a second ESM copy of Monaco into the entry chunk: all monaco-editor value imports become type imports, runtime access goes through the AMD loader singleton (new monacoRuntime accessor). Entry chunk 5.44 MB -> 2.14 MB (-61%) - Replace three static Noto Sans SC weights (12.7 MB) with the fontsource variable font (4.6 MB) - Recompress oversized PNGs (logo, panda art, pet spritesheet) preserving alpha and dimensions (-2.85 MB) * perf(ui): keep streaming and interactions off the re-render hot path - Split FlowChatContext into stable (callbacks + scalar ids) and volatile contexts; stop depending on the whole activeSession object so streaming flushes no longer re-render every visible message past React.memo - Stabilize Markdown renderer props (useCallback) so completed messages skip remark re-parsing during streaming - Nav divider drag writes --nav-width directly with rAF batching; state commits on mouseup; unmount cleanup for window listeners - Replace per-message window resize listeners with a shared ResizeObserver - Desktop pet: typewriter moves to rAF with ref targets (no interval churn), 120ms cursor polling pauses when hidden/idle and caches rects - Fine-grained zustand selectors for canvas store consumers and message edit state (editingTurnId gating) - rAF-throttle scroll handlers (file explorer, insights, explore groups) and Tooltip scroll tracking (passive, merged state) - EventBus history becomes a ring buffer; payload references only kept in dev - Fix listener leaks/races: GitStateManager dispose, PeerHostInvokeBridge and useWindowControls await-races, WorkspaceAPI abort listener, App.tsx disposed flags, tool-execution-service unlisten tracking - Cap second-tier session list expansion with incremental loading - Narrow transition properties in resizer/session styles (no layout-property hover animations) * docs(performance): add multi-dimension performance review reports Four review reports (compile/build, runtime, UI fluency, startup & bundle size) backing the optimizations in this branch, including the findings that were intentionally deferred as follow-ups. * fix(build): repair image 0.25 migration in test code, drop panic=abort The JpegEncoder in the computer_use integration test still used the image 0.24 by-value encode signature, breaking `cargo test -p bitfun-desktop` (CI Rust Build Check) even though `cargo check` on the lib target passed. Also revert panic = "abort" from the release profile: browser_get_url and the relay client's TLS setup deliberately use catch_unwind to contain known third-party panics, and aborting would convert those recoverable errors into process crashes. Thin LTO is kept. * fix(runtime): repair regressions found reviewing the optimization pass - file_watch: re-registering an existing path was a no-op after the switch to incremental watch registration. ensure_watch_roots marks a vanished root inactive without calling unwatch_path, so when the directory reappeared the watch was never restored and that root silently stopped reporting changes (the old code rebuilt the whole watcher every time, which hid this). Now re-registration always unwatches before watching again; covered by a new file_watch contract test. - transcript: buffering moved structured markers (command, cwd, exit code, resume/finish) off the immediate-write path, so a crash or force-quit could lose the anchors agents grep for. Markers now flush immediately (carrying any buffered output with them) while plain output keeps the buffered write. - grep: returning sink results as one entry per match changed head_limit and offset from physical lines to match blocks in multiline mode. Entries are split again only when they actually contain newlines. * fix(ui): repair regressions found reviewing the optimization pass - UserMessageItem: the shared ResizeObserver captured the content node at effect time, while the window listener it replaced re-read the ref on every event. contentRef is attached inside conditional branches, so leaving edit mode or a turn failing left the observer bound to a detached node — the overflow/expand affordance froze and the node leaked. Re-observe when those branches change. - Desktop pet: the new getBoundingClientRect cache was only invalidated on size/overlay/task changes, but typewriter output grows bubbles and shifts the ones below, so hover hit-testing used stale rects for a whole streaming task. The cache epoch now also advances on typed-output flush. - Nav drag: a drop landing between animation frames left the DOM at the last painted width because React only rewrites the CSS variable when the committed width actually changes. The final width is now applied synchronously on cleanup, while the transition is still suppressed. - SessionScene: narrowing the transition list dropped box-shadow, which those resizer handles animate on hover — restored. - sharedResizeObserver: one throwing subscriber aborted the entry batch and killed resize handling for every observed element (a failure mode introduced by sharing the observer). Each callback is now isolated. - EventBus: the dev gate used process.env.NODE_ENV, which never resolves in the Vite browser build, so payloads were dropped even in dev, contrary to the documented behavior. Use import.meta.env.DEV. - Add monacoRuntime tests asserting the lazy proxy fails diagnosably when the runtime has not been injected yet. * docs(build): document the new build escape hatches; keep piped output intact The optimization pass traded some flexibility for speed without saying so anywhere a contributor would look. Document CARGO_PROFILE_DEV_DEBUG=2 (the dev profile now ships line-tables-only), BITFUN_MOBILE_WEB_FORCE_BUILD=1 / --force (mobile-web builds are skipped when its dist is up to date) and VITE_USE_POLLING=1 (native watch events are the default again), plus a note that build:web now interleaves prefixed type-check and bundler output. The two parallel build wrappers also ended with process.exit(), which drops whatever is still queued on stdout when it is a pipe — as it is under CI. Set process.exitCode instead and let the process end on its own. * test(review): harden second-review findings - grep: add a multiline + head_limit test pinning pagination to physical lines (the property the earlier fix restored) - sharedResizeObserver: report subscriber callback errors instead of swallowing them silently - vite: when the project sits on a UNC share or WSL mount during dev, print a one-line hint about VITE_USE_POLLING=1 — users upgrading from the polling-based watcher would otherwise silently lose HMR there * chore(review): fix stale comments, drop dead API, gate new tests in CI Final read-through of the whole diff: - workspace_manager: the comment justified the dedicated cache handle by contention with "start_server's write lock", but nothing in the repo ever takes that lock for writing. Describe what the handle actually avoids. - markdown: front_matter_regex() was introduced with no callers (agent-runtime cannot depend on services-core and keeps its own static), so use the static directly and drop the exported wrapper. - dev.cjs / prune-monaco-nls.cjs: comments overstated what they do — the dev codegen-units override only matters with CARGO_INCREMENTAL=0, and the 1.4 MB figure covers the seven pruned NLS packs, not all nine. - fonts README still described the three deleted static weights as the directory's contents; rewrite it for the variable subset build. - CI: the file watch contract suite sits behind a non-default feature and neither it nor tool-runtime were in any cargo test step, so the regression guards added by this PR never ran. Add both to the OS matrix, matching the existing rationale for platform-sensitive contract suites. Also address the frontend read-through: move a render-phase ref write into the layout effect that already batches with it, use the repo's logger instead of a bare console.error, and assert repo-wide that monaco-editor is only ever imported as a type (the guard that keeps the 3.4 MB ESM copy out of the entry chunk). * ci: scope the new Rust test steps to green platform combinations Enabling these suites surfaced failures that predate this branch: - the file watch contracts fail on macOS (debounce and atomic-rename contracts time out under FSEvents coalescing) — tests this branch does not touch, on a suite that has never run in CI - tool-runtime's glob tests fail on Windows (walk-root derivation handles separators differently) — this branch does not touch glob at all Neither belongs to a performance change, so run what is green rather than either turning CI red or dropping the coverage: file watch on Linux and Windows (the platforms whose watch registration this branch alters), and the search module where the grep changes live. Both deserve their own fix. * ci: quote the search test filter so the workflow parses `search::` at the end of an unquoted YAML scalar reads as a nested mapping, which made the whole workflow file invalid — GitHub failed the run at startup with no jobs at all. --------- Co-authored-by: bowen628 <bowen628@noreply.gitcode.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
A multi-dimension performance review of the project (compile/build performance, runtime performance, UI fluency, startup & bundle size), followed by implementation of the low-to-medium risk findings. The full review reports (findings, evidence with file:line references, and the deferred items) are included under
docs/performance/. Four commits, grouped by dimension. No functional/semantic behavior changes intended.Changes by dimension
1. Compile/build performance (
perf(build))build:web; enable incremental tsc for web-ui/mobile-web; drop the duplicate type-check in desktop packaging CIdebug = "line-tables-only"; injectCARGO_PROFILE_DEV_CODEGEN_UNITS=256intotauri dev(matching the preview path; setCARGO_PROFILE_DEV_DEBUG=2when full debug info is needed)--forceescape hatch) — saves 20-60s per desktop dev cold startimagecrate versions (desktop pinned 0.24 → workspace 0.25), migrate computer_use call sites, and bridgescreenshots-crate buffers at the boundary with a zero-copy raw-bytes rebuildVITE_USE_POLLING=1opt-in)beforeBuildCommand(web + mobile-web) and the dev.cjs prepare steps2. Runtime performance (
perf(runtime))clone/to_value— zero-copy emission when Peer Mode is off (i.e. for almost all events)spawn_blocking(aligning with grep/glob tools), drop per-entry re-stat, preallocateArc, move the diagnostics cache to its own lock (no read-lock held across await)canonicalizeviaspawn_blocking, preallocationBufWriterand flush on timer/rotation/close instead of open+write+flush per chunkraw_inputviaArc<Value>, removing repeated deep copies of potentially hundreds-of-KB tool argumentsis_filestats, no split/join round-trips,mem::takeresult buffersblock_onwith a sync path-table snapshot; incremental watch registration instead of full watcher rebuildsArc<str>tap fan-out, byte ring buffer for head/tail captureWeak3. UI fluency (
perf(ui))activeSessionobject — streaming flushes no longer punch throughReact.memoand re-render every visible message. Combined with auseCallback-stabilized Markdown callback, completed messages skip remark re-parsing entirely during streaming--nav-widthdirectly with rAF batching; state commits on mouseup; unmount cleanup for window listenerseditingTurnIdgating)transition: alland remove layout-property hover animations in resizer/session styles4. Startup & bundle size (
perf(startup))tokio::join!after config init; bound the workspace bootstrap snapshotblock_onwith a 4s timeout that falls back to the existinginitialize_workspace_startup_statefrontend pathmonaco-editorvalue imports become type imports; runtime access goes through the AMD loader singleton (newmonacoRuntimeaccessor). This stops bundling a second full ESM copy of Monaco: entry chunk 5.44 MB → 2.14 MB (−61%)Shipped artifact (measured on a real build)
dist/totaldist/fontsdist/assetsdist/monaco-editorSelf-review pass
Because the optimizations were broad and initially only compile-verified, the
diff was re-reviewed adversarially for behavior regressions before opening this
PR. That pass found and fixed nine real defects (last four commits), the most
important being:
re-registering an already-tracked path became a no-op.
ensure_watch_rootsmarks a vanished root inactive without unwatching it, so when the directory
reappeared the watch was never restored and that root silently stopped
reporting changes — previously masked because the old code rebuilt the entire
watcher every time. Covered by a new contract test that fails when the fix is
reverted.
(command, cwd, exit code) off the immediate-write path, so a force-quit could
lose the anchors agents grep for. Markers now flush immediately; plain output
keeps the buffered write.
UserMessageItemafter leaving editmode or a failed turn, stale hover rects on the desktop pet during
streaming, a nav-drag width jump when the drop landed between frames, and
a shared-observer failure mode where one throwing subscriber killed resize
handling for every element.
Ruled out with evidence rather than assumption: the Monaco type-import
conversion (TS rejects value use of an
import type * asnamespace, and nomodule-level runtime access exists), the
FlowChatContextsplit, the Weak lockmap (no double-lock window),
HeadTailText(differential test against theoriginal char-based implementation, including multibyte input split across
chunk boundaries), and the symlink semantics of
entry.file_type()vspath.is_file().Verification
cargo check -p bitfun-desktop -p bitfun-clipasses (re-verified after rebasing onto latest main)pnpm run type-check:webandpnpm --dir src/web-ui buildpassRemoteConnectDialog.contract.test.ts, a pre-existing LF-assertion failure on Windows CRLF checkouts (file untouched by this PR)cargo test -p bitfun-core -p bitfun-desktop1458 ✓. terminal-core has 2 pre-existing Windows Ctrl-C timing failures (reproduced on unmodified code, in the interrupt lifecycle rather than any changed path; the three terminal tests CI runs all pass)Reviewer notes / risk areas
CARGO_PROFILE_DEV_DEBUG=2std::process::exitcan still drop the last ≤500 ms of pure output; wiringflush_allinto the shutdown hook would close that gap and is left as a follow-upReviewed but intentionally not included
Also evaluated and rejected:
panic = "abort"for the release profile (~5-10% smaller binary). Several production paths rely oncatch_unwindto contain third-party panics rather than take the whole app down —browser_get_urlguards a known wry bug where the WKWebView URL is nil, and the relay client guards TLS connector construction — so aborting would turn recoverable errors into process crashes.Two smaller items surfaced by the self-review are also deliberately left alone:
useCanvasStoresubscribes to all five scoped stores per call to keep hookorder stable across mode switches, so
EditorArea's 22 selector calls mean110 subscriptions instead of 5. It is still a net win (selector evaluations
replace unconditional re-renders of a large subtree), but the tidy fix —
one
useShallowcall — is unsafe here: a singleuseShallowinstance sharedacross five stores would thrash its memo ref between their snapshots.
quantization (~33 ms), roughly one character per second slower.
Higher-risk or product-decision items left for follow-ups: unifying reqwest to a single TLS stack, JSONL turn-snapshot migration, splitting the
bitfun-coremonolith crate, on-demand pet asset distribution (−14 MB), ChatInput refactor, committing Cargo.lock, and making the file tree respect .gitignore by default.