Full Code of KrishKrosh/TrackWeight for AI

main e322cae241d2 cached
28 files
109.8 KB
27.5k tokens
1 requests
Download .txt
Repository: KrishKrosh/TrackWeight
Branch: main
Commit: e322cae241d2
Files: 28
Total size: 109.8 KB

Directory structure:
gitextract_foro58fa/

├── .github/
│   └── workflows/
│       ├── README.md
│       └── build-and-sign-dmg.yml
├── .gitignore
├── ExportOptions.plist
├── LICENSE
├── README.md
├── TrackWeight/
│   ├── Assets.xcassets/
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── ContentView.swift
│   ├── ContentViewModel.swift
│   ├── DebugView.swift
│   ├── HomeView.swift
│   ├── Preview Content/
│   │   └── Preview Assets.xcassets/
│   │       └── Contents.json
│   ├── ScaleView.swift
│   ├── ScaleViewModel.swift
│   ├── SettingsView.swift
│   ├── TrackWeight.entitlements
│   ├── TrackWeightApp.swift
│   ├── TrackWeightView.swift
│   ├── WeighingState.swift
│   └── WeighingViewModel.swift
├── TrackWeight.xcodeproj/
│   ├── project.pbxproj
│   └── project.xcworkspace/
│       ├── contents.xcworkspacedata
│       └── xcshareddata/
│           ├── IDEWorkspaceChecks.plist
│           └── swiftpm/
│               └── Package.resolved
└── scripts/
    ├── README.md
    ├── setup-signing.sh
    └── test-build-locally.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/README.md
================================================
# GitHub Actions Workflows

This directory contains GitHub Actions workflows for the TrackWeight project.

## build-and-sign-dmg.yml

This workflow builds, signs (if certificates are provided), and packages the TrackWeight macOS application into a DMG file.

### Features

- **Automated Building**: Builds the Xcode project using the latest stable Xcode
- **Code Signing**: Supports optional code signing with certificates
- **DMG Creation**: Creates a professional DMG with proper layout and attribution
- **Attribution**: Includes proper credits to the original repository (https://github.com/KrishKrosh/TrackWeight)
- **Release Integration**: Can create GitHub releases with the built DMG
- **Artifact Upload**: Uploads DMG as a GitHub Actions artifact

### Triggers

The workflow runs on:
- Git tags starting with 'v' (e.g., v1.0.0)
- Published releases
- Manual workflow dispatch

### Setup Instructions

#### Required Secrets (for signed builds)

To enable code signing, add these secrets to your GitHub repository:

1. **BUILD_CERTIFICATE_BASE64**: Base64-encoded Developer ID Application certificate (.p12 file)
   ```bash
   base64 -i YourCertificate.p12 | pbcopy
   ```

2. **P12_PASSWORD**: Password for the .p12 certificate file

3. **BUILD_PROVISION_PROFILE_BASE64**: Base64-encoded provisioning profile (optional for Developer ID)
   ```bash
   base64 -i YourProvisioningProfile.mobileprovision | pbcopy
   ```

#### Setting up Certificates

1. Export your Developer ID Application certificate from Keychain Access as a .p12 file
2. Convert to base64 and add as `BUILD_CERTIFICATE_BASE64` secret
3. Add the certificate password as `P12_PASSWORD` secret

#### Unsigned Builds

If 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.

## build-unsigned-dmg.yml

This workflow specifically builds unsigned development versions of the TrackWeight app without requiring any certificates.

### Features

- **No Certificate Requirements**: Builds completely without code signing
- **Development Build**: Creates unsigned development builds that work on any Mac
- **Same DMG Features**: Includes all the same DMG features as the signed version
- **Clear User Instructions**: Includes instructions for running unsigned apps
- **Attribution**: Maintains proper credits to the original repository

### Triggers

The workflow runs on:
- Pushes to main branches and the current working branch
- Manual workflow dispatch

### Benefits

- **Easy Testing**: Perfect for testing builds without setting up certificates
- **No Configuration**: Works immediately without any secrets or setup
- **User-Friendly**: Includes clear instructions for users on how to run unsigned apps

## Choosing the Right Workflow

### Use `build-and-sign-dmg.yml` when:
- You have Apple Developer certificates
- You want to distribute signed, trusted builds
- You're creating official releases

### Use `build-unsigned-dmg.yml` when:
- You don't have certificates
- You want to test builds quickly
- You're developing or experimenting
- You need a simple build process

If 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).

### Usage

#### Manual Trigger
1. Go to Actions tab in your GitHub repository
2. Select "Build and Sign DMG" workflow
3. Click "Run workflow"
4. Optionally check "Create a GitHub release" to create a release

#### Automatic Trigger
1. Create a git tag: `git tag v1.0.0`
2. Push the tag: `git push origin v1.0.0`
3. The workflow will automatically build and create a release

### Output

The workflow produces:
- **DMG file**: TrackWeight-{version}.dmg containing the app and attribution
- **GitHub Release**: (if triggered by tag or manual release creation)
- **Artifacts**: DMG file uploaded as GitHub Actions artifact

### Attribution

This workflow ensures proper attribution to the original TrackWeight repository:
- README.txt file included in DMG with credits
- Release notes include attribution
- Links to original repository: https://github.com/KrishKrosh/TrackWeight

## update-homebrew.yml

This workflow automatically updates the Homebrew cask when a new release is published.

### Features
- Updates version in Homebrew tap repository
- Automatically triggered on release publication
- Can be manually triggered with version input

For more information about the original TrackWeight project, visit: https://github.com/KrishKrosh/TrackWeight

================================================
FILE: .github/workflows/build-and-sign-dmg.yml
================================================
name: Build and Sign DMG

on:
  push:
    tags:
      - 'v*'
    branches:
      - 'copilot/fix-1'  # Enable testing on current working branch
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      create_release:
        description: 'Create a GitHub release'
        required: false
        default: false
        type: boolean

env:
  APP_NAME: TrackWeight
  SCHEME: TrackWeight
  CONFIGURATION: Release

jobs:
  build-and-sign:
    runs-on: macos-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      with:
        submodules: recursive
    
    - name: Setup Xcode
      uses: maxim-lobanov/setup-xcode@v1
      with:
        xcode-version: latest-stable
    
    - name: Import Code-Signing Certificates
      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' }}
      env:
        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
      uses: Apple-Actions/import-codesign-certs@v2
      with:
        p12-file-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
        p12-password: ${{ secrets.P12_PASSWORD }}
    
    - name: Install provisioning profile
      if: ${{ env.BUILD_PROVISION_PROFILE_BASE64 != '' }}
      env:
        BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
      run: |
        PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
        echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
        mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
        cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
    
    - name: Build and Archive App
      run: |
        xcodebuild \
          -project TrackWeight.xcodeproj \
          -scheme ${{ env.SCHEME }} \
          -configuration ${{ env.CONFIGURATION }} \
          -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \
          -destination 'generic/platform=macOS' \
          ARCHS="arm64 x86_64" \
          ONLY_ACTIVE_ARCH=NO \
          archive
    
    - name: Export App
      run: |
        # Use development export method if no signing certificates are available
        if [ -z "${{ secrets.BUILD_CERTIFICATE_BASE64 }}" ]; then
          # Create export options for unsigned build
          cat > "$RUNNER_TEMP/ExportOptions.plist" << EOF
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
        <plist version="1.0">
        <dict>
            <key>method</key>
            <string>debugging</string>
            <key>signingStyle</key>
            <string>manual</string>
            <key>stripSwiftSymbols</key>
            <true/>
            <key>destination</key>
            <string>export</string>
            <key>signingCertificate</key>
            <string>-</string>
            <key>teamID</key>
            <string>-</string>
            <key>uploadBitcode</key>
            <false/>
            <key>uploadSymbols</key>
            <false/>
        </dict>
        </plist>
        EOF
        else
          # Use the existing ExportOptions.plist for signed builds
          cp ExportOptions.plist "$RUNNER_TEMP/ExportOptions.plist"
        fi
        
        xcodebuild \
          -archivePath "$RUNNER_TEMP/${{ env.APP_NAME }}.xcarchive" \
          -exportPath "$RUNNER_TEMP/export" \
          -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \
          -exportArchive

    - name: Re-sign App Components
      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' }}
      env:
        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
        APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      run: |
        echo "🔏 Re-signing framework with Developer ID certificate..."
        FRAMEWORK_PATH="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework"
        if [[ -d "$FRAMEWORK_PATH" ]]; then
          codesign --force --sign "Developer ID Application" \
            --options runtime \
            --timestamp \
            "$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF"
          
          codesign --force --sign "Developer ID Application" \
            --options runtime \
            --timestamp \
            "$FRAMEWORK_PATH"
          
          echo "✅ Framework re-signed successfully"
        fi
        
        # Re-sign the main app to ensure everything is consistent
        echo "🔏 Re-signing main application..."
        codesign --force --sign "Developer ID Application" \
          --options runtime \
          --entitlements "TrackWeight/TrackWeight.entitlements" \
          --timestamp \
          --deep \
          --strict \
          "$RUNNER_TEMP/export/${{ env.APP_NAME }}.app"
        
        echo "✅ Application re-signed successfully"

    - name: Verify Universal Binary and Code Signatures
      run: |
        echo "🏗️ Verifying Universal Binary Architecture..."
        APP_BINARY="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/MacOS/${{ env.APP_NAME }}"
        if [[ -f "$APP_BINARY" ]]; then
          echo "📊 Binary architectures:"
          lipo -archs "$APP_BINARY"
          
          if lipo -archs "$APP_BINARY" | grep -q "arm64" && lipo -archs "$APP_BINARY" | grep -q "x86_64"; then
            echo "✅ Universal binary confirmed: Contains both ARM64 and x86_64"
          else
            echo "❌ Warning: Binary may not be universal"
            lipo -detailed_info "$APP_BINARY"
          fi
        fi
        
        # Check framework architecture if it exists
        FRAMEWORK_PATH="$RUNNER_TEMP/export/${{ env.APP_NAME }}.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework"
        if [[ -d "$FRAMEWORK_PATH" ]]; then
          FRAMEWORK_BINARY="$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF"
          if [[ -f "$FRAMEWORK_BINARY" ]]; then
            echo "📊 Framework architectures:"
            lipo -archs "$FRAMEWORK_BINARY"
          fi
        fi
        
        # Only verify signatures if certificates are available
        if [[ -n "${{ secrets.BUILD_CERTIFICATE_BASE64 }}" ]]; then
          echo "🔍 Verifying main application signature..."
          codesign --verify --verbose "$RUNNER_TEMP/export/${{ env.APP_NAME }}.app"
          
          echo "🔍 Verifying framework signature..."
          if [[ -d "$FRAMEWORK_PATH" ]]; then
            codesign --verify --verbose "$FRAMEWORK_PATH"
          fi
          
          echo "🔍 Deep verification with online validation..."
          codesign --verify --deep --strict --verbose=2 "$RUNNER_TEMP/export/${{ env.APP_NAME }}.app"
          
          echo "✅ All signature verifications passed"
        else
          echo "ℹ️  Skipping signature verification (no certificates provided)"
        fi
    
    - name: Notarize App
      if: ${{ env.BUILD_CERTIFICATE_BASE64 != '' && env.APPLE_ID != '' }}
      env:
        BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
        APPLE_ID: ${{ secrets.APPLE_ID }}
        APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
        APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      run: |
        echo "🍎 Starting notarization process..."
        
        # Create a zip file for notarization (notarytool prefers zip over raw .app)
        cd "$RUNNER_TEMP/export"
        # Use ditto for creating zip compatible with Apple's notarization service  
        ditto -c -k --keepParent "${{ env.APP_NAME }}.app" "${{ env.APP_NAME }}.zip"
        
        # Submit for notarization
        echo "📤 Submitting app for notarization..."
        xcrun notarytool submit "${{ env.APP_NAME }}.zip" \
          --apple-id "$APPLE_ID" \
          --password "$APPLE_ID_PASSWORD" \
          --team-id "$APPLE_TEAM_ID" \
          --wait \
          --timeout 20m
        
        # Staple the notarization ticket to the app
        echo "📎 Stapling notarization ticket..."
        xcrun stapler staple "${{ env.APP_NAME }}.app"
        
        # Verify the stapling worked
        echo "✅ Verifying notarization..."
        xcrun stapler validate "${{ env.APP_NAME }}.app"
        
        echo "🎉 Notarization complete!"

    - name: Install create-dmg
      run: |
        brew install create-dmg
    
    - name: Create DMG
      run: |
        # Create a clean directory with only the app for DMG creation
        echo "📁 Preparing clean DMG contents..."
        DMG_STAGING="$RUNNER_TEMP/dmg_staging"
        rm -rf "$DMG_STAGING"
        mkdir -p "$DMG_STAGING"
        
        # Copy only the app to staging directory
        cp -R "$RUNNER_TEMP/export/${{ env.APP_NAME }}.app" "$DMG_STAGING/"
        
        # Create clean professional DMG
        create-dmg \
          --volname "${{ env.APP_NAME }}" \
          --volicon "$DMG_STAGING/${{ env.APP_NAME }}.app/Contents/Resources/AppIcon.icns" \
          --window-pos 200 120 \
          --window-size 600 300 \
          --icon-size 100 \
          --icon "${{ env.APP_NAME }}.app" 175 120 \
          --hide-extension "${{ env.APP_NAME }}.app" \
          --app-drop-link 425 120 \
          --hdiutil-quiet \
          "$RUNNER_TEMP/${{ env.APP_NAME }}.dmg" \
          "$DMG_STAGING/"
    
    - name: Get version info
      id: version_info
      run: |
        if [[ "${{ github.ref }}" == refs/tags/* ]]; then
          VERSION=${GITHUB_REF#refs/tags/}
        else
          VERSION=$(date +%Y%m%d-%H%M%S)
        fi
        echo "version=$VERSION" >> $GITHUB_OUTPUT
        echo "dmg_name=${{ env.APP_NAME }}.dmg" >> $GITHUB_OUTPUT
    
    - name: Rename DMG with version
      run: |
        mv "$RUNNER_TEMP/${{ env.APP_NAME }}.dmg" "$RUNNER_TEMP/${{ steps.version_info.outputs.dmg_name }}"
    
    - name: Upload DMG as artifact
      uses: actions/upload-artifact@v4
      with:
        name: ${{ steps.version_info.outputs.dmg_name }}
        path: ${{ runner.temp }}/${{ steps.version_info.outputs.dmg_name }}
        retention-days: 30
    
    - name: Create Release
      if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'true')
      uses: softprops/action-gh-release@v1
      with:
        tag_name: ${{ steps.version_info.outputs.version }}
        name: ${{ env.APP_NAME }} ${{ steps.version_info.outputs.version }}
        body: |
          # TrackWeight ${{ steps.version_info.outputs.version }}
          
          Transform your MacBook's trackpad into a precise digital weighing scale!
          
          ## Installation
          1. Download the DMG file below
          2. Open the DMG and drag TrackWeight.app to your Applications folder
          3. Run the app and follow the setup instructions
          
          ## Requirements
          - macOS 13.0 or later (Ventura or newer)
          - MacBook with Force Touch trackpad (2015 or newer MacBook Pro, 2016 or newer MacBook)
          
          ## Usage
          1. Open the scale
          2. Rest your finger on the trackpad
          3. While maintaining finger contact, put your object on the trackpad
          4. Apply minimal pressure while maintaining contact to get the weight
          
        files: |
          ${{ runner.temp }}/${{ steps.version_info.outputs.dmg_name }}
        draft: false
        prerelease: false
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Summary
      run: |
        echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
        echo "✅ Successfully built and packaged TrackWeight DMG" >> $GITHUB_STEP_SUMMARY
        echo "📦 DMG file: ${{ steps.version_info.outputs.dmg_name }}" >> $GITHUB_STEP_SUMMARY
        echo "🔗 Original repository: https://github.com/KrishKrosh/TrackWeight" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "The DMG includes:" >> $GITHUB_STEP_SUMMARY
        echo "- TrackWeight.app (signed and notarized if certificates provided)" >> $GITHUB_STEP_SUMMARY
        echo "- Clean professional DMG with just the app and Applications link" >> $GITHUB_STEP_SUMMARY
        echo "- Proper code signing with hardened runtime enabled" >> $GITHUB_STEP_SUMMARY

================================================
FILE: .gitignore
================================================
# Mac
.DS_Store

# Xcode
xcuserdata/
*.xcuserstate

# Swift Package Manager
Packages.resolved
.swiftpm/
.build/

# Framework
*.framework/
*.xcframework/
*.zip
build/

# AI
.claude/

.env
.secrets

local_build/

================================================
FILE: ExportOptions.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>developer-id</string>
    <key>teamID</key>
    <string>9ZRLG6277G</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>signingCertificate</key>
    <string>Developer ID Application: Krish Shah (9ZRLG6277G)</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>destination</key>
    <string>export</string>
    <key>manageAppVersionAndBuildNumber</key>
    <true/>
    <key>compileBitcode</key>
    <false/>
    <key>uploadBitcode</key>
    <false/>
    <key>uploadSymbols</key>
    <false/>
</dict>
</plist>


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Krish Shah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# TrackWeight

**Turn your MacBook's trackpad into a precise digital weighing scale**

[TrackWeight](
https://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.

https://github.com/user-attachments/assets/7eaf9e0b-3dec-4829-b868-f54a8fd53a84

To use it yourself:

1. Open the scale
2. Rest your finger on the trackpad
3. While maintaining finger contact, put your object on the trackpad
4. Try to put as little pressure on the trackpad while still maintaining contact. This is the weight of your object

## How It Works

TrackWeight 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.

The 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.

## Requirements

- **macOS 13.0+** (Ventura or later)
- **MacBook with Force Touch trackpad** (2015 or newer MacBook Pro, 2016 or newer MacBook)
- **App Sandbox disabled** (required for low-level trackpad access)
- **Xcode 16.0+** and **Swift 6.0+** (for development)

## Installation

### Option 1: Download DMG (Recommended)

1. Go to the [Releases](https://github.com/krishkrosh/TrackWeight/releases) page
2. Download the latest TrackWeight DMG file
3. Open the DMG and drag TrackWeight.app to your Applications folder
4. Run the application (you may need to allow it in System Preferences > Security & Privacy for unsigned builds)

### Option 2: Homebrew
```bash
brew install --cask krishkrosh/apps/trackweight --force
```
 
### Option 3: Build from Source

1. Clone this repository
2. Open `TrackWeight.xcodeproj` in Xcode
3. Disable App Sandbox in the project settings (required for trackpad access)
4. Build and run the application

For more information about setting up the build pipeline, see [.github/workflows/README.md](.github/workflows/README.md).

### Calibration Process

The weight calculations have been validated by:
1. Placing the MacBook trackpad directly on top of a conventional digital scale
2. Applying various known weights while maintaining finger contact with the trackpad
3. Comparing and calibrating the pressure readings against the reference scale measurements
4. Ensuring consistent accuracy across different weight ranges

It turns out that the data we get from MultitouchSupport is already in grams!

## Limitations

- **Finger contact required**: The trackpad only provides pressure readings when it detects capacitance (finger touch), so you cannot weigh objects directly without maintaining contact
- **Surface contact**: Objects being weighed must be placed in a way that doesn't interfere with the required finger contact
- **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

## Technical Details

The application is built using:
- **SwiftUI** for the user interface
- **Combine** for reactive data flow
- **Open Multi-Touch Support library** for low-level trackpad access

### Open Multi-Touch Support Library

This 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:

- Access to global multitouch events on macOS trackpads
- Detailed touch data including position, pressure, angle, and density
- Thread-safe async/await support for touch event streams
- Touch state tracking and comprehensive sensor data

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Disclaimer

This 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.


================================================
FILE: TrackWeight/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "icon_16x16.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "16x16"
    },
    {
      "filename" : "icon_16x16@2x.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "16x16"
    },
    {
      "filename" : "icon_32x32.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "32x32"
    },
    {
      "filename" : "icon_32x32@2x.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "32x32"
    },
    {
      "filename" : "icon_128x128.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "128x128"
    },
    {
      "filename" : "icon_128x128@2x.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "128x128"
    },
    {
      "filename" : "icon_256x256.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "256x256"
    },
    {
      "filename" : "icon_256x256@2x.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "256x256"
    },
    {
      "filename" : "icon_512x512.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "512x512"
    },
    {
      "filename" : "icon_512x512@2x.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "512x512"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: TrackWeight/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: TrackWeight/ContentView.swift
================================================
//
//  ContentView.swift
//  TrackWeight
//

import SwiftUI

struct ContentView: View {
    @State private var showHomePage = true
    @State private var selectedTab = 1 // Start with Scale tab (index 1)
    
    var body: some View {
        if showHomePage {
            HomeView {
                showHomePage = false
            }
            .frame(minWidth: 700, minHeight: 500)
        } else {
            TabView(selection: $selectedTab) {
                TrackWeightView()
                    .tabItem {
                        Image(systemName: "arrow.3.trianglepath")
                        Text("Guided (Experimental)")
                    }
                    .tag(0)
                
                ScaleView()
                    .tabItem {
                        Image(systemName: "scalemass")
                        Text("Scale")
                    }
                    .tag(1)
                
                SettingsView()
                    .tabItem {
                        Image(systemName: "gearshape")
                        Text("Settings")
                    }
                    .tag(2)
            }
            .frame(minWidth: 700, minHeight: 500)
        }
    }
}


#Preview {
    ContentView()
}


================================================
FILE: TrackWeight/ContentViewModel.swift
================================================
//
//  ContentViewModel.swift
//  OMSDemo
//
//  Created by Takuto Nakamura on 2024/03/02.
//

import OpenMultitouchSupport
import SwiftUI

@MainActor
final class ContentViewModel: ObservableObject {
    @Published var touchData = [OMSTouchData]()
    @Published var isListening: Bool = false
    @Published var availableDevices = [OMSDeviceInfo]()
    @Published var selectedDevice: OMSDeviceInfo?

    private let manager = OMSManager.shared
    private var task: Task<Void, Never>?

    init() {
        loadDevices()
    }

    func onAppear() {
        task = Task { [weak self, manager] in
            for await touchData in manager.touchDataStream {
                await MainActor.run {
                    self?.touchData = touchData
                }
            }
        }
    }

    func onDisappear() {
        task?.cancel()
        stop()
    }

    func start() {
        if manager.startListening() {
            isListening = true
        }
    }

    func stop() {
        if manager.stopListening() {
            isListening = false
        }
    }
    
    func loadDevices() {
        availableDevices = manager.availableDevices
        selectedDevice = manager.currentDevice
    }
    
    func selectDevice(_ device: OMSDeviceInfo) {
        if manager.selectDevice(device) {
            selectedDevice = device
        }
    }
}


================================================
FILE: TrackWeight/DebugView.swift
================================================
//
//  DebugView.swift
//  TrackWeight
//
//  Created by Takuto Nakamura on 2024/03/02.
//

import OpenMultitouchSupport
import SwiftUI

struct DebugView: View {
    @StateObject var viewModel = ContentViewModel()
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack {
            // Header with close button
            HStack {
                Text("Debug Console")
                    .font(.title2)
                    .fontWeight(.semibold)
                
                Spacer()
                
                Button(action: {
                    dismiss()
                }) {
                    Image(systemName: "xmark.circle.fill")
                        .font(.title2)
                        .foregroundColor(.secondary)
                }
                .buttonStyle(PlainButtonStyle())
                .help("Close Debug Console")
            }
            .padding(.bottom)

            // Device Selector
            if !viewModel.availableDevices.isEmpty {
                VStack(alignment: .leading) {
                    Text("Trackpad Device:")
                        .font(.headline)
                    Picker("Select Device", selection: Binding(
                        get: { viewModel.selectedDevice },
                        set: { device in
                            if let device = device {
                                viewModel.selectDevice(device)
                            }
                        }
                    )) {
                        ForEach(viewModel.availableDevices, id: \.self) { device in
                            Text("\(device.deviceName) (ID: \(device.deviceID))")
                                .tag(device as OMSDeviceInfo?)
                        }
                    }
                    .pickerStyle(MenuPickerStyle())
                }
                .padding(.bottom)
            }
            
            if viewModel.isListening {
                Button {
                    viewModel.stop()
                } label: {
                    Text("Stop")
                }
            } else {
                Button {
                    viewModel.start()
                } label: {
                    Text("Start")
                }
            }
            Canvas { context, size in
                viewModel.touchData.forEach { touch in
                    let path = makeEllipse(touch: touch, size: size)
                    context.fill(path, with: .color(.primary.opacity(Double(touch.total))))
                }
            }
            .frame(width: 600, height: 400)
            .border(Color.primary)
        }
        .fixedSize()
        .padding()
        .onAppear {
            viewModel.onAppear()
        }
        .onDisappear {
            viewModel.onDisappear()
        }
    }

    private func makeEllipse(touch: OMSTouchData, size: CGSize) -> Path {
        let x = Double(touch.position.x) * size.width
        let y = Double(1.0 - touch.position.y) * size.height
        let u = size.width / 100.0
        let w = Double(touch.axis.major) * u
        let h = Double(touch.axis.minor) * u
        return Path(ellipseIn: CGRect(x: -0.5 * w, y: -0.5 * h, width: w, height: h))
            .rotation(.radians(Double(-touch.angle)), anchor: .topLeading)
            .offset(x: x, y: y)
            .path(in: CGRect(origin: .zero, size: size))
    }
}

#Preview {
    DebugView()
}

================================================
FILE: TrackWeight/HomeView.swift
================================================
//
//  HomeView.swift
//  TrackWeight
//

import SwiftUI

struct HomeView: View {
    let onBegin: () -> Void
    
    var body: some View {
        VStack(spacing: 40) {
            Spacer()
            
            // Title section
            VStack(spacing: 15) {
                Image(systemName: "scalemass")
                    .font(.system(size: 80, weight: .ultraLight))
                    .foregroundStyle(Color.blue)
                
                Text("TrackWeight")
                    .font(.system(size: 48, weight: .bold, design: .rounded))
                    .foregroundStyle(
                        LinearGradient(
                            colors: [.blue, .teal, .cyan],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                    )
            }
            
            // Description section
            VStack(spacing: 20) {
                Text("Transform your MacBook trackpad into a precision scale using Apple's private MultitouchSupport framework to read pressure values with gram-level accuracy.")
                    .font(.system(size: 18, weight: .medium))
                    .foregroundStyle(Color.primary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 550)
                
                // Limitations section
                VStack(spacing: 12) {
                    Text("Important Limitations")
                        .font(.system(size: 16, weight: .semibold))
                        .foregroundStyle(Color.orange)
                    
                    VStack(spacing: 8) {
                        LimitationRow(
                            icon: "hand.point.up.left",
                            text: "Requires finger contact for capacitive detection"
                        )
                        LimitationRow(
                            icon: "chart.line.downtrend.xyaxis",
                            text: "May experience pressure drift when placing objects"
                        )
                        LimitationRow(
                            icon: "cube.fill",
                            text: "Metal/magnetic objects may not work"
                        )
                    }
                }
                .padding(.horizontal, 30)
                .padding(.vertical, 20)
                .background(
                    RoundedRectangle(cornerRadius: 15)
                        .foregroundColor(Color.orange.opacity(0.05))
                        .overlay(
                            RoundedRectangle(cornerRadius: 15)
                                .stroke(Color.orange.opacity(0.2), lineWidth: 1)
                        )
                )
                .frame(maxWidth: 500)
            }
            
            Spacer()
            
            // Begin button
            Button(action: onBegin) {
                HStack(spacing: 10) {
                    Text("Begin")
                        .font(.system(size: 18, weight: .semibold))
                    Image(systemName: "arrow.right")
                        .font(.system(size: 16, weight: .semibold))
                }
                .foregroundStyle(Color.white)
                .frame(width: 140, height: 50)
                .background(
                    RoundedRectangle(cornerRadius: 25)
                        .fill(
                            LinearGradient(
                                colors: [.blue, .teal],
                                startPoint: .leading,
                                endPoint: .trailing
                            )
                        )
                        .shadow(color: .blue.opacity(0.3), radius: 10, x: 0, y: 5)
                )
            }
            .buttonStyle(.plain)
            .scaleEffect(1.0)
            .animation(.spring(response: 0.3, dampingFraction: 0.8), value: true)
            .padding(.vertical, 10)

            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding(.horizontal, 40)
    }
}

struct LimitationRow: View {
    let icon: String
    let text: String
    
    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: icon)
                .font(.system(size: 14, weight: .medium))
                .foregroundStyle(Color.orange)
                .frame(width: 20)
            
            Text(text)
                .font(.system(size: 14, weight: .medium))
                .foregroundStyle(Color.secondary)
                .multilineTextAlignment(.leading)
            
            Spacer()
        }
    }
}

#Preview {
    HomeView(onBegin: {})
}


================================================
FILE: TrackWeight/Preview Content/Preview Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: TrackWeight/ScaleView.swift
================================================
//
//  ScaleView.swift
//  TrackWeight
//

import SwiftUI

struct ScaleView: View {
    @StateObject private var viewModel = ScaleViewModel()
    @State private var scaleCompression: CGFloat = 0
    @State private var displayShake = false
    @State private var particleOffset: CGFloat = 0
    @State private var keyMonitor: Any?
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // Animated gradient background
//                    LinearGradient(
//                        colors: [
//                            Color(red: 0.95, green: 0.97, blue: 1.0),
//                            Color(red: 0.85, green: 0.92, blue: 0.98)
//                        ],
//                        startPoint: .topLeading,
//                        endPoint: .bottomTrailing
//                    )
//                    .ignoresSafeArea()
                
                VStack(spacing: geometry.size.height * 0.06) {
                    // Title with subtitle directly underneath
                    VStack(spacing: 8) {
                        Text("Track Weight")
                            .font(.system(size: min(max(geometry.size.width * 0.05, 24), 42), weight: .bold, design: .rounded))
                            .foregroundStyle(
                                LinearGradient(
                                    colors: [.blue, .teal, .cyan],
                                    startPoint: .leading,
                                    endPoint: .trailing
                                )
                            )
                            .minimumScaleFactor(0.7)
                            .lineLimit(1)
                        
                        Text("Place your finger on the trackpad to begin")
                            .font(.system(size: min(max(geometry.size.width * 0.022, 14), 18), weight: .medium))
                            .foregroundStyle(.gray)
                            .multilineTextAlignment(.center)
                            .frame(maxWidth: geometry.size.width * 0.8)
                            .opacity(viewModel.hasTouch ? 0 : 1)
                            .animation(.easeInOut(duration: 0.5), value: viewModel.hasTouch)
                    }
                    .frame(height: max(geometry.size.height * 0.15, 80)) // Fixed height for title + subtitle
                    .frame(maxWidth: .infinity) // Ensure full width for centering
                    
                    Spacer()
                    
                    // Cartoon Digital Scale - responsive size
                    HStack {
                        Spacer()
                        CartoonScaleView(
                            weight: viewModel.currentWeight,
                            hasTouch: viewModel.hasTouch,
                            compression: $scaleCompression,
                            displayShake: $displayShake,
                            scaleFactor: min(geometry.size.width / 700, geometry.size.height / 500)
                        )
                        Spacer()
                    }
                    
                    Spacer()
                    
                    // Fixed container for button to prevent jumping
                    VStack(spacing: 10) {
                        if viewModel.hasTouch {
                            Text("Press spacebar or click to zero")
                                .font(.system(size: min(max(geometry.size.width * 0.018, 12), 16), weight: .medium))
                                .foregroundStyle(.gray)
                        }
                        
                        Button(action: {
                            viewModel.zeroScale()
                        }) {
                            HStack(spacing: 8) {
                                Image(systemName: "arrow.clockwise")
                                    .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold))
                                Text("Zero Scale")
                                    .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold))
                            }
                            .foregroundStyle(.white)
                            .frame(width: min(max(geometry.size.width * 0.2, 140), 180), 
                                   height: min(max(geometry.size.height * 0.08, 40), 55))
                            .background(
                                RoundedRectangle(cornerRadius: 25)
                                    .fill(
                                        LinearGradient(
                                            colors: [.blue, .teal],
                                            startPoint: .leading,
                                            endPoint: .trailing
                                        )
                                    )
                            )
                        }
                        .buttonStyle(.plain)
                        .opacity(viewModel.hasTouch ? 1 : 0)
                        .scaleEffect(viewModel.hasTouch ? 1 : 0.8)
                        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: viewModel.hasTouch)
                    }
                    .frame(height: min(max(geometry.size.height * 0.15, 80), 100)) // Fixed space for button + instruction
                    .frame(maxWidth: .infinity) // Ensure full width for centering
                }
                .padding(.horizontal, max(geometry.size.width * 0.05, 20))
                .padding(.vertical, max(geometry.size.height * 0.03, 20))
                .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensure the VStack takes full available space
            }
        }
        .focusable()
        .modifier(FocusEffectModifier())
        .onChange(of: viewModel.currentWeight) { newWeight in
            withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
                scaleCompression = CGFloat(min(newWeight / 100.0, 0.2))
            }
        }
        .onAppear {
            viewModel.startListening()
            setupKeyMonitoring()
        }
        .onDisappear {
            viewModel.stopListening()
            removeKeyMonitoring()
        }
    }
    
    private func setupKeyMonitoring() {
        keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
            // Space key code is 49
            if event.keyCode == 49 && viewModel.hasTouch {
                viewModel.zeroScale()
            }
            return event
        }
    }
    
    private func removeKeyMonitoring() {
        if let monitor = keyMonitor {
            NSEvent.removeMonitor(monitor)
            keyMonitor = nil
        }
    }
}

struct CartoonScaleView: View {
    let weight: Float
    let hasTouch: Bool
    @Binding var compression: CGFloat
    @Binding var displayShake: Bool
    let scaleFactor: CGFloat
    
    var body: some View {
        VStack(spacing: 0) {
            // Scale platform (top) - responsive to weight
            RoundedRectangle(cornerRadius: 8)
                .fill(
                    LinearGradient(
                        colors: [.gray.opacity(0.3), .gray.opacity(0.6)],
                        startPoint: .top,
                        endPoint: .bottom
                    )
                )
                .frame(width: 200 * scaleFactor, height: 12 * scaleFactor)
                .offset(y: compression * 15)
            
            // Scale body
            ZStack {
                // Main body
                RoundedRectangle(cornerRadius: 20)
                    .fill(
                        LinearGradient(
                            colors: [
                                Color(red: 0.95, green: 0.95, blue: 0.97),
                                Color(red: 0.85, green: 0.85, blue: 0.90)
                            ],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                    )
                    .frame(width: 250 * scaleFactor, height: 150 * scaleFactor)
                    .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 8)
                
                // Display screen
                RoundedRectangle(cornerRadius: 12)
                    .fill(.black)
                    .frame(width: 180 * scaleFactor, height: 60 * scaleFactor)
                    .offset(y: -10)
                    .overlay(
                        RoundedRectangle(cornerRadius: 12)
                            .fill(
                                LinearGradient(
                                    colors: [.teal.opacity(0.8), .blue.opacity(0.6)],
                                    startPoint: .topLeading,
                                    endPoint: .bottomTrailing
                                )
                            )
                            .frame(width: 176 * scaleFactor, height: 56 * scaleFactor)
                            .offset(y: -10)
                    )
                
                // Weight display
                VStack(spacing: 2) {
                    Text(String(format: "%.1f", weight))
                        .font(.system(size: 32 * scaleFactor, weight: .bold, design: .monospaced))
                        .foregroundStyle(.white)
                        .shadow(color: .teal, radius: hasTouch ? 2 : 0)
                        .animation(.easeInOut(duration: 0.2), value: weight)
                    
                    Text("grams")
                        .font(.system(size: 12 * scaleFactor, weight: .medium))
                        .foregroundStyle(.white.opacity(0.8))
                }
                .offset(y: -10)
                
                // Status indicator - simple and clean
                if hasTouch {
                    Circle()
                        .fill(.teal)
                        .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)
                        .offset(x: 90 * scaleFactor, y: -50 * scaleFactor)
                }
                
                // Fun face on the scale - positioned below the display screen
                VStack(spacing: 8 * scaleFactor) {
                    // Eyes
                    HStack(spacing: 15 * scaleFactor) {
                        Circle()
                            .fill(.black)
                            .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)
                        Circle()
                            .fill(.black)
                            .frame(width: 8 * scaleFactor, height: 8 * scaleFactor)
                    }
                    
                    // Responsive mouth expression
                    Group {
                        if hasTouch && weight > 5 {
                            // Happy mouth when weighing something substantial
                            Path { path in
                                path.move(to: CGPoint(x: 0, y: 0))
                                path.addQuadCurve(to: CGPoint(x: 20, y: 0), control: CGPoint(x: 0, y: 15))
                            }
                            .stroke(.black, lineWidth: 2 * scaleFactor)
                            .frame(width: 20 * scaleFactor, height: 10 * scaleFactor)
                        } else {
                            // Neutral mouth
                            Rectangle()
                                .fill(.black)
                                .frame(width: 12 * scaleFactor, height: 2 * scaleFactor)
                        }
                    }
                    .animation(.easeInOut(duration: 0.3), value: weight > 5)
                }
                .offset(y: 60 * scaleFactor) // Position well below the display screen
            }
            
            // Scale legs
            HStack(spacing: 140 * scaleFactor) {
                ForEach(0..<2, id: \.self) { _ in
                    RoundedRectangle(cornerRadius: 4)
                        .fill(.gray.opacity(0.7))
                        .frame(width: 12 * scaleFactor, height: 25 * scaleFactor)
                        .offset(y: compression * 3)
                }
            }
            .offset(y: -5)
        }
        .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression)
    }
}

struct FocusEffectModifier: ViewModifier {
    func body(content: Content) -> some View {
        if #available(macOS 14.0, *) {
            content.focusEffectDisabled()
        } else {
            content
        }
    }
}

#Preview {
    ScaleView()
}


================================================
FILE: TrackWeight/ScaleViewModel.swift
================================================
//
//  ScaleViewModel.swift
//  TrackWeight
//

import OpenMultitouchSupport
import SwiftUI
import Combine

@MainActor
final class ScaleViewModel: ObservableObject {
    @Published var currentWeight: Float = 0.0
    @Published var zeroOffset: Float = 0.0
    @Published var isListening = false
    @Published var hasTouch = false
    
    private let manager = OMSManager.shared
    private var task: Task<Void, Never>?
    private var rawWeight: Float = 0.0
    
    func startListening() {
        if manager.startListening() {
            isListening = true
        }
        
        task = Task { [weak self, manager] in
            for await touchData in manager.touchDataStream {
                await MainActor.run {
                    self?.processTouchData(touchData)
                }
            }
        }
    }
    
    func stopListening() {
        task?.cancel()
        if manager.stopListening() {
            isListening = false
            hasTouch = false
            currentWeight = 0.0
        }
    }
    
    func zeroScale() {
        if hasTouch {
            zeroOffset = rawWeight
        }
    }
    
    private func processTouchData(_ touchData: [OMSTouchData]) {
        if touchData.isEmpty {
            hasTouch = false
            currentWeight = 0.0
            zeroOffset = 0.0  // Reset zero when finger is lifted
        } else {
            hasTouch = true
            rawWeight = touchData.first?.pressure ?? 0.0
            currentWeight = max(0, rawWeight - zeroOffset)
        }
    }
    
    deinit {
        task?.cancel()
        manager.stopListening()
    }
}

================================================
FILE: TrackWeight/SettingsView.swift
================================================
//
//  SettingsView.swift
//  TrackWeight
//

import OpenMultitouchSupport
import SwiftUI

struct SettingsView: View {
    @StateObject private var viewModel = ContentViewModel()
    @State private var showDebugView = false
    
    var body: some View {
        VStack(spacing: 0) {
            // Minimal Header
            Text("Settings")
                .font(.title)
                .fontWeight(.medium)
                .padding(.top, 32)
                .padding(.bottom, 32)
            
            // Settings Cards
            VStack(spacing: 20) {
                // Device Card
                SettingsCard {
                    VStack(spacing: 20) {
                        // Status Row
                        HStack {
                            HStack(spacing: 12) {
                                Text("Trackpad")
                                    .font(.headline)
                                    .fontWeight(.medium)
                            }
                            
                            Spacer()
                            
                            if !viewModel.availableDevices.isEmpty {
                                Text("\(viewModel.availableDevices.count) device\(viewModel.availableDevices.count == 1 ? "" : "s")")
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                            }
                        }
                        
                        // Device Selector
                        if !viewModel.availableDevices.isEmpty {
                            HStack {
                                Picker("", selection: Binding(
                                    get: { viewModel.selectedDevice },
                                    set: { device in
                                        if let device = device {
                                            viewModel.selectDevice(device)
                                        }
                                    }
                                )) {
                                    ForEach(viewModel.availableDevices, id: \.self) { device in
                                        Text(device.deviceName)
                                            .tag(device as OMSDeviceInfo?)
                                    }
                                }
                                .pickerStyle(MenuPickerStyle())
                                
                                Spacer()
                            }
                        } else {
                            HStack {
                                Text("No devices available")
                                    .foregroundColor(.secondary)
                                Spacer()
                            }
                        }
                    }
                }
                
                // Debug Card
                SettingsCard {
                    Button(action: { showDebugView = true }) {
                        HStack(spacing: 16) {
                            VStack(alignment: .leading, spacing: 4) {
                                Text("Debug Console")
                                    .font(.headline)
                                    .fontWeight(.medium)
                                    .foregroundColor(.primary)
                                
                                Text("Raw touch data & diagnostics")
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                            }
                            
                            Spacer()
                            
                            Image(systemName: "chevron.right")
                                .font(.caption)
                                .fontWeight(.medium)
                                .foregroundColor(.secondary.opacity(0.6))
                        }
                        .contentShape(Rectangle())
                    }
                    .buttonStyle(CardButtonStyle())
                }
            }
            .frame(maxWidth: 480)
            .padding(.horizontal, 40)
            
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(NSColor.windowBackgroundColor))
        .sheet(isPresented: $showDebugView) {
            DebugView()
                .frame(minWidth: 700, minHeight: 500)
        }
        .onAppear {
            viewModel.loadDevices()
        }
    }
}

struct SettingsCard<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        VStack {
            content
        }
        .padding(24)
        .background(Color(NSColor.controlBackgroundColor))
        .cornerRadius(16)
        .shadow(color: Color.black.opacity(0.03), radius: 1, x: 0, y: 1)
        .shadow(color: Color.black.opacity(0.05), radius: 8, x: 0, y: 4)
    }
}

struct CardButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.98 : 1.0)
            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
    }
}

#Preview {
    SettingsView()
} 

================================================
FILE: TrackWeight/TrackWeight.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- Hardened Runtime (required for notarization) -->
    <key>com.apple.security.cs.allow-jit</key>
    <false/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <false/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <false/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <false/>
    
    <!-- App Sandbox disabled (needed for trackpad access) -->
    <key>com.apple.security.app-sandbox</key>
    <false/>
    
    <!-- Device access for trackpad -->
    <key>com.apple.security.device.usb</key>
    <true/>
    <key>com.apple.security.device.serial</key>
    <true/>
    
    <!-- Network access (if needed) -->
    <key>com.apple.security.network.client</key>
    <true/>
</dict>
</plist>


================================================
FILE: TrackWeight/TrackWeightApp.swift
================================================
//
//  TrackWeightApp.swift
//  TrackWeight
//
//  Created by Takuto Nakamura on 2024/03/02.
//

import SwiftUI

@main
struct TrackWeightApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
}


================================================
FILE: TrackWeight/TrackWeightView.swift
================================================
//
//  TrackWeightView.swift
//  TrackWeight
//

import SwiftUI

struct TrackWeightView: View {
    @StateObject private var viewModel = WeighingViewModel()
    
    var body: some View {
        VStack(spacing: 30) {
            switch viewModel.state {
            case .welcome:
                WelcomeView {
                    viewModel.startWeighing()
                }
                
            case .waitingForFinger:
                FingerTimerView(
                    progress: viewModel.fingerTimer,
                    hasDetectedFinger: viewModel.fingerTimer > 0
                )
                
            case .waitingForItem:
                InstructionView(
                    title: "Place your item",
                    subtitle: "While maintaining contact with the trackpad, gently place your item. Use as little pressure as possible with your reference finger.",
                    disclaimer: "Keep your finger still and apply minimal pressure",
                    icon: "cube.box"
                )
                
            case .weighing:
                WeighingView(
                    currentPressure: viewModel.currentPressure,
                    isStabilizing: viewModel.isStabilizing,
                    stabilityProgress: viewModel.stabilityProgress
                )
                
            case .result(let weight):
                ResultView(weight: weight) {
                    viewModel.restart()
                }
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(.windowBackgroundColor))
        .animation(.easeInOut(duration: 0.6), value: viewModel.state)
    }
}

struct WelcomeView: View {
    let onStart: () -> Void
    
    var body: some View {
        VStack(spacing: 25) {
            Image(systemName: "scalemass")
                .font(.system(size: 80, weight: .ultraLight))
                .foregroundStyle(.primary)
            
            Text("TrackWeight")
                .font(.system(size: 36, weight: .bold, design: .rounded))
                .foregroundStyle(.primary)
            
            Text("Turn your trackpad into a precision scale. Place objects and get their weight in grams.")
                .font(.system(size: 16, weight: .medium))
                .foregroundStyle(.secondary)
                .multilineTextAlignment(.center)
                .frame(maxWidth: 400)
            
            VStack(spacing: 15) {
                Button(action: onStart) {
                    Text("Begin")
                        .font(.system(size: 16, weight: .semibold))
                        .foregroundStyle(.white)
                        .frame(width: 120, height: 40)
                        .background(
                            RoundedRectangle(cornerRadius: 20)
                                .fill(.blue)
                        )
                }
                .buttonStyle(.plain)
            }
        }
    }
}

struct FingerTimerView: View {
    let progress: Float
    let hasDetectedFinger: Bool
    
    var body: some View {
        VStack(spacing: 30) {
            Image(systemName: "hand.point.up.left")
                .font(.system(size: 60, weight: .light))
                .foregroundStyle(.blue)
            
            Text("Hold your finger steady")
                .font(.system(size: 28, weight: .bold, design: .rounded))
                .foregroundStyle(.primary)
            
            VStack(spacing: 8) {
                Text("Keep your finger on the trackpad for 3 seconds")
                    .font(.system(size: 16, weight: .medium))
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 300)
                
                Text("This establishes your baseline pressure")
                    .font(.system(size: 14, weight: .medium))
                    .foregroundStyle(.tertiary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 300)
            }
            
            // Bubble filling animation
            ZStack {
                Circle()
                    .stroke(.blue.opacity(0.3), lineWidth: 4)
                    .frame(width: 100, height: 100)
                
                Circle()
                    .fill(.blue.opacity(0.2))
                    .frame(width: 100, height: 100)
                
                if hasDetectedFinger {
                    Circle()
                        .trim(from: 0, to: CGFloat(progress))
                        .stroke(.blue, style: StrokeStyle(lineWidth: 4, lineCap: .round))
                        .frame(width: 100, height: 100)
                        .rotationEffect(.degrees(-90))
                        .animation(.linear(duration: 0.1), value: progress)
                    
                    // Gentle bubble effect
                    Circle()
                        .fill(
                            RadialGradient(
                                colors: [.blue.opacity(0.3), .blue.opacity(0.1)],
                                center: .center,
                                startRadius: 0,
                                endRadius: 50
                            )
                        )
                        .frame(width: CGFloat(progress) * 80 + 20, height: CGFloat(progress) * 80 + 20)
                        .animation(.easeInOut(duration: 0.2), value: progress)
                    
                    Text("\(Int((1 - progress) * 3) + 1)")
                        .font(.system(size: 24, weight: .bold, design: .monospaced))
                        .foregroundStyle(.blue)
                }
            }
            .scaleEffect(hasDetectedFinger ? 1.0 : 0.8)
            .animation(.spring(response: 0.3, dampingFraction: 0.8), value: hasDetectedFinger)
        }
    }
}

struct InstructionView: View {
    let title: String
    let subtitle: String
    let disclaimer: String?
    let icon: String
    
    init(title: String, subtitle: String, disclaimer: String? = nil, icon: String) {
        self.title = title
        self.subtitle = subtitle
        self.disclaimer = disclaimer
        self.icon = icon
    }
    
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: icon)
                .font(.system(size: 60, weight: .light))
                .foregroundStyle(.blue)
            
            Text(title)
                .font(.system(size: 28, weight: .bold, design: .rounded))
                .foregroundStyle(.primary)
            
            VStack(spacing: 10) {
                Text(subtitle)
                    .font(.system(size: 16, weight: .medium))
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 350)
                
                if let disclaimer = disclaimer {
                    Text(disclaimer)
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(.orange)
                        .multilineTextAlignment(.center)
                        .frame(maxWidth: 300)
                }
            }
        }
    }
}

struct WeighingView: View {
    let currentPressure: Float
    let isStabilizing: Bool
    let stabilityProgress: Float
    
    var body: some View {
        VStack(spacing: 30) {
            Text("Weighing...")
                .font(.system(size: 24, weight: .semibold, design: .rounded))
                .foregroundStyle(.primary)
            
            VStack(spacing: 10) {
                Text(String(format: "%.1f", currentPressure))
                    .font(.system(size: 64, weight: .bold, design: .monospaced))
                    .foregroundStyle(.blue)
                    .animation(.easeInOut(duration: 0.2), value: currentPressure)
                
                Text("grams")
                    .font(.system(size: 20, weight: .medium))
                    .foregroundStyle(.secondary)
            }
            
            VStack(spacing: 12) {
                Text("Release pressure while maintaining contact")
                    .font(.system(size: 16, weight: .semibold))
                    .foregroundStyle(.orange)
                    .multilineTextAlignment(.center)
                
                Text("Keep your finger on the trackpad but apply as little pressure as possible")
                    .font(.system(size: 14, weight: .medium))
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 350)
                
                if isStabilizing {
                    VStack(spacing: 8) {
                        Text("Stabilizing...")
                            .font(.system(size: 14, weight: .medium))
                            .foregroundStyle(.orange)
                        
                        // Progress bar
                        ZStack {
                            RoundedRectangle(cornerRadius: 4)
                                .fill(.orange.opacity(0.2))
                                .frame(width: 200, height: 8)
                            
                            HStack {
                                RoundedRectangle(cornerRadius: 4)
                                    .fill(.orange)
                                    .frame(width: 200 * CGFloat(stabilityProgress), height: 8)
                                    .animation(.linear(duration: 0.1), value: stabilityProgress)
                                
                                Spacer()
                            }
                        }
                        .frame(width: 200)
                        
                        Text("\(Int((1 - stabilityProgress) * 2) + 1)s remaining")
                            .font(.system(size: 12, weight: .medium, design: .monospaced))
                            .foregroundStyle(.orange.opacity(0.8))
                    }
                }
            }
            .frame(maxWidth: 350)
        }
    }
}


struct ResultView: View {
    let weight: Float
    let onRestart: () -> Void
    
    var body: some View {
        VStack(spacing: 30) {
            Image(systemName: "checkmark.circle.fill")
                .font(.system(size: 60))
                .foregroundStyle(.green)
                .scaleEffect(1.0)
                .onAppear {
                    withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
                        // Animation handled by parent view
                    }
                }
            
            Text("Your object weighs")
                .font(.system(size: 20, weight: .medium))
                .foregroundStyle(.secondary)
            
            VStack(spacing: 5) {
                Text(String(format: "%.1f", weight))
                    .font(.system(size: 56, weight: .bold, design: .monospaced))
                    .foregroundStyle(.primary)
                
                Text("grams")
                    .font(.system(size: 18, weight: .medium))
                    .foregroundStyle(.secondary)
            }
            
            Button(action: onRestart) {
                Image(systemName: "arrow.clockwise")
                    .font(.system(size: 20, weight: .medium))
                    .foregroundStyle(.blue)
                    .frame(width: 44, height: 44)
                    .background(
                        Circle()
                            .fill(.blue.opacity(0.1))
                    )
            }
            .buttonStyle(.plain)
        }
    }
}

#Preview {
    TrackWeightView()
}


================================================
FILE: TrackWeight/WeighingState.swift
================================================
//
//  WeighingState.swift
//  TrackWeight
//

import Foundation

enum WeighingState: Equatable {
    case welcome
    case waitingForFinger
    case waitingForItem
    case weighing
    case result(weight: Float)
}

================================================
FILE: TrackWeight/WeighingViewModel.swift
================================================
//
//  WeighingViewModel.swift
//  TrackWeight
//

import OpenMultitouchSupport
import SwiftUI
import Combine

@MainActor
final class WeighingViewModel: ObservableObject {
    @Published var state: WeighingState = .welcome
    @Published var currentPressure: Float = 0.0
    @Published var maxPressure: Float = 0.0
    @Published var isListening = false
    @Published var fingerTimer: Float = 0.0 // 0.0 to 1.0 for animation
    
    private let manager = OMSManager.shared
    private var task: Task<Void, Never>?
    private var timerTask: Task<Void, Never>?
    private var baselinePressure: Float = 0.0
    private var hasDetectedFinger = false
    private var hasDetectedItem = false
    private var finalWeight: Float = 0.0
    private let fingerHoldDuration: TimeInterval = 3.0
    private var pressureHistory: [Float] = []
    private let historySize = 10
    private let rateOfChangeThreshold: Float = 5
    
    // Weighing stability properties
    private let stabilityDuration: TimeInterval = 3.0
    private let stabilityAnimationDelay: TimeInterval = 1.0 // Show animation after 1s of stability
    private var stabilityStartTime: Date?
    private var stableWeight: Float = 0.0
    private let stabilityThreshold: Float = 2.0 // Max allowed weight variation
    @Published var stabilityProgress: Float = 0.0
    @Published var isStabilizing: Bool = false
    
    func startWeighing() {
        state = .waitingForFinger
        hasDetectedFinger = false
        hasDetectedItem = false
        baselinePressure = 0.0
        currentPressure = 0.0
        maxPressure = 0.0
        finalWeight = 0.0
        fingerTimer = 0.0
        stabilityProgress = 0.0
        stabilityStartTime = nil
        stableWeight = 0.0
        isStabilizing = false
        pressureHistory.removeAll()
        
        if manager.startListening() {
            isListening = true
        }
        
        task = Task { [weak self, manager] in
            for await touchData in manager.touchDataStream {
                await MainActor.run {
                    self?.processTouchData(touchData)
                }
            }
        }
    }
    
    func restart() {
        stopListening()
        state = .welcome
        fingerTimer = 0.0
        stabilityProgress = 0.0
        stabilityStartTime = nil
        isStabilizing = false
    }
    
    private func stopListening() {
        task?.cancel()
        timerTask?.cancel()
        if manager.stopListening() {
            isListening = false
        }
    }
    
    private func startFingerTimer() {
        timerTask?.cancel()
        fingerTimer = 0.0
        
        timerTask = Task { [weak self] in
            let startTime = Date()
            
            while !Task.isCancelled {
                let elapsed = Date().timeIntervalSince(startTime)
                let progress = min(elapsed / (self?.fingerHoldDuration ?? 3.0), 1.0)
                
                await MainActor.run {
                    self?.fingerTimer = Float(progress)
                }
                
                if progress >= 1.0 {
                    await MainActor.run {
                        self?.completeFingerTimer()
                    }
                    break
                }
                
                try? await Task.sleep(nanoseconds: 16_666_667) // ~60fps
            }
        }
    }
    
    private func resetFingerTimer() {
        timerTask?.cancel()
        fingerTimer = 0.0
    }
    
    private func completeFingerTimer() {
        hasDetectedFinger = true
        baselinePressure = currentPressure
        state = .waitingForItem
        timerTask?.cancel()
    }
    
    private func startStabilityTimer(with weight: Float) {
        // stabilityStartTime and stableWeight are already set in the calling code
        stabilityProgress = 0.0
        isStabilizing = true // Start showing animation since we're already past the 1s delay
        
        timerTask = Task { [weak self] in
            // We start from the point where animation should begin (after 1s delay)
            let animationStartTime = Date()
            let remainingDuration = (self?.stabilityDuration ?? 3.0) - (self?.stabilityAnimationDelay ?? 1.0)
            
            while !Task.isCancelled {
                let elapsed = Date().timeIntervalSince(animationStartTime)
                let progress = min(elapsed / remainingDuration, 1.0)
                
                await MainActor.run {
                    self?.stabilityProgress = Float(progress)
                }
                
                if progress >= 1.0 {
                    await MainActor.run {
                        self?.completeWeighing()
                    }
                    break
                }
                
                try? await Task.sleep(nanoseconds: 16_666_667) // ~60fps
            }
        }
    }
    
    private func completeWeighing() {
        state = .result(weight: currentPressure)
        stopListening()
    }
    
    private func resetStabilityTimer() {
        stabilityStartTime = nil
        stabilityProgress = 0.0
        isStabilizing = false
        timerTask?.cancel()
    }
    
    private func processTouchData(_ touchData: [OMSTouchData]) {
        guard !touchData.isEmpty else {
            // Reset timer if finger is lifted during waiting
            if state == .waitingForFinger && !hasDetectedFinger {
                resetFingerTimer()
            }
            
            if state == .weighing {
                if hasDetectedItem && finalWeight > 0 {
                    state = .result(weight: finalWeight)
                    stopListening()
                }
            }
            return
        }
        
        
        let mainTouch = touchData.first!
        currentPressure = mainTouch.pressure
        
        // Add current pressure to history
        pressureHistory.append(currentPressure)
        if pressureHistory.count > historySize {
            pressureHistory.removeFirst()
        }
        
        // log the average pressure (moving avg)
        let avgPressure = pressureHistory.reduce(0, +) / Float(pressureHistory.count)
        print("average pressure: \(avgPressure)")
        currentPressure = avgPressure
        
        
        switch state {
        case .waitingForFinger:
            if !hasDetectedFinger {
                currentPressure = mainTouch.pressure
                if fingerTimer == 0.0 {
                    startFingerTimer()
                }
            }
            
        case .waitingForItem:
            if hasDetectedFinger {
                // Calculate rate of change if we have enough history
                if pressureHistory.count == historySize && !hasDetectedItem {
                    let rateOfChange = pressureHistory.last! - pressureHistory.first!
                    if rateOfChange > rateOfChangeThreshold {
                        print("pressure before item: \(pressureHistory)")
                        print("Old baseline: \(baselinePressure)")
                        baselinePressure = pressureHistory.first!
                        print("New baseline: \(baselinePressure)")
                        hasDetectedItem = true
                        state = .weighing
                        resetStabilityTimer()
                    }
                }
            } else {
                state = .waitingForFinger
                pressureHistory.removeAll()
            }
            
        case .weighing:
            // Check if weight is stable (hasn't changed by more than 2g)
            let weightDifference = stabilityStartTime != nil ? abs(currentPressure - stableWeight) : 0
            
            if stabilityStartTime == nil {
                // First reading in weighing state - start tracking stability
                stabilityStartTime = Date()
                stableWeight = currentPressure
            } else if weightDifference > stabilityThreshold {
                // Weight became unstable, reset stability tracking
                resetStabilityTimer()
                stabilityStartTime = Date()
                stableWeight = currentPressure
            } else {
                // Weight is stable, check if we should start the stabilization process
                let timeSinceStable = Date().timeIntervalSince(stabilityStartTime!)
                
                if timeSinceStable >= stabilityAnimationDelay && !isStabilizing {
                    // Weight has been stable for at least 1 second, start stabilization timer
                    startStabilityTimer(with: stableWeight)
                }
            }
            
        default:
            break
        }
    }
    
    deinit {
        task?.cancel()
        timerTask?.cancel()
        manager.stopListening()
    }
}


================================================
FILE: TrackWeight.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 56;
	objects = {

/* Begin PBXBuildFile section */
		77292A882B931953001CA3F6 /* TrackWeightApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A872B931953001CA3F6 /* TrackWeightApp.swift */; };
		77292A8A2B931953001CA3F6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A892B931953001CA3F6 /* ContentView.swift */; };
		77292A8C2B931954001CA3F6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77292A8B2B931954001CA3F6 /* Assets.xcassets */; };
		77292A8F2B931954001CA3F6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */; };
		77292A982B931D60001CA3F6 /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A972B931D60001CA3F6 /* ContentViewModel.swift */; };
		77292A9C2B931E01001CA3F6 /* WeighingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A9D2B931E01001CA3F6 /* WeighingState.swift */; };
		77292A9E2B931E02001CA3F6 /* WeighingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */; };
		77292AA02B931E03001CA3F6 /* TrackWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA12B931E03001CA3F6 /* TrackWeightView.swift */; };
		77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA32B931E04001CA3F6 /* DebugView.swift */; };
		77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA52B931E05001CA3F6 /* ScaleView.swift */; };
		77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */; };
		93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A095112E33359600E1E1D1 /* SettingsView.swift */; };
		93A095162E33624200E1E1D1 /* OpenMultitouchSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 93A095152E33624200E1E1D1 /* OpenMultitouchSupport */; };
		93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ABD0202E2E01E200668D4F /* HomeView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		77292A842B931953001CA3F6 /* TrackWeight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrackWeight.app; sourceTree = BUILT_PRODUCTS_DIR; };
		77292A872B931953001CA3F6 /* TrackWeightApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackWeightApp.swift; sourceTree = "<group>"; };
		77292A892B931953001CA3F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
		77292A8B2B931954001CA3F6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
		77292A902B931954001CA3F6 /* TrackWeight.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TrackWeight.entitlements; sourceTree = "<group>"; };
		77292A972B931D60001CA3F6 /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = "<group>"; };
		77292A9D2B931E01001CA3F6 /* WeighingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeighingState.swift; sourceTree = "<group>"; };
		77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeighingViewModel.swift; sourceTree = "<group>"; };
		77292AA12B931E03001CA3F6 /* TrackWeightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackWeightView.swift; sourceTree = "<group>"; };
		77292AA32B931E04001CA3F6 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = "<group>"; };
		77292AA52B931E05001CA3F6 /* ScaleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleView.swift; sourceTree = "<group>"; };
		77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleViewModel.swift; sourceTree = "<group>"; };
		93A095112E33359600E1E1D1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
		93ABD0202E2E01E200668D4F /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		77292A812B931953001CA3F6 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				93A095162E33624200E1E1D1 /* OpenMultitouchSupport in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		77292A7B2B931953001CA3F6 = {
			isa = PBXGroup;
			children = (
				77292A862B931953001CA3F6 /* TrackWeight */,
				77292A852B931953001CA3F6 /* Products */,
				77292A992B931D7A001CA3F6 /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		77292A852B931953001CA3F6 /* Products */ = {
			isa = PBXGroup;
			children = (
				77292A842B931953001CA3F6 /* TrackWeight.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		77292A862B931953001CA3F6 /* TrackWeight */ = {
			isa = PBXGroup;
			children = (
				77292A902B931954001CA3F6 /* TrackWeight.entitlements */,
				77292A8B2B931954001CA3F6 /* Assets.xcassets */,
				77292A872B931953001CA3F6 /* TrackWeightApp.swift */,
				77292A892B931953001CA3F6 /* ContentView.swift */,
				77292A972B931D60001CA3F6 /* ContentViewModel.swift */,
				77292A9D2B931E01001CA3F6 /* WeighingState.swift */,
				93ABD0202E2E01E200668D4F /* HomeView.swift */,
				77292A9F2B931E02001CA3F6 /* WeighingViewModel.swift */,
				93A095112E33359600E1E1D1 /* SettingsView.swift */,
				77292AA12B931E03001CA3F6 /* TrackWeightView.swift */,
				77292AA32B931E04001CA3F6 /* DebugView.swift */,
				77292AA52B931E05001CA3F6 /* ScaleView.swift */,
				77292AA72B931E06001CA3F6 /* ScaleViewModel.swift */,
				77292A8D2B931954001CA3F6 /* Preview Content */,
			);
			path = TrackWeight;
			sourceTree = "<group>";
		};
		77292A8D2B931954001CA3F6 /* Preview Content */ = {
			isa = PBXGroup;
			children = (
				77292A8E2B931954001CA3F6 /* Preview Assets.xcassets */,
			);
			path = "Preview Content";
			sourceTree = "<group>";
		};
		77292A992B931D7A001CA3F6 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		77292A832B931953001CA3F6 /* TrackWeight */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 77292A932B931954001CA3F6 /* Build configuration list for PBXNativeTarget "TrackWeight" */;
			buildPhases = (
				77292A802B931953001CA3F6 /* Sources */,
				77292A812B931953001CA3F6 /* Frameworks */,
				77292A822B931953001CA3F6 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = TrackWeight;
			packageProductDependencies = (
				93A095152E33624200E1E1D1 /* OpenMultitouchSupport */,
			);
			productName = TrackWeight;
			productReference = 77292A842B931953001CA3F6 /* TrackWeight.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		77292A7C2B931953001CA3F6 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				BuildIndependentTargetsInParallel = 1;
				LastSwiftUpdateCheck = 1520;
				LastUpgradeCheck = 1620;
				TargetAttributes = {
					77292A832B931953001CA3F6 = {
						CreatedOnToolsVersion = 15.2;
					};
				};
			};
			buildConfigurationList = 77292A7F2B931953001CA3F6 /* Build configuration list for PBXProject "TrackWeight" */;
			compatibilityVersion = "Xcode 14.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 77292A7B2B931953001CA3F6;
			packageReferences = (
				93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference "OpenMultitouchSupport" */,
			);
			productRefGroup = 77292A852B931953001CA3F6 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				77292A832B931953001CA3F6 /* TrackWeight */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		77292A822B931953001CA3F6 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				77292A8F2B931954001CA3F6 /* Preview Assets.xcassets in Resources */,
				77292A8C2B931954001CA3F6 /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		77292A802B931953001CA3F6 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				77292A8A2B931953001CA3F6 /* ContentView.swift in Sources */,
				77292A982B931D60001CA3F6 /* ContentViewModel.swift in Sources */,
				77292A882B931953001CA3F6 /* TrackWeightApp.swift in Sources */,
				93ABD0212E2E01E200668D4F /* HomeView.swift in Sources */,
				77292A9C2B931E01001CA3F6 /* WeighingState.swift in Sources */,
				77292A9E2B931E02001CA3F6 /* WeighingViewModel.swift in Sources */,
				77292AA02B931E03001CA3F6 /* TrackWeightView.swift in Sources */,
				77292AA22B931E04001CA3F6 /* DebugView.swift in Sources */,
				77292AA42B931E05001CA3F6 /* ScaleView.swift in Sources */,
				77292AA62B931E06001CA3F6 /* ScaleViewModel.swift in Sources */,
				93A095122E33359600E1E1D1 /* SettingsView.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		77292A912B931954001CA3F6 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ARCHS = "$(ARCHS_STANDARD)";
				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEAD_CODE_STRIPPING = YES;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				ENABLE_USER_SCRIPT_SANDBOXING = YES;
				GCC_C_LANGUAGE_STANDARD = gnu17;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = macosx;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		77292A922B931954001CA3F6 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ARCHS = "$(ARCHS_STANDARD)";
				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEAD_CODE_STRIPPING = YES;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_USER_SCRIPT_SANDBOXING = YES;
				GCC_C_LANGUAGE_STANDARD = gnu17;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = NO;
				SDKROOT = macosx;
				SWIFT_COMPILATION_MODE = wholemodule;
			};
			name = Release;
		};
		77292A942B931954001CA3F6 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ARCHS = "$(ARCHS_STANDARD)";
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CODE_SIGN_ENTITLEMENTS = TrackWeight/TrackWeight.entitlements;
				CODE_SIGN_IDENTITY = "Apple Development";
				CODE_SIGN_STYLE = Automatic;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 1;
				DEAD_CODE_STRIPPING = YES;
				DEVELOPMENT_TEAM = 9ZRLG6277G;
				ENABLE_HARDENED_RUNTIME = YES;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MARKETING_VERSION = 1.0;
				ONLY_ACTIVE_ARCH = YES;
				PRODUCT_BUNDLE_IDENTIFIER = com.krishkrosh.trackweight;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 6.0;
			};
			name = Debug;
		};
		77292A952B931954001CA3F6 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ARCHS = "$(ARCHS_STANDARD)";
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CODE_SIGN_ENTITLEMENTS = TrackWeight/TrackWeight.entitlements;
				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
				CODE_SIGN_STYLE = Manual;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 1;
				DEAD_CODE_STRIPPING = YES;
				DEVELOPMENT_TEAM = 9ZRLG6277G;
				ENABLE_HARDENED_RUNTIME = YES;
				ENABLE_PREVIEWS = YES;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MARKETING_VERSION = 1.0;
				ONLY_ACTIVE_ARCH = NO;
				PRODUCT_BUNDLE_IDENTIFIER = com.krishkrosh.trackweight;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_VERSION = 6.0;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		77292A7F2B931953001CA3F6 /* Build configuration list for PBXProject "TrackWeight" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				77292A912B931954001CA3F6 /* Debug */,
				77292A922B931954001CA3F6 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		77292A932B931954001CA3F6 /* Build configuration list for PBXNativeTarget "TrackWeight" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				77292A942B931954001CA3F6 /* Debug */,
				77292A952B931954001CA3F6 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
		93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference "OpenMultitouchSupport" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/KrishKrosh/OpenMultitouchSupport.git";
			requirement = {
				branch = main;
				kind = branch;
			};
		};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		93A095152E33624200E1E1D1 /* OpenMultitouchSupport */ = {
			isa = XCSwiftPackageProductDependency;
			package = 93A095142E33624200E1E1D1 /* XCRemoteSwiftPackageReference "OpenMultitouchSupport" */;
			productName = OpenMultitouchSupport;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = 77292A7C2B931953001CA3F6 /* Project object */;
}


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


================================================
FILE: TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
================================================
{
  "originHash" : "231443e4712351510ab418164c13b6d5894774039d8c118ed8208a5fc9fe884e",
  "pins" : [
    {
      "identity" : "openmultitouchsupport",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/KrishKrosh/OpenMultitouchSupport.git",
      "state" : {
        "branch" : "main",
        "revision" : "fb6991fa1ece8c5faa8eef49190beb2baf071694"
      }
    }
  ],
  "version" : 3
}


================================================
FILE: scripts/README.md
================================================
# Scripts

This directory contains helper scripts for the TrackWeight project.

## setup-signing.sh

A helper script to set up code signing certificates for automated DMG builds.

### Usage

```bash
./scripts/setup-signing.sh
```

### What it does

1. **Guides you through certificate export**: Provides step-by-step instructions to export your Developer ID Application certificate from Keychain Access
2. **Encodes certificates**: Converts your .p12 certificate file to base64 format required for GitHub Secrets
3. **Generates secret values**: Provides the exact values you need to add as GitHub repository secrets
4. **Optional provisioning profile**: Handles provisioning profile encoding if needed

### Prerequisites

- macOS (required for Keychain Access and signing tools)
- Valid Apple Developer ID Application certificate
- Access to GitHub repository settings to add secrets

### Output

The script will generate the values for these GitHub repository secrets:
- `BUILD_CERTIFICATE_BASE64`: Base64-encoded .p12 certificate
- `P12_PASSWORD`: Certificate password
- `BUILD_PROVISION_PROFILE_BASE64`: Base64-encoded provisioning profile (optional)

### Adding Secrets to GitHub

1. Go to your GitHub repository
2. Navigate to Settings > Secrets and variables > Actions
3. Click "New repository secret"
4. Add each secret with the name and value provided by the script

### Attribution

This script is part of the enhanced TrackWeight fork that adds automated build pipelines.

**Original TrackWeight project**: https://github.com/KrishKrosh/TrackWeight  
**Created by**: Krish Shah (@KrishKrosh)

================================================
FILE: scripts/setup-signing.sh
================================================
#!/bin/bash

# setup-signing.sh
# Helper script to set up code signing certificates for TrackWeight DMG builds
# 
# This script helps you prepare the necessary secrets for GitHub Actions
# to build signed DMG files.

set -e

echo "🔐 TrackWeight Code Signing Setup"
echo "=================================="
echo ""
echo "This script helps you set up code signing for automated DMG builds."
echo "You'll need a valid Apple Developer ID Application certificate."
echo ""

# Check if we're on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
    echo "❌ This script must be run on macOS to access Keychain and signing tools."
    exit 1
fi

# Function to encode file to base64
encode_file() {
    local file_path="$1"
    if [[ -f "$file_path" ]]; then
        base64 -i "$file_path"
    else
        echo "❌ File not found: $file_path"
        return 1
    fi
}

echo "Step 1: Export your Developer ID Application certificate"
echo "--------------------------------------------------------"
echo "1. Open Keychain Access"
echo "2. Find your 'Developer ID Application' certificate"
echo "3. Right-click and select 'Export'"
echo "4. Save as .p12 format with a password"
echo ""
read -p "Enter the path to your exported .p12 certificate: " cert_path

if [[ ! -f "$cert_path" ]]; then
    echo "❌ Certificate file not found: $cert_path"
    exit 1
fi

echo ""
read -s -p "Enter the password for your .p12 certificate: " cert_password
echo ""

echo ""
echo "Step 2: Encoding certificate for GitHub Secrets"
echo "----------------------------------------------"

# Encode the certificate
echo "Encoding certificate..."
cert_base64=$(encode_file "$cert_path")

if [[ -z "$cert_base64" ]]; then
    echo "❌ Failed to encode certificate"
    exit 1
fi

echo "✅ Certificate encoded successfully"

echo ""
echo "Step 3: GitHub Repository Secrets"
echo "--------------------------------"
echo "Add these secrets to your GitHub repository:"
echo "(Go to Settings > Secrets and variables > Actions)"
echo ""
echo "1. Secret name: BUILD_CERTIFICATE_BASE64"
echo "   Value: (copy the text below)"
echo ""
echo "$cert_base64"
echo ""
echo "2. Secret name: P12_PASSWORD"
echo "   Value: $cert_password"
echo ""
echo "3. Secret name: APPLE_ID"
echo "   Value: your-apple-id@example.com"
echo ""
echo "4. Secret name: APPLE_ID_PASSWORD"
echo "   Value: (App-specific password - see instructions below)"
echo ""
echo "5. Secret name: APPLE_TEAM_ID"
echo "   Value: (Your 10-character Team ID - see instructions below)"
echo ""

# Check for provisioning profile (optional for Developer ID)
echo "Step 4: Provisioning Profile (Optional)"
echo "--------------------------------------"
read -p "Do you have a provisioning profile to include? (y/n): " include_profile

if [[ "$include_profile" =~ ^[Yy]$ ]]; then
    read -p "Enter the path to your .mobileprovision file: " profile_path
    
    if [[ -f "$profile_path" ]]; then
        profile_base64=$(encode_file "$profile_path")
        echo ""
        echo "3. Secret name: BUILD_PROVISION_PROFILE_BASE64"
        echo "   Value: (copy the text below)"
        echo ""
        echo "$profile_base64"
    else
        echo "❌ Provisioning profile not found: $profile_path"
    fi
else
    echo "📝 Skipping provisioning profile (Developer ID usually doesn't need one)"
fi

echo ""
echo "Step 5: Additional Secrets for Notarization"
echo "-------------------------------------------"
echo "For full notarization (eliminates security warnings), you'll also need:"
echo ""
echo "📧 Apple ID (APPLE_ID):"
echo "   - Use your Apple Developer account email"
echo ""
echo "🔑 App-Specific Password (APPLE_ID_PASSWORD):"
echo "   1. Go to appleid.apple.com"
echo "   2. Sign in with your Apple ID"
echo "   3. In the 'App-Specific Passwords' section, click 'Generate Password'"
echo "   4. Label it something like 'GitHub Actions Notarization'"
echo "   5. Copy the generated password (xxxx-xxxx-xxxx-xxxx)"
echo ""
echo "🏢 Team ID (APPLE_TEAM_ID):"
echo "   1. Go to developer.apple.com"
echo "   2. Sign in and go to 'Membership'"
echo "   3. Your Team ID is the 10-character string (e.g., ABC1234567)"
echo ""
echo "ℹ️  Without these notarization secrets, the app will still be signed but users"
echo "   will see security warnings when trying to run it."
echo ""
echo "🎉 Setup Complete!"
echo "================="
echo ""
echo "Next steps:"
echo "1. Add ALL the secrets to your GitHub repository"
echo "2. Create a git tag to trigger the build: git tag v1.0.0 && git push origin v1.0.0"
echo "3. Or manually trigger the workflow from the Actions tab"
echo ""
echo "The workflow will:"
echo "- Build and sign your app with the provided certificate"
echo "- Notarize the app with Apple (eliminates security warnings)"
echo "- Create a professional DMG with attribution to the original repo"
echo "- Upload the DMG as a release artifact"
echo ""
echo "Original TrackWeight project: https://github.com/KrishKrosh/TrackWeight"

================================================
FILE: scripts/test-build-locally.sh
================================================
#!/bin/bash

# test-build-locally.sh
# Test the GitHub Actions workflow steps locally on macOS

set -e

echo "🧪 Testing TrackWeight Build Workflow Locally"
echo "=============================================="

# Load environment variables from .env file
if [[ -f ".env" ]]; then
  echo "📄 Loading environment variables from .env file..."
  # Export all variables from .env file
  set -a  # automatically export all variables
  source .env
  set +a  # turn off automatic export
  echo "✅ Environment variables loaded"
else
  echo "⚠️  No .env file found - some features may not work"
fi

# Configuration
export APP_NAME="TrackWeight"
export SCHEME="TrackWeight"
export CONFIGURATION="Release"
export BUILD_DIR="$(pwd)/local_build"

# Clean up previous builds
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"

echo ""
echo "Step 1: Building and Archiving App (Universal Binary)"
echo "====================================================="
xcodebuild \
  -project TrackWeight.xcodeproj \
  -scheme "$SCHEME" \
  -configuration "$CONFIGURATION" \
  -archivePath "$BUILD_DIR/$APP_NAME.xcarchive" \
  -destination 'generic/platform=macOS' \
  ARCHS="arm64 x86_64" \
  ONLY_ACTIVE_ARCH=NO \
  archive

echo ""
echo "Step 1.5: Setting up Code Signing (if available)"
echo "================================================"
if [[ -n "$BUILD_CERTIFICATE_BASE64" && -n "$P12_PASSWORD" ]]; then
  echo "🔐 Setting up code signing certificate from .env..."
  
  # Decode and import certificate
  echo "$BUILD_CERTIFICATE_BASE64" | base64 --decode > "$BUILD_DIR/certificate.p12"
  
  # Import into keychain (temporary)
  security import "$BUILD_DIR/certificate.p12" -k ~/Library/Keychains/login.keychain-db -P "$P12_PASSWORD" -T /usr/bin/codesign
  
  echo "✅ Certificate imported successfully"
  echo "📝 Note: Certificate will remain in keychain after script completion"
else
  echo "⚠️  No certificate in .env - will use existing keychain certificates"
fi

echo ""
echo "Step 2: Exporting App"
echo "===================="
# Use existing ExportOptions.plist or create a basic one
if [[ ! -f "ExportOptions.plist" ]]; then
  echo "Creating basic ExportOptions.plist..."
  cat > "$BUILD_DIR/ExportOptions.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>developer-id</string>
    <key>destination</key>
    <string>export</string>
</dict>
</plist>
EOF
else
  cp ExportOptions.plist "$BUILD_DIR/ExportOptions.plist"
fi

        xcodebuild \
          -archivePath "$BUILD_DIR/$APP_NAME.xcarchive" \
          -exportPath "$BUILD_DIR/export" \
          -exportOptionsPlist "$BUILD_DIR/ExportOptions.plist" \
          -exportArchive
  
  # Re-sign the framework explicitly to ensure proper signature
  if [[ -n "$BUILD_CERTIFICATE_BASE64" && -n "$P12_PASSWORD" ]]; then
    echo "🔏 Re-signing framework with Developer ID certificate..."
    FRAMEWORK_PATH="$BUILD_DIR/export/$APP_NAME.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework"
    if [[ -d "$FRAMEWORK_PATH" ]]; then
      codesign --force --sign "Developer ID Application: Krish Shah (9ZRLG6277G)" \
        --options runtime \
        --timestamp \
        "$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF"
      
      codesign --force --sign "Developer ID Application: Krish Shah (9ZRLG6277G)" \
        --options runtime \
        --timestamp \
        "$FRAMEWORK_PATH"
      
      echo "✅ Framework re-signed successfully"
    fi
    
    # Re-sign the main app to ensure everything is consistent
    echo "🔏 Re-signing main application..."
    codesign --force --sign "Developer ID Application: Krish Shah (9ZRLG6277G)" \
      --options runtime \
      --entitlements "TrackWeight/TrackWeight.entitlements" \
      --timestamp \
      --deep \
      --strict \
      "$BUILD_DIR/export/$APP_NAME.app"
    
    echo "✅ Application re-signed successfully"
  fi

echo ""
echo "Step 2.5: Verifying Universal Binary and Code Signatures"
echo "========================================================"
echo "🏗️ Verifying Universal Binary Architecture..."
APP_BINARY="$BUILD_DIR/export/$APP_NAME.app/Contents/MacOS/$APP_NAME"
if [[ -f "$APP_BINARY" ]]; then
  echo "📊 Binary architectures:"
  lipo -archs "$APP_BINARY"
  
  if lipo -archs "$APP_BINARY" | grep -q "arm64" && lipo -archs "$APP_BINARY" | grep -q "x86_64"; then
    echo "✅ Universal binary confirmed: Contains both ARM64 and x86_64"
  else
    echo "❌ Warning: Binary may not be universal"
    lipo -detailed_info "$APP_BINARY"
  fi
fi

# Check framework architecture if it exists
FRAMEWORK_PATH="$BUILD_DIR/export/$APP_NAME.app/Contents/Frameworks/OpenMultitouchSupportXCF.framework"
if [[ -d "$FRAMEWORK_PATH" ]]; then
  FRAMEWORK_BINARY="$FRAMEWORK_PATH/Versions/A/OpenMultitouchSupportXCF"
  if [[ -f "$FRAMEWORK_BINARY" ]]; then
    echo "📊 Framework architectures:"
    lipo -archs "$FRAMEWORK_BINARY"
  fi
fi

echo "🔍 Verifying main application signature..."
codesign --verify --verbose "$BUILD_DIR/export/$APP_NAME.app" || echo "⚠️ Main app signature verification failed"

echo "🔍 Verifying framework signature..." 
if [[ -d "$FRAMEWORK_PATH" ]]; then
  codesign --verify --verbose "$FRAMEWORK_PATH" || echo "⚠️ Framework signature verification failed"
fi

echo "🔍 Checking for hardened runtime..."
RUNTIME_FLAGS=$(codesign --display --verbose "$BUILD_DIR/export/$APP_NAME.app" 2>&1 | grep "flags=")
if [[ "$RUNTIME_FLAGS" == *"runtime"* ]]; then
  echo "✅ Hardened runtime enabled: $RUNTIME_FLAGS"
else
  echo "⚠️ No hardened runtime detected: $RUNTIME_FLAGS"
fi

echo "🔍 Checking certificate validity..."
codesign --display --verbose=4 "$BUILD_DIR/export/$APP_NAME.app" | grep -E "(Authority|Timestamp|TeamIdentifier)" || echo "Certificate details extracted"

echo "🔍 Deep verification with online validation..."
if codesign --verify --deep --strict --verbose=2 "$BUILD_DIR/export/$APP_NAME.app"; then
  echo "✅ Deep verification passed"
else
  echo "⚠️ Deep verification failed but continuing..."
fi

echo ""
echo "Step 3: Testing Notarization (Optional)"
echo "======================================="
if [[ -n "$APPLE_ID" && -n "$APPLE_ID_PASSWORD" && -n "$APPLE_TEAM_ID" ]]; then
  echo "🍎 Starting notarization with provided credentials..."
  
  cd "$BUILD_DIR/export"
  echo "📦 Creating zip file for notarization..."
  # Use ditto for creating zip compatible with Apple's notarization service
  ditto -c -k --keepParent "$APP_NAME.app" "$APP_NAME.zip"
  
  echo "📤 Submitting for notarization..."
  if xcrun notarytool submit "$APP_NAME.zip" \
    --apple-id "$APPLE_ID" \
    --password "$APPLE_ID_PASSWORD" \
    --team-id "$APPLE_TEAM_ID" \
    --wait \
    --timeout 20m; then
    
    echo "✅ Notarization successful!"
    
    echo "📎 Stapling notarization ticket..."
    xcrun stapler staple "$APP_NAME.app"
    
    echo "✅ Verifying notarization..."
    xcrun stapler validate "$APP_NAME.app"
    
    echo "✅ Notarization complete!"
  else
    echo "❌ Notarization failed!"
    echo "🔍 This could be due to:"
    echo "   - Invalid Apple credentials"
    echo "   - App signing issues"
    echo "   - Missing hardened runtime"
    echo "   - Sandbox/entitlement issues"
    echo "   - Deprecated APIs"
    echo ""
    echo "⚠️  Continuing without notarization..."
  fi
  
  # Return to original directory
  cd "$BUILD_DIR"
else
  echo "⚠️  Skipping notarization (set APPLE_ID, APPLE_ID_PASSWORD, APPLE_TEAM_ID to test)"
fi

echo ""
echo "Step 4: Creating DMG"
echo "==================="
# Install create-dmg if not present
if ! command -v create-dmg &> /dev/null; then
  echo "Installing create-dmg..."
  brew install create-dmg
fi

# Create a clean directory with only the app for DMG creation
echo "📁 Preparing clean DMG contents..."
DMG_STAGING="$BUILD_DIR/dmg_staging"
rm -rf "$DMG_STAGING"
mkdir -p "$DMG_STAGING"

# Copy only the app to staging directory
cp -R "$BUILD_DIR/export/$APP_NAME.app" "$DMG_STAGING/"

# Create DMG with appropriate naming
if [[ -n "$APPLE_ID" && -n "$APPLE_ID_PASSWORD" && -n "$APPLE_TEAM_ID" ]]; then
  DMG_NAME="$APP_NAME-local-NOTARIZED.dmg"
else
  DMG_NAME="$APP_NAME-local-SIGNED.dmg"
fi

echo "📀 Creating professional DMG..."
create-dmg \
  --volname "$APP_NAME" \
  --window-pos 200 120 \
  --window-size 600 300 \
  --icon-size 100 \
  --icon "$APP_NAME.app" 175 120 \
  --hide-extension "$APP_NAME.app" \
  --app-drop-link 425 120 \
  --hdiutil-quiet \
  "$BUILD_DIR/$DMG_NAME" \
  "$DMG_STAGING/"

echo ""
echo "🎉 Local Build Complete!"
echo "======================="
echo "📦 DMG created: $BUILD_DIR/$DMG_NAME"
echo "📁 Build directory: $BUILD_DIR"
echo ""
if [[ -n "$APPLE_ID" && -n "$APPLE_ID_PASSWORD" && -n "$APPLE_TEAM_ID" ]]; then
  echo "✅ Notarization was attempted using credentials from .env"
else
  echo "ℹ️  To enable notarization, add these to your .env file:"
  echo "   APPLE_ID=your@email.com"
  echo "   APPLE_ID_PASSWORD=xxxx-xxxx-xxxx-xxxx"
  echo "   APPLE_TEAM_ID=ABC1234567"
fi
echo ""
echo "🔧 Cleaning up temporary files..."
rm -f "$BUILD_DIR/certificate.p12"
rm -rf "$DMG_STAGING"
echo "✅ Cleanup complete" 
Download .txt
gitextract_foro58fa/

├── .github/
│   └── workflows/
│       ├── README.md
│       └── build-and-sign-dmg.yml
├── .gitignore
├── ExportOptions.plist
├── LICENSE
├── README.md
├── TrackWeight/
│   ├── Assets.xcassets/
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── ContentView.swift
│   ├── ContentViewModel.swift
│   ├── DebugView.swift
│   ├── HomeView.swift
│   ├── Preview Content/
│   │   └── Preview Assets.xcassets/
│   │       └── Contents.json
│   ├── ScaleView.swift
│   ├── ScaleViewModel.swift
│   ├── SettingsView.swift
│   ├── TrackWeight.entitlements
│   ├── TrackWeightApp.swift
│   ├── TrackWeightView.swift
│   ├── WeighingState.swift
│   └── WeighingViewModel.swift
├── TrackWeight.xcodeproj/
│   ├── project.pbxproj
│   └── project.xcworkspace/
│       ├── contents.xcworkspacedata
│       └── xcshareddata/
│           ├── IDEWorkspaceChecks.plist
│           └── swiftpm/
│               └── Package.resolved
└── scripts/
    ├── README.md
    ├── setup-signing.sh
    └── test-build-locally.sh
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (120K chars).
[
  {
    "path": ".github/workflows/README.md",
    "chars": 4672,
    "preview": "# GitHub Actions Workflows\n\nThis directory contains GitHub Actions workflows for the TrackWeight project.\n\n## build-and-"
  },
  {
    "path": ".github/workflows/build-and-sign-dmg.yml",
    "chars": 12125,
    "preview": "name: Build and Sign DMG\n\non:\n  push:\n    tags:\n      - 'v*'\n    branches:\n      - 'copilot/fix-1'  # Enable testing on "
  },
  {
    "path": ".gitignore",
    "chars": 209,
    "preview": "# Mac\n.DS_Store\n\n# Xcode\nxcuserdata/\n*.xcuserstate\n\n# Swift Package Manager\nPackages.resolved\n.swiftpm/\n.build/\n\n# Frame"
  },
  {
    "path": "ExportOptions.plist",
    "chars": 744,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2025 Krish Shah\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 4627,
    "preview": "# TrackWeight\n\n**Turn your MacBook's trackpad into a precise digital weighing scale**\n\n[TrackWeight](\nhttps://x.com/Kris"
  },
  {
    "path": "TrackWeight/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1301,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"icon_16x16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : "
  },
  {
    "path": "TrackWeight/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "TrackWeight/ContentView.swift",
    "chars": 1243,
    "preview": "//\n//  ContentView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct ContentView: View {\n    @State private var showHomeP"
  },
  {
    "path": "TrackWeight/ContentViewModel.swift",
    "chars": 1355,
    "preview": "//\n//  ContentViewModel.swift\n//  OMSDemo\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport OpenMultitouchSupp"
  },
  {
    "path": "TrackWeight/DebugView.swift",
    "chars": 3430,
    "preview": "//\n//  DebugView.swift\n//  TrackWeight\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport OpenMultitouchSupport"
  },
  {
    "path": "TrackWeight/HomeView.swift",
    "chars": 4697,
    "preview": "//\n//  HomeView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct HomeView: View {\n    let onBegin: () -> Void\n    \n    v"
  },
  {
    "path": "TrackWeight/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "TrackWeight/ScaleView.swift",
    "chars": 12613,
    "preview": "//\n//  ScaleView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct ScaleView: View {\n    @StateObject private var viewMod"
  },
  {
    "path": "TrackWeight/ScaleViewModel.swift",
    "chars": 1614,
    "preview": "//\n//  ScaleViewModel.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\nimport Combine\n\n@MainActor\nf"
  },
  {
    "path": "TrackWeight/SettingsView.swift",
    "chars": 5371,
    "preview": "//\n//  SettingsView.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\n\nstruct SettingsView: View {\n "
  },
  {
    "path": "TrackWeight/TrackWeight.entitlements",
    "chars": 944,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "TrackWeight/TrackWeightApp.swift",
    "chars": 471,
    "preview": "//\n//  TrackWeightApp.swift\n//  TrackWeight\n//\n//  Created by Takuto Nakamura on 2024/03/02.\n//\n\nimport SwiftUI\n\n@main\ns"
  },
  {
    "path": "TrackWeight/TrackWeightView.swift",
    "chars": 11714,
    "preview": "//\n//  TrackWeightView.swift\n//  TrackWeight\n//\n\nimport SwiftUI\n\nstruct TrackWeightView: View {\n    @StateObject private"
  },
  {
    "path": "TrackWeight/WeighingState.swift",
    "chars": 215,
    "preview": "//\n//  WeighingState.swift\n//  TrackWeight\n//\n\nimport Foundation\n\nenum WeighingState: Equatable {\n    case welcome\n    c"
  },
  {
    "path": "TrackWeight/WeighingViewModel.swift",
    "chars": 8832,
    "preview": "//\n//  WeighingViewModel.swift\n//  TrackWeight\n//\n\nimport OpenMultitouchSupport\nimport SwiftUI\nimport Combine\n\n@MainActo"
  },
  {
    "path": "TrackWeight.xcodeproj/project.pbxproj",
    "chars": 18566,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 56;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "TrackWeight.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "chars": 412,
    "preview": "{\n  \"originHash\" : \"231443e4712351510ab418164c13b6d5894774039d8c118ed8208a5fc9fe884e\",\n  \"pins\" : [\n    {\n      \"identit"
  },
  {
    "path": "scripts/README.md",
    "chars": 1601,
    "preview": "# Scripts\n\nThis directory contains helper scripts for the TrackWeight project.\n\n## setup-signing.sh\n\nA helper script to "
  },
  {
    "path": "scripts/setup-signing.sh",
    "chars": 4936,
    "preview": "#!/bin/bash\n\n# setup-signing.sh\n# Helper script to set up code signing certificates for TrackWeight DMG builds\n# \n# This"
  },
  {
    "path": "scripts/test-build-locally.sh",
    "chars": 9186,
    "preview": "#!/bin/bash\n\n# test-build-locally.sh\n# Test the GitHub Actions workflow steps locally on macOS\n\nset -e\n\necho \"🧪 Testing "
  }
]

About this extraction

This page contains the full source code of the KrishKrosh/TrackWeight GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (109.8 KB), approximately 27.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!