Fix line-by-line reveal off-by-one and stage display memory leaks#3145
Merged
vassbo merged 2 commits intoChurchApps:devfrom Apr 9, 2026
Merged
Conversation
Fix the "reveal line by line" feature where the last line was never revealed due to a mismatch between line counting (getItemWithMostLines) and the rendering filter (TextboxLines.svelte). Fix stage display output getting out of sync, lagging, and freezing after cycling through slides by addressing multiple compounding issues: leaked setInterval in Stagebox.svelte, socket listener accumulation in awaitRequest, unbounded caches, excessive JSON.stringify in reactive statements, insufficient debouncing, and uncancelable nested timeouts. Made-with: Cursor
Collaborator
|
Nice, thanks. Seems good to me. |
vassbo
added a commit
that referenced
this pull request
Apr 9, 2026
* Handle full-section Planning Center repeats (#3099) * Fixed some timeline keyframes not editable #3100 * Timer now flashes in StageShow #3101 * Fixed timer item creating a new timer * Fixed some text inputs not working #3103 * Trim metadata txt import #3050 * Disable full group in groups mode #3094 * Media item thumbnails in StageShow #3003 * Added slide keyframe color - Tweaks * Updated Italian language * Tweaks * Ignore planning center keywords (#3117) * Initial plan * fix: ignore planning center song keywords Agent-Logs-Url: https://github.com/otonielpv/FreeShow/sessions/ded90b8b-4d74-468d-bb88-5514bbbe4321 Co-authored-by: otonielpv <61138950+otonielpv@users.noreply.github.com> * test: polish planning center keyword coverage Agent-Logs-Url: https://github.com/otonielpv/FreeShow/sessions/ded90b8b-4d74-468d-bb88-5514bbbe4321 Co-authored-by: otonielpv <61138950+otonielpv@users.noreply.github.com> * Eliminar planningCenterSongKeywords.test.ts * fix: expand planning center keyword filtering Agent-Logs-Url: https://github.com/otonielpv/FreeShow/sessions/60851d75-a7c2-4dc5-866c-cbf925d3dc85 Co-authored-by: otonielpv <61138950+otonielpv@users.noreply.github.com> * Eliminar planningCenterSongKeywords.test.ts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> * Fixed shortcut uppercase not working #3125 * Updated languages * Fixing bonjour issue #1637 * Slide Out transition inverted to make sense * Tweaks * Slide timeline keyframe curve editor (#3143) * Easing curve editor * Working easing editor * Cleanup timeline easing * Fixes * Add Continuous Loop Scrolling Option for Text Items (#3144) * Commented around code area to work on * Pre merge commit * Completed functional continuous scrolling * Cleaned up slightly - Issues with jumping and whitespace * Fixed jumping problem * Finished UI integration for continuous scrolling effect * Cleaned up comments and changes * Fixed error with continuous looping true and scrolling type none * Cleaned up code * Removed continuous option and made all scrolling continuous * Updated Swedish language * Fixed opening slide timeline breaking some timeline playback #3121 * Tweaks * Fix line-by-line reveal off-by-one and stage display memory leaks (#3145) * Fix line-by-line reveal off-by-one and stage display memory leaks Fix the "reveal line by line" feature where the last line was never revealed due to a mismatch between line counting (getItemWithMostLines) and the rendering filter (TextboxLines.svelte). Fix stage display output getting out of sync, lagging, and freezing after cycling through slides by addressing multiple compounding issues: leaked setInterval in Stagebox.svelte, socket listener accumulation in awaitRequest, unbounded caches, excessive JSON.stringify in reactive statements, insufficient debouncing, and uncancelable nested timeouts. Made-with: Cursor * Cleanup --------- Co-authored-by: Victor <vholz@salesforce.com> Co-authored-by: Kristoffer <kristoffervassbo@gmail.com> * Fixed MIDI velocity issue #3132 * Fixes * Fixed potential memory leaks * Trying to fix startup issue * Fixed scripture style template override style * Style override font size should be relative to actual font size #3039 * Start project item by name action #3137 * Fixed local provider shows overwritten #3041 * Slide timeline fixes * Tweaks * Fixes * Version update - Package audit fix --------- Co-authored-by: Otoniel Pérez Velarde <61138950+otonielpv@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Joshua Sean Coulter <144730072+TheFlugeler@users.noreply.github.com> Co-authored-by: Victor <vicholz@gmail.com> Co-authored-by: Victor <vholz@salesforce.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.
Changes
This PR fixes two user-facing bugs in FreeShow:
Modules/Code Changed
src/frontend/components/helpers/showActions.ts- FixedgetItemWithMostLinesto usetext.value !== undefinedinstead oftext.value.length, aligning the line counting filter with the rendering filter inTextboxLines.svelte. Previously, lines with empty-string text values (e.g., blank lines between verses) were not counted, causingmaxRevealLinesto be smaller than the number of rendered lines, so the last line(s) could never be revealed.src/server/stage/components/Stagebox.svelte- AddedonDestroylifecycle cleanup for asetIntervalthat was leaking on every component teardown. SinceSlide.sveltewraps eachStageboxin{#key $stageLayout}(destroying/recreating on layout changes), each slide change leaked an interval. After cycling through many slides, hundreds of orphaned intervals would overwhelm the event loop.src/server/stage/util/socket.ts- Replaced the per-requestsocket.on("STAGE", receiver)pattern inawaitRequest()with a single persistent listener and apendingRequestsMap keyed bylistenerId. The old approach added a new listener for every dynamic value request; concurrent requests caused O(n) processing per incoming message.src/server/stage/helpers/show.ts- AddedpruneCache()to cap thecachedobject at 100 entries and removeisRequestedentries older than 60 seconds. Both data structures were module-level and grew without bound for the lifetime of the page.src/frontend/components/output/Output.svelte- Replaced 11+JSON.stringifydouble-serialization comparisons in reactive$:statements with cached-previous-value patterns. Each reactive statement was serializing both the old and new value on every store update; now only the new value is serialized and compared against the cached string.src/frontend/utils/sendData.ts- Added aMAX_SENT_CHANNELScap (50) to thesentdeduplication cache, pruning oldest entries when exceeded. The cache previously retainedJSON.stringify(msg.data)for every channel indefinitely.src/frontend/utils/listeners.ts- Increased theoutputsstore subscription debounce from 1ms to 15ms. The previous 1ms debounce was essentially no debounce, causing a flood of IPC messages to all output windows during rapid slide navigation.src/frontend/components/output/layers/SlideContent.svelte- Added a generation counter (updateGeneration) to the four-level nestedsetTimeoutchain inupdateItems(). Previously, only the outermost timeout was cancelable; rapid re-invocations could leave inner timeouts from stale calls executing against outdated state.New Logic
Generation counter for nested timeouts (
SlideContent.svelte): Each call toupdateItems()incrementsupdateGeneration. Every nestedsetTimeoutcallback checksif (gen !== updateGeneration) returnbefore executing, ensuring stale transition chains from prior invocations are discarded.Persistent socket listener with pending-request map (
socket.ts): A singlesocket.on("STAGE", ...)listener is registered once viainitRequestListener(). EachawaitRequest()call stores its{ resolve, timeout }in apendingRequestsMap keyed bylistenerId. When a response arrives, the listener looks up and resolves the matching pending request, avoiding repeated add/remove listener cycles.Cache pruning (
show.ts):pruneCache()is called on eachreplaceDynamicValues()invocation. It trimscachedto the most recent 100 entries and removesisRequestedtimestamps older than 60 seconds.Breaking Changes
None. All changes are internal bug fixes and performance improvements with no API or behavioral changes for end users.
Testing
Manual Testing Steps
Automated Tests
No automated tests were added - this project does not have an existing test suite for these components.
Notes
{#key $stageLayout}insrc/server/stage/components/Slide.sveltewas evaluated for removal but kept for correctness, sinceStagebox.sveltehas non-reactive initialization (let itemStyles = getStyles(...)) that requires full teardown/recreate. With the interval leak now fixed viaonDestroy, the{#key}no longer causes resource leaks.outputsdebounce was set to 15ms as a balance between responsiveness (smooth visual updates) and preventing message storms. This can be tuned if needed.