nook-browser/Nook/main 477k tokens More Tools
```
├── .github/
   ├── workflows/
      ├── enforce-pr-base.yml (100 tokens)
      ├── macos-notarize.yml (900 tokens)
├── .gitignore (500 tokens)
├── CODE_OF_CONDUCT.md (400 tokens)
├── CONTRIBUTING.md (800 tokens)
├── LICENSE (omitted)
├── Nook.xcodeproj/
   ├── project.pbxproj (3.3k tokens)
   ├── project.xcworkspace/
      ├── contents.xcworkspacedata
      ├── xcshareddata/
         ├── WorkspaceSettings.xcsettings
         ├── swiftpm/
            ├── Package.resolved (200 tokens)
   ├── xcshareddata/
      ├── xcschemes/
         ├── Nook.xcscheme (700 tokens)
├── Nook/
   ├── Adapters/
      ├── TabListAdapter.swift (2.9k tokens)
   ├── Assets.xcassets/
      ├── AccentColor.colorset/
         ├── Contents.json
      ├── Contents.json
      ├── noise_texture.imageset/
         ├── Contents.json (100 tokens)
         ├── noise_texture.png
         ├── noise_texture@2x.png
         ├── noise_texture@3x.png
   ├── Components/
      ├── Browser/
         ├── Window/
            ├── SpaceGradientBackgroundView.swift (500 tokens)
            ├── SplitDropCaptureView.swift (700 tokens)
            ├── TabCompositorView.swift (1200 tokens)
            ├── WindowBackgroundView.swift (400 tokens)
            ├── WindowView.swift (3k tokens)
      ├── ColorPicker/
         ├── AngleDial.swift (400 tokens)
         ├── ColorPickerView.swift (800 tokens)
         ├── ColorSwatchRowView.swift (700 tokens)
         ├── GradientCanvasEditor.swift (5.3k tokens)
         ├── GradientEditorView.swift (700 tokens)
         ├── GradientNodePicker.swift (1200 tokens)
         ├── GradientPreview.swift (300 tokens)
         ├── GrainDial.swift (400 tokens)
         ├── GrainSlider.swift (600 tokens)
         ├── TransparencySlider.swift (800 tokens)
      ├── CommandPalette/
         ├── CommandPaletteSuggestionView.swift (900 tokens)
         ├── CommandPaletteView.swift (3.7k tokens)
         ├── GenericSuggestionItem.swift (200 tokens)
         ├── HistorySuggestionItem.swift (800 tokens)
         ├── MiniCommandPaletteView.swift (2.2k tokens)
         ├── TabSuggestionItem.swift (500 tokens)
      ├── Dialog/
         ├── DialogView.swift (300 tokens)
      ├── DragDrop/
         ├── DragDropPreview.swift (5k tokens)
         ├── DragEnabledSidebarView.swift (200 tokens)
         ├── DraggableTabView.swift (1000 tokens)
         ├── InsertionLineView.swift (500 tokens)
         ├── SidebarDropSupport.swift (3.9k tokens)
         ├── SimpleDnD.swift (2000 tokens)
         ├── SimpleDragPreview.swift (7.5k tokens)
         ├── TabDragContainerView.swift (1100 tokens)
      ├── EmojiPicker/
         ├── EmojiPicker.swift (1700 tokens)
      ├── Extensions/
         ├── ExtensionActionView.swift (600 tokens)
         ├── ExtensionPermissionView.swift (2000 tokens)
         ├── PersistentPopover.swift (600 tokens)
         ├── PopupConsoleWindow.swift (1000 tokens)
      ├── FindBar/
         ├── FindBarView.swift (800 tokens)
      ├── MiniWindow/
         ├── MiniWindowButtonStyle.swift (900 tokens)
         ├── MiniWindowToolbar.swift (2.4k tokens)
         ├── MiniWindowWebView.swift (3.5k tokens)
      ├── Navigation/
         ├── HoldGestureButton.swift (400 tokens)
         ├── NavigationHistoryContextMenu.swift (1100 tokens)
         ├── NavigationHistoryMenu.swift (1300 tokens)
         ├── NavigationHistoryOverlay.swift (500 tokens)
      ├── Peek/
         ├── PeekOverlayView.swift (2.6k tokens)
      ├── PulseTextField/
         ├── PulseTextField.swift (600 tokens)
      ├── Settings/
         ├── CacheDetailsView.swift (1800 tokens)
         ├── CacheManagementView.swift (2.9k tokens)
         ├── CookieDetailsView.swift (1400 tokens)
         ├── CookieManagementView.swift (2.6k tokens)
         ├── PrivacySettingsView.swift (3k tokens)
         ├── ProfilePickerView.swift (900 tokens)
         ├── ProfileRowView.swift (800 tokens)
         ├── SettingsTabBar.swift (100 tokens)
         ├── SettingsUtils.swift (200 tokens)
         ├── SettingsView.swift (12.3k tokens)
         ├── ShortcutRecorderView.swift (1100 tokens)
      ├── Sidebar/
         ├── AIChat/
            ├── AISidebarResizeView.swift (800 tokens)
            ├── SidebarAIChat.swift (14.4k tokens)
         ├── FallbackDropBelowEssentialsModifier.swift (300 tokens)
         ├── MediaControls/
            ├── MediaControlsView.swift (2000 tokens)
         ├── Menu/
            ├── DownloadIndicator.swift (400 tokens)
            ├── SidebarMenu.swift (700 tokens)
            ├── SidebarMenuDownloadsHover.swift (1800 tokens)
            ├── SidebarMenuDownloadsTab.swift (1800 tokens)
            ├── SidebarMenuHistoryTab.swift (4.9k tokens)
            ├── SidebarMenuTab.swift (300 tokens)
         ├── NavButtonsView.swift (1700 tokens)
         ├── NewTabButton.swift (400 tokens)
         ├── PinnedButtons/
            ├── EssentialTabsScrollView.swift (600 tokens)
            ├── EssentialsGridLayout.swift (100 tokens)
            ├── PinnedGrid.swift (1600 tokens)
            ├── PinnedTabView.swift (900 tokens)
         ├── SidebarHoverOverlayView.swift (600 tokens)
         ├── SidebarResizeView.swift (800 tokens)
         ├── SidebarView.swift (4.3k tokens)
         ├── SpaceSection/
            ├── SpaceProfileBadge.swift (500 tokens)
            ├── SpaceProfileDropdown.swift (400 tokens)
            ├── SpaceSeparator.swift (200 tokens)
            ├── SpaceTab.swift (1900 tokens)
            ├── SpaceTitle.swift (2.3k tokens)
            ├── SpaceView.swift (6.6k tokens)
            ├── SpacesList/
               ├── SpacesList.swift (600 tokens)
               ├── SpacesListItem.swift (1100 tokens)
            ├── SplitTabRow.swift (1100 tokens)
            ├── TabFolderView.swift (3.3k tokens)
         ├── TabClosureToast/
            ├── TabClosureToast.swift (500 tokens)
         ├── TopBar/
            ├── TopBarCommandPalette.swift (1800 tokens)
            ├── TopBarView.swift (2.2k tokens)
         ├── URLBarFramePreferenceKey.swift
         ├── URLBarView.swift (900 tokens)
         ├── UpdateNotification/
            ├── SidebarUpdateNotification.swift (1000 tokens)
            ├── SidebarUpdateNotificationPreview.swift (600 tokens)
      ├── WebsitePopup/
         ├── OAuthAssistBanner.swift (400 tokens)
         ├── WebsitePopup.swift (200 tokens)
      ├── WebsiteView/
         ├── EmptyWebsiteView.swift (300 tokens)
         ├── WebView.swift (2000 tokens)
         ├── WebsiteLoadingIndicator.swift (300 tokens)
         ├── WebsiteView.swift (4.7k tokens)
      ├── Window/
         ├── DoubleClickView.swift (300 tokens)
         ├── MacButtons.swift (1100 tokens)
      ├── ZoomControls/
         ├── ZoomPopupView.swift (900 tokens)
   ├── ContentView.swift (600 tokens)
   ├── Info.plist (200 tokens)
   ├── Info.plist.bak
   ├── Managers/
      ├── AuthenticationManager/
         ├── AuthenticationManager.swift (1700 tokens)
         ├── BasicAuthCredentialStore.swift (800 tokens)
      ├── BrowserManager/
         ├── BrowserManager.swift (23k tokens)
      ├── CacheManager/
         ├── CacheManager.swift (2.9k tokens)
      ├── CookieManager/
         ├── CookieManager.swift (2.1k tokens)
      ├── DialogManager/
         ├── DialogManager.swift (2.4k tokens)
         ├── Dialogs/
            ├── BasicAuthDialog.swift (700 tokens)
            ├── BrowserImportDialog.swift (300 tokens)
            ├── ProfileCreationDialog.swift (1100 tokens)
            ├── ProfileDeleteConfirmationDialog.swift (500 tokens)
            ├── ProfileRenameDialog.swift (700 tokens)
            ├── SettingsDialog.swift (600 tokens)
            ├── SpaceCreationDialog.swift (800 tokens)
            ├── SpaceEditDialog.swift (1200 tokens)
      ├── DownloadManager/
         ├── DownloadManager.swift (4.3k tokens)
      ├── DragManager/
         ├── DragLockManager.swift (500 tokens)
         ├── TabDragManager.swift (1900 tokens)
      ├── ExtensionManager/
         ├── ExtensionBridge.swift (1400 tokens)
         ├── ExtensionManager.swift (19.8k tokens)
         ├── README-URLSchemeHandler.md
      ├── ExternalMiniWindowManager/
         ├── ExternalMiniWindowManager.swift (1500 tokens)
         ├── MiniBrowserWindowView.swift (600 tokens)
      ├── FindManager/
         ├── FindManager.swift (800 tokens)
      ├── GradientColorManager/
         ├── GradientColorManager.swift (500 tokens)
      ├── HistoryManager/
         ├── HistoryManager.swift (2.3k tokens)
      ├── HoverSidebarManager/
         ├── HoverSidebarManager.swift (1000 tokens)
      ├── ImportManager/
         ├── Arc.swift (2.8k tokens)
         ├── ImportManager.swift (200 tokens)
      ├── KeyboardShortcutManager/
         ├── KeyboardShortcutManager.swift (2.1k tokens)
      ├── MediaControlsManager/
         ├── MediaControlsManager.swift (1900 tokens)
      ├── PeekManager/
         ├── PeekManager.swift (800 tokens)
         ├── PeekSession.swift (300 tokens)
         ├── PeekWebView.swift (1700 tokens)
      ├── PiPManager.swift (1000 tokens)
      ├── PrivacyManager/
         ├── TrackingProtectionManager.swift (2.2k tokens)
      ├── ProfileManager/
         ├── ProfileManager.swift (800 tokens)
      ├── SearchManager/
         ├── SearchManager.swift (2.3k tokens)
         ├── Utils.swift (700 tokens)
      ├── SettingsManager/
         ├── SettingsManager.swift (2.4k tokens)
         ├── SettingsManagerUtils.swift (200 tokens)
      ├── SplitViewManager/
         ├── SplitViewManager.swift (2.6k tokens)
      ├── TabManager/
         ├── TabManager.swift (20.3k tokens)
      ├── ZoomManager/
         ├── ZoomManager.swift (1200 tokens)
   ├── Models/
      ├── BrowserConfig/
         ├── BrowserConfig.swift (1500 tokens)
      ├── BrowserWindowState.swift (800 tokens)
      ├── Cache/
         ├── CacheModels.swift (2.4k tokens)
      ├── Cookie/
         ├── CookieModels.swift (1300 tokens)
      ├── Extension/
         ├── ExtensionModels.swift (500 tokens)
      ├── History/
         ├── HistoryEntity.swift (200 tokens)
      ├── KeyboardShortcut/
         ├── KeyboardShortcut.swift (2.2k tokens)
      ├── Profile/
         ├── Profile.swift (900 tokens)
         ├── ProfileEntity.swift (100 tokens)
      ├── Settings/
         ├── NewDocumentTarget.swift (100 tokens)
      ├── Space/
         ├── GradientNode.swift (200 tokens)
         ├── Space.swift (200 tokens)
         ├── SpaceGradient.swift (2000 tokens)
         ├── SpaceModels.swift (200 tokens)
      ├── Tab/
         ├── Tab.swift (24.9k tokens)
         ├── TabFolder.swift (200 tokens)
         ├── TabsModel.swift (500 tokens)
   ├── Nook.entitlements (100 tokens)
   ├── NookApp.swift (4.4k tokens)
   ├── Protocols/
      ├── TabListDataSource.swift (200 tokens)
   ├── Supporting Files/
      ├── Nook-Bridging-Header.h
   ├── ThirdParty/
      ├── BigUIPaging/
         ├── Examples/
            ├── CardDeckExample.swift (300 tokens)
            ├── CustomPageViewExample.swift (300 tokens)
            ├── ExampleGridToPageView.swift (1200 tokens)
            ├── PageIndicatorExample.swift (800 tokens)
            ├── PageViewBasicExample.swift (400 tokens)
            ├── PageViewForEachExample.swift (100 tokens)
            ├── PageViewNavigationStackExample.swift (900 tokens)
         ├── Implementations/
            ├── PageIndicator/
               ├── PageIndicator+Environment.swift (600 tokens)
               ├── PageIndicator+iOS.swift (800 tokens)
               ├── PageIndicator.swift (1100 tokens)
            ├── PageView/
               ├── Platform/
                  ├── PlatformPageView+iOS.swift (1900 tokens)
                  ├── PlatformPageView+macOS.swift (1900 tokens)
                  ├── PlatformPageView.swift (100 tokens)
               ├── Styles/
                  ├── BookPageViewStyle.swift (300 tokens)
                  ├── BookStackPageViewStyle.swift (200 tokens)
                  ├── CardDeckPageViewStyle.swift (1400 tokens)
                  ├── HistoryStackPageViewStyle.swift (200 tokens)
                  ├── PlainPageViewStyle.swift (200 tokens)
                  ├── PlatformPageViewStyle.swift (100 tokens)
                  ├── ScrollPageViewStyle.swift (200 tokens)
               ├── Types/
                  ├── PageViewDirection.swift (100 tokens)
                  ├── PageViewNavigateAction.swift (200 tokens)
                  ├── PageViewStyle.swift (600 tokens)
                  ├── PageViewStyleConfiguration.swift (500 tokens)
               ├── View/
                  ├── PageView+Environment.swift (600 tokens)
                  ├── PageView+Preferences.swift (100 tokens)
                  ├── PageView.swift (2.3k tokens)
                  ├── PageViewEnvironmentModifier.swift (200 tokens)
                  ├── PageViewNavigationButton.swift (500 tokens)
            ├── Utils/
               ├── View+Inspect.swift (800 tokens)
               ├── View+Measure.swift (100 tokens)
         ├── README.md (2000 tokens)
      ├── HTSymbolHook/
         ├── HTSymbolHook.h (200 tokens)
         ├── HTSymbolHook.m (1200 tokens)
         ├── README.md (200 tokens)
         ├── mach_override/
            ├── .gitignore
            ├── README.markdown (800 tokens)
            ├── Rakefile (100 tokens)
            ├── libudis86/
               ├── decode.c (5.4k tokens)
               ├── decode.h (1100 tokens)
               ├── extern.h (700 tokens)
               ├── input.c (1100 tokens)
               ├── input.h (600 tokens)
               ├── itab.c (63.4k tokens)
               ├── itab.h (2.1k tokens)
               ├── syn-att.c (1200 tokens)
               ├── syn-intel.c (1300 tokens)
               ├── syn.c (1200 tokens)
               ├── syn.h (400 tokens)
               ├── types.h (1500 tokens)
               ├── udint.h (500 tokens)
               ├── udis86.c (1900 tokens)
            ├── mach_override.c (5k tokens)
            ├── mach_override.h (600 tokens)
            ├── udis86.h (300 tokens)
      ├── MuteableWKWebView/
         ├── Info.plist (200 tokens)
         ├── MethodSwizzler.h
         ├── MethodSwizzler.m (200 tokens)
         ├── MuteableWKWebView.h (100 tokens)
         ├── MuteableWKWebView.m (500 tokens)
         ├── MuteableWKWebViewPrivate.h (100 tokens)
         ├── README.md (200 tokens)
         ├── SearchSymbol.h (100 tokens)
         ├── SearchSymbol.m (300 tokens)
         ├── getPage.c (300 tokens)
         ├── getPage.h (100 tokens)
   ├── Utils/
      ├── BarycentricGradientView.swift (1400 tokens)
      ├── BlurEffectView.swift (100 tokens)
      ├── BlurModifier.swift (200 tokens)
      ├── Colors.swift (900 tokens)
      ├── Debug/
         ├── NavigationRootCauseAnalysis.md (1900 tokens)
      ├── DitheringUtils.swift (4.2k tokens)
      ├── DragWindowView.swift (100 tokens)
      ├── ExtensionUtils.swift (500 tokens)
      ├── ForceArrowCursorView.swift (400 tokens)
      ├── GradientInterpolation.swift (500 tokens)
      ├── PasteboardTypes.swift
      ├── Shaders/
         ├── BarycentricShaders.metal (800 tokens)
      ├── WebKit/
         ├── FocusableWKWebView.swift (1700 tokens)
      ├── WebStoreDownloader.swift (1300 tokens)
      ├── WebStoreInjector.js (1300 tokens)
      ├── WebStoreScriptHandler.swift (700 tokens)
      ├── WebViewThemeColorExtension.swift (800 tokens)
      ├── WindowUtils.swift (700 tokens)
   ├── logo-dev.icon/
      ├── Assets/
         ├── Grid.png
         ├── Light Mode.svg (1100 tokens)
      ├── icon.json (400 tokens)
   ├── logo.icon/
      ├── Assets/
         ├── Light Mode.svg (1100 tokens)
      ├── icon.json (300 tokens)
├── README.md (1200 tokens)
├── UI/
   ├── Buttons/
      ├── NavButtons/
         ├── NavButton.swift (700 tokens)
         ├── NavMenuStyle.swift (800 tokens)
      ├── NookButton/
         ├── NookButtonStyle.swift (1900 tokens)
├── assets/
   ├── icon.png
```


## /.github/workflows/enforce-pr-base.yml

```yml path="/.github/workflows/enforce-pr-base.yml" 
name: Enforce PR base branch
on:
  pull_request:
    types: [opened, edited, synchronize]

jobs:
  check-branch:
    runs-on: ubuntu-latest
    steps:
      - name: Fail if PR targets main
        if: github.event.pull_request.base.ref == 'main'
        run: |
          echo "PRs must target dev, not main."
          exit 1

```

## /.github/workflows/macos-notarize.yml

```yml path="/.github/workflows/macos-notarize.yml" 
name: macOS Build, Sign, & Notarize

on:
  push:
    tags:
      - 'v*'
  release:
    types: [published]

jobs:
  build-sign-notarize:
    runs-on: macos-latest
    permissions:
      contents: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Import Developer ID certificate
        id: import-cert
        run: |
          printf "%s" "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > signing_certificate.p12

          security create-keychain -p "" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "" build.keychain

          security import signing_certificate.p12 \
            -k build.keychain \
            -P "$APPLE_CERTIFICATE_PASSWORD" \
            -T /usr/bin/codesign

          security list-keychains -d user -s build.keychain login.keychain
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain

          IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -n1 | awk '{print $2}')
          echo "SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV

          echo "Imported certificate and set up keychain successfully."

      - name: Build app
        run: |
          set -e
          mkdir -p build
          echo "Attempting universal build (arm64 + x86_64)..."
          if ! xcodebuild -scheme Nook -configuration Release -arch arm64 -arch x86_64 -derivedDataPath build; then
            echo "Universal build failed, retrying Apple Silicon only..."
            xcodebuild -scheme Nook -configuration Release -arch arm64 -derivedDataPath build
          fi
          cp -R "build/Build/Products/Release/Nook.app" ./Nook.app

      - name: Codesign app
        run: |
          codesign --deep --force --verify --verbose \
            --options runtime \
            --entitlements Nook/entitlements.plist \
            --sign "$SIGNING_IDENTITY" \
            "Nook.app"

      - name: Verify code signature
        run: |
          codesign --verify --deep --strict --verbose=2 "Nook.app"
          spctl --assess --type execute --verbose "Nook.app"

      - name: Notarize app
        env:
          NOTARY_PROFILE: "nook-notary"
        run: |
          xcrun notarytool store-credentials "$NOTARY_PROFILE" \
            --apple-id "$APPLE_ID" \
            --team-id "$APPLE_TEAM_ID" \
            --password "$APPLE_APP_SPECIFIC_PASSWORD"

          zip -r Nook.zip "Nook.app"
          xcrun notarytool submit "Nook.zip" \
            --keychain-profile "$NOTARY_PROFILE" \
            --wait

      - name: Staple notarization ticket
        run: xcrun stapler staple "Nook.app"

      - name: Create DMG
        run: |
          VERSION=${GITHUB_REF#refs/tags/}
          hdiutil create -volname "Nook ${VERSION}" \
            -srcfolder "Nook.app" \
            -ov -format UDZO "Nook-${VERSION}.dmg"

      - name: Upload DMG to release assets
        uses: softprops/action-gh-release@v2
        with:
          files: Nook-*.dmg
          fail_on_unmatched_files: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Update Sparkle appcast
        if: github.event_name == 'release'
        run: |
          if [ ! -f appcast.xml ]; then
            echo "appcast.xml not found, skipping Sparkle update"
            exit 0
          fi
          VERSION=${GITHUB_REF#refs/tags/}
          DMG_URL="https://github.com/${{ github.repository }}/releases/download/${VERSION}/Nook-${VERSION}.dmg"
          DATE=$(date -R)
          SHORT_VERSION=${VERSION#v}

          ENTRY=$(cat <<EOF
          <item>
            <title>Version ${SHORT_VERSION}</title>
            <sparkle:releaseNotesLink>https://github.com/${{ github.repository }}/releases/tag/${VERSION}</sparkle:releaseNotesLink>
            <pubDate>${DATE}</pubDate>
            <enclosure url="${DMG_URL}" sparkle:version="${SHORT_VERSION}" length="$(stat -f%z Nook-${VERSION}.dmg)" type="application/octet-stream"/>
          </item>
          EOF
          )

          git fetch origin gh-pages
          git checkout gh-pages
          sed -i '' "s|</channel>|${ENTRY}\n</channel>|" appcast.xml
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add appcast.xml
          git commit -m "Add release ${VERSION} to appcast"
          git push origin gh-pages

```

## /.gitignore

```gitignore path="/.gitignore" 
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
sessions-config.json
.claude
Nook/.claude
.crush
/sessions

## User settings
xcuserdata/

# Xcode nonsense
# *.pbxproj
# macOS garbage
.DS_Store

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
'timeline.xctimeline'
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
# IDE
.vscode/

# Xcode build junk
DerivedData/
*.xcresult
*.log
*.profraw

```

## /CODE_OF_CONDUCT.md

# Code of Conduct

## Our Commitment

We are committed to providing a welcoming and inclusive environment for all contributors to Nook, regardless of experience level, background, or identity.

Nook is built by volunteers who contribute their time and expertise out of passion for creating great software. We appreciate everyone who takes time to contribute to making Nook better.

## Expected Behavior

- Be respectful, welcoming, and considerate in all interactions
- Discuss ideas, give/receive constructive criticism gracefully
- Focus on what's best for the community and project
- Show empathy toward other community members
- Help create a positive environment for learning and collaboration

## Unacceptable Behavior

- Harassment, discrimination, or intimidation of any kind
- Offensive, derogatory, or discriminatory comments
- Personal attacks or trolling
- Publishing others' private information without permission
- Spam or off-topic discussions
- Any conduct that would be inappropriate in a professional setting

## Scope

This Code of Conduct applies to all project spaces, including:
- GitHub repository (issues, PRs, discussions)
- Discord community
- Any other official Nook communication channels

## Enforcement

Project maintainers are responsible for clarifying standards and will take appropriate action in response to violations. This may include:
- Warning the individual
- Temporary or permanent ban from project spaces (GitHub, Discord, etc.)
- Reporting to appropriate platforms (GitHub, Discord, etc.)

## Reporting

If you experience or witness unacceptable behavior, please report it by:
- Opening a GitHub issue (for public matters)
- Contacting maintainers directly on Discord

All reports will be handled confidentially and reviewed promptly.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.

---

By participating in the Nook community, you agree to abide by this Code of Conduct.


## /CONTRIBUTING.md

# Contributing to Nook

Thank you for your interest in contributing to Nook! This document provides guidelines and instructions for contributing to the project.

## AI Assistance Notice

If you are using any kind of AI assistance to contribute, this must be disclosed in your pull request, commit, or issue, including the extent to which AI assistance was used (e.g. docs only vs. code generation). If PR responses, commit messages, or comments are being generated by an AI, please disclose that as well.

As a small exception, trivial tab-completion doesn't need to be disclosed, so long as it is limited to single keywords or short phrases.

When using AI assistance, we expect contributors to understand the code that is produced and be able to answer critical questions about it. It isn't a maintainer's job to review a PR so broken that it requires significant rework to be acceptable.

Please be respectful to maintainers and disclose AI assistance.

## 🚀 Development Setup

1. **Clone the repository**
   ```bash
   git clone https://github.com/yourusername/Nook.git
   cd Nook
   ```

2. **Switch to the dev branch**
   ```bash
   git checkout dev
   ```

3. **Open in Xcode**
   ```bash
   open Nook.xcodeproj
   ```

4. **Build and run**
   - Select your target device/simulator
   - Press `Cmd + R` to build and run

## 📝 Code Style & Standards

### Conventions

- Follow existing SwiftUI/AppKit patterns in the codebase
- Use the established file organization structure
- Prefer modern Swift/SwiftUI APIs when possible

### Compatibility & OS Versions

- **Maintain backward compatibility**: The current minimum deployment target is macOS 15.5
- **New features requiring newer OS versions** should use `@available` or `#if available` checks to preserve compatibility
- **Discuss before raising minimum version**: If a feature would significantly benefit from raising the minimum OS version, open an issue to discuss the trade-offs before implementation

## 📋 Pull Request Process

### Before You Start

- **Check for duplicates**: Search existing issues and PRs to ensure no one is already working on the same feature or bug
- **Avoid duplicate work**: Comment on relevant issues to indicate you're working on them
- **For major changes**, consider opening an issue first to discuss the approach

### Creating Pull Requests

**IMPORTANT**: All development work must be done on the `dev` branch:

1. **Create your branch from `dev`**, not `main`:
   ```bash
   git checkout dev
   git pull origin dev
   git checkout -b feature/your-feature-name
   ```

2. **Make your changes** and commit them with clear, descriptive messages

3. **Push to your fork**:
   ```bash
   git push origin feature/your-feature-name
   ```

4. **Open a Pull Request to the `dev` branch**, not `main`
   - Provide a clear description of the changes
   - Reference any related issues
   - Include screenshots or videos for UI changes
   - Disclose any AI assistance used (see AI Assistance Notice above)

5. **Respond to review feedback** promptly and constructively

## 🐛 Issue Reporting

### Bug Reports

Be descriptive and thorough. Include:

- **macOS version**
- **Nook version**
- **Clear steps to reproduce the issue**
- **Expected vs actual behavior**
- **Screenshots if applicable**
- **Relevant console/error messages**

### Feature Requests

Provide detailed context. Include:

- **Clear description of the feature and its use case**
- **Explain how it fits with Nook's goals** (fast, secure, beautiful)
- **Consider implementation complexity and user impact**
- **Check that similar functionality doesn't already exist**

## 💬 Community Guidelines

- Be respectful and constructive
- Help maintain a welcoming environment for all contributors
- Focus on the code and ideas, not individuals

## License

By contributing to Nook, you agree that your contributions will be licensed under the GPL-3.0 License.


## /Nook.xcodeproj/project.pbxproj

```pbxproj path="/Nook.xcodeproj/project.pbxproj" 
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 77;
	objects = {

/* Begin PBXBuildFile section */
		2C16A0262E87430B0070894B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 2C16A0252E87430B0070894B /* Sparkle */; };
		564997D82E9B24CC00D89F78 /* Garnish in Frameworks */ = {isa = PBXBuildFile; productRef = 564997D72E9B24CC00D89F78 /* Garnish */; };
		7FAFC5DA2E3ADDCD009D7DC4 /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = 7FAFC5D92E3ADDCD009D7DC4 /* FaviconFinder */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		7F8340FC2E37F39400674A5D /* Nook.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nook.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
		2CAC4DD12E82457B00870189 /* Exceptions for "Nook" folder in "Nook" target */ = {
			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
			membershipExceptions = (
				Info.plist,
				ThirdParty/BigUIPaging/README.md,
				ThirdParty/HTSymbolHook/README.md,
				ThirdParty/MuteableWKWebView/README.md,
			);
			target = 7F8340FB2E37F39400674A5D /* Nook */;
		};
		564997D22E9B125200D89F78 /* Exceptions for "UI" folder in "Nook" target */ = {
			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
			membershipExceptions = (
				Buttons/NavButtons/NavMenuStyle.swift,
			);
			target = 7F8340FB2E37F39400674A5D /* Nook */;
		};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
		2CAC4D282E82457B00870189 /* Nook */ = {
			isa = PBXFileSystemSynchronizedRootGroup;
			exceptions = (
				2CAC4DD12E82457B00870189 /* Exceptions for "Nook" folder in "Nook" target */,
			);
			path = Nook;
			sourceTree = "<group>";
		};
		564997D02E9B125200D89F78 /* UI */ = {
			isa = PBXFileSystemSynchronizedRootGroup;
			exceptions = (
				564997D22E9B125200D89F78 /* Exceptions for "UI" folder in "Nook" target */,
			);
			path = UI;
			sourceTree = "<group>";
		};
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
		7F8340F92E37F39400674A5D /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				564997D82E9B24CC00D89F78 /* Garnish in Frameworks */,
				7FAFC5DA2E3ADDCD009D7DC4 /* FaviconFinder in Frameworks */,
				2C16A0262E87430B0070894B /* Sparkle in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		7F8340F32E37F39400674A5D = {
			isa = PBXGroup;
			children = (
				7F8340FD2E37F39400674A5D /* Products */,
				564997D02E9B125200D89F78 /* UI */,
				2CAC4D282E82457B00870189 /* Nook */,
			);
			sourceTree = "<group>";
		};
		7F8340FD2E37F39400674A5D /* Products */ = {
			isa = PBXGroup;
			children = (
				7F8340FC2E37F39400674A5D /* Nook.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		7F8340FB2E37F39400674A5D /* Nook */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 7F8341082E37F39500674A5D /* Build configuration list for PBXNativeTarget "Nook" */;
			buildPhases = (
				7F8340F82E37F39400674A5D /* Sources */,
				7F8340F92E37F39400674A5D /* Frameworks */,
				7F8340FA2E37F39400674A5D /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			fileSystemSynchronizedGroups = (
				2CAC4D282E82457B00870189 /* Nook */,
				564997D02E9B125200D89F78 /* UI */,
			);
			name = Nook;
			packageProductDependencies = (
				7FAFC5D92E3ADDCD009D7DC4 /* FaviconFinder */,
				2C16A0252E87430B0070894B /* Sparkle */,
				564997D72E9B24CC00D89F78 /* Garnish */,
			);
			productName = Pulse;
			productReference = 7F8340FC2E37F39400674A5D /* Nook.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		7F8340F42E37F39400674A5D /* Project object */ = {
			isa = PBXProject;
			attributes = {
				BuildIndependentTargetsInParallel = 1;
				LastSwiftUpdateCheck = 2600;
				LastUpgradeCheck = 1640;
				TargetAttributes = {
					7F8340FB2E37F39400674A5D = {
						CreatedOnToolsVersion = 16.4;
					};
				};
			};
			buildConfigurationList = 7F8340F72E37F39400674A5D /* Build configuration list for PBXProject "Nook" */;
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 7F8340F32E37F39400674A5D;
			minimizedProjectReferenceProxies = 1;
			packageReferences = (
				7FAFC5D82E3ADDCD009D7DC4 /* XCRemoteSwiftPackageReference "FaviconFinder" */,
				2C16A0242E87430B0070894B /* XCRemoteSwiftPackageReference "Sparkle" */,
				564997D62E9B24CC00D89F78 /* XCRemoteSwiftPackageReference "Garnish" */,
			);
			preferredProjectObjectVersion = 77;
			productRefGroup = 7F8340FD2E37F39400674A5D /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				7F8340FB2E37F39400674A5D /* Nook */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		7F8340FA2E37F39400674A5D /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		7F8340F82E37F39400674A5D /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		7F8341062E37F39500674A5D /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				DEVELOPMENT_TEAM = 96M8ZZRJK6;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				ENABLE_USER_SCRIPT_SANDBOXING = YES;
				GCC_C_LANGUAGE_STANDARD = gnu17;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
				MACOSX_DEPLOYMENT_TARGET = 15.5;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = macosx;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		7F8341072E37F39500674A5D /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				DEVELOPMENT_TEAM = 96M8ZZRJK6;
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_USER_SCRIPT_SANDBOXING = YES;
				GCC_C_LANGUAGE_STANDARD = gnu17;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
				MACOSX_DEPLOYMENT_TARGET = 15.5;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = macosx;
				SWIFT_COMPILATION_MODE = wholemodule;
			};
			name = Release;
		};
		7F8341092E37F39500674A5D /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = "logo-dev";
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				AUTOMATION_APPLE_EVENTS = NO;
				CODE_SIGN_ENTITLEMENTS = Nook/Nook.entitlements;
				CODE_SIGN_IDENTITY = "Apple Development";
				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
				CODE_SIGN_STYLE = Manual;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 103;
				DEVELOPMENT_TEAM = 8JTS5XWJJN;
				"DEVELOPMENT_TEAM[sdk=macosx*]" = 9DLM793N9T;
				ENABLE_APP_SANDBOX = NO;
				ENABLE_HARDENED_RUNTIME = YES;
				ENABLE_PREVIEWS = YES;
				ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = Nook/Info.plist;
				INFOPLIST_KEY_CFBundleDisplayName = Nook;
				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MARKETING_VERSION = 1.0.3.2;
				PRODUCT_BUNDLE_IDENTIFIER = io.browsewithnook.nook;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				REGISTER_APP_GROUPS = YES;
				RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = YES;
				RUNTIME_EXCEPTION_ALLOW_JIT = YES;
				RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;
				RUNTIME_EXCEPTION_DEBUGGING_TOOL = YES;
				RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = YES;
				RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/Nook/Supporting Files/Nook-Bridging-Header.h";
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		7F83410A2E37F39500674A5D /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = logo;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				AUTOMATION_APPLE_EVENTS = NO;
				CODE_SIGN_ENTITLEMENTS = Nook/Nook.entitlements;
				CODE_SIGN_IDENTITY = "Apple Development";
				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
				CODE_SIGN_STYLE = Manual;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 103;
				DEVELOPMENT_TEAM = 8JTS5XWJJN;
				"DEVELOPMENT_TEAM[sdk=macosx*]" = 9DLM793N9T;
				ENABLE_APP_SANDBOX = NO;
				ENABLE_HARDENED_RUNTIME = YES;
				ENABLE_PREVIEWS = YES;
				ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
				ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
				ENABLE_RESOURCE_ACCESS_CAMERA = NO;
				ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
				ENABLE_RESOURCE_ACCESS_LOCATION = NO;
				ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = Nook/Info.plist;
				INFOPLIST_KEY_CFBundleDisplayName = Nook;
				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MARKETING_VERSION = 1.0.3.2;
				PRODUCT_BUNDLE_IDENTIFIER = io.browsewithnook.nook;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				REGISTER_APP_GROUPS = YES;
				RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = YES;
				RUNTIME_EXCEPTION_ALLOW_JIT = YES;
				RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;
				RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;
				RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;
				RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/Nook/Supporting Files/Nook-Bridging-Header.h";
				SWIFT_VERSION = 5.0;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		7F8340F72E37F39400674A5D /* Build configuration list for PBXProject "Nook" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				7F8341062E37F39500674A5D /* Debug */,
				7F8341072E37F39500674A5D /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		7F8341082E37F39500674A5D /* Build configuration list for PBXNativeTarget "Nook" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				7F8341092E37F39500674A5D /* Debug */,
				7F83410A2E37F39500674A5D /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
		2C16A0242E87430B0070894B /* XCRemoteSwiftPackageReference "Sparkle" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/sparkle-project/Sparkle";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 2.8.0;
			};
		};
		564997D62E9B24CC00D89F78 /* XCRemoteSwiftPackageReference "Garnish" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/Aeastr/Garnish";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 0.0.4;
			};
		};
		7FAFC5D82E3ADDCD009D7DC4 /* XCRemoteSwiftPackageReference "FaviconFinder" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/will-lumley/FaviconFinder";
			requirement = {
				kind = upToNextMajorVersion;
				minimumVersion = 5.1.4;
			};
		};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		2C16A0252E87430B0070894B /* Sparkle */ = {
			isa = XCSwiftPackageProductDependency;
			package = 2C16A0242E87430B0070894B /* XCRemoteSwiftPackageReference "Sparkle" */;
			productName = Sparkle;
		};
		564997D72E9B24CC00D89F78 /* Garnish */ = {
			isa = XCSwiftPackageProductDependency;
			package = 564997D62E9B24CC00D89F78 /* XCRemoteSwiftPackageReference "Garnish" */;
			productName = Garnish;
		};
		7FAFC5D92E3ADDCD009D7DC4 /* FaviconFinder */ = {
			isa = XCSwiftPackageProductDependency;
			package = 7FAFC5D82E3ADDCD009D7DC4 /* XCRemoteSwiftPackageReference "FaviconFinder" */;
			productName = FaviconFinder;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = 7F8340F42E37F39400674A5D /* Project object */;
}

```

## /Nook.xcodeproj/project.xcworkspace/contents.xcworkspacedata

```xcworkspacedata path="/Nook.xcodeproj/project.xcworkspace/contents.xcworkspacedata" 
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>

```

## /Nook.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

```xcsettings path="/Nook.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings" 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

```

## /Nook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

```resolved path="/Nook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" 
{
  "originHash" : "5742fa0d6f2df74b52078520f8ba6f8cafd469cf8227a012762a476c2a2f21d0",
  "pins" : [
    {
      "identity" : "faviconfinder",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/will-lumley/FaviconFinder",
      "state" : {
        "revision" : "9279f4371f4877ca302ba3bf1015f3f58ae4a56c",
        "version" : "5.1.4"
      }
    },
    {
      "identity" : "garnish",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/Aeastr/Garnish",
      "state" : {
        "revision" : "4c49a3f163473fcb1a90f6730999cbd2e3931a18",
        "version" : "0.0.4"
      }
    },
    {
      "identity" : "sparkle",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/sparkle-project/Sparkle",
      "state" : {
        "revision" : "9a1d2a19d3595fcf8d9c447173f9a1687b3dcadb",
        "version" : "2.8.0"
      }
    },
    {
      "identity" : "swiftsoup",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/scinfu/SwiftSoup.git",
      "state" : {
        "revision" : "a81b1a5ac933dee8c6cfccf05d5ffcc5eb8c7ec4",
        "version" : "2.10.0"
      }
    }
  ],
  "version" : 3
}

```

## /Nook.xcodeproj/xcshareddata/xcschemes/Nook.xcscheme

```xcscheme path="/Nook.xcodeproj/xcshareddata/xcschemes/Nook.xcscheme" 
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "2600"
   version = "1.7">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES"
      buildArchitectures = "Automatic">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "7F8340FB2E37F39400674A5D"
               BuildableName = "Nook.app"
               BlueprintName = "Nook"
               ReferencedContainer = "container:Nook.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Release"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      shouldAutocreateTestPlan = "YES">
      <Testables>
         <TestableReference
            skipped = "NO"
            parallelizable = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "2C8925302E88DFF9002236DC"
               BuildableName = "NookUITests.xctest"
               BlueprintName = "NookUITests"
               ReferencedContainer = "container:Nook.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Release"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "7F8340FB2E37F39400674A5D"
            BuildableName = "Nook.app"
            BlueprintName = "Nook"
            ReferencedContainer = "container:Nook.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "7F8340FB2E37F39400674A5D"
            BuildableName = "Nook.app"
            BlueprintName = "Nook"
            ReferencedContainer = "container:Nook.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Release">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      customArchiveName = "Nook Browser"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>

```

## /Nook/Adapters/TabListAdapter.swift

```swift path="/Nook/Adapters/TabListAdapter.swift" 
import Foundation
import SwiftUI
import AppKit
import Combine

/// Adapter for regular tabs in a space
@MainActor
class SpaceRegularTabListAdapter: TabListDataSource, ObservableObject {
    private let tabManager: TabManager
    let spaceId: UUID
    private var cancellable: AnyCancellable?
    
    init(tabManager: TabManager, spaceId: UUID) {
        self.tabManager = tabManager
        self.spaceId = spaceId
        // Relay TabManager changes to table consumers
        self.cancellable = tabManager.objectWillChange
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.objectWillChange.send() }
    }
    
    var tabs: [Tab] {
        guard let space = tabManager.spaces.first(where: { $0.id == spaceId }) else { return [] }
        return tabManager.tabs(in: space)
    }
    
    func moveTab(from sourceIndex: Int, to targetIndex: Int) {
        objectWillChange.send()
        guard sourceIndex < tabs.count else { return }
        let tab = tabs[sourceIndex]
        tabManager.reorderRegular(tab, in: spaceId, to: targetIndex)
    }
    
    func selectTab(at index: Int) {
        guard index < tabs.count else { return }
        tabManager.browserManager?.selectTab(tabs[index])
    }
    
    func closeTab(at index: Int) {
        objectWillChange.send()
        guard index < tabs.count else { return }
        tabManager.removeTab(tabs[index].id)
    }
    
    func toggleMuteTab(at index: Int) {
        objectWillChange.send()
        guard index < tabs.count else { return }
        let tab = tabs[index]
        if tab.hasAudioContent {
            tab.toggleMute()
        }
    }
    
    func contextMenuForTab(at index: Int) -> NSMenu? {
        guard index < tabs.count else { return nil }
        let tab = tabs[index]
        let menu = NSMenu()

        // Move Up
        let upItem = NSMenuItem(title: "Move Up", action: #selector(moveTabUp(_:)), keyEquivalent: "")
        upItem.target = self
        upItem.representedObject = tab
        upItem.isEnabled = !isFirstTab(tab)
        menu.addItem(upItem)

        // Move Down
        let downItem = NSMenuItem(title: "Move Down", action: #selector(moveTabDown(_:)), keyEquivalent: "")
        downItem.target = self
        downItem.representedObject = tab
        downItem.isEnabled = !isLastTab(tab)
        menu.addItem(downItem)

        menu.addItem(NSMenuItem.separator())

        // Pin to Space
        let pinToSpaceItem = NSMenuItem(title: "Pin to Space", action: #selector(pinToSpace(_:)), keyEquivalent: "")
        pinToSpaceItem.target = self
        pinToSpaceItem.representedObject = tab
        menu.addItem(pinToSpaceItem)

        // Pin Globally
        let pinGlobalItem = NSMenuItem(title: "Pin Globally", action: #selector(pinGlobally(_:)), keyEquivalent: "")
        pinGlobalItem.target = self
        pinGlobalItem.representedObject = tab
        menu.addItem(pinGlobalItem)

        // Audio toggle if relevant
        if tab.hasAudioContent || tab.isAudioMuted {
            let title = tab.isAudioMuted ? "Unmute Audio" : "Mute Audio"
            let audioItem = NSMenuItem(title: title, action: #selector(toggleAudio(_:)), keyEquivalent: "")
            audioItem.target = self
            audioItem.representedObject = tab
            menu.addItem(audioItem)
        }

        // Unload operations
        let unloadItem = NSMenuItem(title: "Unload Tab", action: #selector(unloadTab(_:)), keyEquivalent: "")
        unloadItem.target = self
        unloadItem.representedObject = tab
        unloadItem.isEnabled = !tab.isUnloaded
        menu.addItem(unloadItem)

        let unloadAllItem = NSMenuItem(title: "Unload All Inactive Tabs", action: #selector(unloadAllInactive(_:)), keyEquivalent: "")
        unloadAllItem.target = self
        unloadAllItem.representedObject = tab
        menu.addItem(unloadAllItem)

        menu.addItem(NSMenuItem.separator())

        // Close
        let closeItem = NSMenuItem(title: "Close tab", action: #selector(closeTab(_:)), keyEquivalent: "")
        closeItem.target = self
        closeItem.representedObject = tab
        menu.addItem(closeItem)

        return menu
    }
    
    @objc private func moveTabUp(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.moveTabUp(tab.id)
    }

    @objc private func moveTabDown(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.moveTabDown(tab.id)
    }

    @objc private func pinToSpace(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.pinTabToSpace(tab, spaceId: spaceId)
    }

    @objc private func pinGlobally(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.addToEssentials(tab)
    }

    @objc private func toggleAudio(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tab.toggleMute()
    }

    @objc private func unloadTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.unloadTab(tab)
    }

    @objc private func unloadAllInactive(_ sender: NSMenuItem) {
        tabManager.unloadAllInactiveTabs()
    }

    @objc private func closeTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.removeTab(tab.id)
    }

    private func isFirstTab(_ tab: Tab) -> Bool { tabs.first?.id == tab.id }
    private func isLastTab(_ tab: Tab) -> Bool { tabs.last?.id == tab.id }
}

/// Adapter for pinned tabs in a space
@MainActor
class SpacePinnedTabListAdapter: TabListDataSource, ObservableObject {
    private let tabManager: TabManager
    let spaceId: UUID
    private var cancellable: AnyCancellable?
    
    init(tabManager: TabManager, spaceId: UUID) {
        self.tabManager = tabManager
        self.spaceId = spaceId
        self.cancellable = tabManager.objectWillChange
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.objectWillChange.send() }
    }
    
        var tabs: [Tab] {
        tabManager.spacePinnedTabs(for: spaceId)
    }
    
    func moveTab(from sourceIndex: Int, to targetIndex: Int) {
        objectWillChange.send()
        guard sourceIndex < tabs.count else { return }
        let tab = tabs[sourceIndex]
        tabManager.reorderSpacePinned(tab, in: spaceId, to: targetIndex)
    }
    
    func selectTab(at index: Int) {
        guard index < tabs.count else { return }
        tabManager.browserManager?.selectTab(tabs[index])
    }
    
    func closeTab(at index: Int) {
        objectWillChange.send()
        guard index < tabs.count else { return }
        tabManager.removeTab(tabs[index].id)
    }
    
    func toggleMuteTab(at index: Int) {
        objectWillChange.send()
        guard index < tabs.count else { return }
        let tab = tabs[index]
        if tab.hasAudioContent {
            tab.toggleMute()
        }
    }
    
    func contextMenuForTab(at index: Int) -> NSMenu? {
        guard index < tabs.count else { return nil }
        let tab = tabs[index]
        let menu = NSMenu()

        // Unpin from space
        let unpinItem = NSMenuItem(title: "Unpin from Space", action: #selector(unpinFromSpace(_:)), keyEquivalent: "")
        unpinItem.target = self
        unpinItem.representedObject = tab
        menu.addItem(unpinItem)

        // Pin Globally
        let pinGlobalItem = NSMenuItem(title: "Pin Globally", action: #selector(pinGlobally(_:)), keyEquivalent: "")
        pinGlobalItem.target = self
        pinGlobalItem.representedObject = tab
        menu.addItem(pinGlobalItem)

        // Audio toggle if relevant
        if tab.hasAudioContent || tab.isAudioMuted {
            let title = tab.isAudioMuted ? "Unmute Audio" : "Mute Audio"
            let audioItem = NSMenuItem(title: title, action: #selector(toggleAudio(_:)), keyEquivalent: "")
            audioItem.target = self
            audioItem.representedObject = tab
            menu.addItem(audioItem)
        }

        // Unload operations
        let unloadItem = NSMenuItem(title: "Unload Tab", action: #selector(unloadTab(_:)), keyEquivalent: "")
        unloadItem.target = self
        unloadItem.representedObject = tab
        unloadItem.isEnabled = !tab.isUnloaded
        menu.addItem(unloadItem)

        let unloadAllItem = NSMenuItem(title: "Unload All Inactive Tabs", action: #selector(unloadAllInactive(_:)), keyEquivalent: "")
        unloadAllItem.target = self
        unloadAllItem.representedObject = tab
        menu.addItem(unloadAllItem)

        menu.addItem(NSMenuItem.separator())

        // Close tab
        let closeItem = NSMenuItem(title: "Close tab", action: #selector(closeTab(_:)), keyEquivalent: "")
        closeItem.target = self
        closeItem.representedObject = tab
        menu.addItem(closeItem)

        return menu
    }
    
    @objc private func unpinFromSpace(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.unpinTabFromSpace(tab)
    }

    @objc private func pinGlobally(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.addToEssentials(tab)
    }

    @objc private func toggleAudio(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tab.toggleMute()
    }

    @objc private func unloadTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.unloadTab(tab)
    }

    @objc private func unloadAllInactive(_ sender: NSMenuItem) {
        tabManager.unloadAllInactiveTabs()
    }

    @objc private func closeTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.removeTab(tab.id)
    }
}

/// Adapter for essential tabs
@MainActor
class EssentialTabListAdapter: TabListDataSource, ObservableObject {
    private let tabManager: TabManager
    private var cancellable: AnyCancellable?
    
    init(tabManager: TabManager) {
        self.tabManager = tabManager
        // Observe TabManager and relay to collection view
        self.cancellable = tabManager.objectWillChange
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in self?.objectWillChange.send() }
    }
    
    deinit { cancellable?.cancel() }
    
    var tabs: [Tab] {
        // Profile-aware essentials: returns pinned tabs for current profile only
        tabManager.essentialTabs
    }
    
    func moveTab(from sourceIndex: Int, to targetIndex: Int) {
        objectWillChange.send()
        guard sourceIndex < tabs.count else { return }
        let tab = tabs[sourceIndex]
        tabManager.reorderEssential(tab, to: targetIndex)
    }
    
    func selectTab(at index: Int) {
        guard index < tabs.count else { return }
        tabManager.browserManager?.selectTab(tabs[index])
    }
    
    func closeTab(at index: Int) {
        guard index < tabs.count else { return }
        tabManager.removeTab(tabs[index].id)
    }
    
    func toggleMuteTab(at index: Int) {
        guard index < tabs.count else { return }
        let tab = tabs[index]
        if tab.hasAudioContent {
            tab.toggleMute()
        }
    }
    
    func contextMenuForTab(at index: Int) -> NSMenu? {
        guard index < tabs.count else { return nil }
        let tab = tabs[index]
        let menu = NSMenu()

        // Reload
        let reloadItem = NSMenuItem(title: "Reload", action: #selector(reloadTab(_:)), keyEquivalent: "")
        reloadItem.target = self
        reloadItem.representedObject = tab
        menu.addItem(reloadItem)

        menu.addItem(NSMenuItem.separator())

        // Audio toggle if relevant
        if tab.hasAudioContent || tab.isAudioMuted {
            let title = tab.isAudioMuted ? "Unmute Audio" : "Mute Audio"
            let audioItem = NSMenuItem(title: title, action: #selector(toggleAudio(_:)), keyEquivalent: "")
            audioItem.target = self
            audioItem.representedObject = tab
            menu.addItem(audioItem)
            menu.addItem(NSMenuItem.separator())
        }

        // Unload operations
        let unloadItem = NSMenuItem(title: "Unload Tab", action: #selector(unloadTab(_:)), keyEquivalent: "")
        unloadItem.target = self
        unloadItem.representedObject = tab
        unloadItem.isEnabled = !tab.isUnloaded
        menu.addItem(unloadItem)

        let unloadAllItem = NSMenuItem(title: "Unload All Inactive Tabs", action: #selector(unloadAllInactive(_:)), keyEquivalent: "")
        unloadAllItem.target = self
        unloadAllItem.representedObject = tab
        menu.addItem(unloadAllItem)

        menu.addItem(NSMenuItem.separator())

        // Remove from essentials
        let removeItem = NSMenuItem(title: "Remove from Essentials", action: #selector(removeFromEssentials(_:)), keyEquivalent: "")
        removeItem.target = self
        removeItem.representedObject = tab
        menu.addItem(removeItem)

        // Close
        let closeItem = NSMenuItem(title: "Close tab", action: #selector(closeTab(_:)), keyEquivalent: "")
        closeItem.target = self
        closeItem.representedObject = tab
        menu.addItem(closeItem)

        return menu
    }
    
    @objc private func reloadTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tab.refresh()
    }
    
    @objc private func removeFromEssentials(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.removeFromEssentials(tab)
    }

    @objc private func toggleAudio(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tab.toggleMute()
    }

    @objc private func unloadTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.unloadTab(tab)
    }

    @objc private func unloadAllInactive(_ sender: NSMenuItem) {
        tabManager.unloadAllInactiveTabs()
    }

    @objc private func closeTab(_ sender: NSMenuItem) {
        guard let tab = sender.representedObject as? Tab else { return }
        tabManager.removeTab(tab.id)
    }
}

```

## /Nook/Assets.xcassets/AccentColor.colorset/Contents.json

```json path="/Nook/Assets.xcassets/AccentColor.colorset/Contents.json" 
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}

```

## /Nook/Assets.xcassets/Contents.json

```json path="/Nook/Assets.xcassets/Contents.json" 
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}

```

## /Nook/Assets.xcassets/noise_texture.imageset/Contents.json

```json path="/Nook/Assets.xcassets/noise_texture.imageset/Contents.json" 
{
  "images" : [
    {
      "filename" : "noise_texture.png",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "noise_texture@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "filename" : "noise_texture@3x.png",
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


```

## /Nook/Assets.xcassets/noise_texture.imageset/noise_texture.png

Binary file available at https://raw.githubusercontent.com/nook-browser/Nook/refs/heads/main/Nook/Assets.xcassets/noise_texture.imageset/noise_texture.png

## /Nook/Assets.xcassets/noise_texture.imageset/noise_texture@2x.png

Binary file available at https://raw.githubusercontent.com/nook-browser/Nook/refs/heads/main/Nook/Assets.xcassets/noise_texture.imageset/noise_texture@2x.png

## /Nook/Assets.xcassets/noise_texture.imageset/noise_texture@3x.png

Binary file available at https://raw.githubusercontent.com/nook-browser/Nook/refs/heads/main/Nook/Assets.xcassets/noise_texture.imageset/noise_texture@3x.png

## /Nook/Components/Browser/Window/SpaceGradientBackgroundView.swift

```swift path="/Nook/Components/Browser/Window/SpaceGradientBackgroundView.swift" 
import SwiftUI

// Dithered gradient rendering
import CoreGraphics

// Renders the current space's gradient as a bottom background layer
struct SpaceGradientBackgroundView: View {
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var gradientColorManager: GradientColorManager
    @EnvironmentObject var windowState: BrowserWindowState

    private var isActiveWindow: Bool {
        browserManager.activeWindowState?.id == windowState.id
    }

    private var gradient: SpaceGradient {
        isActiveWindow ? gradientColorManager.displayGradient : windowState.activeGradient
    }

    var body: some View {
        ZStack {
            // Always use BarycentricGradientView for active window - it handles 1-3 colors smoothly
            // For inactive windows, also use Barycentric since spaces only have 1-3 colors max
            BarycentricGradientView(gradient: gradient)
        }
        .opacity(max(0.0, min(1.0, gradient.opacity)))
        .allowsHitTesting(false) // Entire background should not intercept input
    }

    private func stops() -> [Gradient.Stop] {
        // Map nodes to stops
        var mapped: [Gradient.Stop] = gradient.sortedNodes.map { node in
            Gradient.Stop(color: Color(hex: node.colorHex), location: CGFloat(node.location))
        }
        // Ensure at least two stops to satisfy LinearGradient requirements
        if mapped.count == 0 {
            // Fallback to default gradient stops
            let def = SpaceGradient.default
            mapped = def.sortedNodes.map { node in
                Gradient.Stop(color: Color(hex: node.colorHex), location: CGFloat(node.location))
            }
        } else if mapped.count == 1 {
            // Duplicate the single color across the full range
            let single = mapped[0]
            mapped = [
                Gradient.Stop(color: single.color, location: 0.0),
                Gradient.Stop(color: single.color, location: 1.0)
            ]
        }
        return mapped
    }

    // Compute start and end UnitPoints using a single trig pass
    private func linePoints(angle: Double) -> (start: UnitPoint, end: UnitPoint) {
        let theta = Angle(degrees: angle).radians
        let dx = cos(theta)
        let dy = sin(theta)
        let start = UnitPoint(x: 0.5 - 0.5 * dx, y: 0.5 - 0.5 * dy)
        let end = UnitPoint(x: 0.5 + 0.5 * dx, y: 0.5 + 0.5 * dy)
        return (start, end)
    }
}

```

## /Nook/Components/Browser/Window/SplitDropCaptureView.swift

```swift path="/Nook/Components/Browser/Window/SplitDropCaptureView.swift" 
import AppKit

final class SplitDropCaptureView: NSView {
    weak var browserManager: BrowserManager?
    weak var splitManager: SplitViewManager?
    var windowId: UUID?
    private var isDragActive: Bool = false

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        wantsLayer = true
        layer?.backgroundColor = NSColor.clear.cgColor
        // Accept plain text drags (UUID string for a Tab)
        registerForDraggedTypes([.string])
        // Transparent to normal mouse events; only DnD uses these callbacks
        isHidden = false
    }

    // Only intercept events during an active drag; otherwise pass through
    override func hitTest(_ point: NSPoint) -> NSView? { isDragActive ? self : nil }

    // MARK: - Dragging
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        isDragActive = true
        updatePreview(sender)
        return .copy
    }

    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
        isDragActive = true
        updatePreview(sender)
        return .copy
    }

    override func draggingExited(_ sender: NSDraggingInfo?) {
        isDragActive = false
        if let windowId { splitManager?.endPreview(cancel: true, for: windowId) }
        // Signal UI to clear any drag-hiding state even on invalid drops
        NotificationCenter.default.post(name: .tabDragDidEnd, object: nil)
    }

    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        guard let bm = browserManager, let sm = splitManager, let windowId else { return false }
        sm.endPreview(cancel: false, for: windowId)
        let pb = sender.draggingPasteboard
        guard let idString = pb.string(forType: .string), let id = UUID(uuidString: idString) else {
            // Invalid payload; clear any lingering drag UI state
            NotificationCenter.default.post(name: .tabDragDidEnd, object: nil)
            return false
        }
        let all = bm.tabManager.allTabs()
        guard let tab = all.first(where: { $0.id == id }) else { return false }
        let side = sideForDrag(sender)
        // Redundant replace guard
        if sm.isSplit(for: windowId) {
            let leftId = sm.leftTabId(for: windowId)
            let rightId = sm.rightTabId(for: windowId)
            if (side == .left && leftId == tab.id) || (side == .right && rightId == tab.id) {
                return true
            }
        }
        if let windowState = bm.windowStates[windowId] {
            sm.enterSplit(with: tab, placeOn: side, in: windowState)
        }
        // Cancel any in-progress sidebar/tab drag to prevent unintended reorder/removal
        DispatchQueue.main.async {
            TabDragManager.shared.cancelDrag()
        }
        isDragActive = false
        return true
    }

    // MARK: - Helpers
    private func updatePreview(_ sender: NSDraggingInfo) {
        let side = sideForDrag(sender)
        if let windowId { splitManager?.beginPreview(side: side, for: windowId) }
    }

    private func sideForDrag(_ sender: NSDraggingInfo) -> SplitViewManager.Side {
        let loc = convert(sender.draggingLocation, from: nil)
        let w = max(bounds.width, 1)
        return loc.x < (w / 2) ? .left : .right
    }
}

```

## /Nook/Components/Browser/Window/TabCompositorView.swift

```swift path="/Nook/Components/Browser/Window/TabCompositorView.swift" 
import SwiftUI
import AppKit
import WebKit

struct TabCompositorView: NSViewRepresentable {
    let browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        view.wantsLayer = true
        view.layer?.backgroundColor = NSColor.clear.cgColor
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {
        // Update the compositor when tabs change or compositor version changes
        updateCompositor(nsView)
    }
    
    private func updateCompositor(_ containerView: NSView) {
        // Remove all existing webview subviews
        containerView.subviews.forEach { $0.removeFromSuperview() }

        // Only add the current tab's webView to avoid WKWebView conflicts
        guard let currentTabId = windowState.currentTabId,
              let currentTab = browserManager.tabsForDisplay(in: windowState).first(where: { $0.id == currentTabId }),
              !currentTab.isUnloaded else {
            return
        }
        
        // Create a window-specific web view for this tab
        let webView = getOrCreateWebView(for: currentTab, in: windowState.id)
        webView.frame = containerView.bounds
        webView.autoresizingMask = [.width, .height]
        containerView.addSubview(webView)
        webView.isHidden = false
    }
    
    private func getOrCreateWebView(for tab: Tab, in windowId: UUID) -> WKWebView {
        // Check if we already have a web view for this tab in this window
        if let existingWebView = browserManager.getWebView(for: tab.id, in: windowId) {
            return existingWebView
        }
        
        // Create a new web view for this tab in this window
        return browserManager.createWebView(for: tab.id, in: windowId)
    }
}

// MARK: - Tab Compositor Manager
@MainActor
class TabCompositorManager: ObservableObject {
    private var unloadTimers: [UUID: Timer] = [:]
    private var lastAccessTimes: [UUID: Date] = [:]
    
    // Default unload timeout (5 minutes)
    var unloadTimeout: TimeInterval = 300
    
    init() {
        // Listen for timeout changes
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleTimeoutChange),
            name: .tabUnloadTimeoutChanged,
            object: nil
        )
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    @objc private func handleTimeoutChange(_ notification: Notification) {
        if let timeout = notification.userInfo?["timeout"] as? TimeInterval {
            setUnloadTimeout(timeout)
        }
    }
    
    func setUnloadTimeout(_ timeout: TimeInterval) {
        self.unloadTimeout = timeout
        // Restart timers with new timeout
        restartAllTimers()
    }
    
    func markTabAccessed(_ tabId: UUID) {
        lastAccessTimes[tabId] = Date()
        restartTimer(for: tabId)
    }
    
    func unloadTab(_ tab: Tab) {
        print("🔄 [Compositor] Unloading tab: \(tab.name)")
        
        // Stop any existing timer
        unloadTimers[tab.id]?.invalidate()
        unloadTimers.removeValue(forKey: tab.id)
        lastAccessTimes.removeValue(forKey: tab.id)
        
        // Unload the webview
        tab.unloadWebView()
    }
    
    func loadTab(_ tab: Tab) {
        print("🔄 [Compositor] Loading tab: \(tab.name)")
        
        // Mark as accessed
        markTabAccessed(tab.id)
        
        // Load the webview if needed
        tab.loadWebViewIfNeeded()
    }
    
    private func restartTimer(for tabId: UUID) {
        // Cancel existing timer
        unloadTimers[tabId]?.invalidate()
        
        // Create new timer
        let timer = Timer.scheduledTimer(withTimeInterval: unloadTimeout, repeats: false) { [weak self] _ in
            Task { @MainActor in
                self?.handleTabTimeout(tabId)
            }
        }
        unloadTimers[tabId] = timer
    }
    
    private func restartAllTimers() {
        // Cancel all existing timers
        unloadTimers.values.forEach { $0.invalidate() }
        unloadTimers.removeAll()
        
        // Restart timers for all accessed tabs
        for tabId in lastAccessTimes.keys {
            restartTimer(for: tabId)
        }
    }
    
    private func handleTabTimeout(_ tabId: UUID) {
        guard let tab = findTab(by: tabId) else { return }
        
        // Don't unload if it's the current tab
        if tab.id == tabId && tab.isCurrentTab {
            // Restart timer for current tab
            restartTimer(for: tabId)
            return
        }
        
        // Don't unload if tab has playing media
        if tab.hasPlayingVideo || tab.hasPlayingAudio || tab.hasAudioContent {
            // Restart timer for tabs with media
            restartTimer(for: tabId)
            return
        }
        
        // Unload the tab
        unloadTab(tab)
    }
    
    private func findTab(by id: UUID) -> Tab? {
        guard let browserManager = browserManager else { return nil }
        return browserManager.tabManager.allTabs().first { $0.id == id }
    }

    private func findTabByWebView(_ webView: WKWebView) -> Tab? {
        guard let browserManager = browserManager else { return nil }
        return browserManager.tabManager.allTabs().first { $0.webView === webView }
    }
    
    // MARK: - Public Interface
    func updateTabVisibility(currentTabId: UUID?) {
        guard let browserManager = browserManager else { return }
        for (windowId, _) in browserManager.compositorContainers() {
            guard let windowState = browserManager.windowStates[windowId] else { continue }
            browserManager.refreshCompositor(for: windowState)
        }
    }
    
    /// Update tab visibility for a specific window
    func updateTabVisibility(for windowState: BrowserWindowState) {
        browserManager?.refreshCompositor(for: windowState)
    }
    
    // MARK: - Dependencies
    weak var browserManager: BrowserManager?
}

```

## /Nook/Components/Browser/Window/WindowBackgroundView.swift

```swift path="/Nook/Components/Browser/Window/WindowBackgroundView.swift" 
//
//  WindowBackgroundView.swift
//  Nook
//
//  Created by Maciek Bagiński on 30/07/2025.
//

import SwiftUI

struct WindowBackgroundView: View {
    @EnvironmentObject var browserManager: BrowserManager

    var body: some View {
        Group {
            if #available(macOS 26.0, *) {
                if browserManager.settingsManager.isLiquidGlassEnabled {
                    Rectangle()
                        .fill(Color.clear)
                        .blur(radius: 40)
                        .glassEffect(in: .rect(cornerRadius: 0))
                        .clipped()
                } else {
                    BlurEffectView(
                        material: browserManager.settingsManager
                            .currentMaterial,
                        state: .active
                    )
                    .overlay(
                        Color.black.opacity(0.25)
                            .blendMode(.darken)
                    )
                }
            } else {
                if browserManager.settingsManager.isLiquidGlassEnabled {
                    Rectangle()
                        .fill(.clear)
                        .background(.thinMaterial)  // Use thinMaterial for liquid glass effect for better compatability
                        .blur(radius: 40)
                        .clipped()
                } else {
                    BlurEffectView(
                        material: browserManager.settingsManager
                            .currentMaterial,
                        state: .active
                    )
                    .overlay(
                        Color.black.opacity(0.25)
                            .blendMode(.darken)
                    )
                }
            }        }
        .backgroundDraggable()
    }
}


```

## /Nook/Components/Browser/Window/WindowView.swift

```swift path="/Nook/Components/Browser/Window/WindowView.swift" 
//
//  WindowView.swift
//  Nook
//
//  Created by Maciek Bagiński on 30/07/2025.
//

import SwiftUI

struct WindowView: View {
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState
    @StateObject private var hoverSidebarManager = HoverSidebarManager()
    @Environment(\.colorScheme) var colorScheme

    // Calculate webview Y offset (where the web content starts)
    private var webViewYOffset: CGFloat {
        // Approximate Y offset for web content start (nav bar + URL bar + padding)
        if browserManager.settingsManager.topBarAddressView {
            return 44  // Top bar height
        } else {
            return 20  // Accounts for navigation area height
        }
    }

    var body: some View {
        let isDark = colorScheme == .dark
        GeometryReader { geometry in
            ZStack {
                // Gradient background for the current space (bottom-most layer)
                SpaceGradientBackgroundView()
                    .environmentObject(windowState)
                
                // Attach background context menu to the window background layer
                Color.white.opacity(isDark ? 0.3 : 0.4)
                    .ignoresSafeArea(.all)
                WindowBackgroundView()
                    .contextMenu {
                        Button("Customize Space Gradient...") {
                            browserManager.showGradientEditor()
                        }
                        .disabled(browserManager.tabManager.currentSpace == nil)
                    }

                // Top bar when enabled
                if browserManager.settingsManager.topBarAddressView {
                    VStack(spacing: 0) {
                        TopBarView()
                            .environmentObject(browserManager)
                            .environmentObject(windowState)
                            .background(Color.clear)
                        
                        mainLayout
                    }
                    
                    // TopBar Command Palette overlay
                    TopBarCommandPalette()
                        .environmentObject(browserManager)
                        .environmentObject(windowState)
                        .zIndex(3000)
                } else {
                    mainLayout
                }

                // Mini command palette anchored exactly to URL bar's top-left
                // Only show when topbar is disabled
                if !browserManager.settingsManager.topBarAddressView {
                    MiniCommandPaletteOverlay()
                        .environmentObject(windowState)
                }

                // Hover-reveal Sidebar overlay (slides in over web content)
                SidebarHoverOverlayView()
                    .environmentObject(hoverSidebarManager)
                    .environmentObject(windowState)

                CommandPaletteView()
                DialogView()

                // Peek overlay for external link previews
                PeekOverlayView()

                // Find bar overlay - centered top bar
                if browserManager.findManager.isFindBarVisible {
                    VStack {
                        HStack {
                            Spacer()
                            FindBarView(findManager: browserManager.findManager)
                                .frame(maxWidth: 500)
                            Spacer()
                        }
                        .padding(.top, 20)
                        Spacer()
                    }
                }

                
                // Toast overlays (matches WebsitePopup style/presentation)
                VStack {
                    HStack {
                        Spacer()
                        VStack(spacing: 8) {
                            // Profile switch toast
                            if windowState.isShowingProfileSwitchToast,
                                let toast = windowState.profileSwitchToast
                            {
                                ProfileSwitchToastView(toast: toast)
                                    .animation(
                                        .spring(
                                            response: 0.5,
                                            dampingFraction: 0.8
                                        ),
                                        value: windowState
                                            .isShowingProfileSwitchToast
                                    )
                                    .onTapGesture {
                                        browserManager.hideProfileSwitchToast(
                                            for: windowState
                                        )
                                    }
                            }

                            // Tab closure toast
                            if browserManager.showTabClosureToast
                                && browserManager.tabClosureToastCount > 0
                            {
                                TabClosureToast()
                                    .environmentObject(browserManager)
                                    .environmentObject(windowState)
                                    .animation(
                                        .spring(
                                            response: 0.5,
                                            dampingFraction: 0.8
                                        ),
                                        value: browserManager
                                            .showTabClosureToast
                                    )
                                    .onTapGesture {
                                        browserManager.hideTabClosureToast()
                                    }
                            }

                            // Zoom popup toast
                            if browserManager.shouldShowZoomPopup {
                                ZoomPopupView(
                                    zoomManager: browserManager.zoomManager,
                                    onZoomIn: {
                                        browserManager.zoomInCurrentTab()
                                    },
                                    onZoomOut: {
                                        browserManager.zoomOutCurrentTab()
                                    },
                                    onZoomReset: {
                                        browserManager.resetZoomCurrentTab()
                                    },
                                    onZoomPresetSelected: { zoomLevel in
                                        browserManager.applyZoomLevel(zoomLevel)
                                    },
                                    onDismiss: {
                                        browserManager.shouldShowZoomPopup = false
                                    }
                                )
                                .animation(
                                    .spring(
                                        response: 0.5,
                                        dampingFraction: 0.8
                                    ),
                                    value: browserManager.shouldShowZoomPopup
                                )
                                .onTapGesture {
                                    browserManager.shouldShowZoomPopup = false
                                }
                            }
                        }
                        .padding(10)
                    }
                    Spacer()
                }
            }
            // Named coordinate space for geometry preferences
            .coordinateSpace(name: "WindowSpace")
            // Keep BrowserManager aware of URL bar frame in window space
            .onPreferenceChange(URLBarFramePreferenceKey.self) { frame in
                browserManager.urlBarFrame = frame
                windowState.urlBarFrame = frame
            }
            // Attach hover sidebar manager lifecycle
            .onAppear {
                hoverSidebarManager.attach(browserManager: browserManager)
                hoverSidebarManager.start()
            }
            .onDisappear {
                hoverSidebarManager.stop()
            }
            .environmentObject(browserManager)
            .environmentObject(browserManager.gradientColorManager)
            .environmentObject(browserManager.splitManager)
            .environmentObject(hoverSidebarManager)
        }
    }

    @ViewBuilder
    private var mainLayout: some View {
        let aiVisible = windowState.isSidebarAIChatVisible
        let aiAppearsOnTrailingEdge = browserManager.settingsManager.sidebarPosition == .left

        HStack(spacing: 0) {
            if aiAppearsOnTrailingEdge {
                sidebarColumn
                websiteColumn
                if aiVisible {
                    aiSidebar
                }
            } else {
                if aiVisible {
                    aiSidebar
                }
                websiteColumn
                sidebarColumn
            }
        }
        .padding(.trailing, windowState.isFullScreen ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .right ? 0 : aiVisible ? 0 : 8))
        .padding(.leading, windowState.isFullScreen ? 0 : (windowState.isSidebarVisible && browserManager.settingsManager.sidebarPosition == .left ? 0 : aiVisible ? 0 : 8))
    }

    private var sidebarColumn: some View {
        SidebarView()
        // Overlay the resize handle spanning the sidebar/webview boundary
        .overlay(alignment: browserManager.settingsManager.sidebarPosition == .left ? .trailing : .leading) {
            if windowState.isSidebarVisible {
                // Position to span 14pts into sidebar and 2pts into web content (moved 6pts left)
                SidebarResizeView()
                
                    .frame(maxHeight: .infinity)
                    .environmentObject(browserManager)
                    .environmentObject(windowState)
                    .zIndex(2000)  // Higher z-index to ensure it's above all other elements
                    .environmentObject(windowState)
            }
        }
            .environmentObject(browserManager)
            .environmentObject(windowState)
    }

    private var websiteColumn: some View {
        VStack(spacing: 0) {
            WebsiteLoadingIndicator()
            WebsiteView()
        }
        .padding(.bottom, 8)
        .zIndex(2000)
    }

    @ViewBuilder
    private var aiSidebar: some View {
        let handleAlignment: Alignment = browserManager.settingsManager.sidebarPosition == .left ? .leading : .trailing

        SidebarAIChat()
            .frame(width: windowState.aiSidebarWidth)
            .overlay(alignment: handleAlignment) {
                AISidebarResizeView()
                    .frame(maxHeight: .infinity)
                    .environmentObject(browserManager)
                    .environmentObject(windowState)
            }
            .transition(
                .move(edge: browserManager.settingsManager.sidebarPosition == .left ? .trailing : .leading)
                    .combined(with: .opacity)
            )
            .environmentObject(browserManager)
            .environmentObject(windowState)
            .environment(browserManager.settingsManager)
    }

}

// MARK: - Profile Switch Toast View
private struct ProfileSwitchToastView: View {
    let toast: BrowserManager.ProfileSwitchToast

    var body: some View {
        HStack {
            Text("Switched to \(toast.toProfile.name)")
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(.white)
            Image(systemName: "person.crop.circle")
                .font(.system(size: 12, weight: .medium))
                .foregroundStyle(.white)
                .frame(width: 14, height: 14)
                .padding(4)
                .background(Color.white.opacity(0.2))
                .clipShape(RoundedRectangle(cornerRadius: 6))
                .overlay {
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(.white.opacity(0.4), lineWidth: 1)
                }
        }
        .padding(12)
        .background(Color(hex: "3E4D2E"))
        .clipShape(RoundedRectangle(cornerRadius: 16))
        .overlay {
            RoundedRectangle(cornerRadius: 16)
                .stroke(.white.opacity(0.2), lineWidth: 2)
        }
        .transition(.scale(scale: 0.0, anchor: .top))
    }
}

// MARK: - Mini Command Palette Overlay (above sidebar and webview)
private struct MiniCommandPaletteOverlay: View {
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState

    var body: some View {
        let isActiveWindow =
            browserManager.activeWindowState?.id == windowState.id
        let isVisible =
            isActiveWindow && windowState.isMiniCommandPaletteVisible
            && !windowState.isCommandPaletteVisible

        ZStack(alignment: browserManager.settingsManager.sidebarPosition == .left ? .topLeading : .topTrailing) {
            if isVisible {
                // Click-away hit target
                Color.clear
                    .contentShape(Rectangle())
                    .ignoresSafeArea()
                    .onTapGesture {
                        browserManager.hideMiniCommandPalette(for: windowState)
                    }

                // Use reported URL bar frame when reliable; otherwise compute manual fallback
                let barFrame = windowState.urlBarFrame
                let hasFrame = barFrame.width > 1 && barFrame.height > 1
                // Match sidebar's internal 8pt padding when geometry is unavailable
                let fallbackX: CGFloat = 8
                let topBarHeight: CGFloat = browserManager.settingsManager.topBarAddressView ? 44 : 0
                let fallbackY: CGFloat =
                    8 /* sidebar top padding */ + 30 /* nav bar */
                    + 8 /* vstack spacing */ + topBarHeight
                let anchorX = hasFrame ? barFrame.minX : fallbackX
                let anchorY = hasFrame ? barFrame.minY : fallbackY
                // let width = hasFrame ? barFrame.width : browserManager.sidebarWidth

                MiniCommandPaletteView(
                    forcedWidth: 400,
                    forcedCornerRadius: 12
                )
                .offset(x: browserManager.settingsManager.sidebarPosition == .left ? anchorX : -anchorX, y: anchorY)
                .zIndex(1)
            }
        }
        .allowsHitTesting(isVisible)
        .zIndex(999) // ensure above web content
    }
}

```

## /Nook/Components/ColorPicker/AngleDial.swift

```swift path="/Nook/Components/ColorPicker/AngleDial.swift" 
import SwiftUI

// MARK: - AngleDial
// Dedicated dial for gradient angle with tick marks
struct AngleDial: View {
    @Binding var angle: Double // degrees 0...360

    var body: some View {
        GeometryReader { proxy in
            let size = min(proxy.size.width, proxy.size.height)
            ZStack {
                Circle().fill(.thinMaterial)
                Circle().strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)

                // tick marks
                ForEach(0..<24, id: \.self) { i in
                    let a = Double(i) / 24.0 * 2 * .pi
                    Capsule()
                        .fill(Color.primary.opacity(i % 6 == 0 ? 0.35 : 0.18))
                        .frame(width: CGFloat(i % 6 == 0 ? 3 : 2), height: CGFloat(i % 6 == 0 ? 10 : 6))
                        .offset(y: -size/2 + 10.0)
                        .rotationEffect(.radians(a))
                }

                // needle and handle
                let a = Angle(degrees: angle)
                let handle = CGPoint(x: cos(a.radians) * (size/2 - 12.0), y: sin(a.radians) * (size/2 - 12.0))
                Path { p in
                    p.move(to: CGPoint(x: size/2, y: size/2))
                    p.addLine(to: CGPoint(x: size/2 + handle.x, y: size/2 + handle.y))
                }
                .stroke(Color.accentColor.opacity(0.8), lineWidth: 2)

                Circle()
                    .fill(Color.accentColor)
                    .frame(width: 12, height: 12)
                    .position(x: size/2 + handle.x, y: size/2 + handle.y)
            }
            .frame(width: size, height: size)
            .contentShape(Circle())
            .gesture(DragGesture(minimumDistance: 0).onChanged { value in
                let center = CGPoint(x: size/2, y: size/2)
                let dx = value.location.x - center.x
                let dy = value.location.y - center.y
                var deg = atan2(dy, dx) * 180 / .pi
                if deg < 0 { deg += 360 }
                angle = deg
            })
        }
    }
}

```

## /Nook/Components/ColorPicker/ColorPickerView.swift

```swift path="/Nook/Components/ColorPicker/ColorPickerView.swift" 
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif

// MARK: - ColorPickerView
// Quadrant-based color grid with 3x3 tones per quadrant
struct ColorPickerView: View {
    // Current selection used to render selection border
    var selectedColor: Color?
    var onColorSelected: (Color) -> Void

    private let cellSize: CGFloat = 32
    private let cornerRadius: CGFloat = 8

    private var columns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 8), count: 3) }

    // Generate tones for a base hue (0...1)
    private func tones(hue: Double) -> [Color] {
        // 3 brightness x 3 saturation
        let saturations: [Double] = [0.45, 0.70, 0.95]
        let brightness: [Double] = [0.45, 0.70, 0.95]
        return brightness.flatMap { b in
            saturations.map { s in
                Color(hue: hue, saturation: s, brightness: b)
            }
        }
    }

    private var quadrants: [(title: String, hue: Double)] {
        [
            ("Blue", 0.60),
            ("Red", 0.00),
            ("Green", 0.33),
            ("Yellow", 0.15)
        ]
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            ForEach(Array(quadrants.enumerated()), id: \.offset) { _, item in
                VStack(alignment: .leading, spacing: 8) {
                    Text(item.title)
                        .font(.caption)
                        .foregroundStyle(.secondary)

                    LazyVGrid(columns: columns, spacing: 8) {
                        ForEach(Array(tones(hue: item.hue).enumerated()), id: \.offset) { _, color in
                            ColorCell(color: color,
                                      isSelected: selectedColor.map { approxEqual($0, color) } ?? false,
                                      size: cellSize,
                                      cornerRadius: cornerRadius) {
                                onColorSelected(color)
                            }
                        }
                    }
                }
                .padding(.vertical, 4)
                Divider()
            }
        }
    }

    // Loosely compare two colors in HSB space
    private func approxEqual(_ a: Color, _ b: Color) -> Bool {
        #if canImport(AppKit)
        let nsA = NSColor(a)
        let nsB = NSColor(b)
        var (ha, sa, ba): (CGFloat, CGFloat, CGFloat) = (0,0,0)
        var (hb, sb, bb): (CGFloat, CGFloat, CGFloat) = (0,0,0)
        nsA.usingColorSpace(.deviceRGB)?.getHue(&ha, saturation: &sa, brightness: &ba, alpha: nil)
        nsB.usingColorSpace(.deviceRGB)?.getHue(&hb, saturation: &sb, brightness: &bb, alpha: nil)
        return abs(ha - hb) < 0.03 && abs(sa - sb) < 0.08 && abs(ba - bb) < 0.08
        #else
        return false
        #endif
    }
}

// MARK: - Cell
private struct ColorCell: View {
    let color: Color
    let isSelected: Bool
    let size: CGFloat
    let cornerRadius: CGFloat
    let action: () -> Void

    @State private var hovering = false

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                .fill(color)
            RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                .strokeBorder(isSelected ? Color.accentColor : Color.black.opacity(0.12), lineWidth: isSelected ? 2 : 1)
                .overlay(
                    RoundedRectangle(cornerRadius: cornerRadius)
                        .fill(Color.black.opacity(hovering ? 0.06 : 0))
                )
        }
        .frame(width: size, height: size)
        .contentShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
        .onTapGesture { action() }
        .onHover { hovering = $0 }
        .animation(.easeInOut(duration: 0.12), value: hovering)
    }
}

```

## /Nook/Components/ColorPicker/ColorSwatchRowView.swift

```swift path="/Nook/Components/ColorPicker/ColorSwatchRowView.swift" 
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif

// MARK: - ColorSwatchRowView
// Horizontal palette row with arrows
struct ColorSwatchRowView: View {
    var selectedColor: Color?
    var onSelect: (Color) -> Void

    private let swatchSize: CGFloat = 28
    private let palettes: [[Color]] = {
        let base: [Color] = [
            Color.white,
            Color(red: 1.0, green: 0.55, blue: 0.75),
            Color.purple,
            Color.red,
            Color.orange,
            Color.yellow,
            Color.green,
            Color.cyan,
            Color.blue,
            Color.gray
        ]
        let alt: [Color] = [
            Color(white: 0.9),
            Color(hue: 0.95, saturation: 0.6, brightness: 0.9),
            Color(hue: 0.7, saturation: 0.5, brightness: 0.8),
            Color(hue: 0.03, saturation: 0.7, brightness: 0.95),
            Color(hue: 0.08, saturation: 0.7, brightness: 0.95),
            Color(hue: 0.13, saturation: 0.8, brightness: 0.95),
            Color(hue: 0.33, saturation: 0.75, brightness: 0.85),
            Color(hue: 0.55, saturation: 0.6, brightness: 0.9),
            Color(hue: 0.62, saturation: 0.7, brightness: 0.85),
            Color(hue: 0.75, saturation: 0.4, brightness: 0.7)
        ]
        return [base, alt]
    }()

    @State private var page: Int = 0

    var body: some View {
        HStack(spacing: 8) {
            Button { page = max(0, page - 1) } label: {
                Image(systemName: "chevron.left")
            }
            .buttonStyle(.plain)
            .foregroundStyle(.secondary)
            .disabled(page == 0)

            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 10) {
                    ForEach(palettes[page].indices, id: \.self) { i in
                        let color = palettes[page][i]
                        Circle()
                            .fill(color)
                            .frame(width: swatchSize, height: swatchSize)
                            .overlay(
                                Circle().strokeBorder(Color.white, lineWidth: 2)
                            )
                            .overlay(
                                Circle().strokeBorder(
                                    (selectedColor.map { approxEqual($0, color) } ?? false) ? Color.accentColor : Color.clear,
                                    lineWidth: 2
                                )
                            )
                            .onTapGesture { onSelect(color) }
                    }
                }
                .padding(.horizontal, 4)
            }

            Button { page = min(palettes.count - 1, page + 1) } label: {
                Image(systemName: "chevron.right")
            }
            .buttonStyle(.plain)
            .foregroundStyle(.secondary)
            .disabled(page >= palettes.count - 1)
        }
    }

    private func approxEqual(_ a: Color, _ b: Color) -> Bool {
        #if canImport(AppKit)
        let nsA = NSColor(a)
        let nsB = NSColor(b)
        var (ha, sa, ba): (CGFloat, CGFloat, CGFloat) = (0,0,0)
        var (hb, sb, bb): (CGFloat, CGFloat, CGFloat) = (0,0,0)
        nsA.usingColorSpace(.deviceRGB)?.getHue(&ha, saturation: &sa, brightness: &ba, alpha: nil)
        nsB.usingColorSpace(.deviceRGB)?.getHue(&hb, saturation: &sb, brightness: &bb, alpha: nil)
        return abs(ha - hb) < 0.03 && abs(sa - sb) < 0.08 && abs(ba - bb) < 0.08
        #else
        return false
        #endif
    }
}


```

## /Nook/Components/ColorPicker/GradientCanvasEditor.swift

```swift path="/Nook/Components/ColorPicker/GradientCanvasEditor.swift" 
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif

// MARK: - GradientCanvasEditor
// Large canvas with dot grid background and draggable color stops
struct GradientCanvasEditor: View {
    @Binding var gradient: SpaceGradient
    @Binding var selectedNodeID: UUID?
    var showDitherOverlay: Bool = true
    @EnvironmentObject var gradientColorManager: GradientColorManager

    // ephemeral Y-positions (0...1) for visual placement only
    @State private var yPositions: [UUID: CGFloat] = [:]
    @State private var xPositions: [UUID: CGFloat] = [:]
    @State private var lightness: Double = 0.6 // HSL L component
    // Lock a primary node identity when in 3-node mode
    @State private var lockedPrimaryID: UUID?
    
    // Haptic feedback state
    @State private var lastHapticSpoke: String? = nil
    @State private var lastHapticRadial: String? = nil

    private let cornerRadius: CGFloat = 16

    var body: some View {
        GeometryReader { proxy in
            let width = proxy.size.width
            let height = proxy.size.height
            let padding: CGFloat = 24
            let center = CGPoint(x: width/2, y: height/2)
            let radius = min(width, height)/2 - padding

            ZStack {
                // No gradient preview in the editor canvas; focus on dot grid + handles

                // Noise overlay (optional)
                if showDitherOverlay {
                    Image("noise_texture")
                        .resizable()
                        .scaledToFill()
                        .opacity(max(0, min(1, gradient.grain)))
                        .blendMode(.overlay)
                        .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
                        .allowsHitTesting(false)
                }

                // Dot grid
                DotGrid()
                    .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
                    .allowsHitTesting(false)

  
                // Draggable handles
                ForEach(gradient.nodes) { node in
                    let posX = xPositions[node.id] ?? CGFloat(node.location)
                    let posY = yPositions[node.id] ?? defaultY(for: node)
                    let initial = CGPoint(x: posX * width, y: posY * height)
                    let clamped = clampToCircle(point: initial, center: center, radius: radius)

                    let primaryID = primaryNodeID()
                    let isPrimary = (gradient.nodes.count == 3) && (node.id == primaryID)
                    let handleSize: CGFloat = isPrimary ? 40 : 26

                    Handle(colorHex: node.colorHex, selected: selectedNodeID == node.id, size: handleSize)
                        .position(clamped)
                        .gesture(DragGesture(minimumDistance: 0)
                            .onChanged { value in
                                // Clamp to circle and map to HSL for color
                                let clamped = clampToCircle(point: value.location, center: center, radius: radius)
                                let nx = max(0, min(1, clamped.x / width))
                                let ny = max(0, min(1, clamped.y / height))
                                updateNodeFromCanvasDrag(node, newX: nx, newY: ny, absolute: clamped, center: center, radius: radius)
                            }
                        )
                        .onTapGesture { selectedNodeID = node.id }
                }

                // Plus / minus at bottom center
                HStack(spacing: 24) {
                    Button(action: removeNode) {
                        Image(systemName: "minus")
                            .font(.title3.weight(.medium))
                            .foregroundStyle(.secondary)
                    }.buttonStyle(.plain)
                     .disabled(gradient.nodes.count <= 1)
                    Button(action: addNode) {
                        Image(systemName: "plus")
                            .font(.title3.weight(.medium))
                            .foregroundStyle(.secondary)
                    }.buttonStyle(.plain)
                     .disabled(gradient.nodes.count >= 3)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
                .padding(.bottom, 10)

                // Border stroke
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)
            }
            .contentShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
            .onAppear { ensurePositions(width: width, height: height, center: center, radius: radius) }
            .onChange(of: gradient.nodes.count) { ensurePositions(width: width, height: height, center: center, radius: radius) }
        }
        .frame(height: 300)
    }

    // MARK: - Helpers
    private func defaultY(for node: GradientNode) -> CGFloat {
        // spread defaults by index for nicer initial layout
        if let idx = gradient.nodes.firstIndex(where: { $0.id == node.id }) {
            return [0.35, 0.55, 0.45][min(idx, 2)]
        }
        return 0.5
    }

    private func ensurePositions(width: CGFloat, height: CGFloat, center: CGPoint, radius: CGFloat) {
        // Special layout when exactly 3 nodes: place both companions on the same circular radius as the primary
        // Also lock the primary node identity once when entering 3-node mode
        if gradient.nodes.count == 3 {
            // Only reassign lockedPrimaryID if it's nil or if the locked node no longer exists
            if lockedPrimaryID == nil {
                lockedPrimaryID = gradient.nodes.min(by: { $0.location < $1.location })?.id
            } else if !gradient.nodes.contains(where: { $0.id == lockedPrimaryID }) {
                lockedPrimaryID = gradient.nodes.min(by: { $0.location < $1.location })?.id
            }
            // Only update preferredPrimaryNodeID if it's different from lockedPrimaryID
            if gradientColorManager.preferredPrimaryNodeID != lockedPrimaryID {
                gradientColorManager.preferredPrimaryNodeID = lockedPrimaryID
            }
        } else if gradient.nodes.count == 2 {
            // No locked primary in 2-node mode; keep or choose a stable preferred primary
            lockedPrimaryID = nil
            if let pid = gradientColorManager.preferredPrimaryNodeID,
               gradient.nodes.contains(where: { $0.id == pid }) {
                // keep existing preferred - don't change it!
            } else {
                // Only reassign if the current preferred doesn't exist
                gradientColorManager.preferredPrimaryNodeID = gradient.nodes.min(by: { $0.location < $1.location })?.id
            }
        } else {
            // Single node: trivially the only node is primary
            lockedPrimaryID = nil
            if gradientColorManager.preferredPrimaryNodeID != gradient.nodes.first?.id {
                gradientColorManager.preferredPrimaryNodeID = gradient.nodes.first?.id
            }
        }
        if gradient.nodes.count == 3, let primaryID = primaryNodeID() {
            let allUnset = gradient.nodes.allSatisfy { xPositions[$0.id] == nil && yPositions[$0.id] == nil }
            if allUnset {
                // Seed primary at top-left; companions at top-right and bottom-center (same radius)
                // Primary (top-left)
                if let p = gradient.nodes.first(where: { $0.id == primaryID }) {
                    let primaryAngle: CGFloat = (.pi * 3.0) / 4.0 // 135° (top-left)
                    let pr = radius * 0.9
                    let px = center.x + cos(primaryAngle) * pr
                    let py = center.y + sin(primaryAngle) * pr
                    xPositions[p.id] = max(0, min(1, px / width))
                    yPositions[p.id] = max(0, min(1, py / height))
                    // DON'T update gradient.nodes[i].location - keep the 1st, 2nd, 3rd roles fixed!
                }
                // Companions: [top-right, bottom-center]
                let rComp = radius * 0.9
                let companionAngles: [CGFloat] = [(.pi / 4.0), (-.pi / 2.0)] // 45°, -90°
                let companions = gradient.nodes.filter { $0.id != primaryID }
                for (i, node) in companions.enumerated() where i < 2 {
                    let ang = companionAngles[i]
                    let x = center.x + cos(ang) * rComp
                    let y = center.y + sin(ang) * rComp
                    xPositions[node.id] = max(0, min(1, x / width))
                    yPositions[node.id] = max(0, min(1, y / height))
                    // DON'T update gradient.nodes[idx].location - keep the 1st, 2nd, 3rd roles fixed!
                }
            }
        }

        for n in gradient.nodes {
            // Load saved positions from the model if available
            if let savedX = n.xPosition, let savedY = n.yPosition {
                xPositions[n.id] = CGFloat(savedX)
                yPositions[n.id] = CGFloat(savedY)
            } else {
                // Use defaults if no saved positions
                if yPositions[n.id] == nil { yPositions[n.id] = defaultY(for: n) }
                if xPositions[n.id] == nil { xPositions[n.id] = CGFloat(n.location) }
                
                // Save the default positions to the model
                if let idx = gradient.nodes.firstIndex(where: { $0.id == n.id }) {
                    gradient.nodes[idx].xPosition = Double(xPositions[n.id] ?? CGFloat(n.location))
                    gradient.nodes[idx].yPosition = Double(yPositions[n.id] ?? defaultY(for: n))
                }
            }
            
            // keep points within circle
            let pt = CGPoint(x: (xPositions[n.id] ?? CGFloat(n.location)) * width,
                             y: (yPositions[n.id] ?? defaultY(for: n)) * height)
            let clamped = clampToCircle(point: pt, center: center, radius: radius)
            xPositions[n.id] = clamped.x / width
            yPositions[n.id] = clamped.y / height
            
            // Update the model with the clamped positions
            if let idx = gradient.nodes.firstIndex(where: { $0.id == n.id }) {
                gradient.nodes[idx].xPosition = Double(xPositions[n.id] ?? CGFloat(n.location))
                gradient.nodes[idx].yPosition = Double(yPositions[n.id] ?? defaultY(for: n))
            }
            
            // Update color based on position to ensure consistency
            updateNodeColorFromPosition(n, width: width, height: height, center: center, radius: radius)
        }
        // purge removed
        xPositions = xPositions.filter { pair in gradient.nodes.contains { $0.id == pair.key } }
        yPositions = yPositions.filter { pair in gradient.nodes.contains { $0.id == pair.key } }
    }

    private func updateNodeFromCanvasDrag(_ node: GradientNode, newX: CGFloat, newY: CGFloat, absolute: CGPoint, center: CGPoint, radius: CGFloat) {
        guard let idx = gradient.nodes.firstIndex(where: { $0.id == node.id }) else { return }
        
        // Check for haptic feedback triggers on invisible gridlines
        checkHapticFeedback(newX: newX, newY: newY)
        
        // Save visual positions in both the dictionaries (for immediate UI updates) and the model (for persistence)
        xPositions[node.id] = newX
        yPositions[node.id] = newY
        gradient.nodes[idx].xPosition = Double(newX)
        gradient.nodes[idx].yPosition = Double(newY)
        
        // DON'T update the persistent location - keep the node's role in the gradient fixed!
        // The location property determines the node's position in the gradient (primary, secondary, etc.)
        // and should NOT change when dragging on the canvas
        
        selectedNodeID = node.id
        
        // Mark the currently dragged node as the active primary for background rendering
        let designatedPrimary = primaryNodeID()
        if node.id == designatedPrimary {
            gradientColorManager.activePrimaryNodeID = node.id
        }

        // Map position on circle to HSL color - this is the key fix
        let hsla = colorFromCircle(point: absolute, center: center, radius: radius, lightness: lightness)
        let updated = colorWithPreservedAlpha(oldHex: gradient.nodes[idx].colorHex, newColor: hsla)
        gradient.nodes[idx].colorHex = updated

        // Auto-place companions only when dragging the designated primary
        // But don't change their gradient positions - they should maintain their 1st, 2nd, 3rd roles
        if gradient.nodes.count == 3, node.id == primaryNodeID() {
            autoPlaceCompanions(primary: node, center: center, radius: radius)
        } else if gradient.nodes.count == 2, node.id == primaryNodeID() {
            autoPlaceCompanions(primary: node, center: center, radius: radius)
        }

        // Push live update to background
        gradientColorManager.setImmediate(gradient)
    }

    private func checkHapticFeedback(newX: CGFloat, newY: CGFloat) {
        #if canImport(AppKit)
        // Simple gridlines that trigger haptic feedback
        let gridLines: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0] // Major gridlines
        let tolerance: CGFloat = 0.02 // How close you need to be to trigger
        
        // Check X-axis gridlines
        for line in gridLines {
            if abs(newX - line) < tolerance {
                if lastHapticSpoke != "x_\(line)" {
                    NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .default)
                    lastHapticSpoke = "x_\(line)"
                }
                break
            }
        }
        
        // Check Y-axis gridlines
        for line in gridLines {
            if abs(newY - line) < tolerance {
                if lastHapticRadial != "y_\(line)" {
                    NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .default)
                    lastHapticRadial = "y_\(line)"
                }
                break
            }
        }
        
        // Reset haptic state when moving away from gridlines
        if lastHapticSpoke != nil && !gridLines.contains(where: { abs(newX - $0) < tolerance }) {
            lastHapticSpoke = nil
        }
        if lastHapticRadial != nil && !gridLines.contains(where: { abs(newY - $0) < tolerance }) {
            lastHapticRadial = nil
        }
        #endif
    }

    private func clampToCircle(point: CGPoint, center: CGPoint, radius: CGFloat) -> CGPoint {
        let dx = point.x - center.x
        let dy = point.y - center.y
        let dist = sqrt(dx*dx + dy*dy)
        if dist <= radius { return point }
        let angle = atan2(dy, dx)
        return CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)
    }

    private func autoPlaceCompanions(primary: GradientNode, center: CGPoint, radius: CGFloat) {
        guard gradient.nodes.count > 1, let pX = xPositions[primary.id], let pY = yPositions[primary.id] else { return }
        let others = gradient.nodes.filter { $0.id != primary.id }
        if gradient.nodes.count == 3 {
            // Place both companions on the same circular radius as primary, across with small angular spread
            let centerNorm = CGPoint(x: 0.5, y: 0.5)
            let dx = pX - centerNorm.x
            let dy = pY - centerNorm.y
            let baseAngle = atan2(dy, dx)
            let r = min(0.49, sqrt(dx*dx + dy*dy)) // same normalized radius
            let opposite = baseAngle + .pi
            let spread: CGFloat = 0.6 // ~34°
            let angles = [opposite - spread, opposite + spread]
            for (i, other) in others.enumerated() where i < 2 {
                let ang = angles[i]
                let pos = CGPoint(x: centerNorm.x + cos(ang) * r, y: centerNorm.y + sin(ang) * r)
                // Only update visual positions, NOT the gradient location
                xPositions[other.id] = pos.x
                yPositions[other.id] = pos.y
                // DON'T update gradient.nodes[idx].location - keep their 1st, 2nd, 3rd roles fixed!
                
                // Update the companion's color to match its new position
                if let idx = gradient.nodes.firstIndex(where: { $0.id == other.id }) {
                    // Save positions to the model
                    gradient.nodes[idx].xPosition = Double(pos.x)
                    gradient.nodes[idx].yPosition = Double(pos.y)
                    
                    let absolute = CGPoint(x: pos.x * (center.x * 2), y: pos.y * (center.y * 2))
                    let hsla = colorFromCircle(point: absolute, center: center, radius: radius, lightness: lightness)
                    let updated = colorWithPreservedAlpha(oldHex: gradient.nodes[idx].colorHex, newColor: hsla)
                    gradient.nodes[idx].colorHex = updated
                }
            }
        } else {
            // Two nodes: place the companion opposite side
            let p = CGPoint(x: pX, y: pY)
            let baseAngle = atan2(p.y - 0.5, p.x - 0.5)
            let dist = min(0.5, sqrt(pow(p.x - 0.5, 2) + pow(p.y - 0.5, 2)))
            let ang = baseAngle + .pi
            let pos = CGPoint(x: 0.5 + cos(ang) * dist, y: 0.5 + sin(ang) * dist)
            if let other = others.first {
                // Only update visual positions, NOT the gradient location
                xPositions[other.id] = pos.x
                yPositions[other.id] = pos.y
                // DON'T update gradient.nodes[idx].location - keep their 1st, 2nd roles fixed!
                
                // Update the companion's color to match its new position
                if let idx = gradient.nodes.firstIndex(where: { $0.id == other.id }) {
                    // Save positions to the model
                    gradient.nodes[idx].xPosition = Double(pos.x)
                    gradient.nodes[idx].yPosition = Double(pos.y)
                    
                    let absolute = CGPoint(x: pos.x * (center.x * 2), y: pos.y * (center.y * 2))
                    let hsla = colorFromCircle(point: absolute, center: center, radius: radius, lightness: lightness)
                    let updated = colorWithPreservedAlpha(oldHex: gradient.nodes[idx].colorHex, newColor: hsla)
                    gradient.nodes[idx].colorHex = updated
                }
            }
        }
    }

    private func primaryNodeID() -> UUID? {
        if let locked = lockedPrimaryID { return locked }
        if gradient.nodes.count <= 2 {
            return gradientColorManager.preferredPrimaryNodeID ?? gradient.nodes.min(by: { $0.location < $1.location })?.id
        }
        return gradient.nodes.min(by: { $0.location < $1.location })?.id
    }

    private func colorFromCircle(point: CGPoint, center: CGPoint, radius: CGFloat, lightness: Double) -> String {
        #if canImport(AppKit)
        let dx = point.x - center.x
        let dy = point.y - center.y
        var angle = atan2(dy, dx)
        if angle < 0 { angle += 2 * .pi }
        
        // Map angle to hue (0-360 degrees)
        let hue = Double(angle / (2 * .pi))
        
        // Calculate distance from center (0 = center, 1 = edge)
        let dist = min(1.0, Double(sqrt(dx*dx + dy*dy) / radius))
        
        // More intuitive color mapping:
        // - Saturation: High at center (vivid colors), very low at edges (pastels)
        // - Brightness: Much higher at edges for light pastel effect
        let saturation = max(0.1, min(1.0, 1.0 - 0.8 * dist))  // 0.1-1.0 range (very low saturation at edges)
        let brightness = max(0.3, min(1.0, lightness + 0.4 * dist))  // Much brighter at edges for pastels
        
        let ns = NSColor(hue: CGFloat(hue), saturation: CGFloat(saturation), brightness: CGFloat(brightness), alpha: 1)
        return ns.toHexString(includeAlpha: true) ?? "#FFFFFFFF"
        #else
        return "#FFFFFFFF"
        #endif
    }

    private func updateNodeColorFromPosition(_ node: GradientNode, width: CGFloat, height: CGFloat, center: CGPoint, radius: CGFloat) {
        guard let idx = gradient.nodes.firstIndex(where: { $0.id == node.id }),
              let xPos = xPositions[node.id],
              let yPos = yPositions[node.id] else { return }
        
        let absolute = CGPoint(x: xPos * width, y: yPos * height)
        let hsla = colorFromCircle(point: absolute, center: center, radius: radius, lightness: lightness)
        let updated = colorWithPreservedAlpha(oldHex: gradient.nodes[idx].colorHex, newColor: hsla)
        gradient.nodes[idx].colorHex = updated
    }

    private func colorWithPreservedAlpha(oldHex: String, newColor: String) -> String {
        let aOld = Color(hex: oldHex)
        #if canImport(AppKit)
        var oa: CGFloat = 1
        var orv: CGFloat = 0, ogv: CGFloat = 0, obv: CGFloat = 0
        NSColor(aOld).usingColorSpace(.sRGB)?.getRed(&orv, green: &ogv, blue: &obv, alpha: &oa)
        var nr: CGFloat = 1, ng: CGFloat = 1, nb: CGFloat = 1, na: CGFloat = 1
        NSColor(Color(hex: newColor)).usingColorSpace(.sRGB)?.getRed(&nr, green: &ng, blue: &nb, alpha: &na)
        let combined = NSColor(srgbRed: nr, green: ng, blue: nb, alpha: oa)
        return combined.toHexString(includeAlpha: true) ?? newColor
        #else
        return newColor
        #endif
    }

    private func addNode() {
        guard gradient.nodes.count < 3 else { return }
        let source = selectedNodeID.flatMap { id in gradient.nodes.first(where: { $0.id == id }) }
        let color = source?.colorHex ?? gradient.nodes.first?.colorHex ?? "#FFFFFFFF"
        
        // Assign gradient positions based on current count to maintain order
        let gradientPosition: Double
        if gradient.nodes.count == 0 {
            gradientPosition = 0.0  // First node is primary (0.0)
        } else if gradient.nodes.count == 1 {
            gradientPosition = 1.0  // Second node is secondary (1.0)
        } else {
            gradientPosition = 0.5  // Third node goes in the middle (0.5)
        }
        
        let new = GradientNode(id: UUID(), colorHex: color, location: gradientPosition, xPosition: 0.5, yPosition: 0.5)
        gradient.nodes.append(new)
        gradient.nodes.sort { $0.location < $1.location }
        selectedNodeID = new.id
        xPositions[new.id] = 0.5
        yPositions[new.id] = 0.5
        
        // Update color based on position after a brief delay to ensure layout is complete
        DispatchQueue.main.async {
            // We'll update the color in the next layout cycle when positions are properly set
            self.gradientColorManager.setImmediate(self.gradient)
        }
        
        gradientColorManager.setImmediate(gradient)
        // Update preferred primary mapping based on new count
        if gradient.nodes.count == 3 {
            if lockedPrimaryID == nil { lockedPrimaryID = gradient.nodes.min(by: { $0.location < $1.location })?.id }
            gradientColorManager.preferredPrimaryNodeID = lockedPrimaryID
        } else {
            gradientColorManager.preferredPrimaryNodeID = nil
        }
    }

    private func removeNode() {
        guard gradient.nodes.count > 1 else { return }
        if let id = selectedNodeID {
            gradient.nodes.removeAll { $0.id == id }
            yPositions.removeValue(forKey: id)
            selectedNodeID = gradient.nodes.first?.id
        } else {
            let removed = gradient.nodes.removeLast()
            yPositions.removeValue(forKey: removed.id)
            selectedNodeID = gradient.nodes.last?.id
        }
        gradientColorManager.setImmediate(gradient)
        // Update preferred primary mapping based on new count
        if gradient.nodes.count == 3 {
            if lockedPrimaryID == nil || (lockedPrimaryID != nil && !gradient.nodes.contains(where: { $0.id == lockedPrimaryID })) {
                lockedPrimaryID = gradient.nodes.min(by: { $0.location < $1.location })?.id
            }
            gradientColorManager.preferredPrimaryNodeID = lockedPrimaryID
        } else {
            lockedPrimaryID = nil
            gradientColorManager.preferredPrimaryNodeID = nil
        }
    }

    private func stops() -> [Gradient.Stop] {
        gradient.nodes
            .sorted(by: { $0.location < $1.location })
            .map { Gradient.Stop(color: Color(hex: $0.colorHex), location: CGFloat($0.location)) }
    }

    private func startPoint() -> UnitPoint {
        let theta = Angle(degrees: gradient.angle).radians
        return UnitPoint(x: 0.5 - 0.5 * cos(theta), y: 0.5 - 0.5 * sin(theta))
    }

    private func endPoint() -> UnitPoint {
        let theta = Angle(degrees: gradient.angle).radians
        return UnitPoint(x: 0.5 + 0.5 * cos(theta), y: 0.5 + 0.5 * sin(theta))
    }
}

// MARK: - Handle
private struct Handle: View {
    let colorHex: String
    let selected: Bool
    let size: CGFloat

    var body: some View {
        Circle()
            .fill(Color(hex: colorHex))
            .frame(width: size, height: size)
            .overlay(
                Circle().strokeBorder(Color.white, lineWidth: 4)
            )
            .overlay(
                Circle().strokeBorder(selected ? Color.accentColor : Color.white.opacity(0), lineWidth: 2)
            )
            .shadow(color: .black.opacity(0.15), radius: 4, x: 0, y: 1)
            .contentShape(Circle())
    }
}

// MARK: - DotGrid
private struct DotGrid: View {
    var body: some View {
        GeometryReader { proxy in
            let w = proxy.size.width
            let h = proxy.size.height
            let spacing: CGFloat = 10
            Canvas { ctx, _ in
                let cols = Int(w / spacing)
                let rows = Int(h / spacing)
                let dot = Path(ellipseIn: CGRect(x: 0, y: 0, width: 1.5, height: 1.5))
                for r in 0...rows {
                    for c in 0...cols {
                        let x = CGFloat(c) * spacing + 2
                        let y = CGFloat(r) * spacing + 2
                        ctx.translateBy(x: x, y: y)
                        ctx.fill(dot, with: .color(Color.black.opacity(0.08)))
                        ctx.translateBy(x: -x, y: -y)
                    }
                }
            }
        }
    }
}

```

## /Nook/Components/ColorPicker/GradientEditorView.swift

```swift path="/Nook/Components/ColorPicker/GradientEditorView.swift" 
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif

// MARK: - GradientEditorView
// Composes preview, node/angle controls, color grid, and transparency/grain
struct GradientEditorView: View {
    @Binding var gradient: SpaceGradient
    @State private var selectedNodeID: UUID?
    @EnvironmentObject var gradientColorManager: GradientColorManager

    // No throttling: update in real time

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            GradientCanvasEditor(gradient: $gradient, selectedNodeID: $selectedNodeID, showDitherOverlay: false)

            ColorSwatchRowView(selectedColor: selectedColor()) { color in
                applyColorSelection(color)
            }

            // Global transparency for the whole gradient layer
            TransparencySlider(gradient: $gradient)
        }
        .padding(16)
        .onAppear { if selectedNodeID == nil { selectedNodeID = gradient.nodes.first?.id } }
        .onChange(of: gradient) { _, newValue in
            // Scrubbing should be immediate to avoid animation token races
            gradientColorManager.setImmediate(newValue)
        }
        .onAppear {
            // Ensure background starts from the current draft gradient
            gradientColorManager.setImmediate(gradient)
            gradientColorManager.beginInteractivePreview()
        }
        .onDisappear {
            gradientColorManager.endInteractivePreview()
        }
    }

    // MARK: - Selection Helpers
    private func selectedNodeIndex() -> Int? {
        if let id = selectedNodeID { return gradient.nodes.firstIndex(where: { $0.id == id }) }
        return gradient.nodes.indices.first
    }

    private func bindingSelectedNode() -> Binding<GradientNode?> {
        Binding<GradientNode?>(
            get: {
                if let idx = selectedNodeIndex() { return gradient.nodes[idx] }
                return nil
            },
            set: { newValue in
                guard let node = newValue, let idx = selectedNodeIndex() else { return }
                gradient.nodes[idx] = node
            }
        )
    }

    private func selectedColor() -> Color? {
        guard let idx = selectedNodeIndex() else { return nil }
        return Color(hex: gradient.nodes[idx].colorHex)
    }

    private func applyColorSelection(_ color: Color) {
        guard let idx = selectedNodeIndex() else { return }
        #if canImport(AppKit)
        // Preserve existing alpha from current node
        let currentNS = NSColor(Color(hex: gradient.nodes[idx].colorHex)).usingColorSpace(.sRGB)
        var oldA: CGFloat = 1.0
        var cr: CGFloat = 1, cg: CGFloat = 1, cb: CGFloat = 1
        currentNS?.getRed(&cr, green: &cg, blue: &cb, alpha: &oldA)

        // Extract new RGB from selected Color
        let newNS = NSColor(color).usingColorSpace(.sRGB)
        var nr: CGFloat = 1, ng: CGFloat = 1, nb: CGFloat = 1, na: CGFloat = 1
        newNS?.getRed(&nr, green: &ng, blue: &nb, alpha: &na)

        let combined = NSColor(srgbRed: nr, green: ng, blue: nb, alpha: oldA)
        gradient.nodes[idx].colorHex = combined.toHexString(includeAlpha: true) ?? gradient.nodes[idx].colorHex
        #endif
    }

    // No bespoke hex helpers: rely on Color(hex:) and NSColor.toHexString
}

```

## /Nook/Components/ColorPicker/GradientNodePicker.swift

```swift path="/Nook/Components/ColorPicker/GradientNodePicker.swift" 
import SwiftUI

// MARK: - GradientNodePicker
// Manage 1-3 gradient nodes and a rotatable angle dial
struct GradientNodePicker: View {
    @Binding var gradient: SpaceGradient
    @Binding var selectedNodeID: UUID?

    private let swatchSize: CGFloat = 40

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            HStack(spacing: 12) {
                nodeSwatches
                Spacer()
                HStack(spacing: 8) {
                    Button(action: removeNode) {
                        Image(systemName: "minus.circle")
                    }
                    .buttonStyle(.plain)
                    .help("Remove node")
                    .disabled(gradient.nodes.count <= 1)

                    Button(action: addNode) {
                        Image(systemName: "plus.circle")
                    }
                    .buttonStyle(.plain)
                    .help("Add node")
                    .disabled(gradient.nodes.count >= 3)
                }
            }

            angleDial

            VStack(alignment: .leading, spacing: 12) {
                ForEach(gradient.nodes) { node in
                    HStack {
                        Circle()
                            .fill(Color(hex: node.colorHex))
                            .frame(width: 14, height: 14)
                        Slider(value: binding(for: node), in: 0...1)
                        Text(String(format: "%.0f%%", (node.location * 100)))
                            .font(.caption)
                            .foregroundStyle(.secondary)
                            .frame(width: 44, alignment: .trailing)
                    }
                }
            }
        }
        .onAppear { if selectedNodeID == nil { selectedNodeID = gradient.nodes.first?.id } }
    }

    // MARK: - Swatches
    private var nodeSwatches: some View {
        HStack(spacing: 8) {
            ForEach(gradient.nodes) { node in
                let isSelected = node.id == selectedNodeID
                Circle()
                    .fill(Color(hex: node.colorHex))
                    .frame(width: swatchSize, height: swatchSize)
                    .overlay(Circle().strokeBorder(isSelected ? Color.accentColor : Color.primary.opacity(0.15), lineWidth: isSelected ? 3 : 1))
                    .shadow(color: .black.opacity(0.08), radius: 3, x: 0, y: 1)
                    .onTapGesture { selectedNodeID = node.id }
                    .contextMenu {
                        Button("Delete", role: .destructive) { removeSpecific(node) }
                            .disabled(gradient.nodes.count <= 1)
                    }
            }
        }
    }

    // MARK: - Angle Dial
    private var angleDial: some View {
        GeometryReader { proxy in
            let size = min(proxy.size.width, proxy.size.height, 140)
            ZStack {
                Circle()
                    .fill(.thinMaterial)
                Circle()
                    .strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)

                // Handle
                let angle = Angle(degrees: gradient.angle)
                let handle = point(onCircleOf: size/2 - 8, angle: angle)
                Circle()
                    .fill(Color.accentColor)
                    .frame(width: 12, height: 12)
                    .position(x: size/2 + handle.x, y: size/2 + handle.y)

                // Direction line
                Path { p in
                    p.move(to: CGPoint(x: size/2, y: size/2))
                    p.addLine(to: CGPoint(x: size/2 + handle.x, y: size/2 + handle.y))
                }
                .stroke(Color.accentColor.opacity(0.7), lineWidth: 2)
            }
            .frame(width: size, height: size)
            .contentShape(Circle())
            .gesture(DragGesture(minimumDistance: 0).onChanged { value in
                let center = CGPoint(x: size/2, y: size/2)
                let dx = value.location.x - center.x
                let dy = value.location.y - center.y
                let degrees = atan2(dy, dx) * 180 / .pi
                // Convert from atan2 (0 at +x) to SwiftUI gradient angle convention
                var adjusted = degrees
                if adjusted < 0 { adjusted += 360 }
                gradient.angle = Double(adjusted)
            })
        }
        .frame(height: 160)
    }

    private func point(onCircleOf radius: CGFloat, angle: Angle) -> CGPoint {
        let r = radius
        let a = CGFloat(angle.radians)
        return CGPoint(x: cos(a) * r, y: sin(a) * r)
    }

    // MARK: - Node CRUD
    private func addNode() {
        guard gradient.nodes.count < 3 else { return }
        let color = gradient.nodes.first?.colorHex ?? "#FFFFFFFF"
        let new = GradientNode(id: UUID(), colorHex: color, location: min(1, max(0, (gradient.nodes.last?.location ?? 0.5) + 0.2)))
        gradient.nodes.append(new)
        selectedNodeID = new.id
        gradient.nodes.sort { $0.location < $1.location }
    }

    private func removeNode() {
        guard gradient.nodes.count > 1 else { return }
        if let id = selectedNodeID, let idx = gradient.nodes.firstIndex(where: { $0.id == id }) {
            gradient.nodes.remove(at: idx)
            selectedNodeID = gradient.nodes.first?.id
        } else {
            _ = gradient.nodes.popLast()
            selectedNodeID = gradient.nodes.last?.id
        }
    }

    private func removeSpecific(_ node: GradientNode) {
        guard gradient.nodes.count > 1 else { return }
        gradient.nodes.removeAll { $0.id == node.id }
        selectedNodeID = gradient.nodes.first?.id
    }

    private func binding(for node: GradientNode) -> Binding<Double> {
        Binding<Double>(
            get: {
                gradient.nodes.first(where: { $0.id == node.id })?.location ?? node.location
            },
            set: { newValue in
                if let idx = gradient.nodes.firstIndex(where: { $0.id == node.id }) {
                    gradient.nodes[idx].location = newValue
                    gradient.nodes.sort { $0.location < $1.location }
                }
            }
        )
    }
}

```

## /Nook/Components/ColorPicker/GradientPreview.swift

```swift path="/Nook/Components/ColorPicker/GradientPreview.swift" 
import SwiftUI

// MARK: - GradientPreview
// Live preview for SpaceGradient with grain overlay
struct GradientPreview: View {
    @Binding var gradient: SpaceGradient
    var showDitherOverlay: Bool = true

    private let cornerRadius: CGFloat = 12
    private let size = CGSize(width: 300, height: 160)

    var body: some View {
        ZStack {
            BarycentricGradientView(gradient: gradient)
                .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))

            if showDitherOverlay {
                Image("noise_texture")
                    .resizable()
                    .scaledToFill()
                    .opacity(max(0, min(1, gradient.grain)))
                    .blendMode(.overlay)
                    .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
                    .allowsHitTesting(false)
            }

            RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                .strokeBorder(Color.primary.opacity(0.12), lineWidth: 1)
        }
        .frame(width: size.width, height: size.height)
        .shadow(color: .black.opacity(0.08), radius: 8, x: 0, y: 4)
        .drawingGroup()
        .opacity(max(0.0, min(1.0, gradient.opacity)))
    }
}

```

## /Nook/Components/ColorPicker/GrainDial.swift

```swift path="/Nook/Components/ColorPicker/GrainDial.swift" 
import SwiftUI

// MARK: - GrainDial
// Circular dial mapping rotation to 0...1 grain value
struct GrainDial: View {
    @Binding var grain: Double // 0...1

    var body: some View {
        VStack(spacing: 8) {
            ZStack {
                GeometryReader { proxy in
                    let size = min(proxy.size.width, proxy.size.height)
                    dial(size: size)
                }
            }
            .frame(height: 120)
            Text("Grain: " + String(format: "%.0f%%", grain * 100))
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    }

    private func dial(size: CGFloat) -> some View {
        ZStack {
            Circle().fill(.thinMaterial)
            Circle().strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)
            let angle = Angle(degrees: grain * 360)
            let handle = point(onCircleOf: size/2 - 8, angle: angle)

            Path { p in
                p.move(to: CGPoint(x: size/2, y: size/2))
                p.addLine(to: CGPoint(x: size/2 + handle.x, y: size/2 + handle.y))
            }
            .stroke(Color.accentColor.opacity(0.7), lineWidth: 2)

            Circle()
                .fill(Color.accentColor)
                .frame(width: 12, height: 12)
                .position(x: size/2 + handle.x, y: size/2 + handle.y)
        }
        .frame(width: size, height: size)
        .contentShape(Circle())
        .gesture(DragGesture(minimumDistance: 0).onChanged { value in
            let center = CGPoint(x: size/2, y: size/2)
            let dx = value.location.x - center.x
            let dy = value.location.y - center.y
            var degrees = atan2(dy, dx) * 180 / .pi
            if degrees < 0 { degrees += 360 }
            grain = max(0, min(1, Double(degrees) / 360.0))
        })
    }

    private func point(onCircleOf radius: CGFloat, angle: Angle) -> CGPoint {
        let r = radius
        let a = CGFloat(angle.radians)
        return CGPoint(x: cos(a) * r, y: sin(a) * r)
    }
}


```

## /Nook/Components/ColorPicker/GrainSlider.swift

```swift path="/Nook/Components/ColorPicker/GrainSlider.swift" 
import SwiftUI

// MARK: - GrainSlider
// Custom horizontal slider with a sine-wave track and vertical white thumb
struct GrainSlider: View {
    @Binding var value: Double
    @StateObject private var dragLockManager = DragLockManager.shared
    @State private var dragSessionID: String = UUID().uuidString
    @State private var isDragging = false // 0...1

    var body: some View {
        GeometryReader { proxy in
            let w = proxy.size.width
            let h = proxy.size.height

            ZStack {
                // Track background
                RoundedRectangle(cornerRadius: h/2, style: .continuous)
                    .fill(Color.black.opacity(0.08))

                // Interpolated wave: amplitude goes 0 -> max with value
                let amplitude = max(0.001, value) * (h * 0.22)
                InterpolatedWave(amplitude: amplitude)
                    .stroke(
                        LinearGradient(colors: [
                            Color.black.opacity(0.15),
                            Color.black.opacity(0.45)
                        ], startPoint: .leading, endPoint: .trailing),
                        lineWidth: 3
                    )
                    .padding(.horizontal, 16)

                // Thumb
                let x = CGFloat(value) * w
                RoundedRectangle(cornerRadius: 6, style: .continuous)
                    .fill(Color.white)
                    .frame(width: 14 + CGFloat(value) * 10, height: (h - 8) + CGFloat(value) * 6)
                    .position(x: min(max(9, x), w - 9), y: h/2)
                    .shadow(color: .black.opacity(0.12), radius: 1, x: 0, y: 1)
            }
            .gesture(DragGesture(minimumDistance: 0).onChanged { g in
                if !isDragging {
                    guard dragLockManager.startDrag(ownerID: dragSessionID) else {
                        print("🚫 [GrainSlider] Drag blocked - \(dragLockManager.debugInfo)")
                        return
                    }
                    isDragging = true
                }
                let x = min(max(0, g.location.x), w)
                value = Double(x / w)
            }.onEnded { _ in
                isDragging = false
                dragLockManager.endDrag(ownerID: dragSessionID)
            })
        }
        .frame(height: 44)
    }
}

private struct InterpolatedWave: Shape {
    let amplitude: CGFloat // 0 = line, else sine amplitude
    func path(in rect: CGRect) -> Path {
        var p = Path()
        let midY = rect.midY
        let length = rect.width
        p.move(to: CGPoint(x: rect.minX, y: midY))
        let step: CGFloat = 2
        let period: CGFloat = 18
        for x in stride(from: CGFloat(0), through: length, by: step) {
            let y = sin(x / period) * amplitude + midY
            p.addLine(to: CGPoint(x: rect.minX + x, y: y))
        }
        return p
    }
}

```

## /Nook/Components/ColorPicker/TransparencySlider.swift

```swift path="/Nook/Components/ColorPicker/TransparencySlider.swift" 
import SwiftUI

// MARK: - TransparencySlider
// Controls global opacity of the gradient layer
struct TransparencySlider: View {
    @Binding var gradient: SpaceGradient
    @EnvironmentObject var gradientColorManager: GradientColorManager
    @State private var localOpacity: Double = 1.0

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Text("Opacity")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                Spacer()
                Text(String(format: "%.0f%%", localOpacity * 100))
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            HStack(spacing: 12) {
                opacityPreview
                    .frame(width: 48, height: 28)
                    .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))

                Slider(value: $localOpacity, in: 0...1)
            }
        }
        .onAppear { localOpacity = clamp(gradient.opacity) }
        .onChange(of: gradient.opacity) { _, newValue in localOpacity = clamp(newValue) }
        .onChange(of: localOpacity) { _, newValue in
            gradient.opacity = clamp(newValue)
            // Push live background update immediately
            gradientColorManager.setImmediate(gradient)
        }
    }

    private var opacityPreview: some View {
        ZStack {
            CheckerboardBackground()
            // Lightweight gradient preview inline
            let pts = linePoints(angle: gradient.angle)
            Rectangle()
                .fill(LinearGradient(gradient: Gradient(stops: stops()), startPoint: pts.start, endPoint: pts.end))
                .opacity(clamp(localOpacity))
            RoundedRectangle(cornerRadius: 6, style: .continuous)
                .strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)
        }
    }

    private func clamp(_ v: Double) -> Double { min(1.0, max(0.0, v)) }

    private func stops() -> [Gradient.Stop] {
        var mapped: [Gradient.Stop] = gradient.sortedNodes.map { node in
            Gradient.Stop(color: Color(hex: node.colorHex), location: CGFloat(node.location))
        }
        if mapped.count == 0 {
            let def = SpaceGradient.default
            mapped = def.sortedNodes.map { node in
                Gradient.Stop(color: Color(hex: node.colorHex), location: CGFloat(node.location))
            }
        } else if mapped.count == 1 {
            let single = mapped[0]
            mapped = [
                Gradient.Stop(color: single.color, location: 0.0),
                Gradient.Stop(color: single.color, location: 1.0)
            ]
        }
        return mapped
    }

    private func linePoints(angle: Double) -> (start: UnitPoint, end: UnitPoint) {
        let theta = Angle(degrees: angle).radians
        let dx = cos(theta)
        let dy = sin(theta)
        let start = UnitPoint(x: 0.5 - 0.5 * dx, y: 0.5 - 0.5 * dy)
        let end = UnitPoint(x: 0.5 + 0.5 * dx, y: 0.5 + 0.5 * dy)
        return (start, end)
    }
}

// MARK: - Checkerboard Background
private struct CheckerboardBackground: View {
    var body: some View {
        GeometryReader { proxy in
            let size = proxy.size
            let tile: CGFloat = 6
            let cols = Int(ceil(size.width / tile))
            let rows = Int(ceil(size.height / tile))
            Canvas { context, _ in
                for r in 0..<rows {
                    for c in 0..<cols {
                        let isDark = (r + c) % 2 == 0
                        let rect = CGRect(x: CGFloat(c) * tile, y: CGFloat(r) * tile, width: tile, height: tile)
                        context.fill(Path(rect), with: .color(isDark ? Color.black.opacity(0.08) : Color.white.opacity(0.9)))
                    }
                }
            }
        }
    }
}

```

## /Nook/Components/CommandPalette/CommandPaletteSuggestionView.swift

```swift path="/Nook/Components/CommandPalette/CommandPaletteSuggestionView.swift" 
//
//  CommandPaletteSuggestionView.swift
//  Nook
//
//  Created by Maciek Bagiński on 31/07/2025.
//

import SwiftUI
import FaviconFinder

struct CommandPaletteSuggestionView: View {
    var favicon: SwiftUI.Image
    var text: String
    var secondaryText: String? = nil
    var isTabSuggestion: Bool = false
    var isSelected: Bool = false
    var historyURL: URL? = nil
    @State private var isHovered: Bool = false
    @State private var resolvedFavicon: SwiftUI.Image? = nil
    
    var body: some View {
        HStack(alignment: .center,spacing: 12) {
            (resolvedFavicon ?? favicon)
                .resizable()
                .scaledToFit()
                .frame(width: 14, height: 14)
                .foregroundStyle(.white.opacity(0.2))
            if let secondary = secondaryText, !secondary.isEmpty {
                HStack(spacing: 6) {
                    Text(text)
                        .font(.system(size: 14, weight: .medium))
                        .lineLimit(1)
                        .truncationMode(.tail)
                    Text("-")
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(.white.opacity(0.35))
                    Text(secondary)
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(.white.opacity(0.5))
                        .lineLimit(1)
                        .truncationMode(.tail)
                }
            } else {
                Text(text)
                    .font(.system(size: 14, weight: .medium))
                    .lineLimit(1)
                    .truncationMode(.tail)
            }
            
            Spacer()
            
            if isTabSuggestion {
                HStack(spacing: 6) {
                    Text("Switch to Tab")
                        .font(.system(size: 12, weight: .medium))
                        .foregroundStyle(.white.opacity(0.5))
                    
                    Image(systemName: "arrow.right")
                        .font(.system(size: 8, weight: .medium))
                        .foregroundStyle(.white.opacity(0.5))
                }
            }
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
        .frame(maxWidth: .infinity)
        .background(backgroundColor)
        .clipShape(RoundedRectangle(cornerRadius: 8))
            
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovered = hovering
            }
        }
        .onAppear {
            guard let url = historyURL else { return }
            Task { await fetchFavicon(for: url) }
        }
    }
    
    private var backgroundColor: Color {
        if isSelected {
            return Color.white.opacity(0.25)
        } else if isHovered {
            return Color.white.opacity(0.15)
        } else {
            return Color.clear
        }
    }
    
    // MARK: - Favicon Fetching (for history items)
    private func fetchFavicon(for url: URL) async {
        let defaultFavicon = SwiftUI.Image(systemName: "globe")
        // Skip favicon fetching for non-web schemes
        guard url.scheme == "http" || url.scheme == "https", url.host != nil else {
            await MainActor.run { self.resolvedFavicon = defaultFavicon }
            return
        }
        
        // Check cache first
        let cacheKey = url.host ?? url.absoluteString
        if let cachedFavicon = Tab.getCachedFavicon(for: cacheKey) {
            await MainActor.run { self.resolvedFavicon = cachedFavicon }
            return
        }
        
        do {
            let favicon = try await FaviconFinder(url: url)
                .fetchFaviconURLs()
                .download()
                .largest()
            if let faviconImage = favicon.image {
                let nsImage = faviconImage.image
                let swiftUIImage = SwiftUI.Image(nsImage: nsImage)
                
                // Cache the favicon
                Tab.cacheFavicon(swiftUIImage, for: cacheKey)
                
                await MainActor.run { self.resolvedFavicon = swiftUIImage }
            } else {
                await MainActor.run { self.resolvedFavicon = defaultFavicon }
            }
        } catch {
            await MainActor.run { self.resolvedFavicon = defaultFavicon }
        }
    }
    
}

```

## /Nook/Components/CommandPalette/CommandPaletteView.swift

```swift path="/Nook/Components/CommandPalette/CommandPaletteView.swift" 
//
//  CommandPaletteView.swift
//  Nook
//
//  Created by Maciek Bagiński on 28/07/2025.
//

import AppKit
import SwiftUI

struct CommandPaletteView: View {
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState
    @EnvironmentObject var gradientColorManager: GradientColorManager
    @State private var searchManager = SearchManager()
    @Environment(\.colorScheme) var colorScheme

    @FocusState private var isSearchFocused: Bool
    @State private var text: String = ""
    @State private var selectedSuggestionIndex: Int = -1
    @State private var hoveredSuggestionIndex: Int? = nil
    
    let commandPaletteWidth: CGFloat = 765
    let commandPaletteHorizontalPadding: CGFloat = 10
    
    /// Active window width
    private var currentWindowWidth: CGFloat {
        return NSApplication.shared.keyWindow?.frame.width ?? 0
    }
    
    /// Check if the command palette fits in the window
    private var isWindowTooNarrow: Bool {
        let requiredWidth = commandPaletteWidth + (commandPaletteHorizontalPadding * 2)
        return currentWindowWidth <= requiredWidth
    }
    
    /// Caclulate the correct command palette width
    private var effectiveCommandPaletteWidth: CGFloat {
        if isWindowTooNarrow {
            return max(200, currentWindowWidth - (commandPaletteHorizontalPadding * 2))
        } else {
            return commandPaletteWidth
        }
    }

    var body: some View {
        let isDark = colorScheme == .dark
        let isActiveWindow =
            browserManager.activeWindowState?.id == windowState.id
        let isVisible = isActiveWindow && windowState.isCommandPaletteVisible

        ZStack {
            Color.clear
                .ignoresSafeArea()
                .contentShape(Rectangle())
                .onTapGesture {
                    browserManager.closeCommandPalette(for: windowState)
                }
                .gesture(WindowDragGesture())

            VStack {
                Spacer()
                HStack {
                    Spacer()
                    VStack {
                        VStack(alignment: .center,spacing: 6) {
                            // Input field - fixed at top of box
                            HStack(spacing: 15) {
                                Image(
                                    systemName: isLikelyURL(text)
                                        ? "globe" : "magnifyingglass"
                                )
                                .id(isLikelyURL(text) ? "globe" : "magnifyingglass")
                                .transition(.blur(intensity: 2, scale: 0.6).animation(.smooth(duration: 0.3)))
                                .font(.system(size: 14, weight: .regular))
                                .foregroundStyle(isDark ? .white : .black)
                                .frame(width: 15)

                                TextField("Search or enter URL...", text: $text)
                                    .textFieldStyle(.plain)
                                    .font(.system(size: 18, weight: .medium))
                                    .foregroundColor(
                                        text.isEmpty
                                            ? isDark
                                                ? .white.opacity(0.25)
                                                : .black.opacity(0.25)
                                            : isDark
                                                ? .white.opacity(0.9)
                                                : .black.opacity(0.9)

                                    )
                                    .tint(gradientColorManager.primaryColor)
                                    .focused($isSearchFocused)
                                    .onKeyPress(.return) {
                                        handleReturn()
                                        return .handled
                                    }
                                    .onKeyPress(.upArrow) {
                                        navigateSuggestions(direction: -1)
                                        return .handled
                                    }
                                    .onKeyPress(.downArrow) {
                                        navigateSuggestions(direction: 1)
                                        return .handled
                                    }
                                    .onChange(of: text) { _, newValue in
                                        selectedSuggestionIndex = -1
                                        searchManager.searchSuggestions(
                                            for: newValue
                                        )
                                        if windowState.commandPalettePrefilledText
                                            != newValue
                                        {
                                            windowState
                                                .commandPalettePrefilledText =
                                                newValue
                                        }
                                    }
                            }
                            .padding(.vertical, 8)
                            .padding(.horizontal, 8)

                            // Separator
                            if !searchManager.suggestions.isEmpty {
                                RoundedRectangle(cornerRadius: 100)
                                    .fill(
                                        isDark
                                            ? Color.white.opacity(0.4)
                                            : Color.black.opacity(0.4)
                                    )
                                    .frame(height: 0.5)
                                    .frame(maxWidth: .infinity)

                            }

                            // Suggestions - expand the box downward
                            if !searchManager.suggestions.isEmpty {
                                let suggestions = searchManager.suggestions
                                CommandPaletteSuggestionsListView(
                                    suggestions: suggestions,
                                    selectedIndex: $selectedSuggestionIndex,
                                    hoveredIndex: $hoveredSuggestionIndex,
                                    onSelect: { suggestion in
                                        selectSuggestion(suggestion)
                                    }
                                )
                            }
                        }
                        .padding(10)
                        .frame(maxWidth: .infinity)
                        .frame(width: effectiveCommandPaletteWidth)
                        .background(.thickMaterial)
                        .clipShape(RoundedRectangle(cornerRadius: 12))
                        .overlay(
                            RoundedRectangle(cornerRadius: 12)
                                .stroke(
                                    Color.white.opacity(isDark ? 0.3 : 0.6),
                                    lineWidth: 0.5
                                )
                        )
                        .shadow(color: .black.opacity(0.4), radius: 50, x: 0, y: 4)
                        .animation(
                            .easeInOut(duration: 0.15),
                            value: searchManager.suggestions.count
                        )
                        Spacer()
                            .border(.red)
                    }
                    .frame(
                        width: effectiveCommandPaletteWidth,
                        height: 328
                    )

                    Spacer()
                }
                Spacer()
            }

        }
        .allowsHitTesting(isVisible)
        .opacity(isVisible ? 1.0 : 0.0)
        .onChange(of: windowState.isCommandPaletteVisible) { _, newVisible in
            if newVisible && isActiveWindow {
                searchManager.setTabManager(browserManager.tabManager)
                searchManager.setHistoryManager(browserManager.historyManager)
                searchManager.updateProfileContext()

                // Pre-fill text if provided and select all for easy replacement
                text = windowState.commandPalettePrefilledText

                DispatchQueue.main.async {
                    isSearchFocused = true
                    // Select all once focused so the URL is highlighted
                    DispatchQueue.main.async {
                        NSApplication.shared.sendAction(
                            #selector(NSText.selectAll(_:)),
                            to: nil,
                            from: nil
                        )
                    }
                }
            } else {
                isSearchFocused = false
                searchManager.clearSuggestions()
                text = ""
                selectedSuggestionIndex = -1
            }
        }
        // Keep search profile context updated while palette is open
        .onChange(of: browserManager.currentProfile?.id) { _, _ in
            if windowState.isCommandPaletteVisible {
                searchManager.updateProfileContext()
                // Clear suggestions to avoid cross-profile residue
                searchManager.clearSuggestions()
            }
        }
        .onKeyPress(.escape) {
            DispatchQueue.main.async {
                browserManager.closeCommandPalette(for: windowState)
            }
            return .handled
        }
        .onChange(of: searchManager.suggestions.count) { _, newCount in
            if newCount == 0 {
                selectedSuggestionIndex = -1
            } else if selectedSuggestionIndex >= newCount {
                selectedSuggestionIndex = -1
            }
        }
        .animation(.easeInOut(duration: 0.15), value: selectedSuggestionIndex)
        .onChange(of: windowState.commandPalettePrefilledText) { _, newValue in
            if isVisible {
                text = newValue
                DispatchQueue.main.async {
                    isSearchFocused = true
                }
            }
        }
    }

    private func isEmoji(_ string: String) -> Bool {
        return string.unicodeScalars.contains { scalar in
            (scalar.value >= 0x1F300 && scalar.value <= 0x1F9FF)
                || (scalar.value >= 0x2600 && scalar.value <= 0x26FF)
                || (scalar.value >= 0x2700 && scalar.value <= 0x27BF)
        }
    }

    // MARK: - Suggestions List Subview
    private struct CommandPaletteSuggestionsListView: View {
        @EnvironmentObject var gradientColorManager: GradientColorManager
        let suggestions: [SearchManager.SearchSuggestion]
        @Binding var selectedIndex: Int
        @Binding var hoveredIndex: Int?
        @Environment(\.colorScheme) var colorScheme
        let onSelect: (SearchManager.SearchSuggestion) -> Void

        var body: some View {
            let isDark = colorScheme == .dark
            LazyVStack(spacing: 5) {
                ForEach(suggestions.indices, id: \.self) { index in
                    let suggestion = suggestions[index]
                    let isHovered = hoveredIndex == index
                    row(for: suggestion, isSelected: selectedIndex == index)
                        .padding(.horizontal, 10)
                        .padding(.vertical, 11)
                        .background(
                            selectedIndex == index
                                ? gradientColorManager.primaryColor
                                : isHovered
                                    ? isDark
                                        ? .white.opacity(0.05)
                                        : .black.opacity(0.05) : .clear
                        )
                        .clipShape(
                            RoundedRectangle(cornerRadius: 6)
                        )
                        .font(.system(size: 13, weight: .semibold))
                        .foregroundStyle(.white)
                        .contentShape(RoundedRectangle(cornerRadius: 6))
                        .onHover { hovering in
                            withAnimation(.easeInOut(duration: 0.12)) {
                                if hovering {
                                    hoveredIndex = index
                                } else {
                                    hoveredIndex = nil
                                }
                            }
                        }
                        .onTapGesture { onSelect(suggestion) }
                }
            }
        }

        @ViewBuilder
        private func row(
            for suggestion: SearchManager.SearchSuggestion,
            isSelected: Bool
        ) -> some View {
            switch suggestion.type {
            case .tab(let tab):
                TabSuggestionItem(tab: tab, isSelected: isSelected)
            case .history(let entry):
                HistorySuggestionItem(entry: entry, isSelected: isSelected)
            case .url:
                GenericSuggestionItem(
                    icon: Image(systemName: "link"),
                    text: suggestion.text,
                    isSelected: isSelected
                )
            case .search:
                GenericSuggestionItem(
                    icon: Image(systemName: "magnifyingglass"),
                    text: suggestion.text,
                    isSelected: isSelected
                )
            }
        }
    }

    private func handleReturn() {
        if selectedSuggestionIndex >= 0
            && selectedSuggestionIndex < searchManager.suggestions.count
        {
            let suggestion = searchManager.suggestions[selectedSuggestionIndex]
            selectSuggestion(suggestion)
        } else {
            // Create new suggestion from text input
            let newSuggestion = SearchManager.SearchSuggestion(
                text: text,
                type: isLikelyURL(text) ? .url : .search
            )
            selectSuggestion(newSuggestion)
        }
    }

    private func selectSuggestion(_ suggestion: SearchManager.SearchSuggestion)
    {
        switch suggestion.type {
        case .tab(let existingTab):
            // Switch to existing tab in this window
            browserManager.selectTab(existingTab, in: windowState)
            print("Switched to existing tab: \(existingTab.name)")
        case .history(let historyEntry):
            if windowState.shouldNavigateCurrentTab
                && browserManager.currentTab(for: windowState) != nil
            {
                // Navigate current tab to history URL
                browserManager.currentTab(for: windowState)?.loadURL(
                    historyEntry.url.absoluteString
                )
                print(
                    "Navigated current tab to history URL: \(historyEntry.url)"
                )
            } else {
                // Create new tab from history entry
                browserManager.createNewTab(in: windowState)
                browserManager.currentTab(for: windowState)?.loadURL(
                    historyEntry.url.absoluteString
                )
                print(
                    "Created new tab from history in window \(windowState.id)"
                )
            }
        case .url, .search:
            if windowState.shouldNavigateCurrentTab
                && browserManager.currentTab(for: windowState) != nil
            {
                // Navigate current tab to new URL with proper normalization
                browserManager.currentTab(for: windowState)?.navigateToURL(
                    suggestion.text
                )
                print("Navigated current tab to: \(suggestion.text)")
            } else {
                // Create new tab
                browserManager.createNewTab(in: windowState)
                browserManager.currentTab(for: windowState)?.navigateToURL(
                    suggestion.text
                )
                print("Created new tab in window \(windowState.id)")
            }
        }

        text = ""
        selectedSuggestionIndex = -1
        browserManager.closeCommandPalette(for: windowState)
    }

    private func navigateSuggestions(direction: Int) {
        let maxIndex = searchManager.suggestions.count - 1

        if direction > 0 {
            selectedSuggestionIndex = min(selectedSuggestionIndex + 1, maxIndex)
        } else {
            selectedSuggestionIndex = max(selectedSuggestionIndex - 1, -1)
        }
    }

    private func iconForSuggestion(_ suggestion: SearchManager.SearchSuggestion)
        -> Image
    {
        switch suggestion.type {
        case .tab(let tab):
            return tab.favicon
        case .history:
            return Image(systemName: "globe")
        case .url:
            return Image(systemName: "link")
        case .search:
            return Image(systemName: "magnifyingglass")
        }
    }

    @ViewBuilder
    private func suggestionRow(
        for suggestion: SearchManager.SearchSuggestion,
        isSelected: Bool
    ) -> some View {
        switch suggestion.type {
        case .tab(let tab):
            TabSuggestionItem(tab: tab, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        case .history(let entry):
            HistorySuggestionItem(entry: entry, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        case .url:
            GenericSuggestionItem(
                icon: Image(systemName: "link"),
                text: suggestion.text,
                isSelected: isSelected
            )
            .foregroundStyle(AppColors.textPrimary)
        case .search:
            GenericSuggestionItem(
                icon: Image(systemName: "magnifyingglass"),
                text: suggestion.text,
                isSelected: isSelected
            )
            .foregroundStyle(AppColors.textPrimary)
        }
    }

    private func urlForSuggestion(_ suggestion: SearchManager.SearchSuggestion)
        -> URL?
    {
        switch suggestion.type {
        case .history(let entry):
            return entry.url
        default:
            return nil
        }
    }

    private func isTabSuggestion(_ suggestion: SearchManager.SearchSuggestion)
        -> Bool
    {
        switch suggestion.type {
        case .tab:
            return true
        case .search, .url, .history:
            return false
        }
    }
}

```

## /Nook/Components/CommandPalette/GenericSuggestionItem.swift

```swift path="/Nook/Components/CommandPalette/GenericSuggestionItem.swift" 
//
//  GenericSuggestionItem.swift
//  Nook
//
//  Created by Maciek Bagiński on 18/08/2025.
//

import SwiftUI

struct GenericSuggestionItem: View {
    let icon: Image
    let text: String
    var isSelected: Bool = false
    
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        let isDark = colorScheme == .dark
        HStack(alignment: .center, spacing: 12) {
            ZStack {
                icon
                    .resizable()
                    .scaledToFit()
                    .frame(width: 14, height: 14)
                    .foregroundStyle(isSelected ? .white : isDark ? .white.opacity(0.7) : .black.opacity(0.7))
            }
            .frame(width: 24, height: 24)
            .clipShape(RoundedRectangle(cornerRadius: 4))

            Text(text)
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(isSelected ? .white : isDark ? .white.opacity(0.6) : .black.opacity(0.8))
                .lineLimit(1)
                .truncationMode(.tail)
            Spacer()
        }
        .frame(maxWidth: .infinity)
    }
}

```

## /Nook/Components/CommandPalette/HistorySuggestionItem.swift

```swift path="/Nook/Components/CommandPalette/HistorySuggestionItem.swift" 
//
//  HistorySuggestionItem.swift
//  Nook
//
//  Created by Maciek Bagiński on 18/08/2025.
//

import SwiftUI
import FaviconFinder

struct HistorySuggestionItem: View {
    let entry: HistoryEntry
    var isSelected: Bool = false
    
    @State private var isHovered: Bool = false
    @State private var resolvedFavicon: SwiftUI.Image? = nil
    @Environment(\.colorScheme) var colorScheme
    
    // Color configuration
    private var colors: ColorConfig {
        ColorConfig(
            isDark: colorScheme == .dark,
            isSelected: isSelected,
            isHovered: isHovered
        )
    }
    
    var body: some View {
        HStack(alignment: .center, spacing: 9) {
            ZStack {
                (resolvedFavicon ?? Image(systemName: "globe"))
                    .resizable()
                    .scaledToFit()
                    .foregroundStyle(colors.faviconColor)
                    .frame(width: 14, height: 14)
            }
            .frame(width: 24, height: 24)
            .background(colors.faviconBackground)
            .clipShape(RoundedRectangle(cornerRadius: 4))
            
            HStack(spacing: 4) {
                Text(entry.displayTitle)
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(colors.titleColor)
                    .lineLimit(1)
                    .truncationMode(.tail)
                
                Text("-")
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(colors.urlColor)
                
                Text(entry.displayURL)
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(colors.urlColor)
                    .lineLimit(1)
                    .truncationMode(.tail)
            }
            Spacer()
        }
        .frame(maxWidth: .infinity)
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovered = hovering
            }
        }
        .onAppear {
            Task { await fetchFavicon(for: entry.url) }
        }
    }
    
    private func fetchFavicon(for url: URL) async {
        let defaultFavicon = SwiftUI.Image(systemName: "globe")
        guard url.scheme == "http" || url.scheme == "https", url.host != nil else {
            await MainActor.run { self.resolvedFavicon = defaultFavicon }
            return
        }
        
        let cacheKey = url.host ?? url.absoluteString
        if let cachedFavicon = Tab.getCachedFavicon(for: cacheKey) {
            await MainActor.run { self.resolvedFavicon = cachedFavicon }
            return
        }
        
        do {
            let favicon = try await FaviconFinder(url: url)
                .fetchFaviconURLs()
                .download()
                .largest()
            if let faviconImage = favicon.image {
                let nsImage = faviconImage.image
                let swiftUIImage = SwiftUI.Image(nsImage: nsImage)
                
                Tab.cacheFavicon(swiftUIImage, for: cacheKey)
                
                await MainActor.run { self.resolvedFavicon = swiftUIImage }
            } else {
                await MainActor.run { self.resolvedFavicon = defaultFavicon }
            }
        } catch {
            await MainActor.run { self.resolvedFavicon = defaultFavicon }
        }
    }
}

// MARK: - Colors simplified
private struct ColorConfig {
    let isDark: Bool
    let isSelected: Bool
    let isHovered: Bool
    
    var titleColor: Color {
        if isSelected {
            return .white
        }
        return isDark ? .white : .black
    }
    
    var urlColor: Color {
        if isSelected {
            return .white.opacity(0.5)
        }
        return isDark ? .white.opacity(0.3) : .black.opacity(0.3)
    }
    
    var faviconColor: Color {
        return .white.opacity(0.5)
    }
    
    var faviconBackground: Color {
        return isSelected ? .white : .clear
    }
}

```

## /Nook/Components/CommandPalette/MiniCommandPaletteView.swift

```swift path="/Nook/Components/CommandPalette/MiniCommandPaletteView.swift" 
//
//  MiniCommandPaletteView.swift
//  Nook
//
//  A compact command palette anchored to the URL bar.
//

import SwiftUI

//
//  MiniCommandPaletteView.swift
//  Nook
//
//  A compact command palette anchored to the URL bar.
//

import SwiftUI

struct MiniCommandPaletteView: View {
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState
    @EnvironmentObject var gradientColorManager: GradientColorManager
    @State private var searchManager = SearchManager()
    @Environment(\.colorScheme) var colorScheme

    @FocusState private var isSearchFocused: Bool
    @State private var text: String = ""
    @State private var selectedSuggestionIndex: Int = -1
    @State private var hoveredSuggestionIndex: Int? = nil

    // Will be overridden by overlay to match URL bar width
    var forcedWidth: CGFloat? = nil
    var forcedCornerRadius: CGFloat? = nil

    var body: some View {
        let isDark = colorScheme == .dark
        let symbolName = isLikelyURL(text) ? "globe" : "magnifyingglass"
        let isActiveWindow = browserManager.activeWindowState?.id == windowState.id
        let suggestions = searchManager.suggestions

        VStack(spacing: 6) {
            inputRow(symbolName: symbolName)
            separatorIfNeeded(hasSuggestions: !suggestions.isEmpty)
            suggestionsListView(suggestions: suggestions)
        }
        .padding(10)
        .frame(width: forcedWidth ?? 460)
        .background(.thickMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 12))
        .overlay(
            RoundedRectangle(cornerRadius: 12)
                .stroke(
                    Color.white.opacity(isDark ? 0.3 : 0.6),
                    lineWidth: 0.5
                )
        )
        .shadow(color: .black.opacity(0.4), radius: 50, x: 0, y: 4)
        .onAppear {
            // Wire managers
            searchManager.setTabManager(browserManager.tabManager)
            searchManager.setHistoryManager(browserManager.historyManager)
            searchManager.updateProfileContext()

            // Ensure prefill and focus when the mini palette is presented
            text = windowState.commandPalettePrefilledText
            DispatchQueue.main.async { isSearchFocused = true }
        }
        .onChange(of: windowState.isMiniCommandPaletteVisible) { _, newVisible in
            if newVisible && isActiveWindow {
                searchManager.setTabManager(browserManager.tabManager)
                searchManager.setHistoryManager(browserManager.historyManager)
                searchManager.updateProfileContext()
                // Pre-fill and focus
                text = windowState.commandPalettePrefilledText
                DispatchQueue.main.async { isSearchFocused = true }
            } else {
                isSearchFocused = false
                searchManager.clearSuggestions()
                text = ""
                selectedSuggestionIndex = -1
            }
        }
        .onKeyPress(.escape) {
            DispatchQueue.main.async { browserManager.hideMiniCommandPalette(for: windowState) }
            return .handled
        }
        .onChange(of: searchManager.suggestions.count) { _, newCount in
            if newCount == 0 {
                selectedSuggestionIndex = -1
            } else if selectedSuggestionIndex >= newCount {
                selectedSuggestionIndex = -1
            }
        }
        .onChange(of: windowState.commandPalettePrefilledText) { _, newValue in
            if isActiveWindow && windowState.isMiniCommandPaletteVisible {
                text = newValue
            }
        }
        .onChange(of: browserManager.currentProfile?.id) { _, _ in
            if isActiveWindow && windowState.isMiniCommandPaletteVisible {
                searchManager.updateProfileContext()
                searchManager.clearSuggestions()
            }
        }
    }

    private func inputRow(symbolName: String) -> some View {
        HStack(spacing: 15) {
            Image(systemName: symbolName)
                .font(.system(size: 14, weight: .regular))
                .foregroundStyle(colorScheme == .dark ? .white : .black)

            TextField("Search or enter URL...", text: $text)
                .textFieldStyle(.plain)
                .font(.system(size: 18, weight: .medium))
                .foregroundColor(
                    text.isEmpty
                    ? colorScheme == .dark
                            ? .white.opacity(0.25)
                            : .black.opacity(0.25)
                    : colorScheme == .dark
                            ? .white.opacity(0.9)
                            : .black.opacity(0.9)

                )
                .tint(gradientColorManager.primaryColor)
                .focused($isSearchFocused)
                .onKeyPress(.return) {
                    handleReturn()
                    return .handled
                }
                .onKeyPress(.upArrow) {
                    navigateSuggestions(direction: -1)
                    return .handled
                }
                .onKeyPress(.downArrow) {
                    navigateSuggestions(direction: 1)
                    return .handled
                }
                .onChange(of: text) { _, newValue in
                    selectedSuggestionIndex = -1
                    searchManager.searchSuggestions(for: newValue)
                    if windowState.commandPalettePrefilledText != newValue {
                        windowState.commandPalettePrefilledText = newValue
                    }
                }
        }
        .padding(.vertical, 12)
        .padding(.horizontal, 12)
    }

    @ViewBuilder
    private func separatorIfNeeded(hasSuggestions: Bool) -> some View {
        if hasSuggestions {
            RoundedRectangle(cornerRadius: 100)
                .fill(
                    colorScheme == .dark
                        ? Color.white.opacity(0.4)
                        : Color.black.opacity(0.4)
                )
                .frame(height: 0.5)
                .frame(maxWidth: .infinity)
        }
    }

    @ViewBuilder
    private func suggestionsListView(suggestions: [SearchManager.SearchSuggestion]) -> some View {
        if suggestions.isEmpty {
            EmptyView()
        } else {
            LazyVStack(spacing: 5) {
                ForEach(suggestions.indices, id: \.self) { index in
                    let suggestion = suggestions[index]
                    suggestionRow(for: suggestion, isSelected: selectedSuggestionIndex == index)
                        .padding(.horizontal, 10)
                        .padding(.vertical, 11)
                        .background(
                            selectedSuggestionIndex == index
                                ? gradientColorManager.primaryColor
                                : hoveredSuggestionIndex == index
                            ? colorScheme == .dark
                                        ? .white.opacity(0.05)
                                        : .black.opacity(0.05) : .clear
                        )                        .cornerRadius(8)
                        .font(.system(size: 13, weight: .semibold))
                        .foregroundStyle(.white)
                        .contentShape(Rectangle())
                        .onHover { hovering in
                            withAnimation(.easeInOut(duration: 0.12)) {
                                if hovering {
                                    hoveredSuggestionIndex = index
                                } else {
                                    hoveredSuggestionIndex = nil
                                }
                            }
                        }
                        .onTapGesture { selectSuggestion(suggestion) }
                }
            }
        }
    }

    private func handleReturn() {
        if selectedSuggestionIndex >= 0 && selectedSuggestionIndex < searchManager.suggestions.count {
            selectSuggestion(searchManager.suggestions[selectedSuggestionIndex])
        } else {
            let newSuggestion = SearchManager.SearchSuggestion(
                text: text,
                type: isLikelyURL(text) ? .url : .search
            )
            selectSuggestion(newSuggestion)
        }
    }

    private func selectSuggestion(_ suggestion: SearchManager.SearchSuggestion) {
        switch suggestion.type {
        case .tab(let existingTab):
            browserManager.selectTab(existingTab, in: windowState)
        case .history(let historyEntry):
            if windowState.shouldNavigateCurrentTab && browserManager.currentTab(for: windowState) != nil {
                browserManager.currentTab(for: windowState)?.loadURL(historyEntry.url.absoluteString)
            } else {
                browserManager.createNewTab(in: windowState)
                browserManager.currentTab(for: windowState)?.loadURL(historyEntry.url.absoluteString)
            }
        case .url, .search:
            if windowState.shouldNavigateCurrentTab && browserManager.currentTab(for: windowState) != nil {
                browserManager.currentTab(for: windowState)?.navigateToURL(suggestion.text)
            } else {
                browserManager.createNewTab(in: windowState)
                browserManager.currentTab(for: windowState)?.navigateToURL(suggestion.text)
            }
        }

        text = ""
        selectedSuggestionIndex = -1
        browserManager.hideMiniCommandPalette(for: windowState)
    }

    private func navigateSuggestions(direction: Int) {
        let maxIndex = searchManager.suggestions.count - 1
        if direction > 0 {
            selectedSuggestionIndex = min(selectedSuggestionIndex + 1, maxIndex)
        } else {
            selectedSuggestionIndex = max(selectedSuggestionIndex - 1, -1)
        }
    }

    @ViewBuilder
    private func suggestionRow(for suggestion: SearchManager.SearchSuggestion, isSelected: Bool) -> some View {
        switch suggestion.type {
        case .tab(let tab):
            TabSuggestionItem(tab: tab, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        case .history(let entry):
            HistorySuggestionItem(entry: entry, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        case .url:
            GenericSuggestionItem(icon: Image(systemName: "link"), text: suggestion.text, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        case .search:
            GenericSuggestionItem(icon: Image(systemName: "magnifyingglass"), text: suggestion.text, isSelected: isSelected)
                .foregroundStyle(AppColors.textPrimary)
        }
    }
}

```

## /Nook/Components/CommandPalette/TabSuggestionItem.swift

```swift path="/Nook/Components/CommandPalette/TabSuggestionItem.swift" 
//
//  TabSuggestionItem.swift
//  Nook
//
//  Created by Maciek Bagiński on 18/08/2025.
//

import SwiftUI

struct TabSuggestionItem: View {
    let tab: Tab
    var isSelected: Bool = false
    
    @State private var isHovered: Bool = false
    @Environment(\.colorScheme) var colorScheme
    @EnvironmentObject var gradientColorManager: GradientColorManager
    
    var body: some View {
        let isDark = colorScheme == .dark
        
        HStack(alignment: .center, spacing: 0) {
            HStack(spacing: 9) {
                ZStack {
                    tab.favicon
                        .resizable()
                        .scaledToFit()
                        .foregroundStyle(.white.opacity(0.5))
                        .frame(width: 14, height: 14)
                }
                .frame(width: 24, height: 24)
                .background(isSelected ? .white : .clear)
                .clipShape(
                    RoundedRectangle(cornerRadius: 4)
                )
                Text(tab.name)
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(isSelected ? .white : isDark ? .white.opacity(0.6) : .black.opacity(0.8))
                    .lineLimit(1)
                    .truncationMode(.tail)
            }
            Spacer()
            HStack(spacing: 10) {
                Text("Switch to Tab")
                    .font(.system(size: 12, weight: .medium))
                    .foregroundStyle(isSelected ? .white : isDark ? .white.opacity(0.3) : .black.opacity(0.3))
                ZStack {
                    Image(systemName: "arrow.right")
                        .font(.system(size: 13, weight: .semibold))
                        .foregroundStyle(isSelected ? gradientColorManager.primaryColor : isDark ? .white.opacity(0.5) : .black.opacity(0.5))
                        .frame(width: 16, height: 16)
                }
                .frame(width: 24, height: 24)
                .background(isSelected ? .white : isDark ? .white.opacity(0.05) : .black.opacity(0.05))
                .clipShape(RoundedRectangle(cornerRadius: 4))

            }
        }
        .frame(maxWidth: .infinity)
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovered = hovering
            }
        }
    }
}

```

## /Nook/Components/Dialog/DialogView.swift

```swift path="/Nook/Components/Dialog/DialogView.swift" 
//
//  DialogView.swift
//  Nook
//
//  Created by Maciek Bagiński on 04/08/2025.
//

import SwiftUI

struct DialogView: View {
    @EnvironmentObject var browserManager: BrowserManager

    var body: some View {
        ZStack {
            if browserManager.dialogManager.isVisible,
               let dialog = browserManager.dialogManager.activeDialog {
                overlayBackground
                dialogContent(dialog)
                    .transition(.asymmetric(
                        insertion: .offset(y: 30).combined(with: .blur(intensity: 3, scale: 1)),
                        removal: .offset(y: -30).combined(with: .blur(intensity: 3, scale: 1))
                    ))
                    .zIndex(1)
            }
        }
        .animation(.bouncy(duration: 0.2, extraBounce: -0.1), value: browserManager.dialogManager.isVisible)
    }

    @ViewBuilder
    private var overlayBackground: some View {
        Color.black.opacity(0.4)
            .ignoresSafeArea()
            .onTapGesture {
                browserManager.dialogManager.closeDialog()
            }
            .transition(.opacity)
    }

    @ViewBuilder
    private func dialogContent(_ dialog: AnyView) -> some View {
        HStack {
            Spacer()
            dialog
            Spacer()
        }
    }
}

```

## /Nook/Components/DragDrop/DragDropPreview.swift

```swift path="/Nook/Components/DragDrop/DragDropPreview.swift" 
//
//  DragDropPreview.swift
//  Nook
//
//  Live preview for testing the advanced drag & drop system
//

import SwiftUI
import AppKit

// MARK: - Mock Models

class MockTab: Identifiable, ObservableObject {
    let id = UUID()
    @Published var name: String
    @Published var favicon: String
    @Published var index: Int
    @Published var spaceId: UUID?
    var url: URL = URL(string: "https://example.com")!
    
    init(name: String, favicon: String, index: Int = 0, spaceId: UUID? = nil) {
        self.name = name
        self.favicon = favicon
        self.index = index
        self.spaceId = spaceId
    }
}

struct MockSpace: Identifiable {
    let id = UUID()
    let name: String
    let icon: String
    
    init(name: String, icon: String = "square.grid.2x2") {
        self.name = name
        self.icon = icon
    }
}

// MARK: - Mock Tab Manager

@MainActor
class MockTabManager: ObservableObject {
    @Published var globalPinnedTabs: [MockTab] = []
    @Published var spacePinnedTabs: [UUID: [MockTab]] = [:]
    @Published var regularTabs: [UUID: [MockTab]] = [:]
    @Published var spaces: [MockSpace] = []
    @Published var currentSpaceId: UUID?
    
    var currentSpace: MockSpace? {
        spaces.first { $0.id == currentSpaceId }
    }
    
    init() {
        setupMockData()
    }
    
    private func setupMockData() {
        // Create spaces
        let devSpace = MockSpace(name: "Development", icon: "hammer")
        let personalSpace = MockSpace(name: "Personal", icon: "person")
        spaces = [devSpace, personalSpace]
        currentSpaceId = devSpace.id
        
        // Global pinned tabs
        globalPinnedTabs = [
            MockTab(name: "GitHub", favicon: "externaldrive.connected.to.line.below", index: 0),
            MockTab(name: "Gmail", favicon: "envelope", index: 1),
            MockTab(name: "Calendar", favicon: "calendar", index: 2)
        ]
        
        // Space pinned tabs
        spacePinnedTabs[devSpace.id] = [
            MockTab(name: "Stack Overflow", favicon: "questionmark.circle", index: 0, spaceId: devSpace.id),
            MockTab(name: "Documentation", favicon: "book", index: 1, spaceId: devSpace.id)
        ]
        
        spacePinnedTabs[personalSpace.id] = [
            MockTab(name: "Reddit", favicon: "bubble.left.and.bubble.right", index: 0, spaceId: personalSpace.id)
        ]
        
        // Regular tabs
        regularTabs[devSpace.id] = [
            MockTab(name: "Claude", favicon: "brain", index: 0, spaceId: devSpace.id),
            MockTab(name: "OpenAI", favicon: "lightbulb", index: 1, spaceId: devSpace.id),
            MockTab(name: "Anthropic", favicon: "sparkles", index: 2, spaceId: devSpace.id)
        ]
        
        regularTabs[personalSpace.id] = [
            MockTab(name: "YouTube", favicon: "play.rectangle", index: 0, spaceId: personalSpace.id),
            MockTab(name: "Netflix", favicon: "tv", index: 1, spaceId: personalSpace.id)
        ]
    }
    
    func handleDragOperation(_ operation: DragOperation) {
#if DEBUG
        print("🎯 Mock drag operation: \(operation.fromContainer) → \(operation.toContainer) at \(operation.toIndex)")
#endif
        
        guard let mockTab = mockTab(for: operation.tab.id) else {
#if DEBUG
            print("❌ Mock tab not found for drag operation: \(operation.tab.id)")
#endif
            return
        }
        
        // Mock implementation - just reorder within same container for now
        switch (operation.fromContainer, operation.toContainer) {
        case (.essentials, .essentials):
            reorderGlobalPinned(mockTab, to: operation.toIndex)
            
        case (.spacePinned(let spaceId), .spacePinned(let toSpaceId)) where spaceId == toSpaceId:
            reorderSpacePinned(mockTab, in: spaceId, to: operation.toIndex)
            
        case (.spaceRegular(let spaceId), .spaceRegular(let toSpaceId)) where spaceId == toSpaceId:
            reorderRegular(mockTab, in: spaceId, to: operation.toIndex)
            
        default:
#if DEBUG
            print("Cross-container moves not implemented in preview")
#endif
        }
    }
    
    private func reorderGlobalPinned(_ tab: MockTab, to index: Int) {
        guard let currentIndex = globalPinnedTabs.firstIndex(where: { $0.id == tab.id }) else { return }
        globalPinnedTabs.remove(at: currentIndex)
        let clampedIndex = min(max(index, 0), globalPinnedTabs.count)
        globalPinnedTabs.insert(tab, at: clampedIndex)
        updateIndices(&globalPinnedTabs)
    }
    
    private func reorderSpacePinned(_ tab: MockTab, in spaceId: UUID, to index: Int) {
        guard var tabs = spacePinnedTabs[spaceId],
              let currentIndex = tabs.firstIndex(where: { $0.id == tab.id }) else { return }
        tabs.remove(at: currentIndex)
        let clampedIndex = min(max(index, 0), tabs.count)
        tabs.insert(tab, at: clampedIndex)
        updateIndices(&tabs)
        spacePinnedTabs[spaceId] = tabs
    }
    
    private func reorderRegular(_ tab: MockTab, in spaceId: UUID, to index: Int) {
        guard var tabs = regularTabs[spaceId],
              let currentIndex = tabs.firstIndex(where: { $0.id == tab.id }) else { return }
        tabs.remove(at: currentIndex)
        let clampedIndex = min(max(index, 0), tabs.count)
        tabs.insert(tab, at: clampedIndex)
        updateIndices(&tabs)
        regularTabs[spaceId] = tabs
    }
    
    private func mockTab(for id: UUID) -> MockTab? {
        if let tab = globalPinnedTabs.first(where: { $0.id == id }) {
            return tab
        }
        for (_, tabs) in spacePinnedTabs {
            if let tab = tabs.first(where: { $0.id == id }) {
                return tab
            }
        }
        for (_, tabs) in regularTabs {
            if let tab = tabs.first(where: { $0.id == id }) {
                return tab
            }
        }
        return nil
    }
    
    private func updateIndices(_ tabs: inout [MockTab]) {
        for (index, tab) in tabs.enumerated() {
            tab.index = index
        }
    }
}

// MARK: - Mock Tab View

struct MockTabView: View {
    @ObservedObject var tab: MockTab
    let action: () -> Void
    @State private var isHovering: Bool = false
    
    var body: some View {
        Button(action: action) {
            HStack(spacing: 8) {
                Image(systemName: tab.favicon)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 16, height: 16)
                    .foregroundColor(.secondary)
                
                Text(tab.name)
                    .font(.system(size: 13, weight: .medium))
                    .foregroundColor(.primary)
                    .lineLimit(1)
                
                Spacer()
                
                if isHovering {
                    Button(action: { 
#if DEBUG
print("Close \(tab.name)")
#endif
 }) {
                        Image(systemName: "xmark")
                            .font(.system(size: 8, weight: .medium))
                            .foregroundColor(.primary)
                            .padding(3)
                            .background(Color.gray.opacity(0.3))
                            .clipShape(RoundedRectangle(cornerRadius: 4))
                    }
                    .buttonStyle(PlainButtonStyle())
                    .transition(.scale.combined(with: .opacity))
                }
            }
            .padding(.horizontal, 8)
            .frame(height: 32)
            .frame(maxWidth: .infinity)
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .fill(isHovering ? Color.gray.opacity(0.1) : Color.clear)
            )
        }
        .buttonStyle(PlainButtonStyle())
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovering = hovering
            }
        }
        .contextMenu {
            Button("Move Up") { 
#if DEBUG
print("Move \(tab.name) up")
#endif
 }
            Button("Move Down") { 
#if DEBUG
print("Move \(tab.name) down")
#endif
 }
            Divider()
            Button("Pin to Space") { 
#if DEBUG
print("Pin \(tab.name) to space")
#endif
 }
            Button("Pin Globally") { 
#if DEBUG
print("Pin \(tab.name) globally")
#endif
 }
        }
    }
}

// MARK: - Mock Pinned Tab View

struct MockPinnedTabView: View {
    @ObservedObject var tab: MockTab
    let action: () -> Void
    @State private var isHovering: Bool = false
    
    var body: some View {
        Button(action: action) {
            ZStack {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.gray.opacity(0.2))
                    .frame(width: 44, height: 44)
                
                Image(systemName: tab.favicon)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 20, height: 20)
                    .foregroundColor(.primary)
            }
        }
        .buttonStyle(PlainButtonStyle())
        .scaleEffect(isHovering ? 1.05 : 1.0)
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovering = hovering
            }
        }
        .contextMenu {
            Button("Unpin") { 
#if DEBUG
print("Unpin \(tab.name)")
#endif
 }
        }
    }
}

// MARK: - Drop Zone View
struct DropZoneView: View {
    @Binding var isTargeted: Bool
    let onDrop: () -> Bool
    
    var body: some View {
        Rectangle()
            .fill(isTargeted ? Color.accentColor : Color.clear)
            .frame(height: 3)
            .animation(.easeInOut(duration: 0.2), value: isTargeted)
    }
}

// MARK: - Main Preview View

struct DragDropPreview: View {
    @StateObject private var dragManager = TabDragManager()
    @StateObject private var tabManager = MockTabManager()
    
    var body: some View {
        TabDragContainerView(
            dragManager: dragManager,
            onDragCompleted: handleDragCompleted
        ) {
            VStack(spacing: 16) {
                Text("🧛 Dragula Preview")
                    .font(.title2)
                    .fontWeight(.bold)
                
                VStack(spacing: 12) {
                    // Global Pinned Tabs (Essentials)
                    VStack(alignment: .leading, spacing: 8) {
                        Text("Essential Tabs (Global)")
                            .font(.headline)
                            .foregroundColor(.secondary)
                        
                        LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 4), spacing: 8) {
                            ForEach(tabManager.globalPinnedTabs.indices, id: \.self) { index in
                                let tab = tabManager.globalPinnedTabs[index]
                                MockPinnedTabView(tab: tab) {
#if DEBUG
                                    print("Activated: \(tab.name)")
#endif
                                }
                                .onDrag {
                                    dragManager.startDrag(tab: convertToRealTab(tab), from: .essentials, at: index)
                                    return NSItemProvider(object: tab.id.uuidString as NSString)
                                }
                                .onDrop(of: [.text], isTargeted: nil) { providers, location in
                                    handleDrop(providers: providers, toContainer: .essentials, atIndex: index)
                                }
                            }
                            
                            // Drop zone at the end of essentials
                            Rectangle()
                                .fill(Color.clear)
                                .frame(width: 44, height: 44)
                                .onDrop(of: [.text], isTargeted: nil) { providers, location in
                                    handleDrop(providers: providers, toContainer: .essentials, atIndex: tabManager.globalPinnedTabs.count)
                                }
                        }
                    }
                    .padding()
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(12)
                    
                    Divider()
                    
                    // Current Space
                    if let currentSpace = tabManager.currentSpace {
                        VStack(alignment: .leading, spacing: 12) {
                            Text("Space: \(currentSpace.name)")
                                .font(.headline)
                                .foregroundColor(.primary)
                            
                            // Space Pinned Tabs
                            if let spacePinned = tabManager.spacePinnedTabs[currentSpace.id], !spacePinned.isEmpty {
                                VStack(alignment: .leading, spacing: 4) {
                                    Text("Pinned in Space")
                                        .font(.subheadline)
                                        .foregroundColor(.secondary)
                                    
                                    ForEach(spacePinned.indices, id: \.self) { index in
                                        let tab = spacePinned[index]
                                        VStack(spacing: 0) {
                                            // Drop zone above each item
                                            if index == 0 {
                                                DropZoneView(isTargeted: .constant(false)) {
                                                    false
                                                }
                                                .onDrop(of: [.text], isTargeted: Binding(
                                                    get: { dragManager.isDragging },
                                                    set: { _ in }
                                                )) { providers, location in
                                                    handleDrop(providers: providers, toContainer: .spacePinned(currentSpace.id), atIndex: index)
                                                }
                                            }
                                            
                                            MockTabView(tab: tab) {
#if DEBUG
                                                print("Activated: \(tab.name)")
#endif
                                            }
                                            .onDrag {
                                                dragManager.startDrag(tab: convertToRealTab(tab), from: .spacePinned(currentSpace.id), at: index)
                                                return NSItemProvider(object: tab.id.uuidString as NSString)
                                            }
                                            
                                            // Drop zone below each item  
                                            DropZoneView(isTargeted: .constant(false)) {
                                                false
                                            }
                                            .onDrop(of: [.text], isTargeted: Binding(
                                                get: { dragManager.isDragging },
                                                set: { _ in }
                                            )) { providers, location in
                                                handleDrop(providers: providers, toContainer: .spacePinned(currentSpace.id), atIndex: index + 1)
                                            }
                                        }
                                    }
                                }
                                
                                Divider()
                                    .padding(.vertical, 4)
                            }
                            
                            // Regular Tabs
                            if let regularTabs = tabManager.regularTabs[currentSpace.id], !regularTabs.isEmpty {
                                VStack(alignment: .leading, spacing: 4) {
                                    Text("Regular Tabs")
                                        .font(.subheadline)
                                        .foregroundColor(.secondary)
                                    
                                    ForEach(regularTabs.indices, id: \.self) { index in
                                        let tab = regularTabs[index]
                                        VStack(spacing: 0) {
                                            // Drop zone above each item
                                            if index == 0 {
                                                Rectangle()
                                                    .fill(dragManager.showInsertionLine && dragManager.insertionIndex == index && dragManager.dropTarget == .spaceRegular(currentSpace.id) ? Color.accentColor : Color.clear)
                                                    .frame(height: 3)
                                                    .onDrop(of: [.text], isTargeted: nil) { providers, location in
                                                        handleDrop(providers: providers, toContainer: .spaceRegular(currentSpace.id), atIndex: index)
                                                    }
                                            }
                                            
                                            MockTabView(tab: tab) {
#if DEBUG
                                                print("Activated: \(tab.name)")
#endif
                                            }
                                            .onDrag {
                                                dragManager.startDrag(tab: convertToRealTab(tab), from: .spaceRegular(currentSpace.id), at: index)
                                                return NSItemProvider(object: tab.id.uuidString as NSString)
                                            }
                                            
                                            // Drop zone below each item
                                            Rectangle()
                                                .fill(dragManager.showInsertionLine && dragManager.insertionIndex == index + 1 && dragManager.dropTarget == .spaceRegular(currentSpace.id) ? Color.accentColor : Color.clear)
                                                .frame(height: 3)
                                                .onDrop(of: [.text], isTargeted: nil) { providers, location in
                                                    handleDrop(providers: providers, toContainer: .spaceRegular(currentSpace.id), atIndex: index + 1)
                                                }
                                        }
                                    }
                                }
                            }
                        }
                        .padding()
                        .background(Color.blue.opacity(0.1))
                        .cornerRadius(12)
                    }
                }
                
                Spacer()
                
                // Debug Info
                VStack(alignment: .leading, spacing: 4) {
                    Text("Debug Info:")
                        .font(.caption)
                        .fontWeight(.bold)
                    Text(#"Is Dragging: \#(dragManager.isDragging.description)"#)
                    Text(#"Dragged Tab: \#(dragManager.draggedTab?.name ?? "None")"#)
                    Text(#"Insertion Index: \#(dragManager.insertionIndex.description)"#)
                    Text(#"Show Line: \#(dragManager.showInsertionLine.description)"#)
                }
                .font(.caption)
                .foregroundColor(.secondary)
                .padding()
                .background(Color.gray.opacity(0.1))
                .cornerRadius(8)
            }
            .padding()
        }
        .insertionLineOverlay(dragManager: dragManager)
        .frame(width: 300, height: 600)
    }
    
    private func handleDragCompleted(_ operation: DragOperation) {
#if DEBUG
        print("🎯 Drag completed: \(operation)")
#endif
        tabManager.handleDragOperation(operation)
    }
    
    private func convertToRealTab(_ mockTab: MockTab) -> Tab {
        // Create a minimal Tab instance for drag purposes
        return Tab(
            id: mockTab.id,
            url: mockTab.url, 
            name: mockTab.name, 
            favicon: mockTab.favicon,
            spaceId: mockTab.spaceId,
            index: mockTab.index
        )
    }
    
    private func handleDrop(providers: [NSItemProvider], toContainer: TabDragManager.DragContainer, atIndex: Int) -> Bool {
#if DEBUG
        print("🎯 Drop attempted: container=\(toContainer), index=\(atIndex)")
#endif
        
        guard let draggedTab = dragManager.draggedTab else {
#if DEBUG
            print("❌ No dragged tab found")
#endif
            return false
        }
        
        // Find the mock tab to move
        guard let mockTab = findMockTab(by: draggedTab.id) else {
#if DEBUG
            print("❌ Could not find mock tab with ID: \(draggedTab.id)")
#endif
            return false
        }
        
        // Remove from source
        removeMockTab(mockTab)
        
        // Add to destination
        insertMockTab(mockTab, toContainer: toContainer, atIndex: atIndex)
        
        // End the drag
        _ = dragManager.endDrag(commit: true)
        
#if DEBUG
        print("✅ Successfully moved \(mockTab.name) to \(toContainer) at index \(atIndex)")
#endif
        return true
    }
    
    private func findMockTab(by id: UUID) -> MockTab? {
        // Search in all containers
        if let tab = tabManager.globalPinnedTabs.first(where: { $0.id == id }) { return tab }
        
        for (_, tabs) in tabManager.spacePinnedTabs {
            if let tab = tabs.first(where: { $0.id == id }) { return tab }
        }
        
        for (_, tabs) in tabManager.regularTabs {
            if let tab = tabs.first(where: { $0.id == id }) { return tab }
        }
        
        return nil
    }
    
    private func removeMockTab(_ tab: MockTab) {
        // Remove from global pinned
        tabManager.globalPinnedTabs.removeAll { $0.id == tab.id }
        
        // Remove from space pinned
        for spaceId in tabManager.spacePinnedTabs.keys {
            tabManager.spacePinnedTabs[spaceId]?.removeAll { $0.id == tab.id }
        }
        
        // Remove from regular tabs
        for spaceId in tabManager.regularTabs.keys {
            tabManager.regularTabs[spaceId]?.removeAll { $0.id == tab.id }
        }
    }
    
    private func insertMockTab(_ tab: MockTab, toContainer: TabDragManager.DragContainer, atIndex: Int) {
        switch toContainer {
        case .essentials:
            let clampedIndex = min(max(atIndex, 0), tabManager.globalPinnedTabs.count)
            tabManager.globalPinnedTabs.insert(tab, at: clampedIndex)
            tab.spaceId = nil
            
        case .spacePinned(let spaceId):
            if tabManager.spacePinnedTabs[spaceId] == nil {
                tabManager.spacePinnedTabs[spaceId] = []
            }
            // Safely access the array with optional chaining instead of force unwrap
            if var spacePinnedArray = tabManager.spacePinnedTabs[spaceId] {
                let clampedIndex = min(max(atIndex, 0), spacePinnedArray.count)
                spacePinnedArray.insert(tab, at: clampedIndex)
                tabManager.spacePinnedTabs[spaceId] = spacePinnedArray
            }
            tab.spaceId = spaceId
            
        case .spaceRegular(let spaceId):
            if tabManager.regularTabs[spaceId] == nil {
                tabManager.regularTabs[spaceId] = []
            }
            // Safely access the array with optional chaining instead of force unwrap
            if var regularTabsArray = tabManager.regularTabs[spaceId] {
                let clampedIndex = min(max(atIndex, 0), regularTabsArray.count)
                regularTabsArray.insert(tab, at: clampedIndex)
                tabManager.regularTabs[spaceId] = regularTabsArray
            }
            tab.spaceId = spaceId
            
        case .none:
#if DEBUG
            print("❌ Invalid drop container")
#endif
        case .folder(_):
            // Handle folder drop container
            break
        }
    }
}

// MARK: - Preview

#Preview {
    DragDropPreview()
        .frame(width: 320, height: 640)
}

```

## /Nook/Components/DragDrop/DragEnabledSidebarView.swift

```swift path="/Nook/Components/DragDrop/DragEnabledSidebarView.swift" 
//
//  DragEnabledSidebarView.swift
//  Nook
//
//  Main sidebar with advanced drag & drop functionality
//

import SwiftUI

struct DragEnabledSidebarView: View {
    @EnvironmentObject var browserManager: BrowserManager
    private var dragManager = TabDragManager.shared
    
    var body: some View {
        SidebarView()
            .environmentObject(browserManager)
            .environmentObject(dragManager)
            .tabDragManager(dragManager)
    }
}

// MARK: - Environment Key for DragManager

struct TabDragManagerKey: EnvironmentKey {
    static let defaultValue: TabDragManager? = nil
}

extension EnvironmentValues {
    var tabDragManager: TabDragManager? {
        get { self[TabDragManagerKey.self] }
        set { self[TabDragManagerKey.self] = newValue }
    }
}

extension View {
    func tabDragManager(_ dragManager: TabDragManager) -> some View {
        environment(\.tabDragManager, dragManager)
    }
}

```

## /Nook/Components/DragDrop/DraggableTabView.swift

```swift path="/Nook/Components/DragDrop/DraggableTabView.swift" 
//
//  DraggableTabView.swift
//  Nook
//
//  Draggable wrapper for tab views with drag state management
//

import SwiftUI
import AppKit

struct DraggableTabView<Content: View>: View {
    let tab: Tab
    let container: TabDragManager.DragContainer
    let index: Int
    let dragManager: TabDragManager
    let content: Content

    @State private var dragGesture = false
    @State private var dragOffset: CGSize = .zero
    @StateObject private var dragLockManager = DragLockManager.shared
    @State private var dragSessionID: String = UUID().uuidString
    
    init(
        tab: Tab,
        container: TabDragManager.DragContainer,
        index: Int,
        dragManager: TabDragManager,
        @ViewBuilder content: () -> Content
    ) {
        self.tab = tab
        self.container = container
        self.index = index
        self.dragManager = dragManager
        self.content = content()
    }
    
    var body: some View {
        content
            .opacity(dragManager.isDragging && dragManager.draggedTab?.id == tab.id ? 0.5 : 1.0)
            .scaleEffect(dragManager.isDragging && dragManager.draggedTab?.id == tab.id ? 0.95 : 1.0)
            .offset(dragOffset)
            .animation(.easeInOut(duration: 0.2), value: dragManager.isDragging)
            .animation(.easeInOut(duration: 0.2), value: dragOffset)
            .onDrag {
                // Pre-emptively acquire drag lock BEFORE anything else
                guard dragLockManager.startDrag(ownerID: dragSessionID) else {
                    print("🚫 [DraggableTabView] Tab drag blocked at onset - \(dragLockManager.debugInfo)")
                    return NSItemProvider(object: tab.id.uuidString as NSString)
                }

                // Start the drag operation with state validation
                DispatchQueue.main.async {
                    // Harden the start guard to prevent concurrent drags
                    guard !dragManager.isDragging else {
                        dragLockManager.endDrag(ownerID: dragSessionID)
                        return
                    }
                    dragManager.startDrag(tab: tab, from: container, at: index)
                }
                return NSItemProvider(object: tab.id.uuidString as NSString)
            }
            .gesture(
                DragGesture(coordinateSpace: .global)
                    .onChanged { value in
                        if !dragGesture {
                            // Pre-emptively acquire drag lock for gesture-based drag
                            guard dragLockManager.startDrag(ownerID: dragSessionID) else {
                                print("🚫 [DraggableTabView] Gesture drag blocked at onset - \(dragLockManager.debugInfo)")
                                return
                            }

                            dragGesture = true
                            // Enhanced drag gesture coordination - prevent duplicate drag start calls
                            if !dragManager.isDragging {
                                dragManager.startDrag(tab: tab, from: container, at: index)
                            }
                        }

                        // Update drag offset for visual feedback
                        dragOffset = value.translation
                    }
                    .onEnded { value in
                        dragGesture = false

                        // Ensure drag offset is properly reset in all scenarios
                        withAnimation(.easeOut(duration: 0.15)) {
                            dragOffset = .zero
                        }

                        // Enhanced drag end handling with state validation
                        if dragManager.isDragging && dragManager.draggedTab?.id == tab.id {
                            // Add validation that the drag manager is in the expected state
                            guard dragManager.draggedTab != nil else {
                                dragLockManager.endDrag(ownerID: dragSessionID)
                                return
                            }
                            // Only cancel drag if no valid drop target
                            if dragManager.dropTarget == .none {
                                dragManager.cancelDrag()
                            }
                        }

                        // Always release drag lock when gesture ends
                        dragLockManager.endDrag(ownerID: dragSessionID)
                    }
            )
            .onReceive(NotificationCenter.default.publisher(for: .tabDragDidEnd)) { _ in
                // Ensure drag lock is released when any tab drag ends
                dragLockManager.endDrag(ownerID: dragSessionID)
            }
    }
}

// MARK: - Extension for easy usage

extension View {
    func draggableTab(
        tab: Tab,
        container: TabDragManager.DragContainer,
        index: Int,
        dragManager: TabDragManager
    ) -> some View {
        DraggableTabView(
            tab: tab,
            container: container,
            index: index,
            dragManager: dragManager
        ) {
            self
        }
    }
}

```

## /Nook/Components/DragDrop/InsertionLineView.swift

```swift path="/Nook/Components/DragDrop/InsertionLineView.swift" 
//
//  InsertionLineView.swift
//  Nook
//
//  Visual insertion line for drag & drop feedback
//

import SwiftUI

struct InsertionLineView: View {
    @ObservedObject var dragManager: TabDragManager
    
    init(dragManager: TabDragManager) {
        self.dragManager = dragManager
    }
    
    var body: some View {
        let shouldShow = dragManager.showInsertionLine && dragManager.insertionLineFrame != .zero
        
        return GeometryReader { _ in
            ZStack {
                if shouldShow {
                    let frame = dragManager.insertionLineFrame
                    
                    RoundedRectangle(cornerRadius: 2)
                        .fill(
                            LinearGradient(
                                colors: [
                                    Color.red.opacity(0.9),
                                    Color.red,
                                    Color.red.opacity(0.9)
                                ],
                                startPoint: .leading,
                                endPoint: .trailing
                            )
                        )
                        .frame(width: max(frame.width, 1), height: max(frame.height, 1))
                        .position(x: frame.midX, y: frame.midY)
                        .animation(.easeInOut(duration: 0.15), value: dragManager.insertionLineFrame)
                        .shadow(color: Color.black.opacity(0.3), radius: 1, x: 0, y: 1)
                        .transition(.scale.combined(with: .opacity))
                        .zIndex(9999)
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .allowsHitTesting(false)
    }
}

struct InsertionLineModifier: ViewModifier {
    let dragManager: TabDragManager
    
    func body(content: Content) -> some View {
        ZStack {
            content
            
            // Separate layer for insertion line with high z-index
            InsertionLineView(dragManager: dragManager)
                .animation(.easeInOut(duration: 0.15), value: dragManager.showInsertionLine)
                .zIndex(9999) // Very high z-index
        }
    }
}

extension View {
    func insertionLineOverlay(dragManager: TabDragManager) -> some View {
        modifier(InsertionLineModifier(dragManager: dragManager))
    }
}

```

## /Nook/Components/DragDrop/SidebarDropSupport.swift

```swift path="/Nook/Components/DragDrop/SidebarDropSupport.swift" 
//
//  SidebarDropSupport.swift
//  Nook
//
//  Shared drop-delegate logic and boundary math adapted from SimpleDragPreview.swift
//  to drive the actual Sidebar (essentials grid, space-pinned list, regular list).
//

import SwiftUI
import UniformTypeIdentifiers
#if canImport(AppKit)
import AppKit
#endif

// MARK: - Grid Boundary Model (Essentials)

enum SidebarGridBoundaryOrientation { case horizontal, vertical }

struct SidebarGridBoundary {
    let index: Int
    let orientation: SidebarGridBoundaryOrientation
    let frame: CGRect // in grid local coordinates
}

// MARK: - Boundary Math (EXACT methodology as in SimpleDragPreview)

struct SidebarDropMath {
    static func computeGridBoundaries(
        frames: [Int: CGRect],
        columns: Int,
        containerWidth: CGFloat,
        gridGap: CGFloat
    ) -> [SidebarGridBoundary] {
        let count = frames.count
        guard count > 0 else { return [] }
        let sorted = (0..<count).compactMap { frames[$0] }
        guard sorted.count == count else { return [] }
        var boundaries: [SidebarGridBoundary] = []

        func rowRange(forRow row: Int) -> Range<Int> {
            let start = row * columns
            let end = min(start + columns, count)
            return start..<end
        }

        func rowOf(index: Int) -> Int { max(0, index / columns) }

        // Precompute per-row vertical span
        let totalRows = Int(ceil(Double(count) / Double(columns)))
        var rowMinY: [CGFloat] = []
        var rowMaxY: [CGFloat] = []
        for r in 0..<totalRows {
            let rr = rowRange(forRow: r)
            let minY = rr.compactMap { frames[$0]?.minY }.min() ?? 0
            let maxY = rr.compactMap { frames[$0]?.maxY }.max() ?? 0
            rowMinY.append(minY)
            rowMaxY.append(maxY)
        }

        for i in 0...count {
            if i == 0 {
                // Before first tile → vertical line at left edge of first column
                if let first = frames[0] {
                    let r = rowOf(index: 0)
                    let x = first.minX - (gridGap / 2)
                    let yMid = (rowMinY[r] + rowMaxY[r]) / 2
                    let h = rowMaxY[r] - rowMinY[r]
                    let f = CGRect(x: x - 1.5, y: yMid - h/2, width: 3, height: h)
                    boundaries.append(SidebarGridBoundary(index: 0, orientation: .vertical, frame: f))
                }
            } else if i == count {
                // After last tile: vertical if row incomplete, else horizontal below last row
                let last = count - 1
                let lastRow = rowOf(index: last)
                let rowStart = rowRange(forRow: lastRow).lowerBound
                let inRowIndex = last - rowStart + 1
                if inRowIndex < columns, let prev = frames[last] {
                    // Vertical at right of last cell in last row
                    let x = prev.maxX + (gridGap / 2)
                    let yMid = (rowMinY[lastRow] + rowMaxY[lastRow]) / 2
                    let h = rowMaxY[lastRow] - rowMinY[lastRow]
                    let f = CGRect(x: x - 1.5, y: yMid - h/2, width: 3, height: h)
                    boundaries.append(SidebarGridBoundary(index: i, orientation: .vertical, frame: f))
                } else {
                    // Horizontal below last row across container width
                    let y = rowMaxY[lastRow] + (gridGap / 2)
                    let f = CGRect(x: 0, y: y - 1.5, width: containerWidth, height: 3)
                    boundaries.append(SidebarGridBoundary(index: i, orientation: .horizontal, frame: f))
                }
            } else if i % columns == 0 {
                // Between rows (horizontal)
                let r = rowOf(index: i)
                let prevRow = max(0, r - 1)
                let y = (rowMaxY[prevRow] + rowMinY[r]) / 2
                let f = CGRect(x: 0, y: y - 1.5, width: containerWidth, height: 3)
                boundaries.append(SidebarGridBoundary(index: i, orientation: .horizontal, frame: f))
            } else {
                // Between columns (vertical) in same row
                if let left = frames[i - 1], let right = frames[i] {
                    let x = (left.maxX + right.minX) / 2
                    // Vertical span is the row height
                    let r = rowOf(index: i)
                    let yMid = (rowMinY[r] + rowMaxY[r]) / 2
                    let h = rowMaxY[r] - rowMinY[r]
                    let f = CGRect(x: x - 1.5, y: yMid - h/2, width: 3, height: h)
                    boundaries.append(SidebarGridBoundary(index: i, orientation: .vertical, frame: f))
                }
            }
        }
        return boundaries
    }

    static func estimatedGridWidth(from frames: [Int: CGRect]) -> CGFloat {
        let minX = frames.values.map { $0.minX }.min() ?? 0
        let maxX = frames.values.map { $0.maxX }.max() ?? 0
        return max(0, maxX - minX)
    }

    static func computeListBoundaries(frames: [Int: CGRect]) -> [CGFloat] {
        let count = frames.count
        guard count > 0 else { return [] }
        let ordered = (0..<count).compactMap { frames[$0] }
        guard ordered.count == count else { return [] }

        var boundaries: [CGFloat] = []
        
        // Before first item
        boundaries.append(ordered[0].minY)
        
        // Between each pair of items - simple midpoints
        for i in 0..<(count - 1) {
            let midpoint = (ordered[i].maxY + ordered[i + 1].minY) / 2
            boundaries.append(midpoint)
        }
        
        // After last item
        boundaries.append(ordered[count - 1].maxY)
        
        return boundaries
    }
}

// MARK: - Drop Delegates (generalized)

struct SidebarSectionDropDelegate: DropDelegate {
    let dragManager: TabDragManager
    let container: TabDragManager.DragContainer
    let boundariesProvider: () -> [CGFloat]
    let insertionLineFrameProvider: (() -> CGRect)?
    // Optional global container frame provider for converting local frames to global
    let globalFrameProvider: (() -> CGRect)?
    let onPerform: (DragOperation) -> Void

    func validateDrop(info: DropInfo) -> Bool { true }

    func dropEntered(info: DropInfo) {
        print("🎯 [SidebarSectionDropDelegate] Drop entered: container=\(container), location=\(info.location)")
        updateTarget(with: info)
    }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        updateTarget(with: info)
        return DropProposal(operation: .move)
    }

    func performDrop(info: DropInfo) -> Bool {
        _ = updateTarget(with: info)
        if let op = dragManager.endDrag(commit: true) {
            onPerform(op)
        }
        return true
    }

    @discardableResult
    private func updateTarget(with info: DropInfo) -> Int {
        let y = info.location.y
        let index = insertionIndex(forY: y)
        let boundaries = boundariesProvider()
        
        // Enhanced state validation - ensure insertionIndex is always valid for current container
        guard index >= 0 else {
            return 0
        }
        
        // Handle insertion line coordination between global and local overlays
        if boundaries.isEmpty {
            // Empty zone: use global insertion line via frame provider
            if let frameProvider = insertionLineFrameProvider {
                let frame = frameProvider()
                print("📐 [SidebarSectionDropDelegate] Empty zone frame (raw): \(frame)")
                // If a global container frame is provided, convert local to global by offsetting
                if let containerFrame = globalFrameProvider?() {
                    let converted = CGRect(
                        x: containerFrame.minX + frame.minX,
                        y: containerFrame.minY + frame.minY,
                        width: frame.width,
                        height: frame.height
                    )
                    print("📐 [SidebarSectionDropDelegate] Empty zone frame (global): \(converted)")
                    dragManager.updateInsertionLine(frame: converted)
                } else {
                    dragManager.updateInsertionLine(frame: frame)
                }
            } else {
                print("⚠️ [SidebarSectionDropDelegate] No frame provider for empty zone")
                dragManager.updateInsertionLine(frame: .zero)
            }
        } else {
            // Non-empty zone: calculate insertion line frame based on boundaries
            print("📐 [SidebarSectionDropDelegate] Non-empty boundaries: \(boundaries.count) items")
            
            // Calculate the frame for the insertion line at the target index
            let targetY = (index < boundaries.count) ? boundaries[index] : (boundaries.last ?? 0)
            let lineHeight: CGFloat = 3

            if let containerFrame = globalFrameProvider?() {
                // Convert local boundary Y into global coordinates using container frame
                let insertionFrame = CGRect(
                    x: containerFrame.minX + 10,
                    y: containerFrame.minY + targetY - lineHeight/2,
                    width: max(containerFrame.width - 20, 1),
                    height: lineHeight
                )
                print("📐 [SidebarSectionDropDelegate] Calculated insertion frame (global): \(insertionFrame)")
                dragManager.updateInsertionLine(frame: insertionFrame)
            } else {
                let containerWidth: CGFloat = 200 // Fallback sidebar width
                let insertionFrame = CGRect(
                    x: 10,
                    y: targetY - lineHeight/2,
                    width: containerWidth - 20,
                    height: lineHeight
                )
                print("📐 [SidebarSectionDropDelegate] Calculated insertion frame (local): \(insertionFrame)")
                dragManager.updateInsertionLine(frame: insertionFrame)
            }
        }
        
        // Add state consistency checks before updating drag target
        if validateDragManagerState(for: container, index: index) {
            dragManager.updateDragTarget(container: container, insertionIndex: index, spaceId: spaceId)
        }
        
        performMoveHapticIfNeeded(currentIndex: index)
        return index
    }

    private var spaceId: UUID? {
        switch container {
        case .spacePinned(let sid): return sid
        case .spaceRegular(let sid): return sid
        default: return nil
        }
    }

    private func insertionIndex(forY y: CGFloat) -> Int {
        let boundaries = boundariesProvider()
        
        // Enhanced empty zone handling - ensure we never return invalid indices
        guard !boundaries.isEmpty else { 
            return 0 
        }
        
        // Add bounds checking for Y coordinates
        guard y.isFinite && !y.isNaN else {
            return 0
        }
        
        // Simple Y-coordinate comparison against midpoints
        for (i, boundary) in boundaries.enumerated() {
            if y <= boundary {
                return max(0, i) // Ensure index is never negative
            }
        }
        
        // Enhanced fallback - ensure we never return invalid indices
        let maxIndex = boundaries.count - 1
        return max(0, maxIndex)
    }

    private func performMoveHapticIfNeeded(currentIndex: Int) {
        // Let TabDragManager handle haptics to avoid duplicate calls
    }
    
    // MARK: - State validation helpers
    
    private func validateDragManagerState(for container: TabDragManager.DragContainer, index: Int) -> Bool {
        // Ensure the drag manager's current state is consistent with the delegate's container
        guard dragManager.isDragging else { return false }
        
        // Add checks that prevent invalid state transitions
        if index < 0 { return false }
        
        // Validate that insertion indices are appropriate for the current container content
        switch container {
        case .none:
            return false
        case .essentials, .spacePinned, .spaceRegular, .folder:
            // Basic validation - more sophisticated checks could be added
            return true
        }
    }
}

struct SidebarGridDropDelegate: DropDelegate {
    let dragManager: TabDragManager
    let container: TabDragManager.DragContainer = .essentials
    let boundariesProvider: () -> [SidebarGridBoundary]
    let onPerform: (DragOperation) -> Void

    func validateDrop(info: DropInfo) -> Bool { true }

    func dropEntered(info: DropInfo) { updateTarget(with: info) }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        updateTarget(with: info)
        return DropProposal(operation: .move)
    }

    func performDrop(info: DropInfo) -> Bool {
        _ = updateTarget(with: info)
        if let op = dragManager.endDrag(commit: true) {
            onPerform(op)
        }
        return true
    }

    @discardableResult
    private func updateTarget(with info: DropInfo) -> Int {
        let index = insertionIndex(for: info.location)
        
        // Add state consistency checks for grid delegate
        guard validateGridDragManagerState(for: index) else {
            return index
        }
        
        dragManager.updateDragTarget(container: container, insertionIndex: index, spaceId: nil)
        
        // Calculate and set insertion line frame for grid
        let bounds = boundariesProvider()
        if !bounds.isEmpty, let boundary = bounds.first(where: { $0.index == index }) {
            print("📐 [SidebarGridDropDelegate] Grid insertion frame: \(boundary.frame)")
            dragManager.updateInsertionLine(frame: boundary.frame)
        } else {
            print("📐 [SidebarGridDropDelegate] No boundary found for index \(index)")
            dragManager.updateInsertionLine(frame: .zero)
        }
        
        return index
    }

    private func insertionIndex(for point: CGPoint) -> Int {
        let bounds = boundariesProvider()
        guard !bounds.isEmpty else { return 0 }
        
        // Add validation for point coordinates
        guard point.x.isFinite && point.y.isFinite && !point.x.isNaN && !point.y.isNaN else {
            return 0
        }
        
        var best = bounds[0]
        var bestDist = distance(point, to: bounds[0])
        for b in bounds.dropFirst() {
            let d = distance(point, to: b)
            if d < bestDist {
                bestDist = d
                best = b
            }
        }
        
        // Ensure returned index is valid
        return max(0, best.index)
    }

    private func distance(_ p: CGPoint, to b: SidebarGridBoundary) -> CGFloat {
        switch b.orientation {
        case .horizontal:
            let y = b.frame.midY
            let dx: CGFloat
            if p.x < b.frame.minX { dx = b.frame.minX - p.x }
            else if p.x > b.frame.maxX { dx = p.x - b.frame.maxX }
            else { dx = 0 }
            let dy = abs(p.y - y)
            return hypot(dx, dy)
        case .vertical:
            let x = b.frame.midX
            let dy: CGFloat
            if p.y < b.frame.minY { dy = b.frame.minY - p.y }
            else if p.y > b.frame.maxY { dy = p.y - b.frame.maxY }
            else { dy = 0 }
            let dx = abs(p.x - x)
            return hypot(dx, dy)
        }
    }
    
    // MARK: - State validation helpers
    
    private func validateGridDragManagerState(for index: Int) -> Bool {
        // Ensure the drag manager's current state is consistent with the grid delegate
        guard dragManager.isDragging else { return false }
        
        // Validate that insertion indices are appropriate for grid container
        guard index >= 0 else { return false }
        
        return true
    }
}

// MARK: - Overlay Helpers

struct SidebarSectionInsertionOverlay: View {
    let isActive: Bool
    let index: Int
    let boundaries: [CGFloat]

    var body: some View {
        let _ = print("🟦 [SidebarSectionInsertionOverlay] isActive=\(isActive), index=\(index), boundaries.count=\(boundaries.count)")
        return GeometryReader { proxy in
            ZStack {
                if isActive {
                    let _ = print("🟦 [SidebarSectionInsertionOverlay] Showing blue line at index \(index)")
                    let y: CGFloat = {
                        if !boundaries.isEmpty, index >= 0, index < boundaries.count {
                            return min(max(boundaries[index], 1.5), proxy.size.height - 1.5)
                        } else {
                            // Enhanced empty zone fallback - position at top third rather than center
                            return max(proxy.size.height / 3, 1.5)
                        }
                    }()
                    
                    let _ = print("🟦 [SidebarSectionInsertionOverlay] Blue line y-position: \(y)")
                    
                    // Enhanced styling for better visibility
                    RoundedRectangle(cornerRadius: 2)
                        .fill(
                            LinearGradient(
                                colors: [
                                    Color.blue.opacity(0.9),
                                    Color.blue,
                                    Color.blue.opacity(0.9)
                                ],
                                startPoint: .leading,
                                endPoint: .trailing
                            )
                        )
                        .frame(width: proxy.size.width, height: 4)
                        .position(x: proxy.size.width / 2, y: y)
                        .shadow(color: Color.black.opacity(0.3), radius: 1, x: 0, y: 1)
                        .animation(.easeInOut(duration: 0.15), value: index)
                        .transition(.scale.combined(with: .opacity))
                        .zIndex(999) // High z-index to appear above content
                }
            }
        }
        .allowsHitTesting(false)
    }
}

struct SidebarGridInsertionOverlay: View {
    let isActive: Bool
    let index: Int
    let boundaries: [SidebarGridBoundary]

    var body: some View {
        GeometryReader { proxy in
            ZStack {
                if isActive {
                    if let b = boundaries.first(where: { $0.index == index }) {
                        switch b.orientation {
                        case .horizontal:
                            Rectangle()
                                .fill(Color.red)
                                .frame(width: b.frame.width, height: 10)
                                .position(x: b.frame.midX, y: b.frame.midY)
                                .shadow(color: .black, radius: 2)
                                .animation(.easeInOut(duration: 0.12), value: index)
                        case .vertical:
                            Rectangle()
                                .fill(Color.red)
                                .frame(width: 10, height: b.frame.height)
                                .position(x: b.frame.midX, y: b.frame.midY)
                                .shadow(color: .black, radius: 2)
                                .animation(.easeInOut(duration: 0.12), value: index)
                        }
                    }
                }
            }
        }
        .allowsHitTesting(false)
    }
}

// MARK: - Haptics helper (kept for parity)
@inline(__always)
private func performMoveHaptic() {
#if canImport(AppKit)
    NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .now)
#endif
}

```

## /Nook/Components/DragDrop/SimpleDnD.swift

```swift path="/Nook/Components/DragDrop/SimpleDnD.swift" 
//
//  SimpleDnD.swift
//  Nook
//
//  Lightweight Ora-style drag & drop for the sidebar.
//  Uses NSItemProvider with Tab UUIDs and simple DropDelegates
//  to reorder/move tabs by calling TabManager directly.
//

import SwiftUI
import AppKit

// MARK: - Target Section

enum SidebarTargetSection: Equatable {
    case essentials
    case spacePinned(UUID)    // spaceId
    case spaceRegular(UUID)   // spaceId
    case folder(UUID)          // folderId
}

// MARK: - Helpers

private func haptic(_ pattern: NSHapticFeedbackManager.FeedbackPattern = .alignment) {
    NSHapticFeedbackManager.defaultPerformer.perform(pattern, performanceTime: .now)
}

@MainActor
private func containerFor(tab: Tab, tabManager: TabManager) -> (TabDragManager.DragContainer, Int, UUID?) {
    if tab.spaceId == nil {
        // Essentials (global pinned)
        return (.essentials, tab.index, nil)
    } else if let folderId = tab.folderId {
        // Tab is in a folder
        return (.folder(folderId), tab.index, tab.spaceId)
    } else if let sid = tab.spaceId {
        // Distinguish space-pinned vs regular by membership
        let pinned = tabManager.spacePinnedTabs(for: sid)
        if pinned.contains(where: { $0.id == tab.id }) {
            return (.spacePinned(sid), tab.index, sid)
        } else {
            return (.spaceRegular(sid), tab.index, sid)
        }
    }
    return (.none, -1, nil)
}

private func targetContainer(from section: SidebarTargetSection) -> (TabDragManager.DragContainer, UUID?) {
    switch section {
    case .essentials:
        return (.essentials, nil)
    case .spacePinned(let sid):
        return (.spacePinned(sid), sid)
    case .spaceRegular(let sid):
        return (.spaceRegular(sid), sid)
    case .folder(let folderId):
        return (.folder(folderId), nil)
    }
}

// MARK: - Item Drop Delegate (reorder relative to an item)

@MainActor
struct SidebarTabDropDelegateSimple: DropDelegate {
    let item: Tab                          // target tab (to)
    @Binding var draggedItem: UUID?
    let targetSection: SidebarTargetSection
    let tabManager: TabManager

    func dropEntered(info: DropInfo) {
        guard let provider = info.itemProviders(for: [.text]).first else { return }
        provider.loadObject(ofClass: NSString.self) { object, _ in
            guard
                let string = object as? String,
                let uuid = UUID(uuidString: string)
            else { return }

            DispatchQueue.main.async {
                let all = tabManager.allTabs()
                guard let from = all.first(where: { $0.id == uuid }) else { return }
                guard from.id != self.item.id else { return }

                let (fromContainer, fromIndex, _) = containerFor(tab: from, tabManager: tabManager)
                let (toContainer, toSpace) = targetContainer(from: self.targetSection)
                let toIndex = self.item.index

                let op = DragOperation(
                    tab: from,
                    fromContainer: fromContainer,
                    fromIndex: max(fromIndex, 0),
                    toContainer: toContainer,
                    toIndex: max(toIndex, 0),
                    toSpaceId: toSpace
                )
                withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
                    tabManager.handleDragOperation(op)
                }
                self.draggedItem = uuid
                haptic(.alignment)
            }
        }
    }

    func validateDrop(info: DropInfo) -> Bool { true }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        DropProposal(operation: .move)
    }

    func dropExited(info: DropInfo) {
        draggedItem = nil
        NotificationCenter.default.post(name: .tabDragDidEnd, object: nil)
    }

    func performDrop(info: DropInfo) -> Bool {
        draggedItem = nil
        NotificationCenter.default.post(name: .tabDragDidEnd, object: nil)
        return true
    }
}

// MARK: - Section Drop Delegate (drop into section/empty area)

@MainActor
struct SidebarSectionDropDelegateSimple: DropDelegate {
    let itemsCount: () -> Int               // current count to append at end
    @Binding var draggedItem: UUID?
    let targetSection: SidebarTargetSection
    let tabManager: TabManager
    let targetIndex: (() -> Int)?
    private let onDropEntered: (() -> Void)?
    private let onDropCompleted: (() -> Void)?
    private let onDropExited: (() -> Void)?

    init(
        itemsCount: @escaping () -> Int,
        draggedItem: Binding<UUID?>,
        targetSection: SidebarTargetSection,
        tabManager: TabManager,
        targetIndex: (() -> Int)? = nil,
        onDropEntered: (() -> Void)? = nil,
        onDropCompleted: (() -> Void)? = nil,
        onDropExited: (() -> Void)? = nil
    ) {
        self.itemsCount = itemsCount
        self._draggedItem = draggedItem
        self.targetSection = targetSection
        self.tabManager = tabManager
        self.targetIndex = targetIndex
        self.onDropEntered = onDropEntered
        self.onDropCompleted = onDropCompleted
        self.onDropExited = onDropExited
    }

    func dropEntered(info: DropInfo) {
        guard let provider = info.itemProviders(for: [.text]).first else { return }
        provider.loadObject(ofClass: NSString.self) { object, _ in
            guard
                let string = object as? String,
                let uuid = UUID(uuidString: string)
            else { return }

            DispatchQueue.main.async {
                self.draggedItem = uuid
                self.onDropEntered?()
            }
        }
    }

    func validateDrop(info: DropInfo) -> Bool { true }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        DropProposal(operation: .move)
    }

    func dropExited(info: DropInfo) {
        finishDrop()
    }

    func performDrop(info: DropInfo) -> Bool {
        if case .folder = targetSection {
            handleFolderDrop(info: info)
        } else {
            handleRegularDrop(info: info)
        }
        return true
    }

    private func handleRegularDrop(info: DropInfo) {
        guard let provider = info.itemProviders(for: [.text]).first else {
            finishDrop()
            return
        }
        provider.loadObject(ofClass: NSString.self) { object, _ in
            guard
                let string = object as? String,
                let uuid = UUID(uuidString: string)
            else {
                DispatchQueue.main.async {
                    self.finishDrop()
                }
                return
            }

            DispatchQueue.main.async {
                let all = tabManager.allTabs()
                guard let from = all.first(where: { $0.id == uuid }) else {
                    self.finishDrop()
                    return
                }

                let (fromContainer, fromIndex, _) = containerFor(tab: from, tabManager: tabManager)
                let (toContainer, toSpace) = targetContainer(from: self.targetSection)
                let toIndex = max(0, self.targetIndex?() ?? self.itemsCount())

                let op = DragOperation(
                    tab: from,
                    fromContainer: fromContainer,
                    fromIndex: max(fromIndex, 0),
                    toContainer: toContainer,
                    toIndex: toIndex,
                    toSpaceId: toSpace
                )
                withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
                    tabManager.handleDragOperation(op)
                }
                self.onDropCompleted?()
                self.finishDrop()
            }
        }
    }

    private func handleFolderDrop(info: DropInfo) {
        guard let provider = info.itemProviders(for: [.text]).first else {
            finishDrop()
            return
        }
        provider.loadObject(ofClass: NSString.self) { object, _ in
            guard
                let string = object as? String,
                let uuid = UUID(uuidString: string)
            else {
                DispatchQueue.main.async {
                    self.finishDrop()
                }
                return
            }

            DispatchQueue.main.async {
                let all = tabManager.allTabs()
                guard let from = all.first(where: { $0.id == uuid }) else {
                    self.finishDrop()
                    return
                }

                let (fromContainer, fromIndex, _) = containerFor(tab: from, tabManager: tabManager)
                let (toContainer, toSpace) = targetContainer(from: self.targetSection)
                let toIndex = max(0, self.targetIndex?() ?? self.itemsCount())

                let op = DragOperation(
                    tab: from,
                    fromContainer: fromContainer,
                    fromIndex: max(fromIndex, 0),
                    toContainer: toContainer,
                    toIndex: toIndex,
                    toSpaceId: toSpace
                )
                withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
                    tabManager.handleDragOperation(op)
                }
                haptic(.alignment)
                self.onDropCompleted?()
                self.finishDrop()
            }
        }
    }

    private func finishDrop() {
        DispatchQueue.main.async {
            self.onDropExited?()
            self.draggedItem = nil
            NotificationCenter.default.post(name: .tabDragDidEnd, object: nil)
        }
    }
}

// MARK: - Drag Provider helper

extension View {
    func onTabDrag(_ id: UUID, draggedItem: Binding<UUID?>) -> some View {
        onDrag {
            draggedItem.wrappedValue = id
            return NSItemProvider(object: id.uuidString as NSString)
        }
    }
}

```

## /Nook/Components/Extensions/ExtensionActionView.swift

```swift path="/Nook/Components/Extensions/ExtensionActionView.swift" 
//
//  ExtensionActionView.swift
//  Nook
//
//  Clean ExtensionActionView using ONLY native WKWebExtension APIs
//

import SwiftUI
import WebKit
import AppKit

@available(macOS 15.5, *)
struct ExtensionActionView: View {
    let extensions: [InstalledExtension]
    @EnvironmentObject var browserManager: BrowserManager
    
    var body: some View {
        HStack(spacing: 4) {
            ForEach(extensions.filter { $0.isEnabled }, id: \.id) { ext in
                ExtensionActionButton(ext: ext)
                    .environmentObject(browserManager)
            }
        }
    }
}

@available(macOS 15.5, *)
struct ExtensionActionButton: View {
    let ext: InstalledExtension
    @EnvironmentObject var browserManager: BrowserManager
    @EnvironmentObject var windowState: BrowserWindowState
    
    var body: some View {
        Button(action: {
            showExtensionPopup()
        }) {
            Group {
                if let iconPath = ext.iconPath,
                   let nsImage = NSImage(contentsOfFile: iconPath) {
                    Image(nsImage: nsImage)
                        .resizable()
                } else {
                    Image(systemName: "puzzlepiece.extension")
                        .foregroundColor(.blue)
                }
            }
            .frame(width: 20, height: 20)
            .background(ActionAnchorView(extensionId: ext.id))
        }
        .buttonStyle(.plain)
        .help(ext.name)
    }
    
    private func showExtensionPopup() {
        print("🎯 Performing action for extension: \(ext.name)")
        
        guard let extensionContext = ExtensionManager.shared.getExtensionContext(for: ext.id) else {
            print("❌ No extension context found")
            return
        }
        
        print("✅ Calling performAction() - this should trigger the delegate")
        if let current = browserManager.currentTab(for: windowState) {
            if let adapter = ExtensionManager.shared.stableAdapter(for: current) {
                extensionContext.performAction(for: adapter)
            } else {
                extensionContext.performAction(for: nil)
            }
        } else {
            extensionContext.performAction(for: nil)
        }
    }
}

@available(macOS 15.5, *)
#Preview {
    ExtensionActionView(extensions: [])
}

// MARK: - Anchor View for Popover Positioning
private struct ActionAnchorView: NSViewRepresentable {
    let extensionId: String

    func makeNSView(context: Context) -> NSView {
        let view = NSView(frame: .zero)
        if #available(macOS 15.5, *) {
            ExtensionManager.shared.setActionAnchor(for: extensionId, anchorView: view)
        }
        return view
    }

    func updateNSView(_ nsView: NSView, context: Context) {
        if #available(macOS 15.5, *) {
            ExtensionManager.shared.setActionAnchor(for: extensionId, anchorView: nsView)
        }
    }
}

```

## /Nook/Components/Settings/SettingsTabBar.swift

```swift path="/Nook/Components/Settings/SettingsTabBar.swift" 
//
//  SettingsTabBar.swift
//  Nook
//
//  Created by Maciek Bagiński on 03/08/2025.
//
import SwiftUI

struct SettingsTabBar: View {
    @EnvironmentObject var browserManager: BrowserManager

    var body: some View {
        ZStack {
            BlurEffectView(
                material: browserManager.settingsManager.currentMaterial,
                state: .active
            )
            HStack {
                MacButtonsView()
                    .frame(width: 70, height: 32)
                Spacer()
                Text(browserManager.settingsManager.currentSettingsTab.name)
                    .font(.headline)
                Spacer()
            }

        }
        .backgroundDraggable()
    }
}

```

## /Nook/Components/Sidebar/URLBarFramePreferenceKey.swift

```swift path="/Nook/Components/Sidebar/URLBarFramePreferenceKey.swift" 
import SwiftUI

struct URLBarFramePreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}


```

## /Nook/Info.plist.bak

Binary file available at https://raw.githubusercontent.com/nook-browser/Nook/refs/heads/main/Nook/Info.plist.bak

## /Nook/Managers/ExtensionManager/README-URLSchemeHandler.md

# README


## /assets/icon.png

Binary file available at https://raw.githubusercontent.com/nook-browser/Nook/refs/heads/main/assets/icon.png


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!