Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix: Idempotent start by plajdo · Pull Request #25 · GoodRequest/GoodReactor · GitHub
Skip to content

fix: Idempotent start - #25

Merged
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start
Jul 4, 2026
Merged

fix: Idempotent start#25
andrej-jasso merged 3 commits into
GoodRequest:mainfrom
plajdo:fix/idempotent-start

Conversation

@plajdo

Copy link
Copy Markdown
Contributor

Added check whether any subscriptions for current Reactor are active. This makes start function idempotent for callers, and effectively prevents creating duplicate subscriptions.

Condition: transform function must be free of side effects, as empty transform will be called multiple times, until first subscription is created for the Reactor.


Disabled auto-start of AnyReactor type-erased type.
Previously this auto-start behaviour caused incorrectly stored Combine/non-combine subscriptions to external events from viewModels. Subscriptions were being stored under the wrapper type, which caused confusion and non-clear event routing.

Added tests to verify.


Effects:

Call to viewModel.start() now has to be explicit and will always be forwarded to correct concrete Reactor type.

@plajdoplajdo mentioned this pull request Jul 1, 2026

@andrej-jassoandrej-jasso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally:

  • Full test suite passes (24 tests, 0 failures), including the new idempotency tests.
  • The start() guard is race-free on the main actor: makeCombineEventStream() itself stores a subscription synchronously, so repeated start() calls bail out even for reactors with an empty transform().
  • Note: removing auto-start from AnyReactor.init is a behavior change for existing users — documented in README, but worth calling out in the next release notes.

@andrej-jasso
andrej-jasso merged commit f9504c1 into GoodRequest:mainJul 4, 2026
@plajdo
plajdo deleted the fix/idempotent-start branch July 10, 2026 13:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@plajdo@andrej-jasso