fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

@pango07@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

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

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

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

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

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

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

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

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

@pango07@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

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

fix(overlay): seed display info from launch args instead of racing IPC - #7

Merged
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race
Aug 14, 2026
Merged

fix(overlay): seed display info from launch args instead of racing IPC#7
pango07 merged 2 commits into
masterfrom
fix/overlay-display-info-race

Conversation

@pango07

Copy link
Copy Markdown
Owner

Found while looking into #5.

the bug

createOverlayWindow pushed display-info on a once('did-finish-load') handler (windows.ts:96), but OverlayApp attaches its listener inside a useEffect (OverlayApp.tsx:197). Nothing orders those two. When the send lands first the message is dropped, and because it's once it never comes again, so displayRef.current stays null for the lifetime of the window.

Every cursor position then falls through to the else branch:

}else{setIsCursorOnThisDisplay(true);setCursorPos(pos);// global screen coords treated as display-local}

That happens to be correct on a single display at origin (0,0), which is why it hasn't bitten anyone yet. On a second monitor, or any display with a non-zero origin, the companion cursor renders at the wrong offset. onElementDetected has the same dependency at OverlayApp.tsx:230, so pointing targets land wrong too.

the fix

Pass the display through webPreferences.additionalArguments, so the preload parses it out of process.argv and the renderer has its coordinate space synchronously on mount. No ordering to get wrong.

The IPC push stays for reloads and is now on rather than once, since the argv value is only correct for the first load.

  • DisplayInfo and the arg prefix moved into shared/types.ts (the shape was inlined in three places)
  • new window.flicky.getDisplayInfo(), returns null in non-overlay windows
  • onDisplayInfo keeps working, now typed off the shared interface

testing

npm run typecheck passes. npm run lint fails on master too, unrelated: eslint 9 wants a flat eslint.config.js and the repo doesn't have one.

Not runtime-tested, I don't have a display here. Worth a quick check on a multi-monitor setup that the companion still tracks correctly on the non-primary display.

note

This is not the mic bug in #5, that's a separate chain I commented on in the issue. This is the second symptom the reporter mentioned (cursor not trailing), though I suspect their specific case is Wayland's getCursorScreenPoint() rather than this. Worth fixing either way.


Generated by Claude Code

createOverlayWindow pushed display-info on a once('did-finish-load')
handler, but OverlayApp attaches its listener inside a useEffect. When
the send landed first the message was dropped and displayRef stayed null
for the lifetime of the window, so cursor positions fell through to the
unmapped branch that treats global screen coordinates as display-local.
That happens to be correct on a single display at origin (0,0), which is
why it went unnoticed, and wrong everywhere else.
Pass the display through webPreferences.additionalArguments so the
preload can parse it synchronously and the renderer has its coordinate
space before the first cursor-position message arrives. The IPC push
stays for reloads, and is now `on` rather than `once` since the argv
value is only correct for the first load.
Refs #5
The comment claimed the argv value was only correct for the first load.
It isn't: a reload re-executes the preload in the same process with the
same argv, so the re-push sends an identical snapshot. It's redundancy,
not an update path, and bounds changes don't flow through it at all —
rebuildOverlays destroys and recreates the window instead. Say so.
Also warn instead of silently returning null when the launch argument
fails to parse, since that failure would otherwise reproduce the exact
symptom this change exists to fix, and note why plain JSON in argv is
safe here (all-numeric fields, so no spaces for Windows to split on).
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
flickyIgnoredIgnoredPreviewAug 10, 2026 7:28pm

Request Review

@pango07
pango07 merged commit 533432e into masterAug 14, 2026
6 checks passed
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

@pango07@claude