Fix Intermittent Load Error#568
Merged
Merged
Conversation
Contributor
cubap
approved these changes
May 13, 2026
Member
cubap
left a comment
There was a problem hiding this comment.
I like this solution. I know it feels like a bit of a heavy touch, btu it is really just an extraction.
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.
Summary
Fixes #541. Project sub-pages intermittently failed to wire up footer buttons (
Identify Lines,Transcribe Text,Manage Collaborators, etc.) and missed permission-based DOM gating. The root cause is a race: inline<script type="module">blocks were registeringtpen-project-loadedlisteners afterProject.fetch()had already dispatched the event, so the listener never fired.This PR introduces a
whenProjectReadyutility that checksTPEN.activeProjectsynchronously first and falls back to a listener only when the project has not yet loaded. Every inline-script consumer that was racing the dispatch has been migrated to it.Root cause
Project.fetch()inapi/Project.jscallsObject.assign(this, data)then immediatelyeventDispatcher.dispatch("tpen-project-loaded", this). When a cached/fast response resolves before module scripts evaluate, the dispatch happens before any inlineeventDispatcher.on('tpen-project-loaded', …)runs, and the handler is registered against an event that already fired. EventTarget does not replay past events, so the inline script silently never executes — buttons stay unwired, permission attributes go unprocessed.The intermittency matches the bug report: less frequent after a warm load, rare on
jekyll s, common in production with fast responses.Changes
New:
utilities/projectReady.js—whenProjectReady(handler)Context-free variant of
onProjectReadyfor inline<script type="module">blocks that have no element to bind to.TPEN.activeProjectis already loaded, the handler is invoked synchronously with a synthetic{ detail: TPEN.activeProject }event so existingev.detail.*handler bodies keep working.tpen-project-loadeddispatch.onProjectReadywas updated to follow the same exactly-once contract and to log handler exceptions viaconsole.errorinstead of swallowing them silently.Migrated consumers
Every inline
<script type="module">and module that was registering a rawtpen-project-loadedlistener:components/check-permissions/checkPermissions.jscomponents/check-permissions/permission-match.js— wrapped inqueueMicrotask(...)so sibling module scripts that addtpen-view/tpen-editmarkup during their own synchronous evaluation are in the DOM before the scancomponents/update-metadata/index.htmlinterfaces/manage-layers/index.htmlinterfaces/manage-project/collaborators.htmlinterfaces/manage-project/index.htmlinterfaces/manage-project/index.jsinterfaces/project/index.htmlinterfaces/project/metadata.htmlinterfaces/project/options.htmlinterfaces/quicktype/index.htmlTests
utilities/__tests__/projectReady.test.jscovers both functions: listener subscription, sync invocation when project already loaded, no-op on missing handler, unsubscribe, the exactly-once contract, and sync-error logging. 11/11 pass vianode --test utilities/__tests__/projectReady.test.js.Follow-ups
_createdAtloaded-sentinel withProject#isLoadedgetter #569 — replace the magic_createdAt"loaded" sentinel with a properProject#isLoadedgetter. Out of scope here to keep this PR focused on the race fix.