[
  {
    "path": ".github/workflows/README.md",
    "content": "# GitHub Actions Workflows\n\nThis directory contains GitHub Actions workflows for the TrackWeight project.\n\n## build-and-sign-dmg.yml\n\nThis workflow builds, signs (if certificates are provided), and packages the TrackWeight macOS application into a DMG file.\n\n### Features\n\n- **Automated Building**: Builds the Xcode project using the latest stable Xcode\n- **Code Signing**: Supports optional code signing with certificates\n- **DMG Creation**: Creates a professional DMG with proper layout and attribution\n- **Attribution**: Includes proper credits to the original repository (https://github.com/KrishKrosh/TrackWeight)\n- **Release Integration**: Can create GitHub releases with the built DMG\n- **Artifact Upload**: Uploads DMG as a GitHub Actions artifact\n\n### Triggers\n\nThe workflow runs on:\n- Git tags starting with 'v' (e.g., v1.0.0)\n- Published releases\n- Manual workflow dispatch\n\n### Setup Instructions\n\n#### Required Secrets (for signed builds)\n\nTo enable code signing, add these secrets to your GitHub repository:\n\n1. **BUILD_CERTIFICATE_BASE64**: Base64-encoded Developer ID Application certificate (.p12 file)\n   ```bash\n   base64 -i YourCertificate.p12 | pbcopy\n   ```\n\n2. **P12_PASSWORD**: Password for the .p12 certificate file\n\n3. **BUILD_PROVISION_PROFILE_BASE64**: Base64-encoded provisioning profile (optional for Developer ID)\n   ```bash\n   base64 -i YourProvisioningProfile.mobileprovision | pbcopy\n   ```\n\n#### Setting up Certificates\n\n1. Export your Developer ID Application certificate from Keychain Access as a .p12 file\n2. Convert to base64 and add as `BUILD_CERTIFICATE_BASE64` secret\n3. Add the certificate password as `P12_PASSWORD` secret\n\n#### Unsigned Builds\n\nIf you don't have code signing certificates, the workflow will automatically create an unsigned development build. These builds can still be used but may require users to manually allow them in System Preferences.\n\n## build-unsigned-dmg.yml\n\nThis workflow specifically builds unsigned development versions of the TrackWeight app without requiring any certificates.\n\n### Features\n\n- **No Certificate Requirements**: Builds completely without code signing\n- **Development Build**: Creates unsigned development builds that work on any Mac\n- **Same DMG Features**: Includes all the same DMG features as the signed version\n- **Clear User Instructions**: Includes instructions for running unsigned apps\n- **Attribution**: Maintains proper credits to the original repository\n\n### Triggers\n\nThe workflow runs on:\n- Pushes to main branches and the current working branch\n- Manual workflow dispatch\n\n### Benefits\n\n- **Easy Testing**: Perfect for testing builds without setting up certificates\n- **No Configuration**: Works immediately without any secrets or setup\n- **User-Friendly**: Includes clear instructions for users on how to run unsigned apps\n\n## Choosing the Right Workflow\n\n### Use `build-and-sign-dmg.yml` when:\n- You have Apple Developer certificates\n- You want to distribute signed, trusted builds\n- You're creating official releases\n\n### Use `build-unsigned-dmg.yml` when:\n- You don't have certificates\n- You want to test builds quickly\n- You're developing or experimenting\n- You need a simple build process\n\nIf no signing certificates are provided, the workflow will create an unsigned development build that can still be distributed and run locally (users may need to allow it in System Preferences > Security & Privacy).\n\n### Usage\n\n#### Manual Trigger\n1. Go to Actions tab in your GitHub repository\n2. Select \"Build and Sign DMG\" workflow\n3. Click \"Run workflow\"\n4. Optionally check \"Create a GitHub release\" to create a release\n\n#### Automatic Trigger\n1. Create a git tag: `git tag v1.0.0`\n2. Push the tag: `git push origin v1.0.0`\n3. The workflow will automatically build and create a release\n\n### Output\n\nThe workflow produces:\n- **DMG file**: TrackWeight-{version}.dmg containing the app and attribution\n- **GitHub Release**: (if triggered by tag or manual release creation)\n- **Artifacts**: DMG file uploaded as GitHub Actions artifact\n\n### Attribution\n\nThis workflow ensures proper attribution to the original TrackWeight repository:\n- README.txt file included in DMG with credits\n- Release notes include attribution\n- Links to original repository: https://github.com/KrishKrosh/TrackWeight\n\n## update-homebrew.yml\n\nThis workflow automatically updates the Homebrew cask when a new release is published.\n\n### Features\n- Updates version in Homebrew tap repository\n- Automatically triggered on release publication\n- Can be manually triggered with version input\n\nFor more information about the original TrackWeight project, visit: https://github.com/KrishKrosh/TrackWeight"
  },
  {
    "path": ".github/workflows/build-and-sign-dmg.yml",
    "content": "name: Build and Sign DMG\n\non:\n  push:\n    tags:\n      - 'v*'\n    branches:\n      - 'copilot/fix-1'  # Enable testing on current working branch\n  release:\n    types: [published]\n  workflow_dispatch:\n    inputs:\n      create_release:\n        description: 'Create a GitHub release'\n        required: false\n        default: false\n        type: boolean\n\nenv:\n  APP_NAME: TrackWeight\n  SCHEME: TrackWeight\n  CONFIGURATION: Release\n\njobs:\n  build-and-sign:\n    runs-on: macos-latest\n    \n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n      with:\n        submodules: recursive\n    \n    - name: Setup Xcode\n      uses: maxim-lobanov/setup-xcode@v1\n      with:\n        xcode-version: latest-stable\n    \n    - name: Import Code-Signing Certificates\n      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' }}\n      env:\n        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n      uses: Apple-Actions/import-codesign-certs@v2\n      with:\n        p12-file-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n        p12-password: ${{ secrets.P12_PASSWORD }}\n    \n    - name: Install provisioning profile\n      if: ${{ env.BUILD_PROVISION_PROFILE_BASE64 != '' }}\n      env:\n        BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}\n      run: |\n        PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision\n        echo -n \"$BUILD_PROVISION_PROFILE_BASE64\" | base64 --decode -o $PP_PATH\n        mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles\n        cp $PP_PATH ~/Library/MobileDevice/Provisioning\\ Profiles\n    \n    - name: Build and Archive App\n      run: |\n        xcodebuild \\\n          -project TrackWeight.xcodeproj \\\n          -scheme ${{ env.SCHEME }} \\\n          -configuration ${{ env.CONFIGURATION }} \\\n          -archivePath \"$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive\" \\\n          -destination 'generic/platform=macOS' \\\n          ARCHS=\"arm64 x86_64\" \\\n          ONLY_ACTIVE_ARCH=NO \\\n          archive\n    \n    - name: Export App\n      run: |\n        # Use development export method if no signing certificates are available\n        if [ -z \"${{ secrets.BUILD_CERTIFICATE_BASE64 }}\" ]; then\n          # Create export options for unsigned build\n          cat > \"$RUNNER_TEMP/ExportOptions.plist\" << EOF\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n        <plist version=\"1.0\">\n        <dict>\n            <key>method</key>\n            <string>debugging</string>\n            <key>signingStyle</key>\n            <string>manual</string>\n            <key>stripSwiftSymbols</key>\n            <true/>\n            <key>destination</key>\n            <string>export</string>\n            <key>signingCertificate</key>\n            <string>-</string>\n            <key>teamID</key>\n            <string>-</string>\n            <key>uploadBitcode</key>\n            <false/>\n            <key>uploadSymbols</key>\n            <false/>\n        </dict>\n        </plist>\n        EOF\n        else\n          # Use the existing ExportOptions.plist for signed builds\n          cp ExportOptions.plist \"$RUNNER_TEMP/ExportOptions.plist\"\n        fi\n        \n        xcodebuild \\\n          -archivePath \"$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive\" \\\n          -exportPath \"$RUNNER_TEMP/export\" \\\n          -exportOptionsPlist \"$RUNNER_TEMP/ExportOptions.plist\" \\\n          -exportArchive\n\n    - name: Re-sign App Components\n      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' }}\n      env:\n        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n        APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\n      run: |\n        echo \"🔏 Re-signing framework with Developer ID certificate...\"\n        FRAMEWORK_PATH=\"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework\"\n        if [[ -d \"$FRAMEWORK_PATH\" ]]; then\n          codesign --force --sign \"Developer ID Application\" \\\n            --options runtime \\\n            --timestamp \\\n            \"$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF\"\n          \n          codesign --force --sign \"Developer ID Application\" \\\n            --options runtime \\\n            --timestamp \\\n            \"$FRAMEWORK_PATH\"\n          \n          echo \"✅ Framework re-signed successfully\"\n        fi\n        \n        # Re-sign the main app to ensure everything is consistent\n        echo \"🔏 Re-signing main application...\"\n        codesign --force --sign \"Developer ID Application\" \\\n          --options runtime \\\n          --entitlements \"TrackWeight/TrackWeight.entitlements\" \\\n          --timestamp \\\n          --deep \\\n          --strict \\\n          \"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app\"\n        \n        echo \"✅ Application re-signed successfully\"\n\n    - name: Verify Universal Binary and Code Signatures\n      run: |\n        echo \"🏗️ Verifying Universal Binary Architecture...\"\n        APP_BINARY=\"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/MacOS/${{ env.APP_NAME }}\"\n        if [[ -f \"$APP_BINARY\" ]]; then\n          echo \"📊 Binary architectures:\"\n          lipo -archs \"$APP_BINARY\"\n          \n          if lipo -archs \"$APP_BINARY\" | grep -q \"arm64\" && lipo -archs \"$APP_BINARY\" | grep -q \"x86_64\"; then\n            echo \"✅ Universal binary confirmed: Contains both ARM64 and x86_64\"\n          else\n            echo \"❌ Warning: Binary may not be universal\"\n            lipo -detailed_info \"$APP_BINARY\"\n          fi\n        fi\n        \n        # Check framework architecture if it exists\n        FRAMEWORK_PATH=\"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework\"\n        if [[ -d \"$FRAMEWORK_PATH\" ]]; then\n          FRAMEWORK_BINARY=\"$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF\"\n          if [[ -f \"$FRAMEWORK_BINARY\" ]]; then\n            echo \"📊 Framework architectures:\"\n            lipo -archs \"$FRAMEWORK_BINARY\"\n          fi\n        fi\n        \n        # Only verify signatures if certificates are available\n        if [[ -n \"${{ secrets.BUILD_CERTIFICATE_BASE64 }}\" ]]; then\n          echo \"🔍 Verifying main application signature...\"\n          codesign --verify --verbose \"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app\"\n          \n          echo \"🔍 Verifying framework signature...\"\n          if [[ -d \"$FRAMEWORK_PATH\" ]]; then\n            codesign --verify --verbose \"$FRAMEWORK_PATH\"\n          fi\n          \n          echo \"🔍 Deep verification with online validation...\"\n          codesign --verify --deep --strict --verbose=2 \"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app\"\n          \n          echo \"✅ All signature verifications passed\"\n        else\n          echo \"ℹ️  Skipping signature verification (no certificates provided)\"\n        fi\n    \n    - name: Notarize App\n      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' && env.APPLE_ID != '' }}\n      env:\n        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n        APPLE_ID: ${{ secrets.APPLE_ID }}\n        APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}\n        APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\n      run: |\n        echo \"🍎 Starting notarization process...\"\n        \n        # Create a zip file for notarization (notarytool prefers zip over raw .app)\n        cd \"$RUNNER_TEMP/export\"\n        # Use ditto for creating zip compatible with Apple's notarization service  \n        ditto -c -k --keepParent \"${{ env.APP_NAME }}.app\" \"${{ env.APP_NAME }}.zip\"\n        \n        # Submit for notarization\n        echo \"📤 Submitting app for notarization...\"\n        xcrun notarytool submit \"${{ env.APP_NAME }}.zip\" \\\n          --apple-id \"$APPLE_ID\" \\\n          --password \"$APPLE_ID_PASSWORD\" \\\n          --team-id \"$APPLE_TEAM_ID\" \\\n          --wait \\\n          --timeout 20m\n        \n        # Staple the notarization ticket to the app\n        echo \"📎 Stapling notarization ticket...\"\n        xcrun stapler staple \"${{ env.APP_NAME }}.app\"\n        \n        # Verify the stapling worked\n        echo \"✅ Verifying notarization...\"\n        xcrun stapler validate \"${{ env.APP_NAME }}.app\"\n        \n        echo \"🎉 Notarization complete!\"\n\n    - name: Install create-dmg\n      run: |\n        brew install create-dmg\n    \n    - name: Create DMG\n      run: |\n        # Create a clean directory with only the app for DMG creation\n        echo \"📁 Preparing clean DMG contents...\"\n        DMG_STAGING=\"$RUNNER_TEMP/dmg_staging\"\n        rm -rf \"$DMG_STAGING\"\n        mkdir -p \"$DMG_STAGING\"\n        \n        # Copy only the app to staging directory\n        cp -R \"$RUNNER_TEMP/export/${{ env.APP_NAME }}.app\" \"$DMG_STAGING/\"\n        \n        # Create clean professional DMG\n        create-dmg \\\n          --volname \"${{ env.APP_NAME }}\" \\\n          --volicon \"$DMG_STAGING/${{ env.APP_NAME }}.app/Contents/Resources/AppIcon.icns\" \\\n          --window-pos 200 120 \\\n          --window-size 600 300 \\\n          --icon-size 100 \\\n          --icon \"${{ env.APP_NAME }}.app\" 175 120 \\\n          --hide-extension \"${{ env.APP_NAME }}.app\" \\\n          --app-drop-link 425 120 \\\n          --hdiutil-quiet \\\n          \"$RUNNER_TEMP/${{ env.APP_NAME }}.dmg\" \\\n          \"$DMG_STAGING/\"\n    \n    - name: Get version info\n      id: version_info\n      run: |\n        if [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n          VERSION=${GITHUB_REF#refs/tags/}\n        else\n          VERSION=$(date +%Y%m%d-%H%M%S)\n        fi\n        echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n        echo \"dmg_name=${{ env.APP_NAME }}.dmg\" >> $GITHUB_OUTPUT\n    \n    - name: Rename DMG with version\n      run: |\n        mv \"$RUNNER_TEMP/${{ env.APP_NAME }}.dmg\" \"$RUNNER_TEMP/${{ steps.version_info.outputs.dmg_name }}\"\n    \n    - name: Upload DMG as artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: ${{ steps.version_info.outputs.dmg_name }}\n        path: ${{ runner.temp }}/${{ steps.version_info.outputs.dmg_name }}\n        retention-days: 30\n    \n    - name: Create Release\n      if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'true')\n      uses: softprops/action-gh-release@v1\n      with:\n        tag_name: ${{ steps.version_info.outputs.version }}\n        name: ${{ env.APP_NAME }} ${{ steps.version_info.outputs.version }}\n        body: |\n          # TrackWeight ${{ steps.version_info.outputs.version }}\n          \n          Transform your MacBook's trackpad into a precise digital weighing scale!\n          \n          ## Installation\n          1. Download the DMG file below\n          2. Open the DMG and drag TrackWeight.app to your Applications folder\n          3. Run the app and follow the setup instructions\n          \n          ## Requirements\n          - macOS 13.0 or later (Ventura or newer)\n          - MacBook with Force Touch trackpad (2015 or newer MacBook Pro, 2016 or newer MacBook)\n          \n          ## Usage\n          1. Open the scale\n          2. Rest your finger on the trackpad\n          3. While maintaining finger contact, put your object on the trackpad\n          4. Apply minimal pressure while maintaining contact to get the weight\n          \n        files: |\n          ${{ runner.temp }}/${{ steps.version_info.outputs.dmg_name }}\n        draft: false\n        prerelease: false\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    \n    - name: Summary\n      run: |\n        echo \"## Build Summary\" >> $GITHUB_STEP_SUMMARY\n        echo \"✅ Successfully built and packaged TrackWeight DMG\" >> $GITHUB_STEP_SUMMARY\n        echo \"📦 DMG file: ${{ steps.version_info.outputs.dmg_name }}\" >> $GITHUB_STEP_SUMMARY\n        echo \"🔗 Original repository: https://github.com/KrishKrosh/TrackWeight\" >> $GITHUB_STEP_SUMMARY\n        echo \"\" >> $GITHUB_STEP_SUMMARY\n        echo \"The DMG includes:\" >> $GITHUB_STEP_SUMMARY\n        echo \"- TrackWeight.app (signed and notarized if certificates provided)\" >> $GITHUB_STEP_SUMMARY\n        echo \"- Clean professional DMG with just the app and Applications link\" >> $GITHUB_STEP_SUMMARY\n        echo \"- Proper code signing with hardened runtime enabled\" >> $GITHUB_STEP_SUMMARY"
  },
  {
    "path": ".gitignore",
    "content": "# Mac\n.DS_Store\n\n# Xcode\nxcuserdata/\n*.xcuserstate\n\n# Swift Package Manager\nPackages.resolved\n.swiftpm/\n.build/\n\n# Framework\n*.framework/\n*.xcframework/\n*.zip\nbuild/\n\n# AI\n.claude/\n\n.env\n.secrets\n\nlocal_build/"
  },
  {
    "path": "ExportOptions.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>method</key>\n    <string>developer-id</string>\n    <key>teamID</key>\n    <string>9ZRLG6277G</string>\n    <key>signingStyle</key>\n    <string>manual</string>\n    <key>signingCertificate</key>\n    <string>Developer ID Application: Krish Shah (9ZRLG6277G)</string>\n    <key>stripSwiftSymbols</key>\n    <true/>\n    <key>destination</key>\n    <string>export</string>\n    <key>manageAppVersionAndBuildNumber</key>\n    <true/>\n    <key>compileBitcode</key>\n    <false/>\n    <key>uploadBitcode</key>\n    <false/>\n    <key>uploadSymbols</key>\n    <false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Krish Shah\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# TrackWeight\n\n**Turn your MacBook's trackpad into a precise digital weighing scale**\n\n[TrackWeight](\nhttps://x.com/KrishRShah/status/1947186835811193330) is a macOS application that transforms your MacBook's trackpad into an accurate weighing scale by leveraging the Force Touch pressure sensors built into modern MacBook trackpads.\n\nhttps://github.com/user-attachments/assets/7eaf9e0b-3dec-4829-b868-f54a8fd53a84\n\nTo use it yourself:\n\n1. Open the scale\n2. Rest your finger on the trackpad\n3. While maintaining finger contact, put your object on the trackpad\n4. Try to put as little pressure on the trackpad while still maintaining contact. This is the weight of your object\n\n## How It Works\n\nTrackWeight utilizes a custom fork of the [Open Multi-Touch Support library](https://github.com/krishkrosh/OpenMultitouchSupport) by [Takuto Nakamura](https://github.com/Kyome22) to gain private access to all mouse and trackpad events on macOS. This library provides detailed touch data including pressure readings that are normally inaccessible to standard applications.\n\nThe key insight is that trackpad pressure events are only generated when there's capacitance detected on the trackpad surface - meaning your finger (or another conductive object) must be in contact with the trackpad. When this condition is met, the trackpad's Force Touch sensors provide precise pressure readings that can be calibrated and converted into weight measurements.\n\n## Requirements\n\n- **macOS 13.0+** (Ventura or later)\n- **MacBook with Force Touch trackpad** (2015 or newer MacBook Pro, 2016 or newer MacBook)\n- **App Sandbox disabled** (required for low-level trackpad access)\n- **Xcode 16.0+** and **Swift 6.0+** (for development)\n\n## Installation\n\n### Option 1: Download DMG (Recommended)\n\n1. Go to the [Releases](https://github.com/krishkrosh/TrackWeight/releases) page\n2. Download the latest TrackWeight DMG file\n3. Open the DMG and drag TrackWeight.app to your Applications folder\n4. Run the application (you may need to allow it in System Preferences > Security & Privacy for unsigned builds)\n\n### Option 2: Homebrew\n```bash\nbrew install --cask krishkrosh/apps/trackweight --force\n```\n \n### Option 3: Build from Source\n\n1. Clone this repository\n2. Open `TrackWeight.xcodeproj` in Xcode\n3. Disable App Sandbox in the project settings (required for trackpad access)\n4. Build and run the application\n\nFor more information about setting up the build pipeline, see [.github/workflows/README.md](.github/workflows/README.md).\n\n### Calibration Process\n\nThe weight calculations have been validated by:\n1. Placing the MacBook trackpad directly on top of a conventional digital scale\n2. Applying various known weights while maintaining finger contact with the trackpad\n3. Comparing and calibrating the pressure readings against the reference scale measurements\n4. Ensuring consistent accuracy across different weight ranges\n\nIt turns out that the data we get from MultitouchSupport is already in grams!\n\n## Limitations\n\n- **Finger contact required**: The trackpad only provides pressure readings when it detects capacitance (finger touch), so you cannot weigh objects directly without maintaining contact\n- **Surface contact**: Objects being weighed must be placed in a way that doesn't interfere with the required finger contact\n- **Metal objects**: Metal objects may be detected as a finger touch, so you may need to place a piece of paper or a cloth between the object and the trackpad to get an accurate reading\n\n## Technical Details\n\nThe application is built using:\n- **SwiftUI** for the user interface\n- **Combine** for reactive data flow\n- **Open Multi-Touch Support library** for low-level trackpad access\n\n### Open Multi-Touch Support Library\n\nThis project relies heavily on the excellent work by **Takuto Nakamura** ([@Kyome22](https://github.com/Kyome22)) and the [Open Multi-Touch Support library](https://github.com/krishkrosh/OpenMultitouchSupport). The library provides:\n\n- Access to global multitouch events on macOS trackpads\n- Detailed touch data including position, pressure, angle, and density\n- Thread-safe async/await support for touch event streams\n- Touch state tracking and comprehensive sensor data\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Disclaimer\n\nThis application is for experimental and educational purposes. While efforts have been made to ensure accuracy, TrackWeight should not be used for critical measurements or commercial applications where precision is essential. Always verify measurements with a calibrated scale for important use cases.\n"
  },
  {
    "path": "TrackWeight/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"icon_16x16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon_16x16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon_32x32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"icon_32x32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"icon_128x128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"icon_128x128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"icon_256x256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"icon_256x256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"icon_512x512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"icon_512x512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "TrackWeight/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "TrackWeight/ContentView.swift",
    "content": "//\n//  ContentView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct ContentView: View {\n    @State private var showHomePage = true\n    @State private var selectedTab = 1 // Start with Scale tab (index 1)\n    \n    var body: some View {\n        if showHomePage {\n            HomeView {\n                showHomePage = false\n            }\n            .frame(minWidth: 700, minHeight: 500)\n        } else {\n            TabView(selection: $selectedTab) {\n                TrackWeightView()\n                    .tabItem {\n                        Image(systemName: \"arrow.3.trianglepath\")\n                        Text(\"Guided (Experimental)\")\n                    }\n                    .tag(0)\n                \n                ScaleView()\n                    .tabItem {\n                        Image(systemName: \"scalemass\")\n                        Text(\"Scale\")\n                    }\n                    .tag(1)\n                \n                SettingsView()\n                    .tabItem {\n                        Image(systemName: \"gearshape\")\n                        Text(\"Settings\")\n                    }\n                    .tag(2)\n            }\n            .frame(minWidth: 700, minHeight: 500)\n        }\n    }\n}\n\n\n#Preview {\n    ContentView()\n}\n"
  },
  {
    "path": "TrackWeight/ContentViewModel.swift",
    "content": "//\n//  ContentViewModel.swift\n//  OMSDemo\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\n\n@MainActor\nfinal class ContentViewModel: ObservableObject {\n    @Published var touchData = [OMSTouchData]()\n    @Published var isListening: Bool = false\n    @Published var availableDevices = [OMSDeviceInfo]()\n    @Published var selectedDevice: OMSDeviceInfo?\n\n    private let manager = OMSManager.shared\n    private var task: Task<Void, Never>?\n\n    init() {\n        loadDevices()\n    }\n\n    func onAppear() {\n        task = Task { [weak self, manager] in\n            for await touchData in manager.touchDataStream {\n                await MainActor.run {\n                    self?.touchData = touchData\n                }\n            }\n        }\n    }\n\n    func onDisappear() {\n        task?.cancel()\n        stop()\n    }\n\n    func start() {\n        if manager.startListening() {\n            isListening = true\n        }\n    }\n\n    func stop() {\n        if manager.stopListening() {\n            isListening = false\n        }\n    }\n    \n    func loadDevices() {\n        availableDevices = manager.availableDevices\n        selectedDevice = manager.currentDevice\n    }\n    \n    func selectDevice(_ device: OMSDeviceInfo) {\n        if manager.selectDevice(device) {\n            selectedDevice = device\n        }\n    }\n}\n"
  },
  {
    "path": "TrackWeight/DebugView.swift",
    "content": "//\n//  DebugView.swift\n//  TrackWeight\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\n\nstruct DebugView: View {\n    @StateObject var viewModel = ContentViewModel()\n    @Environment(\\.dismiss) private var dismiss\n\n    var body: some View {\n        VStack {\n            // Header with close button\n            HStack {\n                Text(\"Debug Console\")\n                    .font(.title2)\n                    .fontWeight(.semibold)\n                \n                Spacer()\n                \n                Button(action: {\n                    dismiss()\n                }) {\n                    Image(systemName: \"xmark.circle.fill\")\n                        .font(.title2)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(PlainButtonStyle())\n                .help(\"Close Debug Console\")\n            }\n            .padding(.bottom)\n\n            // Device Selector\n            if !viewModel.availableDevices.isEmpty {\n                VStack(alignment: .leading) {\n                    Text(\"Trackpad Device:\")\n                        .font(.headline)\n                    Picker(\"Select Device\", selection: Binding(\n                        get: { viewModel.selectedDevice },\n                        set: { device in\n                            if let device = device {\n                                viewModel.selectDevice(device)\n                            }\n                        }\n                    )) {\n                        ForEach(viewModel.availableDevices, id: \\.self) { device in\n                            Text(\"\\(device.deviceName) (ID: \\(device.deviceID))\")\n                                .tag(device as OMSDeviceInfo?)\n                        }\n                    }\n                    .pickerStyle(MenuPickerStyle())\n                }\n                .padding(.bottom)\n            }\n            \n            if viewModel.isListening {\n                Button {\n                    viewModel.stop()\n                } label: {\n                    Text(\"Stop\")\n                }\n            } else {\n                Button {\n                    viewModel.start()\n                } label: {\n                    Text(\"Start\")\n                }\n            }\n            Canvas { context, size in\n                viewModel.touchData.forEach { touch in\n                    let path = makeEllipse(touch: touch, size: size)\n                    context.fill(path, with: .color(.primary.opacity(Double(touch.total))))\n                }\n            }\n            .frame(width: 600, height: 400)\n            .border(Color.primary)\n        }\n        .fixedSize()\n        .padding()\n        .onAppear {\n            viewModel.onAppear()\n        }\n        .onDisappear {\n            viewModel.onDisappear()\n        }\n    }\n\n    private func makeEllipse(touch: OMSTouchData, size: CGSize) -> Path {\n        let x = Double(touch.position.x) * size.width\n        let y = Double(1.0 - touch.position.y) * size.height\n        let u = size.width / 100.0\n        let w = Double(touch.axis.major) * u\n        let h = Double(touch.axis.minor) * u\n        return Path(ellipseIn: CGRect(x: -0.5 * w, y: -0.5 * h, width: w, height: h))\n            .rotation(.radians(Double(-touch.angle)), anchor: .topLeading)\n            .offset(x: x, y: y)\n            .path(in: CGRect(origin: .zero, size: size))\n    }\n}\n\n#Preview {\n    DebugView()\n}"
  },
  {
    "path": "TrackWeight/HomeView.swift",
    "content": "//\n//  HomeView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct HomeView: View {\n    let onBegin: () -> Void\n    \n    var body: some View {\n        VStack(spacing: 40) {\n            Spacer()\n            \n            // Title section\n            VStack(spacing: 15) {\n                Image(systemName: \"scalemass\")\n                    .font(.system(size: 80, weight: .ultraLight))\n                    .foregroundStyle(Color.blue)\n                \n                Text(\"TrackWeight\")\n                    .font(.system(size: 48, weight: .bold, design: .rounded))\n                    .foregroundStyle(\n                        LinearGradient(\n                            colors: [.blue, .teal, .cyan],\n                            startPoint: .leading,\n                            endPoint: .trailing\n                        )\n                    )\n            }\n            \n            // Description section\n            VStack(spacing: 20) {\n                Text(\"Transform your MacBook trackpad into a precision scale using Apple's private MultitouchSupport framework to read pressure values with gram-level accuracy.\")\n                    .font(.system(size: 18, weight: .medium))\n                    .foregroundStyle(Color.primary)\n                    .multilineTextAlignment(.center)\n                    .frame(maxWidth: 550)\n                \n                // Limitations section\n                VStack(spacing: 12) {\n                    Text(\"Important Limitations\")\n                        .font(.system(size: 16, weight: .semibold))\n                        .foregroundStyle(Color.orange)\n                    \n                    VStack(spacing: 8) {\n                        LimitationRow(\n                            icon: \"hand.point.up.left\",\n                            text: \"Requires finger contact for capacitive detection\"\n                        )\n                        LimitationRow(\n                            icon: \"chart.line.downtrend.xyaxis\",\n                            text: \"May experience pressure drift when placing objects\"\n                        )\n                        LimitationRow(\n                            icon: \"cube.fill\",\n                            text: \"Metal/magnetic objects may not work\"\n                        )\n                    }\n                }\n                .padding(.horizontal, 30)\n                .padding(.vertical, 20)\n                .background(\n                    RoundedRectangle(cornerRadius: 15)\n                        .foregroundColor(Color.orange.opacity(0.05))\n                        .overlay(\n                            RoundedRectangle(cornerRadius: 15)\n                                .stroke(Color.orange.opacity(0.2), lineWidth: 1)\n                        )\n                )\n                .frame(maxWidth: 500)\n            }\n            \n            Spacer()\n            \n            // Begin button\n            Button(action: onBegin) {\n                HStack(spacing: 10) {\n                    Text(\"Begin\")\n                        .font(.system(size: 18, weight: .semibold))\n                    Image(systemName: \"arrow.right\")\n                        .font(.system(size: 16, weight: .semibold))\n                }\n                .foregroundStyle(Color.white)\n                .frame(width: 140, height: 50)\n                .background(\n                    RoundedRectangle(cornerRadius: 25)\n                        .fill(\n                            LinearGradient(\n                                colors: [.blue, .teal],\n                                startPoint: .leading,\n                                endPoint: .trailing\n                            )\n                        )\n                        .shadow(color: .blue.opacity(0.3), radius: 10, x: 0, y: 5)\n                )\n            }\n            .buttonStyle(.plain)\n            .scaleEffect(1.0)\n            .animation(.spring(response: 0.3, dampingFraction: 0.8), value: true)\n            .padding(.vertical, 10)\n\n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .padding(.horizontal, 40)\n    }\n}\n\nstruct LimitationRow: View {\n    let icon: String\n    let text: String\n    \n    var body: some View {\n        HStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 14, weight: .medium))\n                .foregroundStyle(Color.orange)\n                .frame(width: 20)\n            \n            Text(text)\n                .font(.system(size: 14, weight: .medium))\n                .foregroundStyle(Color.secondary)\n                .multilineTextAlignment(.leading)\n            \n            Spacer()\n        }\n    }\n}\n\n#Preview {\n    HomeView(onBegin: {})\n}\n"
  },
  {
    "path": "TrackWeight/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "TrackWeight/ScaleView.swift",
    "content": "//\n//  ScaleView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct ScaleView: View {\n    @StateObject private var viewModel = ScaleViewModel()\n    @State private var scaleCompression: CGFloat = 0\n    @State private var displayShake = false\n    @State private var particleOffset: CGFloat = 0\n    @State private var keyMonitor: Any?\n    \n    var body: some View {\n        GeometryReader { geometry in\n            ZStack {\n                // Animated gradient background\n//                    LinearGradient(\n//                        colors: [\n//                            Color(red: 0.95, green: 0.97, blue: 1.0),\n//                            Color(red: 0.85, green: 0.92, blue: 0.98)\n//                        ],\n//                        startPoint: .topLeading,\n//                        endPoint: .bottomTrailing\n//                    )\n//                    .ignoresSafeArea()\n                \n                VStack(spacing: geometry.size.height * 0.06) {\n                    // Title with subtitle directly underneath\n                    VStack(spacing: 8) {\n                        Text(\"Track Weight\")\n                            .font(.system(size: min(max(geometry.size.width * 0.05, 24), 42), weight: .bold, design: .rounded))\n                            .foregroundStyle(\n                                LinearGradient(\n                                    colors: [.blue, .teal, .cyan],\n                                    startPoint: .leading,\n                                    endPoint: .trailing\n                                )\n                            )\n                            .minimumScaleFactor(0.7)\n                            .lineLimit(1)\n                        \n                        Text(\"Place your finger on the trackpad to begin\")\n                            .font(.system(size: min(max(geometry.size.width * 0.022, 14), 18), weight: .medium))\n                            .foregroundStyle(.gray)\n                            .multilineTextAlignment(.center)\n                            .frame(maxWidth: geometry.size.width * 0.8)\n                            .opacity(viewModel.hasTouch ? 0 : 1)\n                            .animation(.easeInOut(duration: 0.5), value: viewModel.hasTouch)\n                    }\n                    .frame(height: max(geometry.size.height * 0.15, 80)) // Fixed height for title + subtitle\n                    .frame(maxWidth: .infinity) // Ensure full width for centering\n                    \n                    Spacer()\n                    \n                    // Cartoon Digital Scale - responsive size\n                    HStack {\n                        Spacer()\n                        CartoonScaleView(\n                            weight: viewModel.currentWeight,\n                            hasTouch: viewModel.hasTouch,\n                            compression: $scaleCompression,\n                            displayShake: $displayShake,\n                            scaleFactor: min(geometry.size.width / 700, geometry.size.height / 500)\n                        )\n                        Spacer()\n                    }\n                    \n                    Spacer()\n                    \n                    // Fixed container for button to prevent jumping\n                    VStack(spacing: 10) {\n                        if viewModel.hasTouch {\n                            Text(\"Press spacebar or click to zero\")\n                                .font(.system(size: min(max(geometry.size.width * 0.018, 12), 16), weight: .medium))\n                                .foregroundStyle(.gray)\n                        }\n                        \n                        Button(action: {\n                            viewModel.zeroScale()\n                        }) {\n                            HStack(spacing: 8) {\n                                Image(systemName: \"arrow.clockwise\")\n                                    .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold))\n                                Text(\"Zero Scale\")\n                                    .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold))\n                            }\n                            .foregroundStyle(.white)\n                            .frame(width: min(max(geometry.size.width * 0.2, 140), 180), \n                                   height: min(max(geometry.size.height * 0.08, 40), 55))\n                            .background(\n                                RoundedRectangle(cornerRadius: 25)\n                                    .fill(\n                                        LinearGradient(\n                                            colors: [.blue, .teal],\n                                            startPoint: .leading,\n                                            endPoint: .trailing\n                                        )\n                                    )\n                            )\n                        }\n                        .buttonStyle(.plain)\n                        .opacity(viewModel.hasTouch ? 1 : 0)\n                        .scaleEffect(viewModel.hasTouch ? 1 : 0.8)\n                        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: viewModel.hasTouch)\n                    }\n                    .frame(height: min(max(geometry.size.height * 0.15, 80), 100)) // Fixed space for button + instruction\n                    .frame(maxWidth: .infinity) // Ensure full width for centering\n                }\n                .padding(.horizontal, max(geometry.size.width * 0.05, 20))\n                .padding(.vertical, max(geometry.size.height * 0.03, 20))\n                .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensure the VStack takes full available space\n            }\n        }\n        .focusable()\n        .modifier(FocusEffectModifier())\n        .onChange(of: viewModel.currentWeight) { newWeight in\n            withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {\n                scaleCompression = CGFloat(min(newWeight / 100.0, 0.2))\n            }\n        }\n        .onAppear {\n            viewModel.startListening()\n            setupKeyMonitoring()\n        }\n        .onDisappear {\n            viewModel.stopListening()\n            removeKeyMonitoring()\n        }\n    }\n    \n    private func setupKeyMonitoring() {\n        keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in\n            // Space key code is 49\n            if event.keyCode == 49 && viewModel.hasTouch {\n                viewModel.zeroScale()\n            }\n            return event\n        }\n    }\n    \n    private func removeKeyMonitoring() {\n        if let monitor = keyMonitor {\n            NSEvent.removeMonitor(monitor)\n            keyMonitor = nil\n        }\n    }\n}\n\nstruct CartoonScaleView: View {\n    let weight: Float\n    let hasTouch: Bool\n    @Binding var compression: CGFloat\n    @Binding var displayShake: Bool\n    let scaleFactor: CGFloat\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // Scale platform (top) - responsive to weight\n            RoundedRectangle(cornerRadius: 8)\n                .fill(\n                    LinearGradient(\n                        colors: [.gray.opacity(0.3), .gray.opacity(0.6)],\n                        startPoint: .top,\n                        endPoint: .bottom\n                    )\n                )\n                .frame(width: 200 * scaleFactor, height: 12 * scaleFactor)\n                .offset(y: compression * 15)\n            \n            // Scale body\n            ZStack {\n                // Main body\n                RoundedRectangle(cornerRadius: 20)\n                    .fill(\n                        LinearGradient(\n                            colors: [\n                                Color(red: 0.95, green: 0.95, blue: 0.97),\n                                Color(red: 0.85, green: 0.85, blue: 0.90)\n                            ],\n                            startPoint: .topLeading,\n                            endPoint: .bottomTrailing\n                        )\n                    )\n                    .frame(width: 250 * scaleFactor, height: 150 * scaleFactor)\n                    .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 8)\n                \n                // Display screen\n                RoundedRectangle(cornerRadius: 12)\n                    .fill(.black)\n                    .frame(width: 180 * scaleFactor, height: 60 * scaleFactor)\n                    .offset(y: -10)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 12)\n                            .fill(\n                                LinearGradient(\n                                    colors: [.teal.opacity(0.8), .blue.opacity(0.6)],\n                                    startPoint: .topLeading,\n                                    endPoint: .bottomTrailing\n                                )\n                            )\n                            .frame(width: 176 * scaleFactor, height: 56 * scaleFactor)\n                            .offset(y: -10)\n                    )\n                \n                // Weight display\n                VStack(spacing: 2) {\n                    Text(String(format: \"%.1f\", weight))\n                        .font(.system(size: 32 * scaleFactor, weight: .bold, design: .monospaced))\n                        .foregroundStyle(.white)\n                        .shadow(color: .teal, radius: hasTouch ? 2 : 0)\n                        .animation(.easeInOut(duration: 0.2), value: weight)\n                    \n                    Text(\"grams\")\n                        .font(.system(size: 12 * scaleFactor, weight: .medium))\n                        .foregroundStyle(.white.opacity(0.8))\n                }\n                .offset(y: -10)\n                \n                // Status indicator - simple and clean\n                if hasTouch {\n                    Circle()\n                        .fill(.teal)\n                        .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)\n                        .offset(x: 90 * scaleFactor, y: -50 * scaleFactor)\n                }\n                \n                // Fun face on the scale - positioned below the display screen\n                VStack(spacing: 8 * scaleFactor) {\n                    // Eyes\n                    HStack(spacing: 15 * scaleFactor) {\n                        Circle()\n                            .fill(.black)\n                            .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)\n                        Circle()\n                            .fill(.black)\n                            .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)\n                    }\n                    \n                    // Responsive mouth expression\n                    Group {\n                        if hasTouch && weight > 5 {\n                            // Happy mouth when weighing something substantial\n                            Path { path in\n                                path.move(to: CGPoint(x: 0, y: 0))\n                                path.addQuadCurve(to: CGPoint(x: 20, y: 0), control: CGPoint(x: 0, y: 15))\n                            }\n                            .stroke(.black, lineWidth: 2 * scaleFactor)\n                            .frame(width: 20 * scaleFactor, height: 10 * scaleFactor)\n                        } else {\n                            // Neutral mouth\n                            Rectangle()\n                                .fill(.black)\n                                .frame(width: 12 * scaleFactor, height: 2 * scaleFactor)\n                        }\n                    }\n                    .animation(.easeInOut(duration: 0.3), value: weight > 5)\n                }\n                .offset(y: 60 * scaleFactor) // Position well below the display screen\n            }\n            \n            // Scale legs\n            HStack(spacing: 140 * scaleFactor) {\n                ForEach(0..<2, id: \\.self) { _ in\n                    RoundedRectangle(cornerRadius: 4)\n                        .fill(.gray.opacity(0.7))\n                        .frame(width: 12 * scaleFactor, height: 25 * scaleFactor)\n                        .offset(y: compression * 3)\n                }\n            }\n            .offset(y: -5)\n        }\n        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression)\n    }\n}\n\nstruct FocusEffectModifier: ViewModifier {\n    func body(content: Content) -> some View {\n        if #available(macOS 14.0, *) {\n            content.focusEffectDisabled()\n        } else {\n            content\n        }\n    }\n}\n\n#Preview {\n    ScaleView()\n}\n"
  },
  {
    "path": "TrackWeight/ScaleViewModel.swift",
    "content": "//\n//  ScaleViewModel.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\nimport Combine\n\n@MainActor\nfinal class ScaleViewModel: ObservableObject {\n    @Published var currentWeight: Float = 0.0\n    @Published var zeroOffset: Float = 0.0\n    @Published var isListening = false\n    @Published var hasTouch = false\n    \n    private let manager = OMSManager.shared\n    private var task: Task<Void, Never>?\n    private var rawWeight: Float = 0.0\n    \n    func startListening() {\n        if manager.startListening() {\n            isListening = true\n        }\n        \n        task = Task { [weak self, manager] in\n            for await touchData in manager.touchDataStream {\n                await MainActor.run {\n                    self?.processTouchData(touchData)\n                }\n            }\n        }\n    }\n    \n    func stopListening() {\n        task?.cancel()\n        if manager.stopListening() {\n            isListening = false\n            hasTouch = false\n            currentWeight = 0.0\n        }\n    }\n    \n    func zeroScale() {\n        if hasTouch {\n            zeroOffset = rawWeight\n        }\n    }\n    \n    private func processTouchData(_ touchData: [OMSTouchData]) {\n        if touchData.isEmpty {\n            hasTouch = false\n            currentWeight = 0.0\n            zeroOffset = 0.0  // Reset zero when finger is lifted\n        } else {\n            hasTouch = true\n            rawWeight = touchData.first?.pressure ?? 0.0\n            currentWeight = max(0, rawWeight - zeroOffset)\n        }\n    }\n    \n    deinit {\n        task?.cancel()\n        manager.stopListening()\n    }\n}"
  },
  {
    "path": "TrackWeight/SettingsView.swift",
    "content": "//\n//  SettingsView.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\n\nstruct SettingsView: View {\n    @StateObject private var viewModel = ContentViewModel()\n    @State private var showDebugView = false\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // Minimal Header\n            Text(\"Settings\")\n                .font(.title)\n                .fontWeight(.medium)\n                .padding(.top, 32)\n                .padding(.bottom, 32)\n            \n            // Settings Cards\n            VStack(spacing: 20) {\n                // Device Card\n                SettingsCard {\n                    VStack(spacing: 20) {\n                        // Status Row\n                        HStack {\n                            HStack(spacing: 12) {\n                                Text(\"Trackpad\")\n                                    .font(.headline)\n                                    .fontWeight(.medium)\n                            }\n                            \n                            Spacer()\n                            \n                            if !viewModel.availableDevices.isEmpty {\n                                Text(\"\\(viewModel.availableDevices.count) device\\(viewModel.availableDevices.count == 1 ? \"\" : \"s\")\")\n                                    .font(.caption)\n                                    .foregroundColor(.secondary)\n                            }\n                        }\n                        \n                        // Device Selector\n                        if !viewModel.availableDevices.isEmpty {\n                            HStack {\n                                Picker(\"\", selection: Binding(\n                                    get: { viewModel.selectedDevice },\n                                    set: { device in\n                                        if let device = device {\n                                            viewModel.selectDevice(device)\n                                        }\n                                    }\n                                )) {\n                                    ForEach(viewModel.availableDevices, id: \\.self) { device in\n                                        Text(device.deviceName)\n                                            .tag(device as OMSDeviceInfo?)\n                                    }\n                                }\n                                .pickerStyle(MenuPickerStyle())\n                                \n                                Spacer()\n                            }\n                        } else {\n                            HStack {\n                                Text(\"No devices available\")\n                                    .foregroundColor(.secondary)\n                                Spacer()\n                            }\n                        }\n                    }\n                }\n                \n                // Debug Card\n                SettingsCard {\n                    Button(action: { showDebugView = true }) {\n                        HStack(spacing: 16) {\n                            VStack(alignment: .leading, spacing: 4) {\n                                Text(\"Debug Console\")\n                                    .font(.headline)\n                                    .fontWeight(.medium)\n                                    .foregroundColor(.primary)\n                                \n                                Text(\"Raw touch data & diagnostics\")\n                                    .font(.caption)\n                                    .foregroundColor(.secondary)\n                            }\n                            \n                            Spacer()\n                            \n                            Image(systemName: \"chevron.right\")\n                                .font(.caption)\n                                .fontWeight(.medium)\n                                .foregroundColor(.secondary.opacity(0.6))\n                        }\n                        .contentShape(Rectangle())\n                    }\n                    .buttonStyle(CardButtonStyle())\n                }\n            }\n            .frame(maxWidth: 480)\n            .padding(.horizontal, 40)\n            \n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color(NSColor.windowBackgroundColor))\n        .sheet(isPresented: $showDebugView) {\n            DebugView()\n                .frame(minWidth: 700, minHeight: 500)\n        }\n        .onAppear {\n            viewModel.loadDevices()\n        }\n    }\n}\n\nstruct SettingsCard<Content: View>: View {\n    let content: Content\n    \n    init(@ViewBuilder content: () -> Content) {\n        self.content = content()\n    }\n    \n    var body: some View {\n        VStack {\n            content\n        }\n        .padding(24)\n        .background(Color(NSColor.controlBackgroundColor))\n        .cornerRadius(16)\n        .shadow(color: Color.black.opacity(0.03), radius: 1, x: 0, y: 1)\n        .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4)\n    }\n}\n\nstruct CardButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)\n            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)\n    }\n}\n\n#Preview {\n    SettingsView()\n} "
  },
  {
    "path": "TrackWeight/TrackWeight.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <!-- Hardened Runtime (required for notarization) -->\n    <key>com.apple.security.cs.allow-jit</key>\n    <false/>\n    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n    <false/>\n    <key>com.apple.security.cs.allow-dyld-environment-variables</key>\n    <false/>\n    <key>com.apple.security.cs.disable-library-validation</key>\n    <false/>\n    \n    <!-- App Sandbox disabled (needed for trackpad access) -->\n    <key>com.apple.security.app-sandbox</key>\n    <false/>\n    \n    <!-- Device access for trackpad -->\n    <key>com.apple.security.device.usb</key>\n    <true/>\n    <key>com.apple.security.device.serial</key>\n    <true/>\n    \n    <!-- Network access (if needed) -->\n    <key>com.apple.security.network.client</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "TrackWeight/TrackWeightApp.swift",
    "content": "//\n//  TrackWeightApp.swift\n//  TrackWeight\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport SwiftUI\n\n@main\nstruct TrackWeightApp: App {\n    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n\nfinal class AppDelegate: NSObject, NSApplicationDelegate {\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }\n}\n"
  },
  {
    "path": "TrackWeight/TrackWeightView.swift",
    "content": "//\n//  TrackWeightView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct TrackWeightView: View {\n    @StateObject private var viewModel = WeighingViewModel()\n    \n    var body: some View {\n        VStack(spacing: 30) {\n            switch viewModel.state {\n            case .welcome:\n                WelcomeView {\n                    viewModel.startWeighing()\n                }\n                \n            case .waitingForFinger:\n                FingerTimerView(\n                    progress: viewModel.fingerTimer,\n                    hasDetectedFinger: viewModel.fingerTimer > 0\n                )\n                \n            case .waitingForItem:\n                InstructionView(\n                    title: \"Place your item\",\n                    subtitle: \"While maintaining contact with the trackpad, gently place your item. Use as little pressure as possible with your reference finger.\",\n                    disclaimer: \"Keep your finger still and apply minimal pressure\",\n                    icon: \"cube.box\"\n                )\n                \n            case .weighing:\n                WeighingView(\n                    currentPressure: viewModel.currentPressure,\n                    isStabilizing: viewModel.isStabilizing,\n                    stabilityProgress: viewModel.stabilityProgress\n                )\n                \n            case .result(let weight):\n                ResultView(weight: weight) {\n                    viewModel.restart()\n                }\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color(.windowBackgroundColor))\n        .animation(.easeInOut(duration: 0.6), value: viewModel.state)\n    }\n}\n\nstruct WelcomeView: View {\n    let onStart: () -> Void\n    \n    var body: some View {\n        VStack(spacing: 25) {\n            Image(systemName: \"scalemass\")\n                .font(.system(size: 80, weight: .ultraLight))\n                .foregroundStyle(.primary)\n            \n            Text(\"TrackWeight\")\n                .font(.system(size: 36, weight: .bold, design: .rounded))\n                .foregroundStyle(.primary)\n            \n            Text(\"Turn your trackpad into a precision scale. Place objects and get their weight in grams.\")\n                .font(.system(size: 16, weight: .medium))\n                .foregroundStyle(.secondary)\n                .multilineTextAlignment(.center)\n                .frame(maxWidth: 400)\n            \n            VStack(spacing: 15) {\n                Button(action: onStart) {\n                    Text(\"Begin\")\n                        .font(.system(size: 16, weight: .semibold))\n                        .foregroundStyle(.white)\n                        .frame(width: 120, height: 40)\n                        .background(\n                            RoundedRectangle(cornerRadius: 20)\n                                .fill(.blue)\n                        )\n                }\n                .buttonStyle(.plain)\n            }\n        }\n    }\n}\n\nstruct FingerTimerView: View {\n    let progress: Float\n    let hasDetectedFinger: Bool\n    \n    var body: some View {\n        VStack(spacing: 30) {\n            Image(systemName: \"hand.point.up.left\")\n                .font(.system(size: 60, weight: .light))\n                .foregroundStyle(.blue)\n            \n            Text(\"Hold your finger steady\")\n                .font(.system(size: 28, weight: .bold, design: .rounded))\n                .foregroundStyle(.primary)\n            \n            VStack(spacing: 8) {\n                Text(\"Keep your finger on the trackpad for 3 seconds\")\n                    .font(.system(size: 16, weight: .medium))\n                    .foregroundStyle(.secondary)\n                    .multilineTextAlignment(.center)\n                    .frame(maxWidth: 300)\n                \n                Text(\"This establishes your baseline pressure\")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundStyle(.tertiary)\n                    .multilineTextAlignment(.center)\n                    .frame(maxWidth: 300)\n            }\n            \n            // Bubble filling animation\n            ZStack {\n                Circle()\n                    .stroke(.blue.opacity(0.3), lineWidth: 4)\n                    .frame(width: 100, height: 100)\n                \n                Circle()\n                    .fill(.blue.opacity(0.2))\n                    .frame(width: 100, height: 100)\n                \n                if hasDetectedFinger {\n                    Circle()\n                        .trim(from: 0, to: CGFloat(progress))\n                        .stroke(.blue, style: StrokeStyle(lineWidth: 4, lineCap: .round))\n                        .frame(width: 100, height: 100)\n                        .rotationEffect(.degrees(-90))\n                        .animation(.linear(duration: 0.1), value: progress)\n                    \n                    // Gentle bubble effect\n                    Circle()\n                        .fill(\n                            RadialGradient(\n                                colors: [.blue.opacity(0.3), .blue.opacity(0.1)],\n                                center: .center,\n                                startRadius: 0,\n                                endRadius: 50\n                            )\n                        )\n                        .frame(width: CGFloat(progress) * 80 + 20, height: CGFloat(progress) * 80 + 20)\n                        .animation(.easeInOut(duration: 0.2), value: progress)\n                    \n                    Text(\"\\(Int((1 - progress) * 3) + 1)\")\n                        .font(.system(size: 24, weight: .bold, design: .monospaced))\n                        .foregroundStyle(.blue)\n                }\n            }\n            .scaleEffect(hasDetectedFinger ? 1.0 : 0.8)\n            .animation(.spring(response: 0.3, dampingFraction: 0.8), value: hasDetectedFinger)\n        }\n    }\n}\n\nstruct InstructionView: View {\n    let title: String\n    let subtitle: String\n    let disclaimer: String?\n    let icon: String\n    \n    init(title: String, subtitle: String, disclaimer: String? = nil, icon: String) {\n        self.title = title\n        self.subtitle = subtitle\n        self.disclaimer = disclaimer\n        self.icon = icon\n    }\n    \n    var body: some View {\n        VStack(spacing: 20) {\n            Image(systemName: icon)\n                .font(.system(size: 60, weight: .light))\n                .foregroundStyle(.blue)\n            \n            Text(title)\n                .font(.system(size: 28, weight: .bold, design: .rounded))\n                .foregroundStyle(.primary)\n            \n            VStack(spacing: 10) {\n                Text(subtitle)\n                    .font(.system(size: 16, weight: .medium))\n                    .foregroundStyle(.secondary)\n                    .multilineTextAlignment(.center)\n                    .frame(maxWidth: 350)\n                \n                if let disclaimer = disclaimer {\n                    Text(disclaimer)\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundStyle(.orange)\n                        .multilineTextAlignment(.center)\n                        .frame(maxWidth: 300)\n                }\n            }\n        }\n    }\n}\n\nstruct WeighingView: View {\n    let currentPressure: Float\n    let isStabilizing: Bool\n    let stabilityProgress: Float\n    \n    var body: some View {\n        VStack(spacing: 30) {\n            Text(\"Weighing...\")\n                .font(.system(size: 24, weight: .semibold, design: .rounded))\n                .foregroundStyle(.primary)\n            \n            VStack(spacing: 10) {\n                Text(String(format: \"%.1f\", currentPressure))\n                    .font(.system(size: 64, weight: .bold, design: .monospaced))\n                    .foregroundStyle(.blue)\n                    .animation(.easeInOut(duration: 0.2), value: currentPressure)\n                \n                Text(\"grams\")\n                    .font(.system(size: 20, weight: .medium))\n                    .foregroundStyle(.secondary)\n            }\n            \n            VStack(spacing: 12) {\n                Text(\"Release pressure while maintaining contact\")\n                    .font(.system(size: 16, weight: .semibold))\n                    .foregroundStyle(.orange)\n                    .multilineTextAlignment(.center)\n                \n                Text(\"Keep your finger on the trackpad but apply as little pressure as possible\")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundStyle(.secondary)\n                    .multilineTextAlignment(.center)\n                    .frame(maxWidth: 350)\n                \n                if isStabilizing {\n                    VStack(spacing: 8) {\n                        Text(\"Stabilizing...\")\n                            .font(.system(size: 14, weight: .medium))\n                            .foregroundStyle(.orange)\n                        \n                        // Progress bar\n                        ZStack {\n                            RoundedRectangle(cornerRadius: 4)\n                                .fill(.orange.opacity(0.2))\n                                .frame(width: 200, height: 8)\n                            \n                            HStack {\n                                RoundedRectangle(cornerRadius: 4)\n                                    .fill(.orange)\n                                    .frame(width: 200 * CGFloat(stabilityProgress), height: 8)\n                                    .animation(.linear(duration: 0.1), value: stabilityProgress)\n                                \n                                Spacer()\n                            }\n                        }\n                        .frame(width: 200)\n                        \n                        Text(\"\\(Int((1 - stabilityProgress) * 2) + 1)s remaining\")\n                            .font(.system(size: 12, weight: .medium, design: .monospaced))\n                            .foregroundStyle(.orange.opacity(0.8))\n                    }\n                }\n            }\n            .frame(maxWidth: 350)\n        }\n    }\n}\n\n\nstruct ResultView: View {\n    let weight: Float\n    let onRestart: () -> Void\n    \n    var body: some View {\n        VStack(spacing: 30) {\n            Image(systemName: \"checkmark.circle.fill\")\n                .font(.system(size: 60))\n                .foregroundStyle(.green)\n                .scaleEffect(1.0)\n                .onAppear {\n                    withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {\n                        // Animation handled by parent view\n                    }\n                }\n            \n            Text(\"Your object weighs\")\n                .font(.system(size: 20, weight: .medium))\n                .foregroundStyle(.secondary)\n            \n            VStack(spacing: 5) {\n                Text(String(format: \"%.1f\", weight))\n                    .font(.system(size: 56, weight: .bold, design: .monospaced))\n                    .foregroundStyle(.primary)\n                \n                Text(\"grams\")\n                    .font(.system(size: 18, weight: .medium))\n                    .foregroundStyle(.secondary)\n            }\n            \n            Button(action: onRestart) {\n                Image(systemName: \"arrow.clockwise\")\n                    .font(.system(size: 20, weight: .medium))\n                    .foregroundStyle(.blue)\n                    .frame(width: 44, height: 44)\n                    .background(\n                        Circle()\n                            .fill(.blue.opacity(0.1))\n                    )\n            }\n            .buttonStyle(.plain)\n        }\n    }\n}\n\n#Preview {\n    TrackWeightView()\n}\n"
  },
  {
    "path": "TrackWeight/WeighingState.swift",
    "content": "//\n//  WeighingState.swift\n//  TrackWeight\n//\n\nimport Foundation\n\nenum WeighingState: Equatable {\n    case welcome\n    case waitingForFinger\n    case waitingForItem\n    case weighing\n    case result(weight: Float)\n}"
  },
  {
    "path": "TrackWeight/WeighingViewModel.swift",
    "content": "//\n//  WeighingViewModel.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\nimport Combine\n\n@MainActor\nfinal class WeighingViewModel: ObservableObject {\n    @Published var state: WeighingState = .welcome\n    @Published var currentPressure: Float = 0.0\n    @Published var maxPressure: Float = 0.0\n    @Published var isListening = false\n    @Published var fingerTimer: Float = 0.0 // 0.0 to 1.0 for animation\n    \n    private let manager = OMSManager.shared\n    private var task: Task<Void, Never>?\n    private var timerTask: Task<Void, Never>?\n    private var baselinePressure: Float = 0.0\n    private var hasDetectedFinger = false\n    private var hasDetectedItem = false\n    private var finalWeight: Float = 0.0\n    private let fingerHoldDuration: TimeInterval = 3.0\n    private var pressureHistory: [Float] = []\n    private let historySize = 10\n    private let rateOfChangeThreshold: Float = 5\n    \n    // Weighing stability properties\n    private let stabilityDuration: TimeInterval = 3.0\n    private let stabilityAnimationDelay: TimeInterval = 1.0 // Show animation after 1s of stability\n    private var stabilityStartTime: Date?\n    private var stableWeight: Float = 0.0\n    private let stabilityThreshold: Float = 2.0 // Max allowed weight variation\n    @Published var stabilityProgress: Float = 0.0\n    @Published var isStabilizing: Bool = false\n    \n    func startWeighing() {\n        state = .waitingForFinger\n        hasDetectedFinger = false\n        hasDetectedItem = false\n        baselinePressure = 0.0\n        currentPressure = 0.0\n        maxPressure = 0.0\n        finalWeight = 0.0\n        fingerTimer = 0.0\n        stabilityProgress = 0.0\n        stabilityStartTime = nil\n        stableWeight = 0.0\n        isStabilizing = false\n        pressureHistory.removeAll()\n        \n        if manager.startListening() {\n            isListening = true\n        }\n        \n        task = Task { [weak self, manager] in\n            for await touchData in manager.touchDataStream {\n                await MainActor.run {\n                    self?.processTouchData(touchData)\n                }\n            }\n        }\n    }\n    \n    func restart() {\n        stopListening()\n        state = .welcome\n        fingerTimer = 0.0\n        stabilityProgress = 0.0\n        stabilityStartTime = nil\n        isStabilizing = false\n    }\n    \n    private func stopListening() {\n        task?.cancel()\n        timerTask?.cancel()\n        if manager.stopListening() {\n            isListening = false\n        }\n    }\n    \n    private func startFingerTimer() {\n        timerTask?.cancel()\n        fingerTimer = 0.0\n        \n        timerTask = Task { [weak self] in\n            let startTime = Date()\n            \n            while !Task.isCancelled {\n                let elapsed = Date().timeIntervalSince(startTime)\n                let progress = min(elapsed / (self?.fingerHoldDuration ?? 3.0), 1.0)\n                \n                await MainActor.run {\n                    self?.fingerTimer = Float(progress)\n                }\n                \n                if progress >= 1.0 {\n                    await MainActor.run {\n                        self?.completeFingerTimer()\n                    }\n                    break\n                }\n                \n                try? await Task.sleep(nanoseconds: 16_666_667) // ~60fps\n            }\n        }\n    }\n    \n    private func resetFingerTimer() {\n        timerTask?.cancel()\n        fingerTimer = 0.0\n    }\n    \n    private func completeFingerTimer() {\n        hasDetectedFinger = true\n        baselinePressure = currentPressure\n        state = .waitingForItem\n        timerTask?.cancel()\n    }\n    \n    private func startStabilityTimer(with weight: Float) {\n        // stabilityStartTime and stableWeight are already set in the calling code\n        stabilityProgress = 0.0\n        isStabilizing = true // Start showing animation since we're already past the 1s delay\n        \n        timerTask = Task { [weak self] in\n            // We start from the point where animation should begin (after 1s delay)\n            let animationStartTime = Date()\n            let remainingDuration = (self?.stabilityDuration ?? 3.0) - (self?.stabilityAnimationDelay ?? 1.0)\n            \n            while !Task.isCancelled {\n                let elapsed = Date().timeIntervalSince(animationStartTime)\n                let progress = min(elapsed / remainingDuration, 1.0)\n                \n                await MainActor.run {\n                    self?.stabilityProgress = Float(progress)\n                }\n                \n                if progress >= 1.0 {\n                    await MainActor.run {\n                        self?.completeWeighing()\n                    }\n                    break\n                }\n                \n                try? await Task.sleep(nanoseconds: 16_666_667) // ~60fps\n            }\n        }\n    }\n    \n    private func completeWeighing() {\n        state = .result(weight: currentPressure)\n        stopListening()\n    }\n    \n    private func resetStabilityTimer() {\n        stabilityStartTime = nil\n        stabilityProgress = 0.0\n        isStabilizing = false\n        timerTask?.cancel()\n    }\n    \n    private func processTouchData(_ touchData: [OMSTouchData]) {\n        guard !touchData.isEmpty else {\n            // Reset timer if finger is lifted during waiting\n            if state == .waitingForFinger && !hasDetectedFinger {\n                resetFingerTimer()\n            }\n            \n            if state == .weighing {\n                if hasDetectedItem && finalWeight > 0 {\n                    state = .result(weight: finalWeight)\n                    stopListening()\n                }\n            }\n            return\n        }\n        \n        \n        let mainTouch = touchData.first!\n        currentPressure = mainTouch.pressure\n        \n        // Add current pressure to history\n        pressureHistory.append(currentPressure)\n        if pressureHistory.count > historySize {\n            pressureHistory.removeFirst()\n        }\n        \n        // log the average pressure (moving avg)\n        let avgPressure = pressureHistory.reduce(0, +) / Float(pressureHistory.count)\n        print(\"average pressure: \\(avgPressure)\")\n        currentPressure = avgPressure\n        \n        \n        switch state {\n        case .waitingForFinger:\n            if !hasDetectedFinger {\n                currentPressure = mainTouch.pressure\n                if fingerTimer == 0.0 {\n                    startFingerTimer()\n                }\n            }\n            \n        case .waitingForItem:\n            if hasDetectedFinger {\n                // Calculate rate of change if we have enough history\n                if pressureHistory.count == historySize && !hasDetectedItem {\n                    let rateOfChange = pressureHistory.last! - pressureHistory.first!\n                    if rateOfChange > rateOfChangeThreshold {\n                        print(\"pressure before item: \\(pressureHistory)\")\n                        print(\"Old baseline: \\(baselinePressure)\")\n                        baselinePressure = pressureHistory.first!\n                        print(\"New baseline: \\(baselinePressure)\")\n                        hasDetectedItem = true\n                        state = .weighing\n                        resetStabilityTimer()\n                    }\n                }\n            } else {\n                state = .waitingForFinger\n                pressureHistory.removeAll()\n            }\n            \n        case .weighing:\n            // Check if weight is stable (hasn't changed by more than 2g)\n            let weightDifference = stabilityStartTime != nil ? abs(currentPressure - stableWeight) : 0\n            \n            if stabilityStartTime == nil {\n                // First reading in weighing state - start tracking stability\n                stabilityStartTime = Date()\n                stableWeight = currentPressure\n            } else if weightDifference > stabilityThreshold {\n                // Weight became unstable, reset stability tracking\n                resetStabilityTimer()\n                stabilityStartTime = Date()\n                stableWeight = currentPressure\n            } else {\n                // Weight is stable, check if we should start the stabilization process\n                let timeSinceStable = Date().timeIntervalSince(stabilityStartTime!)\n                \n                if timeSinceStable >= stabilityAnimationDelay && !isStabilizing {\n                    // Weight has been stable for at least 1 second, start stabilization timer\n                    startStabilityTimer(with: stableWeight)\n                }\n            }\n            \n        default:\n            break\n        }\n    }\n    \n    deinit {\n        task?.cancel()\n        timerTask?.cancel()\n        manager.stopListening()\n    }\n}\n"
  },
  {
    "path": "TrackWeight.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 56;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t77292A882B931953001CA3F6 /* TrackWeightApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A872B931953001CA3F6 /* TrackWeightApp.swift */; };\n\t\t77292A8A2B931953001CA3F6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A892B931953001CA3F6 /* ContentView.swift */; };\n\t\t77292A8C2B931954001CA3F6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77292A8B2B931954001CA3F6 /* Assets.xcassets */; };\n\t\t77292A8F2B931954001CA3F6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */; };\n\t\t77292A982B931D60001CA3F6 /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A972B931D60001CA3F6 /* ContentViewModel.swift */; };\n\t\t77292A9C2B931E01001CA3F6 /* WeighingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A9D2B931E01001CA3F6 /* WeighingState.swift */; };\n\t\t77292A9E2B931E02001CA3F6 /* WeighingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */; };\n\t\t77292AA02B931E03001CA3F6 /* TrackWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA12B931E03001CA3F6 /* TrackWeightView.swift */; };\n\t\t77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA32B931E04001CA3F6 /* DebugView.swift */; };\n\t\t77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA52B931E05001CA3F6 /* ScaleView.swift */; };\n\t\t77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */; };\n\t\t93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A095112E33359600E1E1D1 /* SettingsView.swift */; };\n\t\t93A095162E33624200E1E1D1 /* OpenMultitouchSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 93A095152E33624200E1E1D1 /* OpenMultitouchSupport */; };\n\t\t93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ABD0202E2E01E200668D4F /* HomeView.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t77292A842B931953001CA3F6 /* TrackWeight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrackWeight.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t77292A872B931953001CA3F6 /* TrackWeightApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackWeightApp.swift; sourceTree = \"<group>\"; };\n\t\t77292A892B931953001CA3F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = \"<group>\"; };\n\t\t77292A8B2B931954001CA3F6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\t77292A902B931954001CA3F6 /* TrackWeight.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TrackWeight.entitlements; sourceTree = \"<group>\"; };\n\t\t77292A972B931D60001CA3F6 /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = \"<group>\"; };\n\t\t77292A9D2B931E01001CA3F6 /* WeighingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeighingState.swift; sourceTree = \"<group>\"; };\n\t\t77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeighingViewModel.swift; sourceTree = \"<group>\"; };\n\t\t77292AA12B931E03001CA3F6 /* TrackWeightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackWeightView.swift; sourceTree = \"<group>\"; };\n\t\t77292AA32B931E04001CA3F6 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = \"<group>\"; };\n\t\t77292AA52B931E05001CA3F6 /* ScaleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleView.swift; sourceTree = \"<group>\"; };\n\t\t77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleViewModel.swift; sourceTree = \"<group>\"; };\n\t\t93A095112E33359600E1E1D1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = \"<group>\"; };\n\t\t93ABD0202E2E01E200668D4F /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t77292A812B931953001CA3F6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t93A095162E33624200E1E1D1 /* OpenMultitouchSupport in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t77292A7B2B931953001CA3F6 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t77292A862B931953001CA3F6 /* TrackWeight */,\n\t\t\t\t77292A852B931953001CA3F6 /* Products */,\n\t\t\t\t77292A992B931D7A001CA3F6 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t77292A852B931953001CA3F6 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t77292A842B931953001CA3F6 /* TrackWeight.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t77292A862B931953001CA3F6 /* TrackWeight */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t77292A902B931954001CA3F6 /* TrackWeight.entitlements */,\n\t\t\t\t77292A8B2B931954001CA3F6 /* Assets.xcassets */,\n\t\t\t\t77292A872B931953001CA3F6 /* TrackWeightApp.swift */,\n\t\t\t\t77292A892B931953001CA3F6 /* ContentView.swift */,\n\t\t\t\t77292A972B931D60001CA3F6 /* ContentViewModel.swift */,\n\t\t\t\t77292A9D2B931E01001CA3F6 /* WeighingState.swift */,\n\t\t\t\t93ABD0202E2E01E200668D4F /* HomeView.swift */,\n\t\t\t\t77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */,\n\t\t\t\t93A095112E33359600E1E1D1 /* SettingsView.swift */,\n\t\t\t\t77292AA12B931E03001CA3F6 /* TrackWeightView.swift */,\n\t\t\t\t77292AA32B931E04001CA3F6 /* DebugView.swift */,\n\t\t\t\t77292AA52B931E05001CA3F6 /* ScaleView.swift */,\n\t\t\t\t77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */,\n\t\t\t\t77292A8D2B931954001CA3F6 /* Preview Content */,\n\t\t\t);\n\t\t\tpath = TrackWeight;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t77292A8D2B931954001CA3F6 /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t77292A992B931D7A001CA3F6 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t77292A832B931953001CA3F6 /* TrackWeight */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 77292A932B931954001CA3F6 /* Build configuration list for PBXNativeTarget \"TrackWeight\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t77292A802B931953001CA3F6 /* Sources */,\n\t\t\t\t77292A812B931953001CA3F6 /* Frameworks */,\n\t\t\t\t77292A822B931953001CA3F6 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = TrackWeight;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t93A095152E33624200E1E1D1 /* OpenMultitouchSupport */,\n\t\t\t);\n\t\t\tproductName = TrackWeight;\n\t\t\tproductReference = 77292A842B931953001CA3F6 /* TrackWeight.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t77292A7C2B931953001CA3F6 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1520;\n\t\t\t\tLastUpgradeCheck = 1620;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t77292A832B931953001CA3F6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.2;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 77292A7F2B931953001CA3F6 /* Build configuration list for PBXProject \"TrackWeight\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 77292A7B2B931953001CA3F6;\n\t\t\tpackageReferences = (\n\t\t\t\t93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference \"OpenMultitouchSupport\" */,\n\t\t\t);\n\t\t\tproductRefGroup = 77292A852B931953001CA3F6 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t77292A832B931953001CA3F6 /* TrackWeight */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t77292A822B931953001CA3F6 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t77292A8F2B931954001CA3F6 /* Preview Assets.xcassets in Resources */,\n\t\t\t\t77292A8C2B931954001CA3F6 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t77292A802B931953001CA3F6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t77292A8A2B931953001CA3F6 /* ContentView.swift in Sources */,\n\t\t\t\t77292A982B931D60001CA3F6 /* ContentViewModel.swift in Sources */,\n\t\t\t\t77292A882B931953001CA3F6 /* TrackWeightApp.swift in Sources */,\n\t\t\t\t93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */,\n\t\t\t\t77292A9C2B931E01001CA3F6 /* WeighingState.swift in Sources */,\n\t\t\t\t77292A9E2B931E02001CA3F6 /* WeighingViewModel.swift in Sources */,\n\t\t\t\t77292AA02B931E03001CA3F6 /* TrackWeightView.swift in Sources */,\n\t\t\t\t77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */,\n\t\t\t\t77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */,\n\t\t\t\t77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */,\n\t\t\t\t93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t77292A912B931954001CA3F6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t77292A922B931954001CA3F6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = NO;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t77292A942B931954001CA3F6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = TrackWeight/TrackWeight.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = 9ZRLG6277G;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.krishkrosh.trackweight;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t77292A952B931954001CA3F6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = TrackWeight/TrackWeight.entitlements;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"Developer ID Application\";\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = 9ZRLG6277G;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tONLY_ACTIVE_ARCH = NO;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.krishkrosh.trackweight;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t77292A7F2B931953001CA3F6 /* Build configuration list for PBXProject \"TrackWeight\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t77292A912B931954001CA3F6 /* Debug */,\n\t\t\t\t77292A922B931954001CA3F6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t77292A932B931954001CA3F6 /* Build configuration list for PBXNativeTarget \"TrackWeight\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t77292A942B931954001CA3F6 /* Debug */,\n\t\t\t\t77292A952B931954001CA3F6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference \"OpenMultitouchSupport\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/KrishKrosh/OpenMultitouchSupport.git\";\n\t\t\trequirement = {\n\t\t\t\tbranch = main;\n\t\t\t\tkind = branch;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t93A095152E33624200E1E1D1 /* OpenMultitouchSupport */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference \"OpenMultitouchSupport\" */;\n\t\t\tproductName = OpenMultitouchSupport;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = 77292A7C2B931953001CA3F6 /* Project object */;\n}\n"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"231443e4712351510ab418164c13b6d5894774039d8c118ed8208a5fc9fe884e\",\n  \"pins\" : [\n    {\n      \"identity\" : \"openmultitouchsupport\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/KrishKrosh/OpenMultitouchSupport.git\",\n      \"state\" : {\n        \"branch\" : \"main\",\n        \"revision\" : \"fb6991fa1ece8c5faa8eef49190beb2baf071694\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "scripts/README.md",
    "content": "# Scripts\n\nThis directory contains helper scripts for the TrackWeight project.\n\n## setup-signing.sh\n\nA helper script to set up code signing certificates for automated DMG builds.\n\n### Usage\n\n```bash\n./scripts/setup-signing.sh\n```\n\n### What it does\n\n1. **Guides you through certificate export**: Provides step-by-step instructions to export your Developer ID Application certificate from Keychain Access\n2. **Encodes certificates**: Converts your .p12 certificate file to base64 format required for GitHub Secrets\n3. **Generates secret values**: Provides the exact values you need to add as GitHub repository secrets\n4. **Optional provisioning profile**: Handles provisioning profile encoding if needed\n\n### Prerequisites\n\n- macOS (required for Keychain Access and signing tools)\n- Valid Apple Developer ID Application certificate\n- Access to GitHub repository settings to add secrets\n\n### Output\n\nThe script will generate the values for these GitHub repository secrets:\n- `BUILD_CERTIFICATE_BASE64`: Base64-encoded .p12 certificate\n- `P12_PASSWORD`: Certificate password\n- `BUILD_PROVISION_PROFILE_BASE64`: Base64-encoded provisioning profile (optional)\n\n### Adding Secrets to GitHub\n\n1. Go to your GitHub repository\n2. Navigate to Settings > Secrets and variables > Actions\n3. Click \"New repository secret\"\n4. Add each secret with the name and value provided by the script\n\n### Attribution\n\nThis script is part of the enhanced TrackWeight fork that adds automated build pipelines.\n\n**Original TrackWeight project**: https://github.com/KrishKrosh/TrackWeight  \n**Created by**: Krish Shah (@KrishKrosh)"
  },
  {
    "path": "scripts/setup-signing.sh",
    "content": "#!/bin/bash\n\n# setup-signing.sh\n# Helper script to set up code signing certificates for TrackWeight DMG builds\n# \n# This script helps you prepare the necessary secrets for GitHub Actions\n# to build signed DMG files.\n\nset -e\n\necho \"🔐 TrackWeight Code Signing Setup\"\necho \"==================================\"\necho \"\"\necho \"This script helps you set up code signing for automated DMG builds.\"\necho \"You'll need a valid Apple Developer ID Application certificate.\"\necho \"\"\n\n# Check if we're on macOS\nif [[ \"$OSTYPE\" != \"darwin\"* ]]; then\n    echo \"❌ This script must be run on macOS to access Keychain and signing tools.\"\n    exit 1\nfi\n\n# Function to encode file to base64\nencode_file() {\n    local file_path=\"$1\"\n    if [[ -f \"$file_path\" ]]; then\n        base64 -i \"$file_path\"\n    else\n        echo \"❌ File not found: $file_path\"\n        return 1\n    fi\n}\n\necho \"Step 1: Export your Developer ID Application certificate\"\necho \"--------------------------------------------------------\"\necho \"1. Open Keychain Access\"\necho \"2. Find your 'Developer ID Application' certificate\"\necho \"3. Right-click and select 'Export'\"\necho \"4. Save as .p12 format with a password\"\necho \"\"\nread -p \"Enter the path to your exported .p12 certificate: \" cert_path\n\nif [[ ! -f \"$cert_path\" ]]; then\n    echo \"❌ Certificate file not found: $cert_path\"\n    exit 1\nfi\n\necho \"\"\nread -s -p \"Enter the password for your .p12 certificate: \" cert_password\necho \"\"\n\necho \"\"\necho \"Step 2: Encoding certificate for GitHub Secrets\"\necho \"----------------------------------------------\"\n\n# Encode the certificate\necho \"Encoding certificate...\"\ncert_base64=$(encode_file \"$cert_path\")\n\nif [[ -z \"$cert_base64\" ]]; then\n    echo \"❌ Failed to encode certificate\"\n    exit 1\nfi\n\necho \"✅ Certificate encoded successfully\"\n\necho \"\"\necho \"Step 3: GitHub Repository Secrets\"\necho \"--------------------------------\"\necho \"Add these secrets to your GitHub repository:\"\necho \"(Go to Settings > Secrets and variables > Actions)\"\necho \"\"\necho \"1. Secret name: BUILD_CERTIFICATE_BASE64\"\necho \"   Value: (copy the text below)\"\necho \"\"\necho \"$cert_base64\"\necho \"\"\necho \"2. Secret name: P12_PASSWORD\"\necho \"   Value: $cert_password\"\necho \"\"\necho \"3. Secret name: APPLE_ID\"\necho \"   Value: your-apple-id@example.com\"\necho \"\"\necho \"4. Secret name: APPLE_ID_PASSWORD\"\necho \"   Value: (App-specific password - see instructions below)\"\necho \"\"\necho \"5. Secret name: APPLE_TEAM_ID\"\necho \"   Value: (Your 10-character Team ID - see instructions below)\"\necho \"\"\n\n# Check for provisioning profile (optional for Developer ID)\necho \"Step 4: Provisioning Profile (Optional)\"\necho \"--------------------------------------\"\nread -p \"Do you have a provisioning profile to include? (y/n): \" include_profile\n\nif [[ \"$include_profile\" =~ ^[Yy]$ ]]; then\n    read -p \"Enter the path to your .mobileprovision file: \" profile_path\n    \n    if [[ -f \"$profile_path\" ]]; then\n        profile_base64=$(encode_file \"$profile_path\")\n        echo \"\"\n        echo \"3. Secret name: BUILD_PROVISION_PROFILE_BASE64\"\n        echo \"   Value: (copy the text below)\"\n        echo \"\"\n        echo \"$profile_base64\"\n    else\n        echo \"❌ Provisioning profile not found: $profile_path\"\n    fi\nelse\n    echo \"📝 Skipping provisioning profile (Developer ID usually doesn't need one)\"\nfi\n\necho \"\"\necho \"Step 5: Additional Secrets for Notarization\"\necho \"-------------------------------------------\"\necho \"For full notarization (eliminates security warnings), you'll also need:\"\necho \"\"\necho \"📧 Apple ID (APPLE_ID):\"\necho \"   - Use your Apple Developer account email\"\necho \"\"\necho \"🔑 App-Specific Password (APPLE_ID_PASSWORD):\"\necho \"   1. Go to appleid.apple.com\"\necho \"   2. Sign in with your Apple ID\"\necho \"   3. In the 'App-Specific Passwords' section, click 'Generate Password'\"\necho \"   4. Label it something like 'GitHub Actions Notarization'\"\necho \"   5. Copy the generated password (xxxx-xxxx-xxxx-xxxx)\"\necho \"\"\necho \"🏢 Team ID (APPLE_TEAM_ID):\"\necho \"   1. Go to developer.apple.com\"\necho \"   2. Sign in and go to 'Membership'\"\necho \"   3. Your Team ID is the 10-character string (e.g., ABC1234567)\"\necho \"\"\necho \"ℹ️  Without these notarization secrets, the app will still be signed but users\"\necho \"   will see security warnings when trying to run it.\"\necho \"\"\necho \"🎉 Setup Complete!\"\necho \"=================\"\necho \"\"\necho \"Next steps:\"\necho \"1. Add ALL the secrets to your GitHub repository\"\necho \"2. Create a git tag to trigger the build: git tag v1.0.0 && git push origin v1.0.0\"\necho \"3. Or manually trigger the workflow from the Actions tab\"\necho \"\"\necho \"The workflow will:\"\necho \"- Build and sign your app with the provided certificate\"\necho \"- Notarize the app with Apple (eliminates security warnings)\"\necho \"- Create a professional DMG with attribution to the original repo\"\necho \"- Upload the DMG as a release artifact\"\necho \"\"\necho \"Original TrackWeight project: https://github.com/KrishKrosh/TrackWeight\""
  },
  {
    "path": "scripts/test-build-locally.sh",
    "content": "#!/bin/bash\n\n# test-build-locally.sh\n# Test the GitHub Actions workflow steps locally on macOS\n\nset -e\n\necho \"🧪 Testing TrackWeight Build Workflow Locally\"\necho \"==============================================\"\n\n# Load environment variables from .env file\nif [[ -f \".env\" ]]; then\n  echo \"📄 Loading environment variables from .env file...\"\n  # Export all variables from .env file\n  set -a  # automatically export all variables\n  source .env\n  set +a  # turn off automatic export\n  echo \"✅ Environment variables loaded\"\nelse\n  echo \"⚠️  No .env file found - some features may not work\"\nfi\n\n# Configuration\nexport APP_NAME=\"TrackWeight\"\nexport SCHEME=\"TrackWeight\"\nexport CONFIGURATION=\"Release\"\nexport BUILD_DIR=\"$(pwd)/local_build\"\n\n# Clean up previous builds\nrm -rf \"$BUILD_DIR\"\nmkdir -p \"$BUILD_DIR\"\n\necho \"\"\necho \"Step 1: Building and Archiving App (Universal Binary)\"\necho \"=====================================================\"\nxcodebuild \\\n  -project TrackWeight.xcodeproj \\\n  -scheme \"$SCHEME\" \\\n  -configuration \"$CONFIGURATION\" \\\n  -archivePath \"$BUILD_DIR/$APP_NAME.xcarchive\" \\\n  -destination 'generic/platform=macOS' \\\n  ARCHS=\"arm64 x86_64\" \\\n  ONLY_ACTIVE_ARCH=NO \\\n  archive\n\necho \"\"\necho \"Step 1.5: Setting up Code Signing (if available)\"\necho \"================================================\"\nif [[ -n \"$BUILD_CERTIFICATE_BASE64\" && -n \"$P12_PASSWORD\" ]]; then\n  echo \"🔐 Setting up code signing certificate from .env...\"\n  \n  # Decode and import certificate\n  echo \"$BUILD_CERTIFICATE_BASE64\" | base64 --decode > \"$BUILD_DIR/certificate.p12\"\n  \n  # Import into keychain (temporary)\n  security import \"$BUILD_DIR/certificate.p12\" -k ~/Library/Keychains/login.keychain-db -P \"$P12_PASSWORD\" -T /usr/bin/codesign\n  \n  echo \"✅ Certificate imported successfully\"\n  echo \"📝 Note: Certificate will remain in keychain after script completion\"\nelse\n  echo \"⚠️  No certificate in .env - will use existing keychain certificates\"\nfi\n\necho \"\"\necho \"Step 2: Exporting App\"\necho \"====================\"\n# Use existing ExportOptions.plist or create a basic one\nif [[ ! -f \"ExportOptions.plist\" ]]; then\n  echo \"Creating basic ExportOptions.plist...\"\n  cat > \"$BUILD_DIR/ExportOptions.plist\" << EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>method</key>\n    <string>developer-id</string>\n    <key>destination</key>\n    <string>export</string>\n</dict>\n</plist>\nEOF\nelse\n  cp ExportOptions.plist \"$BUILD_DIR/ExportOptions.plist\"\nfi\n\n        xcodebuild \\\n          -archivePath \"$BUILD_DIR/$APP_NAME.xcarchive\" \\\n          -exportPath \"$BUILD_DIR/export\" \\\n          -exportOptionsPlist \"$BUILD_DIR/ExportOptions.plist\" \\\n          -exportArchive\n  \n  # Re-sign the framework explicitly to ensure proper signature\n  if [[ -n \"$BUILD_CERTIFICATE_BASE64\" && -n \"$P12_PASSWORD\" ]]; then\n    echo \"🔏 Re-signing framework with Developer ID certificate...\"\n    FRAMEWORK_PATH=\"$BUILD_DIR/export/$APP_NAME.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework\"\n    if [[ -d \"$FRAMEWORK_PATH\" ]]; then\n      codesign --force --sign \"Developer ID Application: Krish Shah (9ZRLG6277G)\" \\\n        --options runtime \\\n        --timestamp \\\n        \"$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF\"\n      \n      codesign --force --sign \"Developer ID Application: Krish Shah (9ZRLG6277G)\" \\\n        --options runtime \\\n        --timestamp \\\n        \"$FRAMEWORK_PATH\"\n      \n      echo \"✅ Framework re-signed successfully\"\n    fi\n    \n    # Re-sign the main app to ensure everything is consistent\n    echo \"🔏 Re-signing main application...\"\n    codesign --force --sign \"Developer ID Application: Krish Shah (9ZRLG6277G)\" \\\n      --options runtime \\\n      --entitlements \"TrackWeight/TrackWeight.entitlements\" \\\n      --timestamp \\\n      --deep \\\n      --strict \\\n      \"$BUILD_DIR/export/$APP_NAME.app\"\n    \n    echo \"✅ Application re-signed successfully\"\n  fi\n\necho \"\"\necho \"Step 2.5: Verifying Universal Binary and Code Signatures\"\necho \"========================================================\"\necho \"🏗️ Verifying Universal Binary Architecture...\"\nAPP_BINARY=\"$BUILD_DIR/export/$APP_NAME.app/Contents/MacOS/$APP_NAME\"\nif [[ -f \"$APP_BINARY\" ]]; then\n  echo \"📊 Binary architectures:\"\n  lipo -archs \"$APP_BINARY\"\n  \n  if lipo -archs \"$APP_BINARY\" | grep -q \"arm64\" && lipo -archs \"$APP_BINARY\" | grep -q \"x86_64\"; then\n    echo \"✅ Universal binary confirmed: Contains both ARM64 and x86_64\"\n  else\n    echo \"❌ Warning: Binary may not be universal\"\n    lipo -detailed_info \"$APP_BINARY\"\n  fi\nfi\n\n# Check framework architecture if it exists\nFRAMEWORK_PATH=\"$BUILD_DIR/export/$APP_NAME.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework\"\nif [[ -d \"$FRAMEWORK_PATH\" ]]; then\n  FRAMEWORK_BINARY=\"$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF\"\n  if [[ -f \"$FRAMEWORK_BINARY\" ]]; then\n    echo \"📊 Framework architectures:\"\n    lipo -archs \"$FRAMEWORK_BINARY\"\n  fi\nfi\n\necho \"🔍 Verifying main application signature...\"\ncodesign --verify --verbose \"$BUILD_DIR/export/$APP_NAME.app\" || echo \"⚠️ Main app signature verification failed\"\n\necho \"🔍 Verifying framework signature...\" \nif [[ -d \"$FRAMEWORK_PATH\" ]]; then\n  codesign --verify --verbose \"$FRAMEWORK_PATH\" || echo \"⚠️ Framework signature verification failed\"\nfi\n\necho \"🔍 Checking for hardened runtime...\"\nRUNTIME_FLAGS=$(codesign --display --verbose \"$BUILD_DIR/export/$APP_NAME.app\" 2>&1 | grep \"flags=\")\nif [[ \"$RUNTIME_FLAGS\" == *\"runtime\"* ]]; then\n  echo \"✅ Hardened runtime enabled: $RUNTIME_FLAGS\"\nelse\n  echo \"⚠️ No hardened runtime detected: $RUNTIME_FLAGS\"\nfi\n\necho \"🔍 Checking certificate validity...\"\ncodesign --display --verbose=4 \"$BUILD_DIR/export/$APP_NAME.app\" | grep -E \"(Authority|Timestamp|TeamIdentifier)\" || echo \"Certificate details extracted\"\n\necho \"🔍 Deep verification with online validation...\"\nif codesign --verify --deep --strict --verbose=2 \"$BUILD_DIR/export/$APP_NAME.app\"; then\n  echo \"✅ Deep verification passed\"\nelse\n  echo \"⚠️ Deep verification failed but continuing...\"\nfi\n\necho \"\"\necho \"Step 3: Testing Notarization (Optional)\"\necho \"=======================================\"\nif [[ -n \"$APPLE_ID\" && -n \"$APPLE_ID_PASSWORD\" && -n \"$APPLE_TEAM_ID\" ]]; then\n  echo \"🍎 Starting notarization with provided credentials...\"\n  \n  cd \"$BUILD_DIR/export\"\n  echo \"📦 Creating zip file for notarization...\"\n  # Use ditto for creating zip compatible with Apple's notarization service\n  ditto -c -k --keepParent \"$APP_NAME.app\" \"$APP_NAME.zip\"\n  \n  echo \"📤 Submitting for notarization...\"\n  if xcrun notarytool submit \"$APP_NAME.zip\" \\\n    --apple-id \"$APPLE_ID\" \\\n    --password \"$APPLE_ID_PASSWORD\" \\\n    --team-id \"$APPLE_TEAM_ID\" \\\n    --wait \\\n    --timeout 20m; then\n    \n    echo \"✅ Notarization successful!\"\n    \n    echo \"📎 Stapling notarization ticket...\"\n    xcrun stapler staple \"$APP_NAME.app\"\n    \n    echo \"✅ Verifying notarization...\"\n    xcrun stapler validate \"$APP_NAME.app\"\n    \n    echo \"✅ Notarization complete!\"\n  else\n    echo \"❌ Notarization failed!\"\n    echo \"🔍 This could be due to:\"\n    echo \"   - Invalid Apple credentials\"\n    echo \"   - App signing issues\"\n    echo \"   - Missing hardened runtime\"\n    echo \"   - Sandbox/entitlement issues\"\n    echo \"   - Deprecated APIs\"\n    echo \"\"\n    echo \"⚠️  Continuing without notarization...\"\n  fi\n  \n  # Return to original directory\n  cd \"$BUILD_DIR\"\nelse\n  echo \"⚠️  Skipping notarization (set APPLE_ID, APPLE_ID_PASSWORD, APPLE_TEAM_ID to test)\"\nfi\n\necho \"\"\necho \"Step 4: Creating DMG\"\necho \"===================\"\n# Install create-dmg if not present\nif ! command -v create-dmg &> /dev/null; then\n  echo \"Installing create-dmg...\"\n  brew install create-dmg\nfi\n\n# Create a clean directory with only the app for DMG creation\necho \"📁 Preparing clean DMG contents...\"\nDMG_STAGING=\"$BUILD_DIR/dmg_staging\"\nrm -rf \"$DMG_STAGING\"\nmkdir -p \"$DMG_STAGING\"\n\n# Copy only the app to staging directory\ncp -R \"$BUILD_DIR/export/$APP_NAME.app\" \"$DMG_STAGING/\"\n\n# Create DMG with appropriate naming\nif [[ -n \"$APPLE_ID\" && -n \"$APPLE_ID_PASSWORD\" && -n \"$APPLE_TEAM_ID\" ]]; then\n  DMG_NAME=\"$APP_NAME-local-NOTARIZED.dmg\"\nelse\n  DMG_NAME=\"$APP_NAME-local-SIGNED.dmg\"\nfi\n\necho \"📀 Creating professional DMG...\"\ncreate-dmg \\\n  --volname \"$APP_NAME\" \\\n  --window-pos 200 120 \\\n  --window-size 600 300 \\\n  --icon-size 100 \\\n  --icon \"$APP_NAME.app\" 175 120 \\\n  --hide-extension \"$APP_NAME.app\" \\\n  --app-drop-link 425 120 \\\n  --hdiutil-quiet \\\n  \"$BUILD_DIR/$DMG_NAME\" \\\n  \"$DMG_STAGING/\"\n\necho \"\"\necho \"🎉 Local Build Complete!\"\necho \"=======================\"\necho \"📦 DMG created: $BUILD_DIR/$DMG_NAME\"\necho \"📁 Build directory: $BUILD_DIR\"\necho \"\"\nif [[ -n \"$APPLE_ID\" && -n \"$APPLE_ID_PASSWORD\" && -n \"$APPLE_TEAM_ID\" ]]; then\n  echo \"✅ Notarization was attempted using credentials from .env\"\nelse\n  echo \"ℹ️  To enable notarization, add these to your .env file:\"\n  echo \"   APPLE_ID=your@email.com\"\n  echo \"   APPLE_ID_PASSWORD=xxxx-xxxx-xxxx-xxxx\"\n  echo \"   APPLE_TEAM_ID=ABC1234567\"\nfi\necho \"\"\necho \"🔧 Cleaning up temporary files...\"\nrm -f \"$BUILD_DIR/certificate.p12\"\nrm -rf \"$DMG_STAGING\"\necho \"✅ Cleanup complete\" "
  }
]