6.2 KiB
Dynamic Island Alignment Logic Analysis
Problem Statement
The Dynamic Island window sometimes gets misaligned (moves to the left) when switching between tabs, especially when going from stats tab (with 4+ graphs that make the window wider) back to other tabs.
Current Implementation Analysis
When Window is Created
createDynamicIslandWindow()creates window withopenNotchSize(fixed size)positionWindow()centers it using current window width- Window is perfectly centered initially
When User Switches to Stats Tab (4+ graphs)
coordinator.$currentViewtriggersupdateWindowSizeIfNeeded()calculateRequiredNotchSize()returns larger size (openNotchSize.width + extraWidth)updateWindowSizeIfNeeded()resizes window and re-centers using new width- Window should stay centered with new larger size
When User Switches Away from Stats Tab
coordinator.$currentViewtriggersupdateWindowSizeIfNeeded()againcalculateRequiredNotchSize()returns normalopenNotchSizeupdateWindowSizeIfNeeded()resizes window back and re-centers using normal width- THIS IS WHERE THE PROBLEM OCCURS
Root Cause Analysis
The Issue: Timing and State Mismatch
The problem is likely that coordinator.currentView and the actual UI state are not perfectly synchronized. Here's what probably happens:
- User clicks another tab
coordinator.$currentViewfires immediatelyupdateWindowSizeIfNeeded()calculates size as if we're NOT on stats tab- BUT the ContentView might still be showing stats content (transition not complete)
- Window gets resized to small size while content is still large
- This causes misalignment because content and window size don't match
Why It Sometimes Works
- When the UI transition is fast enough, everything stays in sync
- When there's any delay or animation, they get out of sync
Potential Solutions
Option 1: Delay the Window Resize
Add a small delay to ensure UI transition is complete before resizing window.
Option 2: Remove Window Resizing, Use Content Clipping
Let the window stay large and let the content handle its own sizing internally.
Option 3: Better State Synchronization
Make sure window resizing only happens when we're 100% sure the content has updated.
Option 4: Use ContentView's dynamicNotchSize Instead
The ContentView already has dynamicNotchSize logic. Maybe the window should use that instead of having its own calculation.
Recommended Approach
Option 2 seems safest: Remove dynamic window resizing entirely and let the content handle sizing.
The ContentView already has:
var dynamicNotchSize: CGSize {
// calculation logic
}
And uses it in:
.frame(maxWidth: dynamicNotchSize.width, maxHeight: dynamicNotchSize.height)
This means the content already knows how to size itself. The window can stay at the maximum possible size, and the content will center itself within that space.
Test Plan
- Remove all dynamic window resizing logic
- Set window to maximum possible size (openNotchSize.width + max extra width)
- Let ContentView handle all dynamic sizing internally
- Test alignment stays perfect
This eliminates the timing issue completely because the window never changes size after initial creation.
CRITICAL DISCOVERY - Clipboard is NOT a Tab View
Major Finding
The .clipboard case doesn't exist in NotchViews enum!
public enum NotchViews {
case home
case shelf
case timer
case stats
}
What This Means
- All my protective logic for
coordinator.currentView == .clipboardis ineffective - Clipboard functionality works differently - likely as a popover or overlay, not a tab
- The hover collapse issue is NOT about tab transitions but about popover interactions
Investigation Results
- ContentView switch statement only handles:
.home,.shelf,.timer,.stats - No clipboard tab content rendering in the main view switch
- Clipboard must be implemented as popover/overlay system
New Hypothesis
The clipboard "area" the user refers to is likely:
- ClipboardHistoryPopover - a popover that opens over the Dynamic Island
- ClipboardManager integration - clipboard content shown in existing tabs
- Overlay system - clipboard UI overlaid on top of main content
Critical Issue Identified
My previous fix added protections for .clipboard view that DOESN'T EXIST, meaning:
- Protections are not working
- Hover collapse still happening during clipboard interactions
- Need to find actual clipboard interaction boundaries
Next Steps
- Find real clipboard implementation - how is clipboard content actually shown?
- Identify clipboard hover boundaries - what UI element represents "clipboard area"?
- Fix actual interaction conflicts - coordinate between clipboard popover and main hover logic
When This Caused Problems:
- "Show on all displays" toggle: Creates new window with default
openNotchSize, losing dynamic sizing - Screen changes:
adjustWindowPosition()creates new windows with wrong size - App restarts/other events: Any time a new window is created, it ignores current dynamic sizing needs
The Wrong Code:
// OLD - ALWAYS used openNotchSize
let window = DynamicIslandWindow(
contentRect: NSRect(x: 0, y: 0, width: openNotchSize.width, height: openNotchSize.height),
...
The Fix:
// NEW - Uses current required size
let requiredSize = calculateRequiredNotchSize()
let window = DynamicIslandWindow(
contentRect: NSRect(x: 0, y: 0, width: requiredSize.width, height: requiredSize.height),
...
Why This Fixes Everything:
✅ "Show on all displays" toggle: New windows created with correct size
✅ Tab switching: No more size mismatch when repositioning
✅ Screen changes: New windows respect current graph settings
✅ Consistent behavior: All window creation uses same sizing logic
The Real Issue Was:
- Windows created with wrong size, then positioned correctly = misalignment
- Content expecting larger window, but window is small = positioning errors
- Dynamic sizing only worked if you never triggered window recreation
Result:
Perfect alignment in ALL scenarios - no more window size/content size mismatches!