[
  {
    "path": ".github/CODEOWNERS",
    "content": "# https://help.github.com/en/articles/about-code-owners\n\n@shapehq/devops"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐛 Bug Report\ndescription: File a bug report.\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: What happened?\n      description: Please describe the bug.\n      placeholder: Description of the bug.\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: What are the steps to reproduce?\n      description: Please describe the steps to reproduce the bug.\n      placeholder: |\n        Step 1: ...\n        Step 2: ...\n        Step 3: ...\n    validations:\n      required: true\n  - type: textarea\n    id: expected-behavior\n    attributes:\n      label: What is the expected behavior?\n      description: Please describe the behavior you expect of Spices.\n      placeholder: I expect that Spices would...\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: ✨ Feature Request\ndescription: Suggest an idea for this project\nlabels: [\"feature\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this feature request!\n  - type: textarea\n    id: problem\n    attributes:\n      label: Is your feature request related to a problem?\n      description: A clear and concise description of what the problem is.\n      placeholder: Yes, the problem is that...\n    validations:\n      required: true\n  - type: textarea\n    id: solution\n    attributes:\n      label: What solution would you like?\n      description: A clear and concise description of what you want to happen.\n      placeholder: I would like that...\n    validations:\n      required: true\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: What alternatives have you considered?\n      description: A clear and concise description of any alternative solutions or features you've considered.\n      placeholder: I have considered to...\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: Any additional context?\n      description: Add any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.yml",
    "content": "name: ❓ Question\ndescription: Ask a question about this project\nlabels: [\"question\"]\nbody:\n  - type: textarea\n    id: question\n    attributes:\n      label: Your Question\n      description: A clear and concisely formulated question.\n      placeholder: I'd like to ask...\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: Any additional context?\n      description: Add any other context or screenshots about the question request here.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n<!--- Describe your changes in detail -->\n\n## Motivation and Context\n<!--- Why is this change required? What problem does it solve? -->\n<!--- If it fixes an open issue, please link to the issue here. -->\n\n## Screenshots (if appropriate):\n\n## Types of changes\n<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\non:\n  workflow_dispatch: {}\n  pull_request:\n    types: [opened, synchronize]\npermissions: read-all\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\nenv:\n  XCODE_SCHEME: Spices\njobs:\n  build:\n    name: Build\n    runs-on: macos-15\n    strategy:\n      matrix:\n        include:\n          - xcode: 16.4\n            destination: iPhone 16 Pro\n            os: 18.5\n    env:\n      DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer  \n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Build\n        run: |\n          set -o pipefail &&\\\n          xcodebuild build\\\n            -scheme ${{ env.XCODE_SCHEME }}\\\n            -sdk iphonesimulator\\\n            -destination \"platform=iOS Simulator,name=${{ matrix.destination }},OS=${{ matrix.os }}\"\\\n          | xcbeautify --renderer github-actions\n"
  },
  {
    "path": ".github/workflows/build_documentation.yml",
    "content": "name: Build Documentation\non:\n  workflow_dispatch: {}\n  pull_request:\n    branches:\n      - main\npermissions: read-all\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\nenv:\n  XCODE_SCHEME: Spices\njobs:\n  build:\n    name: Build Documentation\n    runs-on: macos-15\n    strategy:\n      matrix:\n        include:\n          - xcode: 16.4\n    env:\n      DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Build Documentation\n        run: |\n          set -o pipefail &&\\\n          xcodebuild docbuild\\\n            -scheme ${{ env.XCODE_SCHEME }}\\\n            -destination 'generic/platform=iOS'\\\n            -derivedDataPath .derived-data\\\n          | xcbeautify --renderer github-actions\n"
  },
  {
    "path": ".github/workflows/build_example_project.yml",
    "content": "name: Build Example Project\non:\n  workflow_dispatch: {}\n  pull_request:\n    types: [opened, synchronize]\npermissions: read-all\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\nenv:\n  XCODE_PROJECT_PATH: Examples/Example.xcodeproj\n  SWIFTUI_XCODE_SCHEME: SwiftUIExample\n  UIKIT_XCODE_SCHEME: UIKitExample\njobs:\n  build_swiftui:\n    name: Build SwiftUI Example Project\n    runs-on: macos-15\n    strategy:\n      matrix:\n        include:\n          - xcode: 16.4\n            destination: iPhone 16 Pro\n            os: 18.5\n    env:\n      DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Build\n        run: |\n          set -o pipefail &&\\\n          xcodebuild build\\\n            -project ${{ env.XCODE_PROJECT_PATH }}\\\n            -scheme ${{ env.SWIFTUI_XCODE_SCHEME }}\\\n            -sdk iphonesimulator\\\n            -destination \"platform=iOS Simulator,name=${{ matrix.destination }},OS=${{ matrix.os }}\"\\\n          | xcbeautify --renderer github-actions\n  build_uikit:\n    name: Build UIKit Example Project\n    runs-on: macos-15\n    continue-on-error: true\n    strategy:\n      matrix:\n        include:\n          - xcode: 16.4\n            destination: iPhone 16 Pro\n            os: 18.5\n    env:\n      DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Build\n        run: |\n          set -o pipefail &&\\\n          xcodebuild build\\\n            -project ${{ env.XCODE_PROJECT_PATH }}\\\n            -scheme ${{ env.UIKIT_XCODE_SCHEME }}\\\n            -sdk iphonesimulator\\\n            -destination \"platform=iOS Simulator,name=${{ matrix.destination }},OS=${{ matrix.os }}\"\\\n          | xcbeautify --renderer github-actions\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: CodeQL\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n  schedule:\n    - cron: '19 15 * * 1'\npermissions: read-all\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\nenv:\n  PACKAGE_XCODE_SCHEME: Spices\n  SWIFTUI_EXAMPLE_XCODE_SCHEME: SwiftUIExample\n  UIKIT_EXAMPLE_XCODE_SCHEME: UIKitExample\n  EXAMPLE_XCODE_PROJECT_PATH: Examples/Example.xcodeproj\n  BUILD_DESTINATION: platform=iOS Simulator,name=iPhone 16,OS=18.5\n  DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-15') || 'ubuntu-latest' }}\n    permissions:\n      security-events: write\n      packages: read\n      actions: read\n      contents: read\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: actions\n          build-mode: none\n        - language: swift\n          build-mode: manual\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n    - name: Build Swift Package\n      if: matrix.build-mode == 'manual' && matrix.language == 'swift'\n      shell: bash\n      run: |\n        set -o pipefail &&\\\n        xcodebuild build-for-testing\\\n          -scheme ${{ env.PACKAGE_XCODE_SCHEME }}\\\n          -sdk iphonesimulator\\\n          -destination \"${{ env.BUILD_DESTINATION }}\"\\\n        | xcbeautify --renderer github-actions\n    - name: Build SwiftUI Example Project\n      if: matrix.build-mode == 'manual' && matrix.language == 'swift'\n      shell: bash\n      run: |\n        set -o pipefail &&\\\n        xcodebuild build\\\n          -project ${{ env.EXAMPLE_XCODE_PROJECT_PATH }}\\\n          -scheme ${{ env.SWIFTUI_EXAMPLE_XCODE_SCHEME }}\\\n          -sdk iphonesimulator\\\n          -destination \"${{ env.BUILD_DESTINATION }}\"\\\n        | xcbeautify --renderer github-actions\n    - name: Build UIKit Example Project\n      if: matrix.build-mode == 'manual' && matrix.language == 'swift'\n      shell: bash\n      run: |\n        set -o pipefail &&\\\n        xcodebuild build\\\n          -project ${{ env.EXAMPLE_XCODE_PROJECT_PATH }}\\\n          -scheme ${{ env.UIKIT_EXAMPLE_XCODE_SCHEME }}\\\n          -sdk iphonesimulator\\\n          -destination \"${{ env.BUILD_DESTINATION }}\"\\\n        | xcbeautify --renderer github-actions\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/swiftlint.yml",
    "content": "name: SwiftLint\non:\n  workflow_dispatch: {}\n  pull_request:\n    types: [opened, synchronize]\npermissions: read-all\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\njobs:\n  SwiftLint:\n    runs-on: macos-13\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Run SwiftLint\n        run: swiftlint --quiet --reporter github-actions-logging\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Run Tests\non:\n  workflow_dispatch:\n  pull_request:\n    types: [opened, synchronize]\npermissions: read-all\nenv:\n  XCODE_SCHEME: Spices\njobs:\n  run-tests:\n    name: Run Tests\n    runs-on: macos-15\n    strategy:\n      matrix:\n        include:\n          - xcode: 16.4\n            destination: iPhone 16 Pro\n            os: 18.5\n    env:\n      DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n      - name: Test\n        run: |\n          set -o pipefail &&\\\n          xcodebuild -scheme ${{ env.XCODE_SCHEME }} test\\\n            -destination \"platform=iOS Simulator,name=${{ matrix.destination }},OS=${{ matrix.os }}\"\\\n          | xcbeautify --renderer github-actions\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.swiftpm\n.build\nxcuserdata\n"
  },
  {
    "path": ".spi.yml",
    "content": "version: 1\nbuilder:\n  configs:\n    - documentation_targets: [Spices]\n      platform: ios\n"
  },
  {
    "path": ".swiftlint.yml",
    "content": "disabled_rules:\n  - nesting\nopt_in_rules:\n  - anonymous_argument_in_multiline_closure\n  - array_init\n  - collection_alignment\n  - conditional_returns_on_newline\n  - contains_over_filter_count\n  - contains_over_filter_is_empty\n  - contains_over_first_not_nil\n  - contains_over_range_nil_comparison\n  - convenience_type\n  - discarded_notification_center_observer\n  - discouraged_assert\n  - discouraged_none_name\n  - discouraged_object_literal\n  - discouraged_optional_boolean\n  - empty_collection_literal\n  - empty_count\n  - empty_string\n  - empty_xctest_method\n  - explicit_init\n  - fallthrough\n  - fatal_error_message\n  - file_name_no_space\n  - first_where\n  - flatmap_over_map_reduce\n  - identical_operands\n  - implicitly_unwrapped_optional\n  - joined_default_parameter\n  - last_where\n  - literal_expression_end_indentation\n  - lower_acl_than_parent\n  - modifier_order\n  - number_separator\n  - operator_usage_whitespace\n  - overridden_super_call\n  - pattern_matching_keywords\n  - prefer_self_in_static_references\n  - prefer_self_type_over_type_of_self\n  - prefer_zero_over_explicit_init\n  - prohibited_interface_builder\n  - prohibited_super_call\n  - reduce_into\n  - redundant_nil_coalescing\n  - redundant_type_annotation\n  - single_test_class\n  - sorted_first_last\n  - sorted_imports\n  - static_operator\n  - switch_case_on_newline\n  - test_case_accessibility\n  - toggle_bool\n  - trailing_closure\n  - unneeded_parentheses_in_closure_argument\n  - untyped_error_in_catch\n  - vertical_parameter_alignment_on_call\n  - yoda_condition\nanalyzer_rules:\n  - capture_variable\nline_length:\n  warning: 150\n  error: 175\n  ignores_comments: true\ntype_name:\n  max_length:\n    warning: 60\n    error: 60\nidentifier_name:\n  max_length:\n    warning: 60\n    error: 60\n  min_length:\n    warning: 1\n"
  },
  {
    "path": "Examples/Example.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 77;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t72FF7CD92D6602DE00483C43 /* Spices in Frameworks */ = {isa = PBXBuildFile; productRef = 72FF7CD82D6602DE00483C43 /* Spices */; };\n\t\t72FF7CDB2D6602E300483C43 /* Spices in Frameworks */ = {isa = PBXBuildFile; productRef = 72FF7CDA2D6602E300483C43 /* Spices */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t725C9D2E2D5E26F100C79FDC /* SwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t728FD6682D649EC8006B0CB2 /* UIKitExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIKitExample.app; sourceTree = BUILT_PRODUCTS_DIR; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\t728FD67B2D649EC9006B0CB2 /* Exceptions for \"UIKitExample\" folder in \"UIKitExample\" target */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tInfo.plist,\n\t\t\t);\n\t\t\ttarget = 728FD6672D649EC8006B0CB2 /* UIKitExample */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t725C9D302D5E26F100C79FDC /* SwiftUIExample */ = {\n\t\t\tisa = PBXFileSystemSynchronizedRootGroup;\n\t\t\tpath = SwiftUIExample;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t728FD6692D649EC8006B0CB2 /* UIKitExample */ = {\n\t\t\tisa = PBXFileSystemSynchronizedRootGroup;\n\t\t\texceptions = (\n\t\t\t\t728FD67B2D649EC9006B0CB2 /* Exceptions for \"UIKitExample\" folder in \"UIKitExample\" target */,\n\t\t\t);\n\t\t\tpath = UIKitExample;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t725C9D2B2D5E26F100C79FDC /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t72FF7CD92D6602DE00483C43 /* Spices in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t728FD6652D649EC8006B0CB2 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t72FF7CDB2D6602E300483C43 /* Spices in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t725C9D252D5E26F100C79FDC = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t725C9D302D5E26F100C79FDC /* SwiftUIExample */,\n\t\t\t\t728FD6692D649EC8006B0CB2 /* UIKitExample */,\n\t\t\t\t728FD69A2D64AB1D006B0CB2 /* Frameworks */,\n\t\t\t\t725C9D2F2D5E26F100C79FDC /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t725C9D2F2D5E26F100C79FDC /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t725C9D2E2D5E26F100C79FDC /* SwiftUIExample.app */,\n\t\t\t\t728FD6682D649EC8006B0CB2 /* UIKitExample.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t728FD69A2D64AB1D006B0CB2 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t725C9D2D2D5E26F100C79FDC /* SwiftUIExample */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 725C9D3C2D5E26F200C79FDC /* Build configuration list for PBXNativeTarget \"SwiftUIExample\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t725C9D2A2D5E26F100C79FDC /* Sources */,\n\t\t\t\t725C9D2B2D5E26F100C79FDC /* Frameworks */,\n\t\t\t\t725C9D2C2D5E26F100C79FDC /* Resources */,\n\t\t\t\t72FF7BB02D65B97C00483C43 /* Run SwiftLint */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t725C9D302D5E26F100C79FDC /* SwiftUIExample */,\n\t\t\t);\n\t\t\tname = SwiftUIExample;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t72FF7CD82D6602DE00483C43 /* Spices */,\n\t\t\t);\n\t\t\tproductName = Spices;\n\t\t\tproductReference = 725C9D2E2D5E26F100C79FDC /* SwiftUIExample.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t728FD6672D649EC8006B0CB2 /* UIKitExample */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 728FD67C2D649EC9006B0CB2 /* Build configuration list for PBXNativeTarget \"UIKitExample\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t728FD6642D649EC8006B0CB2 /* Sources */,\n\t\t\t\t728FD6652D649EC8006B0CB2 /* Frameworks */,\n\t\t\t\t728FD6662D649EC8006B0CB2 /* Resources */,\n\t\t\t\t72FF7BAF2D65B94F00483C43 /* Run SwiftLint */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t728FD6692D649EC8006B0CB2 /* UIKitExample */,\n\t\t\t);\n\t\t\tname = UIKitExample;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t72FF7CDA2D6602E300483C43 /* Spices */,\n\t\t\t);\n\t\t\tproductName = UIKitExample;\n\t\t\tproductReference = 728FD6682D649EC8006B0CB2 /* UIKitExample.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t725C9D262D5E26F100C79FDC /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1620;\n\t\t\t\tLastUpgradeCheck = 1620;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t725C9D2D2D5E26F100C79FDC = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.2;\n\t\t\t\t\t};\n\t\t\t\t\t728FD6672D649EC8006B0CB2 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.2;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 725C9D292D5E26F100C79FDC /* Build configuration list for PBXProject \"Example\" */;\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 725C9D252D5E26F100C79FDC;\n\t\t\tminimizedProjectReferenceProxies = 1;\n\t\t\tpackageReferences = (\n\t\t\t\t72FF7CD72D6602D600483C43 /* XCLocalSwiftPackageReference \"../../spices\" */,\n\t\t\t);\n\t\t\tpreferredProjectObjectVersion = 77;\n\t\t\tproductRefGroup = 725C9D2F2D5E26F100C79FDC /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t725C9D2D2D5E26F100C79FDC /* SwiftUIExample */,\n\t\t\t\t728FD6672D649EC8006B0CB2 /* UIKitExample */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t725C9D2C2D5E26F100C79FDC /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t728FD6662D649EC8006B0CB2 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t72FF7BAF2D65B94F00483C43 /* Run SwiftLint */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run SwiftLint\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export PATH=\\\"$PATH:/opt/homebrew/bin\\\"\\nif which swiftlint >/dev/null; then\\n  swiftlint --config ../.swiftlint.yml\\nelse\\n  echo \\\"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\\\"\\nfi\\n\";\n\t\t};\n\t\t72FF7BB02D65B97C00483C43 /* Run SwiftLint */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run SwiftLint\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"export PATH=\\\"$PATH:/opt/homebrew/bin\\\"\\nif which swiftlint >/dev/null; then\\n  swiftlint --config ../.swiftlint.yml\\nelse\\n  echo \\\"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\\\"\\nfi\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t725C9D2A2D5E26F100C79FDC /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t728FD6642D649EC8006B0CB2 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t725C9D3A2D5E26F200C79FDC /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 17.6;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t725C9D3B2D5E26F200C79FDC /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 17.6;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t725C9D3D2D5E26F200C79FDC /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"SwiftUIExample/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = 8NQFWJHC63;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = dk.shape.Spices.SwiftUIExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t725C9D3E2D5E26F200C79FDC /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"SwiftUIExample/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = 8NQFWJHC63;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = dk.shape.Spices.SwiftUIExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t728FD6792D649EC9006B0CB2 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = 8NQFWJHC63;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = UIKitExample/Info.plist;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = dk.shape.Spices.UIKitExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t728FD67A2D649EC9006B0CB2 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = 8NQFWJHC63;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = UIKitExample/Info.plist;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = dk.shape.Spices.UIKitExample;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t725C9D292D5E26F100C79FDC /* Build configuration list for PBXProject \"Example\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t725C9D3A2D5E26F200C79FDC /* Debug */,\n\t\t\t\t725C9D3B2D5E26F200C79FDC /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t725C9D3C2D5E26F200C79FDC /* Build configuration list for PBXNativeTarget \"SwiftUIExample\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t725C9D3D2D5E26F200C79FDC /* Debug */,\n\t\t\t\t725C9D3E2D5E26F200C79FDC /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t728FD67C2D649EC9006B0CB2 /* Build configuration list for PBXNativeTarget \"UIKitExample\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t728FD6792D649EC9006B0CB2 /* Debug */,\n\t\t\t\t728FD67A2D649EC9006B0CB2 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCLocalSwiftPackageReference section */\n\t\t72FF7CD72D6602D600483C43 /* XCLocalSwiftPackageReference \"../../spices\" */ = {\n\t\t\tisa = XCLocalSwiftPackageReference;\n\t\t\trelativePath = ../../spices;\n\t\t};\n/* End XCLocalSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t72FF7CD82D6602DE00483C43 /* Spices */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 72FF7CD72D6602D600483C43 /* XCLocalSwiftPackageReference \"../../spices\" */;\n\t\t\tproductName = Spices;\n\t\t};\n\t\t72FF7CDA2D6602E300483C43 /* Spices */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 72FF7CD72D6602D600483C43 /* XCLocalSwiftPackageReference \"../../spices\" */;\n\t\t\tproductName = Spices;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = 725C9D262D5E26F100C79FDC /* Project object */;\n}\n"
  },
  {
    "path": "Examples/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Examples/Example.xcodeproj/xcshareddata/xcschemes/SwiftUIExample.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1620\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\"\n      buildArchitectures = \"Automatic\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"725C9D2D2D5E26F100C79FDC\"\n               BuildableName = \"SwiftUIExample.app\"\n               BlueprintName = \"SwiftUIExample\"\n               ReferencedContainer = \"container:Example.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"725C9D2D2D5E26F100C79FDC\"\n            BuildableName = \"SwiftUIExample.app\"\n            BlueprintName = \"SwiftUIExample\"\n            ReferencedContainer = \"container:Example.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"725C9D2D2D5E26F100C79FDC\"\n            BuildableName = \"SwiftUIExample.app\"\n            BlueprintName = \"SwiftUIExample\"\n            ReferencedContainer = \"container:Example.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Examples/SwiftUIExample/AppSpiceStore.swift",
    "content": "import Foundation\nimport Spices\nimport SwiftUI\n\nenum ServiceEnvironment: String, CaseIterable {\n    case production\n    case staging\n}\n\nfinal class AppSpiceStore: SpiceStore {\n    @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n    @Spice(name: \"API URL\") var apiURL = \"http://example.com\"\n\n    @Spice(presentation: .inline) var debugging = DebuggingSpiceStore()\n\n    @Spice var featureFlags = FeatureFlagsSpiceStore()\n    @Spice(presentation: .push) var helloWorld = VStack {\n        Image(systemName: \"globe\")\n            .imageScale(.large)\n            .foregroundStyle(.tint)\n        Text(\"Hello, world!\")\n    }\n    .padding()\n    @Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n}\n\nfinal class DebuggingSpiceStore: SpiceStore {\n    @Spice var enableLogging = false\n    @Spice var clearCache = {\n        try await Task.sleep(for: .seconds(1))\n        URLCache.shared.removeAllCachedResponses()\n    }\n\n    fileprivate init() {}\n}\n\nfinal class FeatureFlagsSpiceStore: SpiceStore {\n    @Spice var notifications = false\n    @Spice var fastRefreshWidgets = false\n\n    fileprivate init() {}\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"AppIconLight.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"filename\" : \"AppIconDark.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"tinted\"\n        }\n      ],\n      \"filename\" : \"AppIconTinted.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/ContentView.swift",
    "content": "import Spices\nimport SwiftUI\n\nstruct ContentView: View {\n    @EnvironmentObject private var spiceStore: AppSpiceStore\n    @State private var isShowingEditorPopover = false\n\n    var body: some View {\n        NavigationStack {\n            Form {\n                Section {\n                    Text(\n                        \"This is an example app showcasing the Spices framework.\"\n                        + \"\\n\\n\"\n                        + \"The following illustrates how spices can be observed using SwiftUI.\"\n                    )\n                    .foregroundStyle(.secondary)\n                }\n                Section {\n                    LabeledContent(\"Environment\") {\n                        Text(String(describing: spiceStore.environment))\n                    }\n                    LabeledContent(\"API URL\") {\n                        Text(spiceStore.apiURL)\n                    }\n                }\n                Section {\n                    LabeledContent(\"Enable Logging\") {\n                        Text(spiceStore.debugging.enableLogging ? \"Yes\" : \"No\")\n                    }\n                } header: {\n                    Text(\"Debugging\")\n                }\n                Section {\n                    LabeledContent(\"Notifications\") {\n                        Text(spiceStore.featureFlags.notifications ? \"Yes\" : \"No\")\n                    }\n                    LabeledContent(\"Fast Refresh Widgets\") {\n                        Text(spiceStore.featureFlags.fastRefreshWidgets ? \"Yes\" : \"No\")\n                    }\n                } header: {\n                    Text(\"Feature Flags\")\n                } footer: {\n                    Text(\"Shake to edit spices.\")\n                        .frame(maxWidth: .infinity, alignment: .center)\n                        .padding(.top, 30)\n                }\n            }\n            .navigationTitle(\"Example\")\n            .toolbar {\n                #if os(visionOS)\n                ToolbarItem(placement: .topBarTrailing) {\n                    Button(\"Edit spices\") {\n                        isShowingEditorPopover = true\n                    }\n                    .popover(isPresented: $isShowingEditorPopover) {\n                        SpiceEditor(editing: spiceStore)\n                    }\n                }\n                #endif\n            }\n        }\n        #if DEBUG\n        .presentSpiceEditorOnShake(editing: spiceStore)\n        #endif\n    }\n}\n\n#Preview {\n    ContentView()\n        .environmentObject(AppSpiceStore())\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/ExampleApp.swift",
    "content": "import SwiftUI\n\n@main\nstruct ExampleApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n        .environmentObject(AppSpiceStore())\n    }\n}\n"
  },
  {
    "path": "Examples/SwiftUIExample/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/AppDelegate.swift",
    "content": "import UIKit\n\n@main\nfinal class AppDelegate: UIResponder, UIApplicationDelegate {\n    func application(\n        _ application: UIApplication,\n        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n    ) -> Bool {\n        true\n    }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/AppSpiceStore.swift",
    "content": "import Foundation\nimport Spices\nimport SwiftUI\n\nenum ServiceEnvironment: String, CaseIterable {\n    case production\n    case staging\n}\n\nfinal class AppSpiceStore: SpiceStore {\n    @MainActor\n    static let shared = AppSpiceStore()\n\n    @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n    @Spice(name: \"API URL\") var apiURL = \"http://example.com\"\n\n    @Spice(presentation: .inline) var debugging = DebuggingSpiceStore()\n\n    @Spice var featureFlags = FeatureFlagsSpiceStore()\n    @Spice(presentation: .push) var helloWorld = VStack {\n        Image(systemName: \"globe\")\n            .imageScale(.large)\n            .foregroundStyle(.tint)\n        Text(\"Hello, world!\")\n    }\n    .padding()\n    @Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n\n    private init() {}\n}\n\nfinal class DebuggingSpiceStore: SpiceStore {\n    @Spice var enableLogging = false\n    @Spice var clearCache = {\n        try await Task.sleep(for: .seconds(1))\n        URLCache.shared.removeAllCachedResponses()\n    }\n\n    fileprivate init() {}\n}\n\nfinal class FeatureFlagsSpiceStore: SpiceStore {\n    @Spice var notifications = false\n    @Spice var fastRefreshWidgets = false\n\n    fileprivate init() {}\n}\n"
  },
  {
    "path": "Examples/UIKitExample/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"AppIconLight.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"filename\" : \"AppIconDark.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"tinted\"\n        }\n      ],\n      \"filename\" : \"AppIconTinted.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"13122.16\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\" useSafeAreas=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"13104.12\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"375\" height=\"667\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" xcode11CocoaTouchSystemColor=\"systemBackgroundColor\" cocoaTouchSystemColor=\"whiteColor\"/>\n                        <viewLayoutGuide key=\"safeArea\" id=\"6Tk-OE-BBY\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "Examples/UIKitExample/ContentViewController.swift",
    "content": "import Combine\nimport Spices\nimport UIKit\n\nfinal class ContentViewController: UIViewController {\n    private enum Section {\n        case intro\n        case environment\n        case debugging\n        case featureFlags\n    }\n\n    fileprivate enum Item: Hashable {\n        struct TextParameters: Hashable {\n            let text: String\n        }\n\n        struct TitleValueParameters: Hashable {\n            let title: String\n            let value: String\n        }\n\n        case text(TextParameters)\n        case titleValue(TitleValueParameters)\n\n        var cellStyle: UITableViewCell.CellStyle {\n            switch self {\n            case .text:\n                .default\n            case .titleValue:\n                .value1\n            }\n        }\n\n        static func text(_ text: String) -> Self {\n            .text(.init(text: text))\n        }\n\n        static func titleValue(title: String, value: String) -> Self {\n            .titleValue(.init(title: title, value: value))\n        }\n    }\n\n    private final class DataSource: UITableViewDiffableDataSource<Section, Item> {\n        override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {\n            guard let section = sectionIdentifier(for: section) else {\n                return nil\n            }\n            switch section {\n            case .featureFlags:\n                return \"Feature Flags\"\n            case .debugging:\n                return \"Debugging\"\n            case .intro, .environment:\n                return nil\n            }\n        }\n    }\n\n    private let spiceStore = AppSpiceStore.shared\n    private var diffableDataSource: DataSource?\n    private var cancellables: Set<AnyCancellable> = []\n    private let tableView: UITableView = {\n        let this = UITableView(frame: .zero, style: .insetGrouped)\n        this.translatesAutoresizingMaskIntoConstraints = false\n        return this\n    }()\n    private lazy var editSpicesItem = UIBarButtonItem(title: \"Edit spices\", style: .plain, target: self, action: #selector(editSpicesButtonPressed))\n\n    override func loadView() {\n        view = tableView\n    }\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        title = \"Example\"\n        tableView.delegate = self\n        setupDataSource()\n        updateSnapshot()\n        observeSpices()\n        updateNavigationItem()\n    }\n}\n\nprivate extension ContentViewController {\n    private func setupDataSource() {\n        diffableDataSource = DataSource(tableView: tableView) { tableView, indexPath, item in\n            let cell = tableView.dequeueReusableCell(ofStyle: item.cellStyle, indexPath: indexPath)\n            cell.populate(with: item)\n            return cell\n        }\n        tableView.dataSource = diffableDataSource\n    }\n\n    private func updateSnapshot() {\n        let introItems: [Item] = [\n            .text(\n                \"This is an example app showcasing the Spices framework.\"\n                + \"\\n\\n\"\n                + \"The following illustrates how spices can be observed using Combine from UIKit.\"\n            )\n        ]\n        let environmentItems: [Item] = [\n            .titleValue(\n                title: \"Environment\",\n                value: String(describing: spiceStore.environment)\n            ),\n            .titleValue(\n                title: \"API URL\",\n                value: spiceStore.apiURL\n            )\n        ]\n        let debuggingItems: [Item] = [\n            .titleValue(\n                title: \"Enable Logging\",\n                value: spiceStore.debugging.enableLogging ? \"Yes\" : \"No\"\n            )\n        ]\n        let featureFlagsItems: [Item] = [\n            .titleValue(\n                title: \"Notifications\",\n                value: spiceStore.featureFlags.notifications ? \"Yes\" : \"No\"\n            ),\n            .titleValue(\n                title: \"Fast Refresh Widgets\",\n                value: spiceStore.featureFlags.fastRefreshWidgets ? \"Yes\" : \"No\"\n            )\n        ]\n        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()\n        snapshot.appendSections([.intro, .environment, .debugging, .featureFlags])\n        snapshot.appendItems(introItems, toSection: .intro)\n        snapshot.appendItems(environmentItems, toSection: .environment)\n        snapshot.appendItems(debuggingItems, toSection: .debugging)\n        snapshot.appendItems(featureFlagsItems, toSection: .featureFlags)\n        diffableDataSource?.apply(snapshot, animatingDifferences: false)\n    }\n\n    private func observeSpices() {\n        Publishers.CombineLatest4(\n            spiceStore.$environment,\n            spiceStore.debugging.$enableLogging,\n            spiceStore.$apiURL,\n            spiceStore.featureFlags.$notifications\n        )\n        .combineLatest(spiceStore.featureFlags.$fastRefreshWidgets)\n        .sink { [weak self] _, _ in\n            self?.updateSnapshot()\n        }\n        .store(in: &cancellables)\n    }\n\n    private func updateNavigationItem() {\n        #if os(visionOS)\n        navigationItem.rightBarButtonItem = editSpicesItem\n        #endif\n    }\n\n    @objc private func editSpicesButtonPressed() {\n        let editor = SpiceEditorViewController(editing: spiceStore)\n        editor.modalPresentationStyle = .popover\n        editor.popoverPresentationController?.sourceItem = editSpicesItem\n        present(editor, animated: true)\n    }\n}\n\nextension ContentViewController: UITableViewDelegate {\n    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {\n        guard let section = diffableDataSource?.sectionIdentifier(for: section) else {\n            return nil\n        }\n        guard case .featureFlags = section else {\n            return nil\n        }\n        let label = UILabel()\n        label.numberOfLines = 0\n        label.textColor = .secondaryLabel\n        label.font = .preferredFont(forTextStyle: .footnote)\n        label.textAlignment = .center\n        label.text = \"\\n\\nShake to edit spices.\"\n        return label\n    }\n}\n\nprivate extension UITableViewCell {\n    func populate(with item: ContentViewController.Item) {\n        selectionStyle = .none\n        switch item {\n        case .text(let parameters):\n            var configuration = defaultContentConfiguration()\n            configuration.text = parameters.text\n            configuration.textProperties.color = .secondaryLabel\n            contentConfiguration = configuration\n        case .titleValue(let parameters):\n            var configuration = defaultContentConfiguration()\n            configuration.text = parameters.title\n            configuration.secondaryText = parameters.value\n            contentConfiguration = configuration\n        }\n    }\n}\n\nprivate extension UITableView {\n    func dequeueReusableCell(ofStyle style: UITableViewCell.CellStyle, indexPath: IndexPath) -> UITableViewCell {\n        let reuseIdentifier = \"UITableViewCell[\\(style.rawValue)]\"\n        guard let cell = dequeueReusableCell(withIdentifier: reuseIdentifier) else {\n            return UITableViewCell(style: style, reuseIdentifier: reuseIdentifier)\n        }\n        return cell\n    }\n}\n"
  },
  {
    "path": "Examples/UIKitExample/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>UIApplicationSceneManifest</key>\n\t<dict>\n\t\t<key>UIApplicationSupportsMultipleScenes</key>\n\t\t<false/>\n\t\t<key>UISceneConfigurations</key>\n\t\t<dict>\n\t\t\t<key>UIWindowSceneSessionRoleApplication</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>UISceneConfigurationName</key>\n\t\t\t\t\t<string>Default Configuration</string>\n\t\t\t\t\t<key>UISceneDelegateClassName</key>\n\t\t\t\t\t<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t</dict>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "Examples/UIKitExample/SceneDelegate.swift",
    "content": "import Spices\nimport UIKit\n\nfinal class SceneDelegate: UIResponder, UIWindowSceneDelegate {\n    var window: UIWindow?\n\n    func scene(\n        _ scene: UIScene,\n        willConnectTo session: UISceneSession,\n        options connectionOptions: UIScene.ConnectionOptions\n    ) {\n        guard let windowScene = scene as? UIWindowScene else {\n            fatalError(\"Expected scene of type \\(UIWindowScene.self) but got \\(type(of: scene))\")\n        }\n        #if DEBUG\n        window = SpiceEditorWindow(windowScene: windowScene, editing: AppSpiceStore.shared)\n        #else\n        window = UIWindow(windowScene: windowScene)\n        #endif\n        window?.rootViewController = makeRootViewController()\n        window?.makeKeyAndVisible()\n    }\n}\n\nprivate extension SceneDelegate {\n    private func makeRootViewController() -> UINavigationController {\n        let viewController = ContentViewController()\n        let navigationController = UINavigationController(rootViewController: viewController)\n        navigationController.navigationBar.prefersLargeTitles = true\n        return navigationController\n    }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Shape ApS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version: 6.0\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"Spices\",\n    platforms: [.iOS(.v15), .visionOS(.v1)],\n    products: [\n        .library(name: \"Spices\", targets: [\"Spices\"])\n    ],\n    targets: [\n        .target(name: \"Spices\"),\n        .testTarget(name: \"SpicesTests\", dependencies: [\n            \"Spices\"\n        ])\n    ]\n)\n"
  },
  {
    "path": "README.md",
    "content": "# 🫙🌶 Spices\n\n<div align=\"center\">\n<h4>Spices makes it straightforward to create in-app debug menus by generating native UI from Swift.</h4>\n\n<img src=\"spices.png\" alt=\"Logo for Spices. A wooden shelf holds four glass jars filled with different spices. Above the shelf is a circular sign with the word 'Spices' and a red crossed-out bug.\" />\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fshapehq%2Fspices%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/shapehq/spices)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fshapehq%2Fspices%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/shapehq/spices)\\\n[![Build](https://github.com/shapehq/shpspices/actions/workflows/build.yml/badge.svg)](https://github.com/shapehq/shpspices/actions/workflows/build.yml)\n[![Build Example Project](https://github.com/shapehq/shpspices/actions/workflows/build_example_project.yml/badge.svg)](https://github.com/shapehq/shpspices/actions/workflows/build_example_project.yml)\n[![SwiftLint](https://github.com/shapehq/shpspices/actions/workflows/swiftlint.yml/badge.svg)](https://github.com/shapehq/shpspices/actions/workflows/swiftlint.yml)\\\n[![Run Tests](https://github.com/shapehq/spices/actions/workflows/test.yml/badge.svg)](https://github.com/shapehq/spices/actions/workflows/test.yml)\n[![Build Documentation](https://github.com/shapehq/spices/actions/workflows/build_documentation.yml/badge.svg)](https://github.com/shapehq/spices/actions/workflows/build_documentation.yml)\n[![CodeQL](https://github.com/shapehq/spices/actions/workflows/codeql.yml/badge.svg)](https://github.com/shapehq/spices/actions/workflows/codeql.yml)\n</div>\n\n- [👋 Introduction](#-introduction)\n- [🚀 Getting Started](#-getting-started)\n    - [Step 1: Add the Spices Swift Package](#step-1-add-the-spices-swift-package)\n    - [Step 2: Create an In-App Debug Menu](#step-2-create-an-in-app-debug-menu)\n    - [Step 3: Present the In-App Debug Menu](#step-3-present-the-in-app-debug-menu)\n    - [Step 4: Observing Values](#step-4-observing-values)\n- [🧪 Example Projects](#-example-projects)\n- [📖 Documentation](#-documentation)\n  - [Toggles](#toggles)\n  - [Pickers](#pickers)\n  - [Buttons](#buttons)\n  - [Text Fields](#text-fields)\n  - [Group Settings Using Nested Spice Stores](#group-settings-using-nested-spice-stores)\n  - [Inject Your Own Views](#inject-your-own-views)\n  - [Require Restart](#require-restart)\n  - [Display Custom Name](#display-custom-name)\n  - [Specify Editor Title](#specify-editor-title)\n  - [Store Values in Custom UserDefaults](#store-values-in-custom-userdefaults)\n  - [Store Values Under Custom Key](#store-values-under-custom-key)\n  - [Using with @AppStorage](#using-with-appstorage)\n- [🤔 Why \"Spices\"?](#-why-spices)\n\n## 👋 Introduction\n\nSpices generates native in-app debug menus from Swift code using the `@Spice` property wrapper and `SpiceStore` protocol and stores settings in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults).\n\nWe built Spices at [Shape](https://shape.dk) (becoming [Framna](https://framna.com)) to provide a frictionless API for quickly creating these menus. Common use cases include environment switching, resetting state, and enabling features during development.\n\n<img src=\"code.gif?raw=true\" />\n\n\n## 🚀 Getting Started\n\nThis section details the steps needed to add an in-app debug menu using Spices.\n\n### Step 1: Add the Spices Swift Package\n\nAdd Spices to your Xcode project or Swift package.\n\n```swift\nlet package = Package(\n    dependencies: [\n        .package(url: \"git@github.com:shapehq/spices.git\", from: \"4.0.0\")\n    ]\n)\n```\n\n### Step 2: Create an In-App Debug Menu\n\nSpices uses [reflection](https://en.wikipedia.org/wiki/Reflective_programming) to generate UI from the properties of a type conforming to the `SpiceStore` protocol\n\n> [!IMPORTANT]\n> Reflection is a technique that should be used with care. We use it in Spices, a tool meant purely for debugging, in order to make it frictionless to add a debug menu.\n\nThe following shows an example conformance to the SpiceDispenser protocol. You may copy this into your project to get started.\n\n```swift\nenum ServiceEnvironment: String, CaseIterable {\n    case production\n    case staging\n}\n\nclass AppSpiceStore: SpiceStore {\n    @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n    @Spice var enableLogging = false\n    @Spice var clearCache = {\n        try await Task.sleep(for: .seconds(1))\n        URLCache.shared.removeAllCachedResponses()\n    }\n    @Spice var featureFlags = FeatureFlagsSpiceStore()\n}\n\nclass FeatureFlagsSpiceStore: SpiceStore {\n    @Spice var notifications = false\n    @Spice var fastRefreshWidgets = false\n}\n```\n\nBased on the above code, Spices will generate an in-app debug menu like the one shown below.\n\n<img src=\"/example.gif\" width=\"300\"/>\n\n### Step 3: Present the In-App Debug Menu\n\nThe app must be configured to display the spice editor. The approach depends on whether your app is using a SwiftUI or UIKit lifecycle.\n\n> [!WARNING]\n> The in-app debug menu may contain sensitive information.  Ensure it's only accessible in debug and beta builds by excluding the menu's presentation code from release builds using conditional compilation (e.g., `#if DEBUG`).  The examples in this section demonstrate this technique.\n\n#### SwiftUI Lifecycle\n\nUse the `presentSpiceEditorOnShake(_:)` view modifier to show the editor when the device is shaken.\n\n```swift\nstruct ContentView: View {\n    @StateObject var spiceStore = AppSpiceStore()\n\n    var body: some View {\n        VStack {\n            Image(systemName: \"globe\")\n                .imageScale(.large)\n                .foregroundStyle(.tint)\n            Text(\"Hello, world!\")\n        }\n        .padding()\n        #if DEBUG\n        .presentSpiceEditorOnShake(editing: spiceStore)\n        #endif\n    }\n}\n```\n\nAlternatively, manually initialize and display an instance of `SpiceEditor`.\n\n```swift\nstruct ContentView: View {\n    @StateObject var spiceStore = AppSpiceStore()\n    @State var isSpiceEditorPresented = false\n\n    var body: some View {\n        Button {\n            isSpiceEditorPresented = true\n        } label: {\n            Text(\"Present Spice Editor\")\n        }\n        .sheet(isPresented: $isSpiceEditorPresented) {\n            SpiceEditor(editing: spiceStore)\n        }\n    }\n}\n```\n\n#### UIKit Lifecycle\n\nUse the an instance of `SpiceEditorWindow` to show the editor when the device is shaken.\n\n```swift\nclass SceneDelegate: UIResponder, UIWindowSceneDelegate {\n    var window: UIWindow?\n\n    func scene(\n        _ scene: UIScene,\n        willConnectTo session: UISceneSession,\n        options connectionOptions: UIScene.ConnectionOptions\n    ) {\n        let windowScene = scene as! UIWindowScene\n        #if DEBUG\n        window = SpiceEditorWindow(windowScene: windowScene, editing: AppSpiceStore.shared)\n        #else\n        window = UIWindow(windowScene: windowScene)\n        #endif\n        window?.rootViewController = ViewController()\n        window?.makeKeyAndVisible()\n    }\n}\n```\n\nAlternatively, initialize an instance of `SpiceEditorViewController` and present it.\n\n```swift\nlet viewController = SpiceEditorViewController(editing: AppSpiceStore.shared)\npresent(spicesViewController, animated: true)\n```\n\n### Step 4: Observing Values\n\nThe currently selected value can be referenced through a spice store:\n\n```swift\nAppSpiceStore.environment\n```\n\n#### SwiftUI Lifecycle\n\nSpice stores conforming to the `SpiceStore` protocol also conform to [ObservableObject](https://developer.apple.com/documentation/combine/observableobject), and as such, can be observed from SwiftUI using [StateObject](https://developer.apple.com/documentation/swiftui/stateobject), [ObservedObject](https://developer.apple.com/documentation/swiftui/observedobject), or [EnvironmentObject](https://developer.apple.com/documentation/swiftui/environmentobject).\n\n```swift\nclass AppSpiceStore: SpiceStore {\n    @Spice var enableLogging = false\n}\n\nstruct ContentView: View {\n    @StateObject var spiceStore = AppSpiceStore()\n    \n    var body: some View {\n        Text(\"Is logging enabled: \" + (spiceStore.enableLogging ? \"👍\" : \"👎\"))\n    }\n}\n```\n\n#### UIKit Lifecycle\n\nProperties using the `@Spice` property wrapper exposes a publisher that can be used to observe changes to the value using [Combine](https://developer.apple.com/documentation/combine).\n\n```swift\nclass ContentViewController: UIViewController {\n    private let spiceStore = AppSpiceStore.shared\n    private var cancellables: Set<AnyCancellable> = []\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        spiceStore.$enableLogging\n            .sink { isEnabled in\n                print(\"Is logging enabled: \" + (isEnabled ? \"👍\" : \"👎\"))\n            }\n            .store(in: &cancellables)\n    }\n}\n```\n\n## 🧪 Example Projects\n\nThe example projects in the [Examples](/Examples) folder shows how Spices can be used to add an in-app debug menu to iOS apps with SwiftUI and UIKit lifecycles.\n\n## 📖 Documentation\n\n<a href=\"https://swiftpackageindex.com/shapehq/spices/documentation\"><img src=\"https://swiftpackageindex.com/images/logo.svg\" width=\"40\" align=\"left\" /></a>\n<h3>The documentation is <a href=\"https://swiftpackageindex.com/shapehq/spices/documentation\">available on Swift Package Index</a>.</h3>\n<br/>\n\nThe following sections document select APIs and use cases.\n\n### Toggles\n\nToggles are created for boolean variables in a spice store.\n\n```swift\n@Spice var enableLogging = false\n```\n\n### Pickers\n\nPickers are created for types conforming to both [RawRepresentable](https://developer.apple.com/documentation/swift/rawrepresentable) and [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable). This is typically enums.\n\n```swift\nenum ServiceEnvironment: String, CaseIterable {\n    case production\n    case staging\n}\n\nclass AppSpiceStore: SpiceStore {\n    @Spice var environment: ServiceEnvironment = .production\n}\n```\n\nConforming the type to `SpicesTitleProvider` lets you override the displayed name for each case.\n\n```swift\nenum ServiceEnvironment: String, CaseIterable, SpicesTitleProvider {\n    case production\n    case staging\n\n    var spicesTitle: String {\n        switch self {\n        case .production:\n            \"🚀 Production\"\n        case .staging:\n            \"🧪 Staging\"\n        }\n    }\n}\n```\n\n### Buttons\n\nClosures with no arguments are treated as buttons.\n\n```swift\n@Spice var clearCache = {\n    URLCache.shared.removeAllCachedResponses()\n}\n```\n\nProviding an asynchronous closure causes a loading indicator to be displayed for the duration of the operation.\n\n```swift\n@Spice var clearCache = {\n    try await Task.sleep(for: .seconds(1))\n    URLCache.shared.removeAllCachedResponses()\n}\n```\n\nAn error message is automatically shown if the closure throws an error.\n\n### Text Fields\n\nText fields are created for string variables in a spice store.\n\n```swift\n@Spice var url = \"http://example.com\"\n```\n\n### Group Settings Using Nested Spice Stores\n\nSpice stores can be nested to create a hierarchical user interface.\n\n```swift\nclass AppSpiceStore: SpiceStore {\n    @Spice var featureFlags = FeatureFlagsSpiceStore()\n}\n\nclass FeatureFlagsSpiceStore: SpiceStore {\n    @Spice var notifications = false\n    @Spice var fastRefreshWidgets = false\n}\n```\n\nBy default, a nested spice store is presented as a new screen in the navigation stack. This behavior is equivalent to:\n\n```swift\n@Spice(presentation: .push) var featureFlags = FeatureFlagsSpiceStore()\n```\n\nA nested spice store can also be presented as a modal instead of being pushed onto the navigation stack:\n\n```swift\n@Spice(presentation: .modal) var featureFlags = FeatureFlagsSpiceStore()\n```\n\nAlternatively, it can be displayed as an inlined section within the settings list:\n\n```swift\n@Spice(presentation: .inline) var featureFlags = FeatureFlagsSpiceStore()\n```\n\nWhen inlining a nested spice store, a header and footer can be provided for better context:\n\n```swift\n@Spice(\n  presentation: .push,\n  header: \"Features\",\n  footer: \"Test features that are yet to be released.\"\n)\nvar featureFlags = FeatureFlagsSpiceStore()\n```\n\n### Inject Your Own Views\n\nYou can embed your own views into Spices, for example, to display static information.\n\nThe `@Spice` property wrapper allows you to define custom views within Spices settings. These views can be inlined by default or presented using different styles.\n\nBy default, views are inlined within the settings list:\n\n```swift\n@Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n```\n\nYou can change the presentation style using the presentation argument.\n\nThe `.push` presentation pushes the view onto the navigation stack.\n\n```swift\n@Spice(presentation: .push) var helloWorld = VStack {\n    Image(systemName: \"globe\")\n        .imageScale(.large)\n        .foregroundStyle(.tint)\n    Text(\"Hello, world!\")\n}\n.padding()\n```\n\nThe `.modal` presentation presents the view modally on top of Spices.\n\n```swift\n@Spice(presentation: .modal) var helloWorld = // ...\n```\n\n### Require Restart\n\nSetting `requiresRestart` to true will cause the app to be shut down after changing the value. Use this only when necessary, as users do not expect a restart.\n\n```swift\n@Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n```\n\n### Display Custom Name\n\nBy default, the editor displays a formatted version of the property name. You can override this by manually specifying a custom name.\n\n```swift\n@Spice(name: \"Debug Logging\") var enableLogging = false\n```\n\n### Specify Editor Title\n\nBy default the editor will be displayed with the title \"Debug Menu\". This can be customized as follows.\n\n**SwiftUI Lifecycle**\n\nThe `presentSpiceEditorOnShake(editing:title:)` view modifier takes a title as follows.\n\n```swift\n.presentSpiceEditorOnShake(editing: spiceStore, title: \"Config\")\n```\n\nThe title can also be specified when manually creating and presenting an instance of `SpiceEditor`.\n\n```swift\nSpiceEditor(editing: spiceStore, title: \"Config\")\n```\n\n**UIKit Lifecycle**\n\nThe ``SpiceEditorWindow`` can be initialized with a title as follows.\n\n```swift\nSpiceEditorWindow(windowScene: windowScene, editing: AppSpiceStore.shared, title: \"Config\")\n```\n\nThe title can also be specified when manually creating and presenting an instance of `SpiceEditorViewController`.\n\n```swift\nlet viewController = SpiceEditorViewController(editing: AppSpiceStore.shared, title: \"Config\")\n```\n\n### Store Values in Custom UserDefaults\n\nBy default, values are stored in [UserDefaults.standard](https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard). To use a different [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) instance, such as for sharing data with an app group, implement the `userDefaults` property of `SpiceStore`.\n\n```swift\nclass AppSpiceStore: SpiceStore {\n    let userDefaults = UserDefaults(suiteName: \"group.dk.shape.example\")\n}\n```\n\n### Store Values Under Custom Key\n\nValues are stored in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) using a key derived from the property name, optionally prefixed with the names of nested spice stores. You can override this by specifying a custom key.\n\n```swift\n@Spice(key: \"env\") var environment: ServiceEnvironment = .production\n```\n\n### Using with @AppStorage\n\nValues are stored in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) and can be used with [@AppStorage](https://developer.apple.com/documentation/swiftui/appstorage) for seamless integration in SwiftUI.\n\n```swift\nstruct ExampleView: View {\n    @AppStorage(\"enableLogging\") var enableLogging = false\n\n    var body: some View {\n        Toggle(isOn: $enableLogging) {\n            Text(\"Enable Logging\")\n        }\n    }\n}\n```\n\n## 🤔 Why \"Spices\"?\n\nThe name \"Spices\" evolved from our original repository, \"ConfigVars\", which we used internally at [Shape](https://shape.dk) (becoming [Framna](https://framna.com)) for several years. That early version didn’t use reflection, but when we experimented with a new implementation that did, we jokingly called it \"spicing it up.\" The idea stuck, and we realized developers could also use the package to \"spice up\" their own projects, adding extra debugging \"spices\" as needed.\n\nFor the first few years, the project was called \"Config Vars\", but we never really loved that name. It felt too generic. When we decided to open-source the package, we considered reverting to the original name or using other generic alternatives like \"configs,\" \"variables,\" \"tweaks,\" or \"configuration variables.\"\n\nHowever, these terms are so widely used and have so many different meanings that we worried about causing naming conflicts in developers' codebases. Ultimately, we stuck with \"Spices\" because it’s unique, memorable, and less likely to clash with existing concepts.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease contact us at security@shape.dk if you find a security vulnerability in the software. Do note that we do not offer a bug bounty program.\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/AsyncButtonMenuItem.swift",
    "content": "import Foundation\n\nstruct AsyncButtonMenuItem: MenuItem {\n    let id = UUID().uuidString\n    let name: Name\n    let requiresRestart: Bool\n    let storage: AnyStorage<Spice.AsyncButtonHandler>\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/ButtonMenuItem.swift",
    "content": "import Foundation\n\nstruct ButtonMenuItem: MenuItem {\n    let id = UUID().uuidString\n    let name: Name\n    let requiresRestart: Bool\n    let storage: AnyStorage<Spice.ButtonHandler>\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/ChildSpiceStoreMenuItem.swift",
    "content": "import Foundation\n\nstruct ChildSpiceStoreMenuItem: MenuItem {\n    enum PresentationStyle {\n        case modal\n        case push\n        case inline(header: String?, footer: String?)\n    }\n\n    let id = UUID().uuidString\n    let name: Name\n    let presentationStyle: PresentationStyle\n    let spiceStore: any SpiceStore\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/MenuItem.swift",
    "content": "protocol MenuItem {\n    var id: String { get }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/MenuItemProvider.swift",
    "content": "protocol MenuItemProvider {\n    var menuItem: any MenuItem { get }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/PickerMenuItem.swift",
    "content": "import Combine\nimport Foundation\n\nfinal class PickerMenuItem: MenuItem, ObservableObject {\n    struct Option: Hashable, Identifiable {\n        let id: String\n        let title: String\n\n        fileprivate let write: () -> Void\n\n        static var unsupported: Self {\n            Self(id: \"__spices_unsupported\", title: \"<unsupported>\") {}\n        }\n\n        static func == (lhs: PickerMenuItem.Option, rhs: PickerMenuItem.Option) -> Bool {\n            lhs.id == rhs.id && lhs.title == rhs.title\n        }\n\n        func hash(into hasher: inout Hasher) {\n            hasher.combine(id)\n            hasher.combine(title)\n        }\n    }\n\n    let id = UUID().uuidString\n    let name: Name\n    let requiresRestart: Bool\n    @Published var options: [Option]\n    @Published var selection: Option {\n        didSet {\n            selection.write()\n        }\n    }\n\n    private var cancellables: Set<AnyCancellable> = []\n\n    init<Value: RawRepresentable & CaseIterable>(\n        name: Name,\n        storage: AnyStorage<Value>,\n        requiresRestart: Bool\n    ) {\n        self.name = name\n        self.requiresRestart = requiresRestart\n        let (options, selection) = Self.options(from: storage)\n        self.options = options\n        self.selection = selection\n        observeValue(in: storage)\n    }\n}\n\nprivate extension PickerMenuItem {\n    private static func options<Value: CaseIterable & RawRepresentable>(\n        from storage: AnyStorage<Value>\n    ) -> (options: [Option], selection: Option) {\n        var options = Value.allCases.map { Option($0, writingTo: storage) }\n        let selection: Option\n        if let selectedOption = options.first(where: { $0.id == storage.value.optionId }) {\n            selection = selectedOption\n        } else {\n            selection = .unsupported\n            options.insert(.unsupported, at: 0)\n        }\n        return (options, selection)\n    }\n\n    private func observeValue<Value: CaseIterable & RawRepresentable>(in storage: AnyStorage<Value>) {\n        storage.publisher.sink { [weak self] newValue in\n            guard newValue.optionId != self?.selection.id else {\n                return\n            }\n            guard let newSelection = self?.options.first(where: { $0.id == newValue.optionId }) else {\n                return\n            }\n            self?.selection = newSelection\n        }\n        .store(in: &cancellables)\n    }\n}\n\nprivate extension PickerMenuItem.Option {\n    init<Value: RawRepresentable & CaseIterable>(_ value: Value, writingTo storage: AnyStorage<Value>) {\n        let title = if let spicesTitleProvider = value as? SpicesTitleProvider {\n            spicesTitleProvider.spicesTitle\n        } else {\n            String(describing: value).camelCaseToNaturalText()\n        }\n        self.init(id: value.optionId, title: title) {\n            storage.value = value\n        }\n    }\n}\n\nprivate extension CaseIterable where Self: RawRepresentable {\n    var optionId: String {\n        String(describing: self)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/TextFieldMenuItem.swift",
    "content": "import Combine\nimport Foundation\n\nfinal class TextFieldMenuItem: MenuItem, ObservableObject {\n    let id = UUID().uuidString\n    let name: Name\n    let requiresRestart: Bool\n    @Published var value: String {\n        didSet {\n            if value != storage.value {\n                storage.value = value\n            }\n        }\n    }\n\n    private let storage: AnyStorage<String>\n    private var cancellables: Set<AnyCancellable> = []\n\n    init(name: Name, requiresRestart: Bool, storage: AnyStorage<String>) {\n        self.name = name\n        self.requiresRestart = requiresRestart\n        self.storage = storage\n        self.value = storage.value\n        storage.publisher.sink { [weak self] newValue in\n            if newValue != self?.value {\n                self?.value = newValue\n            }\n        }\n        .store(in: &cancellables)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/ToggleMenuItem.swift",
    "content": "import Combine\nimport Foundation\n\nfinal class ToggleMenuItem: MenuItem, ObservableObject {\n    let id = UUID().uuidString\n    let name: Name\n    let requiresRestart: Bool\n    @Published var value: Bool {\n        didSet {\n            if value != storage.value {\n                storage.value = value\n            }\n        }\n    }\n\n    private let storage: AnyStorage<Bool>\n    private var cancellables: Set<AnyCancellable> = []\n\n    init(name: Name, requiresRestart: Bool, storage: AnyStorage<Bool>) {\n        self.name = name\n        self.requiresRestart = requiresRestart\n        self.storage = storage\n        self.value = storage.value\n        storage.publisher.sink { [weak self] newValue in\n            if newValue != self?.value {\n                self?.value = newValue\n            }\n        }\n        .store(in: &cancellables)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/MenuItems/ViewMenuItem.swift",
    "content": "import Foundation\nimport SwiftUI\n\nfinal class ViewMenuItem: MenuItem {\n    enum PresentationStyle {\n        case modal\n        case push\n        case inline\n    }\n\n    let id = UUID().uuidString\n    let name: Name\n    let presentationStyle: PresentationStyle\n    let content: AnyView\n\n    init(name: Name, presentationStyle: PresentationStyle, content: AnyView) {\n        self.name = name\n        self.presentationStyle = presentationStyle\n        self.content = content\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Name.swift",
    "content": "final class Name {\n    var rawValue: String {\n        get {\n            explicitValue ?? _value ?? \"<name unavailable>\"\n        }\n        set {\n            _value = newValue\n        }\n    }\n\n    private let explicitValue: String?\n    private var _value: String?\n\n    init(_ value: String? = nil) {\n        explicitValue = value\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Preparable.swift",
    "content": "protocol Preparable {\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore)\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Storage/AnyStorage.swift",
    "content": "import Combine\n\nfinal class AnyStorage<Value>: ObservableObject {\n    private(set) var isPrepared = false\n    let publisher: AnyPublisher<Value, Never>\n    var value: Value {\n        get {\n            read()\n        }\n        set {\n            objectWillChange.send()\n            write(newValue)\n        }\n    }\n\n    private let read: () -> Value\n    private let write: (Value) -> Void\n    private let prepare: (String, any SpiceStore) -> Void\n\n    init<S: Storage>(_ storage: S) where S.Value == Value {\n        read = { storage.value }\n        write = { storage.value = $0 }\n        prepare = { storage.prepare(propertyName: $0, ownedBy: $1) }\n        publisher = storage.publisher\n    }\n}\n\nextension AnyStorage: Preparable {\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore) {\n        prepare(propertyName, spiceStore)\n        isPrepared = true\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Storage/Storage.swift",
    "content": "import Combine\n\nprotocol Storage: AnyObject, Preparable {\n    associatedtype Value\n    var value: Value { get set }\n    var publisher: AnyPublisher<Value, Never> { get }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Storage/ThrowingStorage.swift",
    "content": "import Combine\n\nfinal class ThrowingStorage<Value>: Storage {\n    var value: Value {\n        get {\n            initialValue\n        }\n        // swiftlint:disable:next unused_setter_value\n        set {\n            fatalError(setterMessage)\n        }\n    }\n\n    let publisher: AnyPublisher<Value, Never>\n\n    private let initialValue: Value\n    private let setterMessage: String\n    private let passthroughSubject = PassthroughSubject<Value, Never>()\n\n    init(default initialValue: Value, setterMessage: String) {\n        self.initialValue = initialValue\n        self.setterMessage = setterMessage\n        self.publisher = passthroughSubject.eraseToAnyPublisher()\n    }\n\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore) {}\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Storage/UserDefaultsStorage.swift",
    "content": "import Combine\nimport Foundation\n\nfinal class UserDefaultsStorage<Value>: Storage {\n    var value: Value {\n        get {\n            backingValue\n        }\n        set {\n            if !isValuesEqual(newValue, backingValue) {\n                write?(newValue)\n                spiceStoreOrThrow.publishObjectWillChange()\n                backingValue = newValue\n                subject.send(newValue)\n            }\n        }\n    }\n    var publisher: AnyPublisher<Value, Never> {\n        subject.eraseToAnyPublisher()\n    }\n\n    private let subject: CurrentValueSubject<Value, Never>\n    private let preferredKey: String?\n    private var read: (() -> Value)?\n    private var write: ((Value) -> Void)?\n    private var key: String {\n        preferredKey ?? spiceStoreOrThrow.key(fromPropertyNamed: propertyNameOrThrow)\n    }\n    private var userDefaults: UserDefaults {\n        spiceStoreOrThrow.userDefaults\n    }\n    private var propertyName: String?\n    private var propertyNameOrThrow: String {\n        guard let propertyName else {\n            fatalError(\"\\(type(of: self)) cannot be used without a spice name\")\n        }\n        return propertyName\n    }\n    private weak var spiceStore: (any SpiceStore)?\n    private var spiceStoreOrThrow: any SpiceStore {\n        guard let spiceStore else {\n            fatalError(\"\\(type(of: self)) cannot be used without a reference to a spice store\")\n        }\n        return spiceStore\n    }\n    private var backingValue: Value\n    private let isValuesEqual: (Value, Value) -> Bool\n    private var cancellables: Set<AnyCancellable> = []\n\n    init(default value: Value, key: String?) where Value: Equatable {\n        backingValue = value\n        preferredKey = key\n        subject = CurrentValueSubject(value)\n        isValuesEqual = { $0 == $1 }\n        read = { [weak self] in\n            guard let self else {\n                return value\n            }\n            return self.userDefaults.object(forKey: self.key) as? Value ?? value\n        }\n        write = { [weak self] newValue in\n            guard let self else {\n                return\n            }\n            self.userDefaults.setValue(newValue, forKey: self.key)\n        }\n    }\n\n    init(default value: Value, key: String?) where Value: RawRepresentable, Value.RawValue: Equatable {\n        backingValue = value\n        preferredKey = key\n        subject = CurrentValueSubject(value)\n        isValuesEqual = { $0.rawValue == $1.rawValue }\n        read = { [weak self] in\n            guard let self, let rawValue = self.userDefaults.object(forKey: self.key) as? Value.RawValue else {\n                return value\n            }\n            return Value(rawValue: rawValue) ?? value\n        }\n        write = { [weak self] newValue in\n            guard let self else {\n                return\n            }\n            self.userDefaults.setValue(newValue.rawValue, forKey: self.key)\n        }\n    }\n}\n\nprivate extension UserDefaultsStorage {\n    private func observeUserDefaults() {\n        NotificationCenter.default\n            .publisher(for: UserDefaults.didChangeNotification)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                guard let self, let read = self.read else {\n                    return\n                }\n                let value = read()\n                guard !self.isValuesEqual(value, backingValue) else {\n                    return\n                }\n                self.spiceStoreOrThrow.publishObjectWillChange()\n                self.backingValue = value\n                self.subject.send(value)\n            }\n            .store(in: &cancellables)\n    }\n}\n\nextension UserDefaultsStorage: Preparable {\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore) {\n        self.propertyName = propertyName\n        self.spiceStore = spiceStore\n        if let read {\n            let value = read()\n            if !isValuesEqual(value, backingValue) {\n                backingValue = value\n                subject.send(value)\n            }\n        }\n        observeUserDefaults()\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/String+Helpers.swift",
    "content": "import Foundation\n\nextension String {\n    func removing(prefix: String) -> Self {\n        guard hasPrefix(prefix) else {\n            return self\n        }\n        return String(dropFirst(prefix.count))\n    }\n\n    /// Split into camel case words, preserving initialisms like URL and HTTP.\n    func camelCaseToNaturalText() -> String {\n        var pieces = [String]()\n        var currentPiece = \"\"\n\n        for (idx, character) in zip(indices, self) {\n            if idx == startIndex {\n                currentPiece += character.uppercased()\n            } else if character.isUppercase {\n                // Small check: recognize runs of multiple uppercase letters and\n                // consider them part of the same word until the start of the next word.\n                let previousIndex = index(before: idx)\n                let nextIndex = index(after: idx)\n                let previous = self[previousIndex]\n                let next = nextIndex < endIndex ? self[nextIndex] : nil\n                if previous.isUppercase && next?.isLowercase == true {\n                    // Previous word was an initialism, and this uppercase letter starts a new word\n                    // containing the next character.\n                    pieces.append(currentPiece)\n                    currentPiece = String(character)\n                } else if previous.isUppercase {\n                    // Continue the initialism.\n                    currentPiece.append(character)\n                } else {\n                    // Previous word was not an initialism, start a new word.\n                    pieces.append(currentPiece)\n                    currentPiece = character.uppercased()\n                }\n            } else {\n                currentPiece.append(character)\n            }\n        }\n\n        // Commit the last bit of the phrase.\n        if !currentPiece.isEmpty {\n            pieces.append(currentPiece)\n        }\n\n        return pieces.joined(separator: \" \")\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/UIApplication+Helpers.swift",
    "content": "#if canImport(UIKit)\nimport UIKit\n\nextension UIApplication {\n    func shp_restart() {\n        let topViewController = shp_activeWindow?.rootViewController?.shp_topViewController\n        let alertController = UIAlertController(\n            title: \"Restart Required\",\n            message: \"Shutting down app...\",\n            preferredStyle: .alert\n        )\n        topViewController?.present(alertController, animated: true)\n        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n            self.performSelector(\n                onMainThread: NSSelectorFromString(\"su\" + \"sp\" + \"end\"),\n                with: nil,\n                waitUntilDone: true\n            )\n            Thread.sleep(forTimeInterval: 0.2)\n            exit(0)\n        }\n    }\n\n    // swiftlint:disable:next identifier_name\n    var shp_activeWindow: UIWindow? {\n        guard let preferredScene = shp_preferredScene else {\n            return nil\n        }\n        return preferredScene.windows.first { $0.isKeyWindow } ?? preferredScene.windows.first\n    }\n}\n\nprivate extension UIApplication {\n    // swiftlint:disable:next identifier_name\n    private var shp_preferredScene: UIWindowScene? {\n        let windowScenes = connectedScenes.compactMap { $0 as? UIWindowScene }\n        if let scene = windowScenes.first(where: { $0.activationState == .foregroundActive }) {\n            return scene\n        } else {\n            return windowScenes.first\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/Spices/Internal/UIViewController+Helpers.swift",
    "content": "#if canImport(UIKit)\nimport UIKit\n\nextension UIViewController {\n    // swiftlint:disable:next identifier_name\n    var shp_topViewController: UIViewController {\n        var topViewController = self\n        while let presentedViewController = topViewController.presentedViewController {\n            topViewController = presentedViewController\n        }\n        return topViewController\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/Spices/Internal/View+RestartOnChange.swift",
    "content": "#if canImport(UIKit)\nimport SwiftUI\n\npublic extension View {\n    func restartApp(_ isActive: Binding<Bool>) -> some View {\n        modifier(RestartOnChangeViewModifier(isActive: isActive))\n    }\n}\n\nprivate struct RestartOnChangeViewModifier: ViewModifier {\n    @Binding var isActive: Bool\n\n    func body(content: Content) -> some View {\n        content.onChange(of: isActive) { newValue in\n            if newValue {\n                UIApplication.shared.shp_restart()\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/AsyncButtonMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct AsyncButtonMenuItemView: View {\n    let menuItem: AsyncButtonMenuItem\n\n    @EnvironmentObject private var userInteraction: UserInteraction\n    @State private var isLoading = false\n    @State private var isErrorPresented = false\n    @State private var error: Error?\n\n    var body: some View {\n        Button {\n            Task {\n                defer {\n                    userInteraction.isEnabled = true\n                    isLoading = false\n                }\n                do {\n                    userInteraction.isEnabled = false\n                    isLoading = true\n                    try await menuItem.storage.value()\n                    if menuItem.requiresRestart {\n                        UIApplication.shared.shp_restart()\n                    }\n                } catch {\n                    self.error = error\n                    isErrorPresented = true\n                }\n            }\n        } label: {\n            HStack {\n                Text(menuItem.name.rawValue)\n                Spacer()\n                ProgressView()\n                    .progressViewStyle(.circular)\n                    .opacity(isLoading ? 1 : 0)\n            }\n        }\n        .errorAlert(isPresented: $isErrorPresented, showing: error)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/ButtonMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct ButtonMenuItemView: View {\n    let menuItem: ButtonMenuItem\n\n    @State private var isErrorPresented = false\n    @State private var error: Error?\n\n    var body: some View {\n        Button {\n            do {\n                try menuItem.storage.value()\n                if menuItem.requiresRestart {\n                    UIApplication.shared.shp_restart()\n                }\n            } catch {\n                self.error = error\n                self.isErrorPresented = true\n            }\n        } label: {\n            Text(menuItem.name.rawValue)\n        }\n        .errorAlert(isPresented: $isErrorPresented, showing: error)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/ChildSpiceStoreMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct ChildSpiceStoreMenuItemView: View {\n    let menuItem: ChildSpiceStoreMenuItem\n    let dismiss: () -> Void\n\n    var body: some View {\n        switch menuItem.presentationStyle {\n        case .modal:\n            ModalPresentationView(menuItem: menuItem)\n        case .push:\n            NavigationLink {\n                ChildMenuItemListView(menuItem: menuItem, dismiss: dismiss)\n            } label: {\n                Text(menuItem.name.rawValue)\n            }\n        case let .inline(header, footer):\n            Section {\n                MenuItemListContent(\n                    menuItems: menuItem.spiceStore.menuItems,\n                    dismiss: dismiss\n                )\n            } header: {\n                if let header {\n                    Text(header)\n                }\n            } footer: {\n                if let footer {\n                    Text(footer)\n                }\n            }\n        }\n    }\n}\n\nprivate extension ChildSpiceStoreMenuItemView {\n    struct ChildMenuItemListView: View {\n        let menuItem: ChildSpiceStoreMenuItem\n        let dismiss: () -> Void\n\n        var body: some View {\n            MenuItemListView(\n                items: menuItem.spiceStore.menuItems,\n                title: menuItem.name.rawValue,\n                dismiss: dismiss\n            )\n            .navigationBarTitleDisplayMode(.inline)\n        }\n    }\n}\n\nprivate extension ChildSpiceStoreMenuItemView {\n    struct ModalPresentationView: View {\n        let menuItem: ChildSpiceStoreMenuItem\n\n        @State private var isModalPresented = false\n\n        var body: some View {\n            Button {\n                isModalPresented = true\n            } label: {\n                Text(menuItem.name.rawValue)\n            }\n            .sheet(isPresented: $isModalPresented) {\n                NavigationView {\n                    ChildMenuItemListView(menuItem: menuItem) {\n                        isModalPresented = false\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/MenuItemListContent.swift",
    "content": "import SwiftUI\n\nstruct MenuItemListContent: View {\n    let menuItems: [MenuItem]\n    let dismiss: () -> Void\n\n    var body: some View {\n        ForEach(menuItems, id: \\.id) { menuItem in\n            MenuItemView(menuItem: menuItem, dismiss: dismiss)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/MenuItemListView.swift",
    "content": "import SwiftUI\n\nstruct MenuItemListView: View {\n    @EnvironmentObject private var userInteraction: UserInteraction\n    private let title: String\n    private let menuItems: [MenuItem]\n    private let dismiss: () -> Void\n\n    init(items menuItems: [MenuItem], title: String, dismiss: @escaping () -> Void) {\n        self.title = title\n        self.menuItems = menuItems\n        self.dismiss = dismiss\n    }\n\n    var body: some View {\n        Form {\n            MenuItemListContent(menuItems: menuItems, dismiss: dismiss)\n        }\n        .disabled(!userInteraction.isEnabled)\n        .navigationTitle(title)\n        .toolbar {\n            ToolbarItem(placement: .primaryAction) {\n                Button {\n                    dismiss()\n                } label: {\n                    Text(\"Done\").fontWeight(.bold)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/MenuItemView.swift",
    "content": "import SwiftUI\n\nstruct MenuItemView: View {\n    let menuItem: any MenuItem\n    let dismiss: () -> Void\n\n    var body: some View {\n        if let menuItem = menuItem as? ToggleMenuItem {\n            ToggleMenuItemView(menuItem: menuItem)\n        } else if let menuItem = menuItem as? PickerMenuItem {\n            PickerMenuItemView(menuItem: menuItem)\n        } else if let menuItem = menuItem as? TextFieldMenuItem {\n            TextFieldMenuItemView(menuItem: menuItem)\n        } else if let menuItem = menuItem as? ButtonMenuItem {\n            ButtonMenuItemView(menuItem: menuItem)\n        } else if let menuItem = menuItem as? AsyncButtonMenuItem {\n            AsyncButtonMenuItemView(menuItem: menuItem)\n        } else if let menuItem = menuItem as? ChildSpiceStoreMenuItem {\n            ChildSpiceStoreMenuItemView(menuItem: menuItem, dismiss: dismiss)\n        } else if let menuItem = menuItem as? ViewMenuItem {\n            ViewMenuItemView(menuItem: menuItem)\n        } else {\n            fatalError(\"Unknown menu item of type \\(type(of: menuItem))\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/PickerMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct PickerMenuItemView: View {\n    @ObservedObject private var menuItem: PickerMenuItem\n    @State private var selection: PickerMenuItem.Option\n    @State private var restartApp = false\n\n    init(menuItem: PickerMenuItem) {\n        self.menuItem = menuItem\n        self.selection = menuItem.selection\n    }\n\n    var body: some View {\n        Picker(selection: $selection) {\n            ForEach(menuItem.options) { option in\n                Text(option.title)\n                    .tag(option)\n            }\n        } label: {\n            Text(menuItem.name.rawValue)\n        }\n        .onChange(of: selection) { newValue in\n            if newValue != menuItem.selection {\n                menuItem.selection = newValue\n                restartApp = menuItem.requiresRestart\n            }\n        }\n        .onChange(of: menuItem.selection) { newValue in\n            if newValue != selection {\n                selection = newValue\n            }\n        }\n        .restartApp($restartApp)\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/TextFieldMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct TextFieldMenuItemView: View {\n    @ObservedObject var menuItem: TextFieldMenuItem\n\n    @State private var editingValue: String = \"\"\n    @State private var restartApp = false\n\n    var body: some View {\n        GeometryReader { proxy in\n            HStack {\n                Text(menuItem.name.rawValue)\n                Spacer()\n                TextField(\"Value\", text: $editingValue)\n                    .multilineTextAlignment(.trailing)\n                    .submitLabel(.done)\n                    .frame(minWidth: proxy.size.width / 2)\n            }\n            .frame(height: proxy.size.height, alignment: .center)\n        }\n        .onSubmit {\n            menuItem.value = editingValue\n            restartApp = menuItem.requiresRestart\n        }\n        .restartApp($restartApp)\n        .onAppear {\n            editingValue = menuItem.value\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/ToggleMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct ToggleMenuItemView: View {\n    @ObservedObject var menuItem: ToggleMenuItem\n\n    @State private var isOn = false\n    @State private var restartApp = false\n\n    var body: some View {\n        Toggle(isOn: $isOn) {\n            Text(menuItem.name.rawValue)\n        }\n        .onChange(of: isOn) { newValue in\n            if newValue != menuItem.value {\n                menuItem.value = newValue\n                restartApp = menuItem.requiresRestart\n            }\n        }\n        .onChange(of: menuItem.value) { newValue in\n            if newValue != isOn {\n                isOn = newValue\n            }\n        }\n        .restartApp($restartApp)\n        .onAppear {\n            isOn = menuItem.value\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/UserInteraction.swift",
    "content": "import SwiftUI\n\nfinal class UserInteraction: ObservableObject {\n    @Published var isEnabled = true\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/View+ConfigureSheetPresentation.swift",
    "content": "import SwiftUI\n\n@available(iOS 16, *)\nprivate struct ConfigureSheetPresentationViewModifier: ViewModifier {\n    func body(content: Content) -> some View {\n        content.presentationDetents([.medium, .large])\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func configureSheetPresentation() -> some View {\n        if #available(iOS 16, *) {\n            modifier(ConfigureSheetPresentationViewModifier())\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/View+ErrorAlert.swift",
    "content": "import SwiftUI\n\nextension View {\n    func errorAlert(isPresented: Binding<Bool>, showing error: Error? = nil) -> some View {\n        modifier(ErrorAlertViewModifier(isPresented: isPresented, error: error))\n    }\n}\n\nprivate struct ErrorAlertViewModifier: ViewModifier {\n    @Binding var isPresented: Bool\n    let error: Error?\n\n    func body(content: Content) -> some View {\n        content\n            .alert(\"Error Occurred\", isPresented: $isPresented) {\n                Button(\"OK\", role: .cancel) {\n                    isPresented = false\n                }\n            } message: {\n                if let error {\n                    Text(error.localizedDescription)\n                } else {\n                    Text(\"An unknown error occurred during the operation.\")\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Internal/Views/ViewMenuItemView.swift",
    "content": "import SwiftUI\n\nstruct ViewMenuItemView: View {\n    let menuItem: ViewMenuItem\n\n    var body: some View {\n        switch menuItem.presentationStyle {\n        case .modal:\n            ModalPresentationView(menuItem.name.rawValue, content: menuItem.content)\n        case .push:\n            NavigationLink(menuItem.name.rawValue, destination: menuItem.content)\n        case .inline:\n            menuItem.content\n        }\n    }\n}\n\nprivate extension ViewMenuItemView {\n    struct ModalPresentationView<Content: View>: View {\n        private let title: String\n        private let content: Content\n        @State private var isModalPresented = false\n\n        init(_ title: String, content: Content) {\n            self.title = title\n            self.content = content\n        }\n\n        var body: some View {\n            Button {\n                isModalPresented = true\n            } label: {\n                Text(title)\n            }\n            .sheet(isPresented: $isModalPresented) {\n                content\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/PresentationStyle.swift",
    "content": "/// A type that represents different styles for presenting a view within the in-app debug menu.\npublic protocol PresentationStyle {}\n\n/// A presentation style that displays the view modally on top of the Spices settings.\npublic struct ModalPresentationStyle: PresentationStyle {\n    fileprivate init() {}\n}\n\n/// A presentation style that pushes the view onto the navigation stack.\npublic struct PushPresentationStyle: PresentationStyle {\n    fileprivate init() {}\n}\n\n/// A presentation style that inlines the view within the settings list.\npublic struct InlinePresentationStyle: PresentationStyle {\n    fileprivate init() {}\n}\n\npublic extension PresentationStyle {\n    /// The modal presentation style, which presents the view modally.\n    ///\n    /// ## Example Usage\n    ///\n    /// Use the presentation style to present a nested spice store modally.\n    ///\n    /// ```swift\n    /// @Spice(presentation: .modal) var featureFlags = FeatureFlagsSpiceStore()\n    ///\n    /// The presentation style can also be used to present a view modally.\n    ///\n    /// ```swift\n    /// @Spice(presentation: .modal) var helloWorld = VStack {\n    ///     Image(systemName: \"globe\")\n    ///         .imageScale(.large)\n    ///         .foregroundStyle(.tint)\n    ///     Text(\"Hello, world!\")\n    /// }\n    /// .padding()\n    /// ```\n    static var modal: ModalPresentationStyle {\n        ModalPresentationStyle()\n    }\n\n    /// The push presentation style, which pushes the view onto the navigation stack.\n    ///\n    /// ## Example Usage\n    ///\n    /// Use the presentation style to push a nested spice store onto the navigation stack.\n    ///\n    /// ```swift\n    /// @Spice(presentation: .push) var featureFlags = FeatureFlagsSpiceStore()\n    /// \n    /// The presentation style can also be used to push a view onto the navigation stack.\n    /// \n    /// ```swift\n    /// @Spice(presentation: .push) var helloWorld = VStack {\n    ///     Image(systemName: \"globe\")\n    ///         .imageScale(.large)\n    ///         .foregroundStyle(.tint)\n    ///     Text(\"Hello, world!\")\n    /// }\n    /// .padding()\n    /// ```\n    static var push: PushPresentationStyle {\n        PushPresentationStyle()\n    }\n\n    /// The inline presentation style, which inlines the view within the settings list.\n    ///\n    /// ## Example Usage\n    ///\n    /// Use the presentation style to inline a nested spice store within the current list.\n    ///\n    /// ```swift\n    /// @Spice(presentation: .inline) var featureFlags = FeatureFlagsSpiceStore()\n    ///\n    /// The presentation style can also be used to inline a view.\n    ///\n    /// ```swift\n    /// @Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n    /// ```\n    static var inline: InlinePresentationStyle {\n        InlinePresentationStyle()\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Spice.swift",
    "content": "// swiftlint:disable file_length\nimport Combine\nimport Foundation\nimport SwiftUI\n\n/// A property wrapper for exposing settings in a generated in-app debug menus.\n///\n/// Spices generates native in-app debug menus from Swift code using the `Spice` property wrapper and ``SpiceStore`` protocol and stores settings in `UserDefaults`.\n///\n/// ## Example Usage\n///\n/// A spice story can be created as follows.\n///\n/// ```swift\n/// class AppSpiceStore: SpiceStore {\n///     @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n///     @Spice var enableLogging = false\n///     @Spice var clearCache = {\n///         try await Task.sleep(for: .seconds(1))\n///         URLCache.shared.removeAllCachedResponses()\n///     }\n/// }\n/// ```\n///\n/// A spice store conforms to `ObservableObject` and can be observed in SwiftUI.\n///\n/// ```swift\n/// struct ContentView: View {\n///     @EnvironmentObject private var spiceStore: AppSpiceStore\n///\n///     var body: some View {\n///         Text(\"Is logging enabled: \" + (spiceStore.enableLogging ? \"👍\" : \"👎\"))\n///     }\n/// }\n/// ```\n///\n/// Values can also be observed in UIKit.\n///\n/// ```swift\n/// final class ContentViewController: UIViewController {\n///     private let spiceStore = AppSpiceStore.shared\n///     private var cancellables: Set<AnyCancellable> = []\n///\n///     override func viewDidLoad() {\n///         super.viewDidLoad()\n///         spiceStore.$enableLogging\n///             .sink { isEnabled in\n///                 print(\"Is logging enabled: \" + (isEnabled ? \"👍\" : \"👎\"))\n///             }\n///             .store(in: &cancellables)\n///     }\n/// }\n/// ```\n@propertyWrapper public struct Spice<Value> {\n    /// Type alias for a synchronous button handler.\n    public typealias ButtonHandler = () throws -> Void\n    /// Type alias for an asynchronous button handler.\n    public typealias AsyncButtonHandler = () async throws -> Void\n    /// The wrapped value is unavailable.\n    ///\n    /// Getting or setting the value will throw a fatal error.\n    @available(*, unavailable, message: \"@Spice can only be applied to classes\")\n    public var wrappedValue: Value {\n        get { fatalError(\"Getting the wrapped value from a @Spice property wrapper is not supported\") }\n        // swiftlint:disable:next unused_setter_value\n        set { fatalError(\"Setting the wrapped value on a @Spice property wrapper is not supported\") }\n    }\n    /// A publisher that emits the current value of the setting whenever it changes.\n    ///\n    /// Use the publisher to observe changes in UIKit and SwiftUI.\n    ///\n    /// ```swift\n    /// spiceStore.$myValue.sink { newValue in\n    ///     // ...\n    /// }\n    /// ```\n    public var projectedValue: AnyPublisher<Value, Never> {\n        storage.publisher\n    }\n\n    let name: Name\n    let menuItem: any MenuItem\n\n    private let storage: AnyStorage<Value>\n\n    /// Initializes a `Spice` property wrapper for a boolean setting.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice var enableLogging = false\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The initial value of the boolean setting.\n    ///   - key: The key used to store the setting in UserDefaults. Defaults to a key generated from the property name.\n    ///   - name: The display name of the setting. Defaults to a formatted version of the property name.\n    ///   - requiresRestart: Set to `true` to restart the application when changing the value. Defaults to `false`.\n    public init(\n        wrappedValue: Value,\n        key: String? = nil,\n        name: String? = nil,\n        requiresRestart: Bool = false\n    ) where Value == Bool {\n        self.name = Name(name)\n        self.storage = AnyStorage(UserDefaultsStorage(default: wrappedValue, key: key))\n        self.menuItem = ToggleMenuItem(\n            name: self.name,\n            requiresRestart: requiresRestart,\n            storage: self.storage\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a string setting.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice(name: \"API URL\") var apiURL = \"http://example.com\"\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The initial value of the string setting.\n    ///   - key: The key used to store the setting in UserDefaults. Defaults to a key generated from the property name.\n    ///   - name: The display name of the setting. Defaults to a formatted version of the property name.\n    ///   - requiresRestart: Set to `true` to restart the application when changing the value. Defaults to `false`.\n    public init(\n        wrappedValue: Value,\n        key: String? = nil,\n        name: String? = nil,\n        requiresRestart: Bool = false\n    ) where Value == String {\n        self.name = Name(name)\n        self.storage = AnyStorage(UserDefaultsStorage(default: wrappedValue, key: key))\n        self.menuItem = TextFieldMenuItem(\n            name: self.name,\n            requiresRestart: requiresRestart,\n            storage: self.storage\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for an enum setting.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// enum ServiceEnvironment: String, CaseIterable {\n    ///     case production\n    ///     case staging\n    /// }\n    ///\n    /// @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The initial value of the enum setting.\n    ///   - key: The key used to store the setting in UserDefaults. Defaults to a key generated from the property name.\n    ///   - name: The display name of the setting. Defaults to a formatted version of the property name.\n    ///   - requiresRestart: Set to `true` to restart the application when changing the value. Defaults to `false`.\n    public init(\n        wrappedValue: Value,\n        key: String? = nil,\n        name: String? = nil,\n        requiresRestart: Bool = false\n    ) where Value: RawRepresentable & CaseIterable, Value.RawValue: Equatable {\n        self.name = Name(name)\n        self.storage = AnyStorage(UserDefaultsStorage(default: wrappedValue, key: key))\n        self.menuItem = PickerMenuItem(\n            name: self.name,\n            storage: self.storage,\n            requiresRestart: requiresRestart\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a synchronous button action.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    ///  @Spice var clearCache = {\n    ///      URLCache.shared.removeAllCachedResponses()\n    ///  }\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The closure representing the button's action.\n    ///   - name: The display name of the setting. Defaults to a formatted version of the property name.\n    ///   - requiresRestart: Set to `true` to restart the application when changing the value. Defaults to `false`.\n    public init(\n        wrappedValue: Value,\n        name: String? = nil,\n        requiresRestart: Bool = false\n    ) where Value == ButtonHandler {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot set closure of a button spice.\"\n        ))\n        self.menuItem = ButtonMenuItem(\n            name: self.name,\n            requiresRestart: requiresRestart,\n            storage: self.storage\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a asynchronous button action.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    ///  @Spice var clearCache = {\n    ///      try await Task.sleep(for: .seconds(1))\n    ///      URLCache.shared.removeAllCachedResponses()\n    ///  }\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The closure representing the button's action.\n    ///   - name: The display name of the setting. Defaults to a formatted version of the property name.\n    ///   - requiresRestart: Set to `true` to restart the application when changing the value. Defaults to `false`.\n    public init(\n        wrappedValue: Value,\n        name: String? = nil,\n        requiresRestart: Bool = false\n    ) where Value == AsyncButtonHandler {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot set closure of button spice.\"\n        ))\n        self.menuItem = AsyncButtonMenuItem(\n            name: self.name,\n            requiresRestart: requiresRestart,\n            storage: self.storage\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a child spice store.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice var featureFlags = FeatureFlagsSpiceStore()\n    /// ```\n    ///\n    /// - Parameters:\n    ///   - wrappedValue: The spice store to create hierarchial navigation to.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.\n    public init(wrappedValue: Value, name: String? = nil) where Value: SpiceStore {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot assign new reference to nested spice store.\"\n        ))\n        self.menuItem = ChildSpiceStoreMenuItem(\n            name: self.name,\n            presentationStyle: .push,\n            spiceStore: wrappedValue\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a child spice store.\n    /// - Parameters:\n    ///   - wrappedValue: The spice store to create hierarchial navigation to.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.\n    ///   - presentation: Presentation style of the spice store.\n    public init(\n        wrappedValue: Value,\n        name: String? = nil,\n        presentation: PushPresentationStyle\n    ) where Value: SpiceStore {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot assign new reference to nested spice store.\"\n        ))\n        self.menuItem = ChildSpiceStoreMenuItem(\n            name: self.name,\n            presentationStyle: .push,\n            spiceStore: wrappedValue\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a child spice store.\n    /// - Parameters:\n    ///   - wrappedValue: The spice store to create hierarchial navigation to.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.\n    ///   - presentation: Presentation style of the spice store.\n    ///   - header: Title of the section header.\n    ///   - footer: Title of the section footer.\n    public init(\n        wrappedValue: Value,\n        name: String? = nil,\n        presentation: ModalPresentationStyle,\n        header: String? = nil,\n        footer: String? = nil\n    ) where Value: SpiceStore {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot assign new reference to nested spice store.\"\n        ))\n        self.menuItem = ChildSpiceStoreMenuItem(\n            name: self.name,\n            presentationStyle: .modal,\n            spiceStore: wrappedValue\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a child spice store.\n    /// - Parameters:\n    ///   - wrappedValue: The spice store to create hierarchial navigation to.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.\n    ///   - presentation: Presentation style of the spice store.\n    ///   - header: Title of the section header.\n    ///   - footer: Title of the section footer.\n    public init(\n        wrappedValue: Value,\n        name: String? = nil,\n        presentation: InlinePresentationStyle,\n        header: String? = nil,\n        footer: String? = nil\n    ) where Value: SpiceStore {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: wrappedValue,\n            setterMessage: \"Cannot assign new reference to nested spice store.\"\n        ))\n        self.menuItem = ChildSpiceStoreMenuItem(\n            name: self.name,\n            presentationStyle: .inline(header: header, footer: footer),\n            spiceStore: wrappedValue\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a custom view.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The custom view to embed.\n    public init(wrappedValue: some View) where Value == AnyView {\n        self.name = Name(nil)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: AnyView(wrappedValue),\n            setterMessage: \"Cannot assign new reference to a custom view spice.\"\n        ))\n        self.menuItem = ViewMenuItem(\n            name: self.name,\n            presentationStyle: .inline,\n            content: AnyView(wrappedValue)\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a custom view.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice var version = LabeledContent(\"Version\", value: \"1.0 (1)\")\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The custom view to embed.\n    ///   - presentation: Presentation style of the custom view.\n    public init(wrappedValue: some View, presentation: InlinePresentationStyle) where Value == AnyView {\n        self.name = Name(nil)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: AnyView(wrappedValue),\n            setterMessage: \"Cannot assign new reference to a custom view spice.\"\n        ))\n        self.menuItem = ViewMenuItem(\n            name: self.name,\n            presentationStyle: .inline,\n            content: AnyView(wrappedValue)\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a custom view presented modally.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice(presentation: .modal) var helloWorld = VStack {\n    ///     Image(systemName: \"globe\")\n    ///         .imageScale(.large)\n    ///         .foregroundStyle(.tint)\n    ///     Text(\"Hello, world!\")\n    /// }\n    /// .padding()\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The custom view to embed.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.   \n    ///   - presentation: Presentation style of the custom view.\n    public init(\n        wrappedValue: some View,\n        name: String? = nil,\n        presentation: ModalPresentationStyle\n    ) where Value == AnyView {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: AnyView(wrappedValue),\n            setterMessage: \"Cannot assign new reference to a custom view spice.\"\n        ))\n        self.menuItem = ViewMenuItem(\n            name: self.name,\n            presentationStyle: .modal,\n            content: AnyView(wrappedValue)\n        )\n    }\n\n    /// Initializes a `Spice` property wrapper for a custom view pushed onto the navigation stack.\n    ///\n    /// **Example Usage:**\n    ///\n    /// ```swift\n    /// @Spice(presentation: .push) var helloWorld = VStack {\n    ///     Image(systemName: \"globe\")\n    ///         .imageScale(.large)\n    ///         .foregroundStyle(.tint)\n    ///     Text(\"Hello, world!\")\n    /// }\n    /// .padding()\n    /// ```\n    /// - Parameters:\n    ///   - wrappedValue: The custom view to embed.\n    ///   - name: The display name of the spice store. Defaults to a formatted version of the property name.\n    ///   - presentation: Presentation style of the custom view.\n    public init(\n        wrappedValue: some View,\n        name: String? = nil,\n        presentation: PushPresentationStyle\n    ) where Value == AnyView {\n        self.name = Name(name)\n        self.storage = AnyStorage(ThrowingStorage(\n            default: AnyView(wrappedValue),\n            setterMessage: \"Cannot assign new reference to a custom view spice.\"\n        ))\n        self.menuItem = ViewMenuItem(\n            name: self.name,\n            presentationStyle: .push,\n            content: AnyView(wrappedValue)\n        )\n    }\n\n    /// A static subscript that provides access to the `Spice` property wrapper's value within a `SpiceStore`.\n    ///\n    /// This allows for reading and writing the value of the setting.\n    static public subscript<T: SpiceStore>(\n        _enclosingInstance instance: T,\n        wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, Value>,\n        storage storageKeyPath: ReferenceWritableKeyPath<T, Self>\n    ) -> Value {\n        get {\n            instance.prepareIfNeeded()\n            return instance[keyPath: storageKeyPath].storage.value\n        }\n        set {\n            instance.prepareIfNeeded()\n            instance[keyPath: storageKeyPath].storage.value = newValue\n        }\n    }\n}\n\nextension Spice: Preparable {\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore) {\n        name.rawValue = propertyName.camelCaseToNaturalText()\n        storage.prepare(propertyName: propertyName, ownedBy: spiceStore)\n        prepareChildSpiceStoreIfNeeded(propertyName: propertyName, parent: spiceStore)\n    }\n}\n\nprivate extension Spice {\n    private func prepareChildSpiceStoreIfNeeded(propertyName: String, parent: any SpiceStore) {\n        guard let childSpiceStore = storage.value as? any SpiceStore else {\n            return\n        }\n        guard childSpiceStore.parent == nil else {\n            fatalError(\"A child spice store can only be referenced from one parent.\")\n        }\n        childSpiceStore.parent = parent\n        childSpiceStore.propertyName = propertyName\n        childSpiceStore.prepareIfNeeded()\n    }\n}\n\nextension Spice: MenuItemProvider {}\n// swiftlint:enable file_length\n"
  },
  {
    "path": "Sources/Spices/SpiceEditor.swift",
    "content": "import SwiftUI\n\n/// A SwiftUI view that presents an in-app debug menu for editing settings managed by a ``SpiceStore``.\n///\n/// `SpiceEditor` displays a list of menu items corresponding to the ``Spice`` properties defined in a ``SpiceStore``.\n///\n/// ## Example Usage\n///\n/// ```swift\n/// struct ContentView: View {\n///     @EnvironmentObject private var spiceStore: AppSpiceStore\n///     @State private var isSpiceEditorPresented = false\n///\n///     var body: some View {\n///         VStack {\n///             Button(\"Open Debug Menu\") {\n///                 isSpiceEditorPresented = true\n///             }\n///         }\n///         .sheet(isPresented: $isSpiceEditorPresented) {\n///             SpiceEditor(editing: spiceStore)\n///         }\n///     }\n/// }\n/// ```\n///\n/// The `SpiceEditor` can also be presented when the device is shaken using the `presentSpiceEditorOnShake(_:)` view modifier.\n///\n/// ```swift\n/// struct ContentView: View {\n///     @EnvironmentObject private var spiceStore: AppSpiceStore\n///\n///     var body: some View {\n///         NavigationStack {\n///             // ...\n///         }\n///         #if DEBUG\n///         .presentSpiceEditorOnShake(editing: spiceStore)\n///         #endif\n///     }\n/// }\n/// ```\npublic struct SpiceEditor: View {\n    private let title: String\n    private let spiceStore: any SpiceStore\n    @Environment(\\.dismiss) private var dismiss\n\n    /// Initializes a `SpiceEditor` with a ``SpiceStore``.\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    public init(editing spiceStore: any SpiceStore) {\n        self.spiceStore = spiceStore\n        self.title = \"Debug Menu\"\n    }\n\n    /// Initializes a `SpiceEditor` with a ``SpiceStore``.\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    /// - Parameter title: The title displayed in the navigation bar.\n    public init(editing spiceStore: any SpiceStore, title: String) {\n        self.spiceStore = spiceStore\n        self.title = title\n    }\n\n    /// The content of the view.\n    public var body: some View {\n        NavigationView {\n            MenuItemListView(items: spiceStore.menuItems, title: title) {\n                dismiss()\n            }\n        }\n        .configureSheetPresentation()\n        .environmentObject(UserInteraction())\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/SpiceEditorViewController.swift",
    "content": "#if canImport(UIKit)\nimport SwiftUI\nimport UIKit\n\n/// A UIKit view controller that hosts a ``SpiceEditor`` view for editing settings in a ``SpiceStore``.\n///\n/// ## Example Usage\n///\n/// ```swift\n/// let viewController = SpiceEditorViewController(editing: AppSpiceStore.shared)\n/// present(viewController, animated: true)\n/// ```\n///\n/// The `SpiceEditorViewController` can also be presented when the device is shaken using ``SpiceEditorWindow``.\npublic final class SpiceEditorViewController: UIHostingController<SpiceEditor> {\n    /// Initializes a `SpiceEditorViewController` with a ``SpiceStore``.\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    public init(editing spiceStore: any SpiceStore) {\n        super.init(rootView: SpiceEditor(editing: spiceStore))\n        configureSheetPresentation()\n    }\n\n    /// Initializes a `SpiceEditorViewController` with a ``SpiceStore``.\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    /// - Parameter title: The title displayed in the navigation bar.\n    public init(editing spiceStore: any SpiceStore, title: String) {\n        super.init(rootView: SpiceEditor(editing: spiceStore, title: title))\n        configureSheetPresentation()\n    }\n\n    @MainActor @preconcurrency dynamic required init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n\nprivate extension SpiceEditorViewController {\n    private func configureSheetPresentation() {\n        #if !os(visionOS)\n        sheetPresentationController?.detents = [.medium(), .large()]\n        #endif\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/Spices/SpiceEditorWindow.swift",
    "content": "#if canImport(UIKit)\nimport SwiftUI\nimport UIKit\n\n/// A `UIWindow` subclass that facilitates the presentation of an in-app debug menu for editing settings managed by a ``SpiceStore``.\n///\n/// `SpiceEditorWindow` provides a shake-to-show gesture for presenting the debug menu. When the device is shaken, it presents a ``SpiceEditorViewController``\n/// modally.\n///\n/// **Example Usage:**\n///\n/// The following demonstrates how `SpiceEditorWindow` can be used in a scene delegate.\n///\n/// - Important: The in-app debug menu may contain sensitive information. Ensure it's only accessible in debug and beta builds by excluding the menu's presentation code from release builds using conditional compilation (e.g., `#if DEBUG`). The examples in this section demonstrate this technique.\n///\n/// ```swift\n/// #if DEBUG\n/// window = SpiceEditorWindow(windowScene: windowScene, editing: AppSpiceStore.shared)\n/// #else\n/// window = UIWindow(windowScene: windowScene)\n/// #endif\n/// ```\nopen class SpiceEditorWindow: UIWindow {\n    private static weak var presentedSpicesEditorViewController: UIViewController?\n    private let spiceStore: (any SpiceStore)?\n    private let title: String?\n\n    /// Initializes a `SpiceEditorWindow` with a `UIWindowScene`.\n     ///\n     /// This initializer does not associate a `SpiceStore` with the window, so the shake gesture will not present the debug menu.\n     ///\n     /// - Parameter windowScene: The scene to which the window belongs.\n    override public init(windowScene: UIWindowScene) {\n        self.spiceStore = nil\n        self.title = nil\n        super.init(windowScene: windowScene)\n    }\n\n    /// Initializes a `SpiceEditorWindow` with a `UIWindowScene` and a ``SpiceStore``.\n    ///\n    /// This initializer associates a `SpiceStore` with the window, enabling the shake gesture to present the debug menu for the provided store.\n    ///\n    /// - Parameters:\n    ///   - windowScene: The scene to which the window belongs.\n    ///   - spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    public init(windowScene: UIWindowScene, editing spiceStore: any SpiceStore) {\n        self.spiceStore = spiceStore\n        self.title = nil\n        super.init(windowScene: windowScene)\n    }\n\n    /// Initializes a `SpiceEditorWindow` with a `UIWindowScene` and a ``SpiceStore``.\n    ///\n    /// This initializer associates a `SpiceStore` with the window, enabling the shake gesture to present the debug menu for the provided store.\n    ///\n    /// - Parameters:\n    ///   - windowScene: The scene to which the window belongs.\n    ///   - spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    ///   - title: The title displayed in the navigation bar.\n    public init(windowScene: UIWindowScene, editing spiceStore: any SpiceStore, title: String) {\n        self.spiceStore = spiceStore\n        self.title = title\n        super.init(windowScene: windowScene)\n    }\n\n    /// Unavailable initializer.\n    public required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    /// Overrides the default motion handling to present the debug menu on a shake gesture.\n    override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {\n        if let spiceStore, motion == .motionShake {\n            presentSpicesEditor(editing: spiceStore)\n        }\n        super.motionEnded(motion, with: event)\n    }\n}\n\nprivate extension SpiceEditorWindow {\n    private func presentSpicesEditor(editing spiceStore: any SpiceStore) {\n        guard SpiceEditorWindow.presentedSpicesEditorViewController == nil else {\n            return\n        }\n        let window = UIApplication.shared.shp_activeWindow\n        let topViewController = window?.rootViewController?.shp_topViewController\n        let viewController = if let title {\n            SpiceEditorViewController(editing: spiceStore, title: title)\n        } else {\n            SpiceEditorViewController(editing: spiceStore)\n        }\n        topViewController?.present(viewController, animated: true)\n        SpiceEditorWindow.presentedSpicesEditorViewController = viewController\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/Spices/SpiceStore.swift",
    "content": "import Combine\nimport Foundation\nimport ObjectiveC\n\nnonisolated(unsafe) private var idKey: UInt8 = 0\nnonisolated(unsafe) private var nameKey: UInt8 = 0\nnonisolated(unsafe) private var propertyNameKey: UInt8 = 0\nnonisolated(unsafe) private var isPreparedKey: UInt8 = 0\nnonisolated(unsafe) private var parentKey: UInt8 = 0\n\n/// A protocol for classes that manage a collection of settings exposed in an in-app debug menu.\n///\n/// `SpiceStore` objects own and manage ``Spice`` property wrappers, providing a central point for accessing and observing settings.\n///\n/// ## Example Usage\n///\n/// ```swift\n/// class AppSpiceStore: SpiceStore {\n///     @Spice(requiresRestart: true) var environment: ServiceEnvironment = .production\n///     @Spice var enableLogging = false\n///     @Spice var clearCache = {\n///         try await Task.sleep(for: .seconds(1))\n///         URLCache.shared.removeAllCachedResponses()\n///     }\n///\n///     let featureFlags = FeatureFlagsSpiceStore()\n/// }\n/// ```\npublic protocol SpiceStore: AnyObject, ObservableObject {\n    /// The `UserDefaults` instance used for persisting settings.\n    ///\n    /// You can use this property to share settings among apps, or when developing an app extension, to share preferences or other data between the extension and its containing app.\n    ///\n    /// The default implementation returns `UserDefaults.standard`.\n    var userDefaults: UserDefaults { get }\n}\n\npublic extension SpiceStore {\n    var userDefaults: UserDefaults {\n        .standard\n    }\n}\n\npublic extension SpiceStore {\n    /// Ensures that the `SpiceStore` is prepared before accessing its projected values.\n    ///\n    /// This method checks whether the `SpiceStore` has already been prepared. If not, it marks it as prepared\n    /// and invokes the `prepare()` method.\n    ///\n    /// You typically do not need to call this method manually, as preparation happens automatically. However,\n    /// if ``Spice/projectedValue`` is accessed before the corresponding property has been read or written,\n    /// you must explicitly call `prepareIfNeeded()` to avoid accessing an unprepared state.\n    ///\n    /// - Important: If the `SpiceStore` is not prepared, accessing a projected value will trigger an assertion failure.\n    ///\n    func prepareIfNeeded() {\n        guard !isPrepared else {\n            return\n        }\n        isPrepared = true\n        prepare()\n    }\n}\n\nextension SpiceStore {\n    var id: String {\n        if let value = objc_getAssociatedObject(self, &idKey) as? String {\n            return value\n        } else {\n            let value = UUID().uuidString\n            objc_setAssociatedObject(self, &idKey, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)\n            return value\n        }\n    }\n\n    var propertyName: String {\n        get {\n            objc_getAssociatedObject(self, &propertyNameKey) as? String ?? \"<name unavailable>\"\n        }\n        set {\n            objc_setAssociatedObject(self, &propertyNameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)\n        }\n    }\n\n    var parent: (any SpiceStore)? {\n        get {\n            objc_getAssociatedObject(self, &parentKey) as? any SpiceStore\n        }\n        set {\n            objc_setAssociatedObject(self, &parentKey, newValue, .OBJC_ASSOCIATION_ASSIGN)\n        }\n    }\n\n    private var isPrepared: Bool {\n        get {\n            (objc_getAssociatedObject(self, &isPreparedKey) as? Bool) ?? false\n        }\n        set {\n            objc_setAssociatedObject(self, &isPreparedKey, newValue, .OBJC_ASSOCIATION_ASSIGN)\n        }\n    }\n\n    private var path: [String] {\n        if let parent {\n            return parent.path + [propertyName]\n        } else {\n            return []\n        }\n    }\n\n    var menuItems: [MenuItem] {\n        prepareIfNeeded()\n        let mirror = Mirror(reflecting: self)\n        return mirror.children.compactMap { _, value in\n            guard let spice = value as? MenuItemProvider else {\n                return nil\n            }\n            return spice.menuItem\n        }\n    }\n\n    func key(fromPropertyNamed propertyName: String) -> String {\n        (path + [propertyName]).joined(separator: \".\")\n    }\n\n    func publishObjectWillChange() {\n        let publisher = objectWillChange as? ObservableObjectPublisher\n        publisher?.send()\n        parent?.publishObjectWillChange()\n    }\n\n    private func prepare() {\n        let mirror = Mirror(reflecting: self)\n        for (name, value) in mirror.children {\n            guard let name, let spice = value as? Preparable else {\n                continue\n            }\n            let propertyName = name.removing(prefix: \"_\")\n            spice.prepare(propertyName: propertyName, ownedBy: self)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Spices/Spices.docc/Extensions/SpiceStore.md",
    "content": "# ``SpiceStore``\n\n## Topics\n\n### Storage\n\n- ``SpiceStore/userDefaults``\n\n### Preparation\n\n- ``SpiceStore/prepareIfNeeded()``\n"
  },
  {
    "path": "Sources/Spices/Spices.docc/Spices.md",
    "content": "# ``Spices``\n\nSpices makes it straightforward to create in-app debug menus by generating native UI from Swift.\n\n## Overview\n\nSpices generates native in-app debug menus from Swift code using the ``Spice`` property wrapper and ``SpiceStore`` protocol and stores settings in `UserDefaults`.\n\nWe built Spices at [Shape](https://shape.dk) (becoming [Framna](https://framna.com)) to provide a frictionless API for quickly creating these menus. Common use cases include environment switching, resetting state, and enabling features during development.\n\nSee [the README on GitHub](https://github.com/shapehq/spices) for reference documentation.\n\n![iPhone screen recording showing an in-app debug menu.](example.gif)\n\n## Topics\n\n### Essentials\n\n- ``Spice``\n- ``SpiceStore``\n\n### Present the In-App Debug Menu\n\n> Note: The in-app debug menu may contain sensitive information. Ensure it's only accessible in debug and beta builds by excluding the menu's presentation code from release builds using conditional compilation.\n\n- ``SpiceEditor``\n- ``SpiceEditorViewController``\n- ``SpiceEditorWindow``\n- ``SwiftUICore/View/presentSpiceEditorOnShake(editing:)``\n\n### Customization\n\n- ``SpicesTitleProvider``\n"
  },
  {
    "path": "Sources/Spices/SpicesTitleProvider.swift",
    "content": "/// A protocol for providing custom titles for enum values used with a ``Spice`` property wrapper.\n///\n/// By default, Spices generates titles from the enum's cases. Conforming to `SpicesTitleProvider` allows you to override these default titles\n/// to provide more descriptive, user-friendly, or localized names in the debug menu.\n///\n/// ## Example usage\n///\n/// ```swift\n/// class AppSpiceStore: SpiceStore {\n///     @Spice var environment: ServiceEnvironment =.production\n/// }\n/// \n/// enum ServiceEnvironment: String, CaseIterable, SpicesTitleProvider {\n///     case production\n///     case staging\n///\n///     var spicesTitle: String {\n///         switch self {\n///         case .production:\n///             \"🚀 Production\"\n///         case .staging:\n///             \"🧪 Staging\"\n///         }\n///     }\n/// }\n/// ```\npublic protocol SpicesTitleProvider {\n    /// The title to be displayed in the in-app debug menu.\n    var spicesTitle: String { get }\n}\n"
  },
  {
    "path": "Sources/Spices/View+SpiceEditor.swift",
    "content": "#if canImport(UIKit)\nimport SwiftUI\n\npublic extension View {\n    /// Presents a ``SpiceEditor`` for the given ``SpiceStore`` when the device is shaken.\n    ///\n    /// ## Example Usage\n    ///\n    /// The following shows how the view modifier can be used to present the in-app debug menu when the device is shaken.\n    ///\n    /// The view modifier should typically be used at the root of your app's view hierarchy.\n    ///\n    /// - Important: The in-app debug menu may contain sensitive information. Ensure it's only accessible in debug and beta builds by excluding the menu's presentation code from release builds using conditional compilation (e.g., `#if DEBUG`). The examples in this section demonstrate this technique.\n    ///\n    /// ```swift\n    /// struct ContentView: View {\n    ///     @EnvironmentObject private var spiceStore: AppSpiceStore\n    ///\n    ///     var body: some View {\n    ///         NavigationStack {\n    ///             // ...\n    ///         }\n    ///         #if DEBUG\n    ///         .presentSpiceEditorOnShake(editing: spiceStore)\n    ///         #endif\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    /// - Returns: A modified view that presents the ``SpiceEditor`` on shake.\n    @ViewBuilder\n    func presentSpiceEditorOnShake<T: SpiceStore>(editing spiceStore: T) -> some View {\n        modifier(PresentSpiceEditorOnShakeViewModifier {\n            SpiceEditor(editing: spiceStore)\n        })\n    }\n\n    /// Presents a ``SpiceEditor`` for the given ``SpiceStore`` when the device is shaken.\n    ///\n    /// ## Example Usage\n    ///\n    /// The following shows how the view modifier can be used to present the in-app debug menu when the device is shaken.\n    ///\n    /// The view modifier should typically be used at the root of your app's view hierarchy.\n    ///\n    /// - Important: The in-app debug menu may contain sensitive information. Ensure it's only accessible in debug and beta builds by excluding the menu's presentation code from release builds using conditional compilation (e.g., `#if DEBUG`). The examples in this section demonstrate this technique.\n    ///\n    /// ```swift\n    /// struct ContentView: View {\n    ///     @EnvironmentObject private var spiceStore: AppSpiceStore\n    ///\n    ///     var body: some View {\n    ///         NavigationStack {\n    ///             // ...\n    ///         }\n    ///         #if DEBUG\n    ///         .presentSpiceEditorOnShake(editing: spiceStore)\n    ///         #endif\n    ///     }\n    /// }\n    /// ```\n    ///\n    /// - Parameter spiceStore: The ``SpiceStore`` containing the settings to be edited.\n    /// - Parameter title: The title displayed in the navigation bar.\n    /// - Returns: A modified view that presents the ``SpiceEditor`` on shake.\n    @ViewBuilder\n    func presentSpiceEditorOnShake<T: SpiceStore>(editing spiceStore: T, title: String) -> some View {\n        modifier(PresentSpiceEditorOnShakeViewModifier {\n            SpiceEditor(editing: spiceStore, title: title)\n        })\n    }\n}\n\nprivate struct PresentSpiceEditorOnShakeViewModifier<Editor: View>: ViewModifier {\n    private let editor: Editor\n\n    init(@ViewBuilder content: () -> Editor) {\n        self.editor = content()\n    }\n\n    func body(content: Content) -> some View {\n        content\n            .onReceive(NotificationCenter.default.publisher(\n                for: UIWindow.presentSpiceEditorNotification\n            )) { publisher in\n                guard let window = publisher.object as? UIWindow, window.isKeyWindow else {\n                    return\n                }\n                guard PresentedSpiceEditorBox.viewController == nil else {\n                    return\n                }\n                let viewController = UIHostingController(rootView: editor)\n                #if !os(visionOS)\n                viewController.sheetPresentationController?.detents = [.medium(), .large()]\n                #endif\n                window.rootViewController?.shp_topViewController.present(viewController, animated: true)\n                PresentedSpiceEditorBox.viewController = viewController\n            }\n    }\n}\n\n@MainActor\nprivate struct PresentedSpiceEditorBox {\n    static weak var viewController: UIViewController?\n\n    private init() {}\n}\n\nextension UIWindow {\n    fileprivate static let presentSpiceEditorNotification = Notification.Name(\"presentSpiceEditorNotification\")\n\n    override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {\n        if motion == .motionShake {\n            NotificationCenter.default.post(name: Self.presentSpiceEditorNotification, object: self)\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Tests/SpicesTests/AnyStorageTests.swift",
    "content": "import Combine\nimport Foundation\n@testable import Spices\nimport Testing\n\n@Suite\nfinal class AnyStorageTests {\n    private var cancellables: Set<AnyCancellable> = []\n\n    @Test\n    func it_reads_value() async throws {\n        let storage = MockStorage(default: \"Hello world!\")\n        let sut = AnyStorage(storage)\n        #expect(sut.value == \"Hello world!\")\n    }\n\n    @Test\n    func it_writes_value() async throws {\n        let storage = MockStorage(default: \"Hello world!\")\n        let sut = AnyStorage(storage)\n        sut.value = \"Foo\"\n        #expect(storage.value == \"Foo\")\n    }\n\n    @Test\n    func it_notifies_observer_of_changes() async throws {\n        let storage = MockStorage(default: \"Hello world!\")\n        let sut = AnyStorage(storage)\n        var didReceiveChange = false\n        sut.objectWillChange.sink {\n            didReceiveChange = true\n        }\n        .store(in: &cancellables)\n        sut.value = \"Foo\"\n        #expect(didReceiveChange == true)\n    }\n\n    @Test\n    func it_updates_prepared_state() async throws {\n        let storage = MockStorage(default: \"Hello world!\")\n        let sut = AnyStorage(storage)\n        #expect(sut.isPrepared == false)\n        sut.prepare(propertyName: \"foo\", ownedBy: MockSpiceStore())\n        #expect(sut.isPrepared == true)\n    }\n}\n"
  },
  {
    "path": "Tests/SpicesTests/CamelCaseToNaturalTextTests.swift",
    "content": "import Foundation\n@testable import Spices\nimport Testing\n\n@Suite\nstruct CamelCaseToNaturalTextTests {\n    @Test\n    func it_handles_simple_cases() async throws {\n        #expect(\"simpleName\".camelCaseToNaturalText() == \"Simple Name\")\n        #expect(\"useLocalURL\".camelCaseToNaturalText() == \"Use Local URL\")\n        #expect(\"environment\".camelCaseToNaturalText() == \"Environment\")\n        #expect(\"enableLogging\".camelCaseToNaturalText() == \"Enable Logging\")\n        #expect(\"clearCache\".camelCaseToNaturalText() == \"Clear Cache\")\n        #expect(\"featureFlags\".camelCaseToNaturalText() == \"Feature Flags\")\n        #expect(\"notifications\".camelCaseToNaturalText() == \"Notifications\")\n        #expect(\"fastRefreshWidgets\".camelCaseToNaturalText() == \"Fast Refresh Widgets\")\n        #expect(\"ignoreNextHTTPRequest\".camelCaseToNaturalText() == \"Ignore Next HTTP Request\")\n\n        // Suboptimal, but heuristics for these would be annoying.\n        #expect(\"httpVersion\".camelCaseToNaturalText() == \"Http Version\")\n        #expect(\"apiURL\".camelCaseToNaturalText() == \"Api URL\")\n    }\n}\n"
  },
  {
    "path": "Tests/SpicesTests/Helpers/UserDefaults+Helpers.swift",
    "content": "import Foundation\n\nextension UserDefaults {\n    func removeAll() {\n        let domain = Bundle.main.bundleIdentifier!\n        removePersistentDomain(forName: domain)\n    }\n}\n"
  },
  {
    "path": "Tests/SpicesTests/Mocks/MockEnvironment.swift",
    "content": "enum MockEnvironment: String, CaseIterable {\n    case production\n    case staging\n}\n"
  },
  {
    "path": "Tests/SpicesTests/Mocks/MockSpiceStore.swift",
    "content": "import Spices\n\nfinal class MockSpiceStore: SpiceStore {\n    nonisolated(unsafe) static var buttonClosureCalled = false\n    nonisolated(unsafe) static var asynButtonClosureCalled = false\n\n    @Spice var boolValue = false\n    @Spice var textValue = \"Hello\"\n    @Spice var enumValue: MockEnvironment = .production\n    @Spice var buttonValue = {\n        MockSpiceStore.buttonClosureCalled = true\n    }\n    @Spice var asyncButtonValue: () async throws -> Void = {\n        MockSpiceStore.asynButtonClosureCalled = true\n    }\n\n    @Spice(name: \"Hello World!\") var namedValue = false\n}\n"
  },
  {
    "path": "Tests/SpicesTests/Mocks/MockStorage.swift",
    "content": "import Combine\n@testable import Spices\n\nfinal class MockStorage<Value>: Storage, Preparable {\n    var publisher: AnyPublisher<Value, Never> {\n        subject.eraseToAnyPublisher()\n    }\n    var value: Value {\n        get {\n            subject.value\n        }\n        set {\n            subject.send(newValue)\n        }\n    }\n\n    private let subject: CurrentValueSubject<Value, Never>\n\n    init(default value: Value) {\n        subject = CurrentValueSubject(value)\n    }\n\n    func prepare(propertyName: String, ownedBy spiceStore: any SpiceStore) {}\n}\n"
  },
  {
    "path": "Tests/SpicesTests/SpiceTests.swift",
    "content": "import Combine\nimport Foundation\n@testable import Spices\nimport Testing\n\n@MainActor @Suite(.serialized)\nfinal class SpiceTests {\n    private var cancellables: Set<AnyCancellable> = []\n\n    @Test func it_reads_default_bool_value() {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        #expect(sut.boolValue == false)\n    }\n\n    @Test func it_stores_bool_value_in_user_defaults() {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        #expect(sut.userDefaults.object(forKey: \"boolValue\") == nil)\n        sut.boolValue = true\n        #expect(sut.userDefaults.bool(forKey: \"boolValue\") == true)\n    }\n\n    @Test func it_reads_default_enum_Value() {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        #expect(sut.enumValue == .production)\n    }\n\n    @Test func it_stores_enum_value_in_user_defaults() {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        #expect(sut.userDefaults.object(forKey: \"enumValue\") == nil)\n        sut.enumValue = .staging\n        #expect(sut.userDefaults.string(forKey: \"enumValue\") == MockEnvironment.staging.rawValue)\n    }\n\n    @Test func it_stores_string_value_in_user_defaults() {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        #expect(sut.userDefaults.object(forKey: \"textValue\") == nil)\n        sut.textValue = \"Test value\"\n        #expect(sut.userDefaults.string(forKey: \"textValue\") == \"Test value\")\n    }\n\n    @Test func it_stores_button_closure() throws {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        MockSpiceStore.buttonClosureCalled = false\n        try sut.buttonValue()\n        #expect(MockSpiceStore.buttonClosureCalled == true)\n    }\n\n    @Test func it_stores_async_button_closure() async throws {\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        MockSpiceStore.asynButtonClosureCalled = false\n        try await sut.asyncButtonValue()\n        #expect(MockSpiceStore.asynButtonClosureCalled == true)\n    }\n\n    @Test func it_sink_receives_initial_value() async throws {\n        var initialValue: MockEnvironment?\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        sut.prepareIfNeeded()\n        sut.$enumValue.sink { newValue in\n            initialValue = newValue\n        }\n        .store(in: &cancellables)\n        #expect(initialValue == .production)\n    }\n\n    @Test func it_sink_receives_initial_value_if_it_has_been_changed() async throws {\n        var initialValue: MockEnvironment?\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        sut.userDefaults.set(MockEnvironment.staging.rawValue, forKey: \"enumValue\")\n        sut.prepareIfNeeded()\n        sut.$enumValue.sink { newValue in\n            initialValue = newValue\n        }\n        .store(in: &cancellables)\n        #expect(initialValue == .staging)\n    }\n\n    @Test func it_publishes_values() async throws {\n        var publishedValue: MockEnvironment?\n        let sut = MockSpiceStore()\n        sut.userDefaults.removeAll()\n        sut.prepareIfNeeded()\n        sut.$enumValue.sink { newValue in\n            publishedValue = newValue\n        }\n        .store(in: &cancellables)\n        #expect(sut.enumValue == .production)\n        sut.enumValue = .staging\n        #expect(publishedValue == .staging)\n    }\n}\n"
  },
  {
    "path": "Tests/SpicesTests/UserDefaultsStorageTests.swift",
    "content": "import Combine\nimport Foundation\n@testable import Spices\nimport Testing\n\n@MainActor @Suite(.serialized)\nfinal class UserDefaultsStorageTests {\n    private var cancellables: Set<AnyCancellable> = []\n\n    @Test\n    func it_returns_default_value() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"default\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        #expect(sut.value == \"default\")\n    }\n\n    @Test\n    func it_stores_value_under_property_name() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"default\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        sut.value = \"Hello world!\"\n        #expect(spiceStore.userDefaults.string(forKey: \"foo\") == \"Hello world!\")\n    }\n\n    @Test\n    func it_stores_value_under_provided_key() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"default\", key: \"bar\")\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        sut.value = \"Hello world!\"\n        #expect(spiceStore.userDefaults.string(forKey: \"bar\") == \"Hello world!\")\n    }\n\n    @Test\n    func it_stores_raw_representable_values() async throws {\n        func makeStorage<Value: RawRepresentable & CaseIterable>(\n            default defaultValue: Value\n        ) -> UserDefaultsStorage<Value> where Value.RawValue: Equatable {\n            UserDefaultsStorage(default: defaultValue, key: nil)\n        }\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = makeStorage(default: MockEnvironment.production)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        sut.value = MockEnvironment.staging\n        #expect(spiceStore.userDefaults.string(forKey: \"foo\") == \"staging\")\n    }\n\n    @Test\n    func it_publishes_initial_value() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        spiceStore.userDefaults.set(\"Hello world!\", forKey: \"foo\")\n        let sut = UserDefaultsStorage(default: \"default\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        var readValue: String?\n        _ = sut.publisher.sink { value in\n            readValue = value\n        }\n        #expect(readValue == \"Hello world!\")\n    }\n\n    @Test\n    func it_publishes_values() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"foo\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        let _: Void = try await withCheckedThrowingContinuation { @MainActor continuation in\n            let timeoutTask = Task {\n                // Wait for a second and if that time passes, assume we will not get notified,\n                // in which case the test succeeded.\n                try await Task.sleep(nanoseconds: 1_000_000_000)\n                continuation.resume()\n            }\n            sut.publisher.sink { value in\n                if value == \"bar\" {\n                    // We received the new value, so all is good.\n                    timeoutTask.cancel()\n                    continuation.resume()\n                }\n            }\n            .store(in: &cancellables)\n            // Setting the same value. This should not result in a value being published.\n            sut.value = \"bar\"\n        }\n    }\n\n    @Test\n    func it_skips_publishing_same_value() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"foo\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        let _: Void = try await withCheckedThrowingContinuation { @MainActor continuation in\n            let timeoutTask = Task {\n                // Wait for a second and if that time passes, assume we will not get notified,\n                // in which case the test succeeded.\n                try await Task.sleep(nanoseconds: 1_000_000_000)\n                continuation.resume()\n            }\n            var count = 0\n            sut.publisher.sink { value in\n                count += 1\n                if count == 1 {\n                    // This is the initial value received upon subscribing.\n                } else if count == 2 {\n                    // This is the second value. We did not expect this, so throw an error.\n                    timeoutTask.cancel()\n                    let error = NSError(domain: \"dk.shape.Spices\", code: -1, userInfo: [\n                        NSLocalizedDescriptionKey: \"Received unexpected value: \\(value)\"\n                    ])\n                    continuation.resume(throwing: error)\n                }\n            }\n            .store(in: &cancellables)\n            // Setting the same value. This should not result in a value being published.\n            sut.value = \"foo\"\n        }\n    }\n\n    @Test\n    func it_publishes_when_user_defaults_change() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"foo\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        let _: Void = try await withCheckedThrowingContinuation { @MainActor continuation in\n            let timeoutTask = Task {\n                // Wait for a second and if that time passes, assume we will not get notified,\n                // in which case we throw an error as the test has failed.\n                try await Task.sleep(nanoseconds: 1_000_000_000)\n                let error = NSError(domain: \"dk.shape.Spices\", code: -1, userInfo: [\n                    NSLocalizedDescriptionKey: \"Operation timed out\"\n                ])\n                continuation.resume(throwing: error)\n            }\n            sut.publisher.sink { value in\n                if value == \"bar\" {\n                    // We received the new value, so all is good.\n                    timeoutTask.cancel()\n                    continuation.resume()\n                }\n            }\n            .store(in: &cancellables)\n            // Updating UserDefaults should cause a value to be published.\n            spiceStore.userDefaults.set(\"bar\", forKey: \"foo\")\n        }\n    }\n\n    @Test\n    func it_skips_publishing_when_user_defaults_is_updated_with_same_value() async throws {\n        let spiceStore = MockSpiceStore()\n        spiceStore.userDefaults.removeAll()\n        let sut = UserDefaultsStorage(default: \"foo\", key: nil)\n        sut.prepare(propertyName: \"foo\", ownedBy: spiceStore)\n        let _: Void = try await withCheckedThrowingContinuation { @MainActor continuation in\n            let timeoutTask = Task {\n                // Wait for a second and if that time passes, assume we will not get notified,\n                // in which case the test succeeded.\n                try await Task.sleep(nanoseconds: 1_000_000_000)\n                continuation.resume()\n            }\n            var count = 0\n            sut.publisher.sink { value in\n                count += 1\n                if count == 1 {\n                    // This is the initial value received upon subscribing.\n                } else if count == 2 {\n                    // This is the second value. We did not expect this, so throw an error.\n                    timeoutTask.cancel()\n                    let error = NSError(domain: \"dk.shape.Spices\", code: -1, userInfo: [\n                        NSLocalizedDescriptionKey: \"Received unexpected value: \\(value)\"\n                    ])\n                    continuation.resume(throwing: error)\n                }\n            }\n            .store(in: &cancellables)\n            // Assign the same value as we currentl have in user defaults.\n            spiceStore.userDefaults.set(\"foo\", forKey: \"foo\")\n        }\n    }\n}\n"
  }
]