From 94573ad1110d137ffed0aa5ee6869cd716e4e92a Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 19:36:17 -0600 Subject: [PATCH 01/12] fix(deploy): add baseUrl for GitHub Pages and disable caching - Add experiments.baseUrl: "/thumbcode" for GitHub Pages subdirectory - Inject no-cache meta tags into HTML files for staging environment - Fixes Expo app loading issue on GitHub Pages deployment Co-Authored-By: Claude Opus 4.5 --- .github/workflows/deploy-gh-pages.yml | 6 ++++++ app.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index 1f2e152d..63eb70e2 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -41,6 +41,12 @@ jobs: - name: Build web app run: npx expo export --platform web + - name: Inject no-cache headers for staging + run: | + # Add no-cache meta tags to all HTML files for staging environment + find dist -name "*.html" -exec sed -i 's//\n \n \n /' {} \; + echo "Injected no-cache headers into $(find dist -name '*.html' | wc -l) HTML files" + - name: Setup Pages uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 diff --git a/app.json b/app.json index 1d140362..4cc111e2 100644 --- a/app.json +++ b/app.json @@ -48,7 +48,8 @@ ] ], "experiments": { - "typedRoutes": true + "typedRoutes": true, + "baseUrl": "/thumbcode" }, "extra": { "router": { From 523faea7f49f1487fbfb0c2edf55d1808d1129c7 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:06:58 -0600 Subject: [PATCH 02/12] feat(infra): add Render staging + refactor CI/CD workflows ## Changes ### Deployment Infrastructure - Add render.yaml for Render.com staging deployment - Configure security headers (X-Frame-Options, X-Content-Type-Options) - Set up SPA routing with proper cache headers - Remove baseUrl from app.json (Render serves from root) ### Version Management (DRY) - Add .nvmrc with Node 22 LTS - Add packageManager field to package.json (pnpm@10.11.0) - Update setup-thumbcode action to read from .nvmrc - Remove hardcoded version numbers from workflows ### Workflow Refactoring - Refactor ci.yml with latest action SHAs and E2E job - Create cd.yml for EAS builds and deployments - Update deploy-gh-pages.yml for documentation only - Pin all GitHub Actions to latest SHAs: - checkout@v6.0.2 - setup-node@v6 - pnpm/action-setup@v4 - expo/expo-github-action@v8 - codecov-action@v5 - coverallsapp@v2 - sonarcloud-github-action@v5 ### CI/CD Structure - ci.yml: lint, typecheck, test, build, e2e - cd.yml: EAS updates, Android/iOS builds, store submissions - deploy-gh-pages.yml: documentation with TypeDoc Co-Authored-By: Claude Opus 4.5 --- .github/actions/setup-thumbcode/action.yml | 17 +- .github/workflows/cd.yml | 205 +++++++++++++++++++++ .github/workflows/ci.yml | 51 ++++- .github/workflows/deploy-gh-pages.yml | 116 ++++++++++-- .nvmrc | 1 + app.json | 3 +- package.json | 1 + render.yaml | 42 +++++ 8 files changed, 397 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/cd.yml create mode 100644 .nvmrc create mode 100644 render.yaml diff --git a/.github/actions/setup-thumbcode/action.yml b/.github/actions/setup-thumbcode/action.yml index a5d51458..3260df07 100644 --- a/.github/actions/setup-thumbcode/action.yml +++ b/.github/actions/setup-thumbcode/action.yml @@ -2,14 +2,10 @@ name: 'Setup ThumbCode Environment' description: 'Reusable setup action for ThumbCode workflows - installs Node, pnpm, dependencies, and generates tokens' inputs: - node-version: - description: 'Node.js version to use' + node-version-file: + description: 'File containing Node.js version (defaults to .nvmrc)' required: false - default: '20' - pnpm-version: - description: 'pnpm version to use' - required: false - default: '10' + default: '.nvmrc' generate-tokens: description: 'Whether to generate design tokens' required: false @@ -24,13 +20,12 @@ runs: steps: - name: Install pnpm uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c # v4 - with: - version: ${{ inputs.pnpm-version }} + # Reads version from packageManager field in package.json - name: Setup Node.js - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 with: - node-version: ${{ inputs.node-version }} + node-version-file: ${{ inputs.node-version-file }} cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..fb6fa90e --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,205 @@ +name: CD + +on: + push: + branches: [main] + workflow_dispatch: + inputs: + platform: + description: 'Platform to build' + required: true + default: 'all' + type: choice + options: + - all + - android + - ios + - web + profile: + description: 'Build profile' + required: true + default: 'preview' + type: choice + options: + - development + - preview + - production + +# Prevent concurrent deployments +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + pull-requests: write + +jobs: + # ============================================ + # EAS Update for PRs (OTA Updates) + # ============================================ + eas-update: + name: EAS Update (PR Preview) + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + + steps: + - name: Check for EXPO_TOKEN + run: | + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then + echo "EXPO_TOKEN secret is required for EAS operations" + echo "Learn more: https://docs.expo.dev/eas-update/github-actions" + exit 1 + fi + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Setup Expo and EAS + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Create preview update + uses: expo/expo-github-action/preview@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + command: eas update --auto + + # ============================================ + # EAS Build - Android + # ============================================ + build-android: + name: Build Android + if: | + github.event_name == 'push' || + (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')) + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Check for EXPO_TOKEN + run: | + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then + echo "EXPO_TOKEN secret is required for EAS builds" + exit 1 + fi + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Setup Expo and EAS + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Build Android + run: | + PROFILE="${{ github.event.inputs.profile || 'preview' }}" + eas build --platform android --profile $PROFILE --non-interactive --no-wait + + # ============================================ + # EAS Build - iOS + # ============================================ + build-ios: + name: Build iOS + if: | + github.event_name == 'push' || + (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')) + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Check for EXPO_TOKEN + run: | + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then + echo "EXPO_TOKEN secret is required for EAS builds" + exit 1 + fi + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Setup Expo and EAS + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Build iOS + run: | + PROFILE="${{ github.event.inputs.profile || 'preview' }}" + eas build --platform ios --profile $PROFILE --non-interactive --no-wait + + # ============================================ + # Production Deployment (App Store / Play Store) + # ============================================ + submit-android: + name: Submit to Play Store + if: | + github.event_name == 'workflow_dispatch' && + github.event.inputs.profile == 'production' && + (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android') + needs: build-android + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Setup Expo and EAS + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Submit to Play Store + run: eas submit --platform android --latest --non-interactive + + submit-ios: + name: Submit to App Store + if: | + github.event_name == 'workflow_dispatch' && + github.event.inputs.profile == 'production' && + (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios') + needs: build-ios + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Setup Expo and EAS + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Submit to App Store + run: eas submit --platform ios --latest --non-interactive + env: + EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d8e2d26..fbfa5438 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 # Shallow clones disabled for SonarCloud + fetch-depth: 0 # Shallow clones disabled for SonarCloud - name: Setup ThumbCode environment uses: ./.github/actions/setup-thumbcode @@ -69,7 +69,7 @@ jobs: run: pnpm run test:coverage - name: Upload coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 if: always() with: token: ${{ secrets.CODECOV_TOKEN }} @@ -78,7 +78,7 @@ jobs: name: codecov-umbrella - name: Upload coverage to Coveralls - uses: coverallsapp/github-action@643bc377ffa44ace6394b2b5d0d3950076de9f63 # v2.3.0 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2 if: always() with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -87,7 +87,7 @@ jobs: flag-name: unit-tests - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@e44258b109568baa0df60ed515909fc6c72cba92 # v2.3.0 + uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 # v5 if: always() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -98,8 +98,8 @@ jobs: -Dsonar.pullrequest.branch=${{ github.head_ref }} -Dsonar.pullrequest.base=${{ github.base_ref }} - build: - name: Build + build-web: + name: Build Web runs-on: ubuntu-latest timeout-minutes: 15 @@ -116,13 +116,48 @@ jobs: CI: true - name: Upload build artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: success() with: name: web-build path: dist/ retention-days: 7 + e2e-web: + name: E2E Tests (Web) + runs-on: ubuntu-latest + timeout-minutes: 20 + needs: build-web + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup ThumbCode environment + uses: ./.github/actions/setup-thumbcode + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Download web build + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + name: web-build + path: dist/ + + - name: Run E2E tests + run: pnpm run test:e2e:web + env: + CI: true + + - name: Upload Playwright report + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: failure() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + # Finalize Coveralls parallel build coveralls-finish: name: Finish Coveralls @@ -132,7 +167,7 @@ jobs: steps: - name: Coveralls Finished - uses: coverallsapp/github-action@643bc377ffa44ace6394b2b5d0d3950076de9f63 # v2.3.0 + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index 63eb70e2..d072b8cb 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -1,8 +1,13 @@ -name: Deploy to GitHub Pages +name: Deploy Documentation to GitHub Pages on: push: branches: [main] + paths: + - 'docs/**' + - 'src/**/*.ts' + - 'src/**/*.tsx' + - '.github/workflows/deploy-gh-pages.yml' workflow_dispatch: permissions: @@ -17,7 +22,7 @@ concurrency: jobs: build: - name: Build Staging Web App + name: Build Documentation runs-on: ubuntu-latest timeout-minutes: 10 @@ -25,35 +30,110 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c # v4 + # Reads version from packageManager field in package.json + + - name: Use Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 with: - version: 8 + node-version-file: '.nvmrc' + cache: 'pnpm' - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - - name: Build web app - run: npx expo export --platform web + - name: Generate API documentation with TypeDoc + run: | + pnpm add -D typedoc typedoc-plugin-markdown + npx typedoc --out docs-dist/api src/index.ts --plugin typedoc-plugin-markdown || true - - name: Inject no-cache headers for staging + - name: Build documentation site run: | - # Add no-cache meta tags to all HTML files for staging environment - find dist -name "*.html" -exec sed -i 's//\n \n \n /' {} \; - echo "Injected no-cache headers into $(find dist -name '*.html' | wc -l) HTML files" + # Create a simple docs site from markdown + mkdir -p docs-dist + + # Copy documentation files + cp -r docs/* docs-dist/ 2>/dev/null || true + cp README.md docs-dist/ 2>/dev/null || true + cp CLAUDE.md docs-dist/ 2>/dev/null || true + cp DECISIONS.md docs-dist/ 2>/dev/null || true + + # Create index.html + cat > docs-dist/index.html << 'HTMLEOF' + + + + + + ThumbCode Documentation + + + + +

ThumbCode Documentation

+

Code with your thumbs. A decentralized multi-agent mobile development platform.

+ +
+

Quick Links

+ +
+ + + +
+

Brand Guidelines

+ +
+ + + HTMLEOF - name: Setup Pages uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 with: - path: 'dist' + path: 'docs-dist' deploy: name: Deploy to GitHub Pages diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/app.json b/app.json index 4cc111e2..1d140362 100644 --- a/app.json +++ b/app.json @@ -48,8 +48,7 @@ ] ], "experiments": { - "typedRoutes": true, - "baseUrl": "/thumbcode" + "typedRoutes": true }, "extra": { "router": { diff --git a/package.json b/package.json index 8488030e..ececff18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "thumbcode", "version": "0.1.0", + "packageManager": "pnpm@10.11.0", "description": "Code with your thumbs. A decentralized multi-agent mobile development platform.", "main": "expo-router/entry", "scripts": { diff --git a/render.yaml b/render.yaml new file mode 100644 index 00000000..da3278a4 --- /dev/null +++ b/render.yaml @@ -0,0 +1,42 @@ +# Render Blueprint for ThumbCode Staging +# https://render.com/docs/blueprint-spec + +services: + - type: web + name: thumbcode-staging + runtime: static + buildCommand: pnpm install --frozen-lockfile && npx expo export --platform web + staticPublishPath: ./dist + pullRequestPreviewsEnabled: true + headers: + # Security headers + - path: /* + name: X-Frame-Options + value: DENY + - path: /* + name: X-Content-Type-Options + value: nosniff + - path: /* + name: Referrer-Policy + value: strict-origin-when-cross-origin + # Staging: no-cache for HTML to always get fresh builds + - path: /* + name: Cache-Control + value: no-cache, no-store, must-revalidate + # But cache static assets aggressively (they're hashed) + - path: /assets/* + name: Cache-Control + value: public, max-age=31536000, immutable + - path: /_expo/* + name: Cache-Control + value: public, max-age=31536000, immutable + routes: + # SPA fallback - all routes serve index.html + - type: rewrite + source: /* + destination: /index.html + envVars: + - key: NODE_VERSION + value: 22 + - key: PNPM_VERSION + value: 10 From 5a38a49b55552d70a285e4a20715ebd15b99f85b Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:13:02 -0600 Subject: [PATCH 03/12] fix(infra): address code review findings ## Fixes from code review ### Critical - Fix EXPO_TOKEN secret check to use env block pattern (prevents secret leakage) ### Major - Add pull_request trigger to cd.yml so eas-update job will run - Add path filters to conserve EAS build minutes (src/**, app.json, eas.json) - Add typedoc and typedoc-plugin-markdown to devDependencies - Fix render.yaml: add corepack enable for packageManager support - Remove runtime typedoc installation in deploy-gh-pages.yml ### Minor - Pin exact Node version in .nvmrc (22.12.0) - Add HSTS header (Strict-Transport-Security) - Add Permissions-Policy header for additional security Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 35 ++++++++++++++++++++------- .github/workflows/deploy-gh-pages.yml | 4 +-- .nvmrc | 2 +- package.json | 2 ++ render.yaml | 8 +++++- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index fb6fa90e..77dfaffc 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,6 +3,17 @@ name: CD on: push: branches: [main] + paths: + - 'src/**' + - 'app.json' + - 'eas.json' + - 'package.json' + pull_request: + branches: [main] + paths: + - 'src/**' + - 'app.json' + - 'eas.json' workflow_dispatch: inputs: platform: @@ -49,11 +60,13 @@ jobs: steps: - name: Check for EXPO_TOKEN + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: | - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "EXPO_TOKEN secret is required for EAS operations" + if [ -z "$EXPO_TOKEN" ]; then + echo "::warning::EXPO_TOKEN secret is not configured. Skipping EAS update." echo "Learn more: https://docs.expo.dev/eas-update/github-actions" - exit 1 + exit 0 fi - name: Checkout code @@ -79,16 +92,18 @@ jobs: build-android: name: Build Android if: | - github.event_name == 'push' || + (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')) runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Check for EXPO_TOKEN + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: | - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "EXPO_TOKEN secret is required for EAS builds" + if [ -z "$EXPO_TOKEN" ]; then + echo "::error::EXPO_TOKEN secret is required for EAS builds" exit 1 fi @@ -115,16 +130,18 @@ jobs: build-ios: name: Build iOS if: | - github.event_name == 'push' || + (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')) runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Check for EXPO_TOKEN + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: | - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "EXPO_TOKEN secret is required for EAS builds" + if [ -z "$EXPO_TOKEN" ]; then + echo "::error::EXPO_TOKEN secret is required for EAS builds" exit 1 fi diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index d072b8cb..108d724d 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -44,9 +44,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Generate API documentation with TypeDoc - run: | - pnpm add -D typedoc typedoc-plugin-markdown - npx typedoc --out docs-dist/api src/index.ts --plugin typedoc-plugin-markdown || true + run: npx typedoc --out docs-dist/api src/index.ts --plugin typedoc-plugin-markdown - name: Build documentation site run: | diff --git a/.nvmrc b/.nvmrc index 2bd5a0a9..1d9b7831 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +22.12.0 diff --git a/package.json b/package.json index ececff18..e43a374c 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,8 @@ "jest-expo": "~52.0.6", "react-test-renderer": "18.3.1", "serve": "^14.2.5", + "typedoc": "^0.28.0", + "typedoc-plugin-markdown": "^5.0.0", "typescript": "~5.6.0" }, "pnpm": { diff --git a/render.yaml b/render.yaml index da3278a4..15556f30 100644 --- a/render.yaml +++ b/render.yaml @@ -5,7 +5,7 @@ services: - type: web name: thumbcode-staging runtime: static - buildCommand: pnpm install --frozen-lockfile && npx expo export --platform web + buildCommand: corepack enable && pnpm install --frozen-lockfile && npx expo export --platform web staticPublishPath: ./dist pullRequestPreviewsEnabled: true headers: @@ -19,6 +19,12 @@ services: - path: /* name: Referrer-Policy value: strict-origin-when-cross-origin + - path: /* + name: Strict-Transport-Security + value: max-age=31536000; includeSubDomains + - path: /* + name: Permissions-Policy + value: camera=(), microphone=(), geolocation=() # Staging: no-cache for HTML to always get fresh builds - path: /* name: Cache-Control From e332f68720a37aa75595347294d64b5317cea827 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:17:04 -0600 Subject: [PATCH 04/12] fix(cd): add Android submission credentials + fix render.yaml header precedence CodeRabbit review fixes: - Add EXPO_ANDROID_SERVICE_ACCOUNT_KEY_PATH env var to submit-android job matching the iOS submission pattern - Reorder render.yaml Cache-Control headers: specific paths (/assets/*, /_expo/*) now come before general /* to avoid nondeterministic matching Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 2 ++ render.yaml | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 77dfaffc..d0938a61 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -191,6 +191,8 @@ jobs: - name: Submit to Play Store run: eas submit --platform android --latest --non-interactive + env: + EXPO_ANDROID_SERVICE_ACCOUNT_KEY_PATH: ${{ secrets.EXPO_ANDROID_SERVICE_ACCOUNT_KEY_PATH }} submit-ios: name: Submit to App Store diff --git a/render.yaml b/render.yaml index 15556f30..0af11634 100644 --- a/render.yaml +++ b/render.yaml @@ -25,17 +25,18 @@ services: - path: /* name: Permissions-Policy value: camera=(), microphone=(), geolocation=() - # Staging: no-cache for HTML to always get fresh builds - - path: /* - name: Cache-Control - value: no-cache, no-store, must-revalidate - # But cache static assets aggressively (they're hashed) + # Cache static assets aggressively (hashed filenames) + # NOTE: Specific paths must come BEFORE /* to avoid nondeterministic matching - path: /assets/* name: Cache-Control value: public, max-age=31536000, immutable - path: /_expo/* name: Cache-Control value: public, max-age=31536000, immutable + # Staging: no-cache for HTML to always get fresh builds + - path: /* + name: Cache-Control + value: no-cache, no-store, must-revalidate routes: # SPA fallback - all routes serve index.html - type: rewrite From b111e5be3355c0380d25ade920d63aaa3a9ffa44 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:20:49 -0600 Subject: [PATCH 05/12] fix(cd): remove app store submissions pending credential setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove submit-android and submit-ios jobs (see issue #66) - Fix typedoc-plugin-markdown version (^5.0.0 → ^4.9.0) - Update pnpm-lock.yaml with corrected dependencies Current release strategy: - Web staging: Render.com (automatic via render.yaml) - Mobile builds: Download from expo.dev after EAS build - App store submissions: Pending credential configuration Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 66 ++-------------- package.json | 2 +- pnpm-lock.yaml | 161 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 159 insertions(+), 70 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d0938a61..99310574 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -163,62 +163,12 @@ jobs: eas build --platform ios --profile $PROFILE --non-interactive --no-wait # ============================================ - # Production Deployment (App Store / Play Store) + # NOTE: App Store Submissions # ============================================ - submit-android: - name: Submit to Play Store - if: | - github.event_name == 'workflow_dispatch' && - github.event.inputs.profile == 'production' && - (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android') - needs: build-android - runs-on: ubuntu-latest - timeout-minutes: 30 - environment: production - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup ThumbCode environment - uses: ./.github/actions/setup-thumbcode - - - name: Setup Expo and EAS - uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Submit to Play Store - run: eas submit --platform android --latest --non-interactive - env: - EXPO_ANDROID_SERVICE_ACCOUNT_KEY_PATH: ${{ secrets.EXPO_ANDROID_SERVICE_ACCOUNT_KEY_PATH }} - - submit-ios: - name: Submit to App Store - if: | - github.event_name == 'workflow_dispatch' && - github.event.inputs.profile == 'production' && - (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios') - needs: build-ios - runs-on: ubuntu-latest - timeout-minutes: 30 - environment: production - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup ThumbCode environment - uses: ./.github/actions/setup-thumbcode - - - name: Setup Expo and EAS - uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Submit to App Store - run: eas submit --platform ios --latest --non-interactive - env: - EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }} + # Production submission jobs are not yet configured. + # See issue #66 for required credentials and setup. + # + # Current release strategy: + # - Web staging: Automatic via Render.com (render.yaml) + # - Mobile builds: Download from expo.dev after EAS build completes + # - Debug APK: Can be downloaded from expo.dev build artifacts diff --git a/package.json b/package.json index e43a374c..b6328eb4 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "react-test-renderer": "18.3.1", "serve": "^14.2.5", "typedoc": "^0.28.0", - "typedoc-plugin-markdown": "^5.0.0", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "~5.6.0" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c2f0836..5e20c1a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,7 +109,7 @@ importers: version: 4.17.22 nativewind: specifier: ^4.1.0 - version: 4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)) + version: 4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) openai: specifier: ^4.70.0 version: 4.104.0(ws@8.19.0)(zod@3.25.76) @@ -145,7 +145,7 @@ importers: version: 0.19.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwindcss: specifier: ^3.4.0 - version: 3.4.19(tsx@4.21.0) + version: 3.4.19(tsx@4.21.0)(yaml@2.8.2) zod: specifier: ^3.23.0 version: 3.25.76 @@ -216,6 +216,12 @@ importers: serve: specifier: ^14.2.5 version: 14.2.5 + typedoc: + specifier: ^0.28.0 + version: 0.28.16(typescript@5.6.3) + typedoc-plugin-markdown: + specifier: ^4.9.0 + version: 4.9.0(typedoc@0.28.16(typescript@5.6.3)) typescript: specifier: ~5.6.0 version: 5.6.3 @@ -350,7 +356,7 @@ importers: version: 4.0.22(ca3003e1f7f8d1ffc6d1fe044df18e11) nativewind: specifier: ^4.1.0 - version: 4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)) + version: 4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) react: specifier: '>=18.0.0' version: 18.3.1 @@ -1518,6 +1524,9 @@ packages: resolution: {integrity: sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw==} hasBin: true + '@gerrit0/mini-shiki@3.21.0': + resolution: {integrity: sha512-9PrsT5DjZA+w3lur/aOIx3FlDeHdyCEFlv9U+fmsVyjPZh61G5SYURQ/1ebe2U63KbDmI2V8IhIUegWb8hjOyg==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2002,6 +2011,21 @@ packages: '@segment/loosely-validate-event@2.0.0': resolution: {integrity: sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==} + '@shikijs/engine-oniguruma@3.21.0': + resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + + '@shikijs/langs@3.21.0': + resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + + '@shikijs/themes@3.21.0': + resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + + '@shikijs/types@3.21.0': + resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -2062,6 +2086,9 @@ packages: '@types/hammerjs@2.0.46': resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2118,6 +2145,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -4711,6 +4741,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + loader-runner@4.3.1: resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} @@ -4787,6 +4820,9 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -4801,6 +4837,10 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} @@ -4819,6 +4859,9 @@ packages: mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -5447,6 +5490,10 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6345,6 +6392,19 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedoc-plugin-markdown@4.9.0: + resolution: {integrity: sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.16: + resolution: {integrity: sha512-x4xW77QC3i5DUFMBp0qjukOTnr/sSg+oEs86nB3LjDslvAmwe/PUGDWbe3GrIqt59oTqoXK5GRK9tAa0sYMiog==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x + typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -6358,6 +6418,9 @@ packages: resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -6696,6 +6759,11 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -8246,6 +8314,14 @@ snapshots: find-up: 5.0.0 js-yaml: 4.1.1 + '@gerrit0/mini-shiki@3.21.0': + dependencies: + '@shikijs/engine-oniguruma': 3.21.0 + '@shikijs/langs': 3.21.0 + '@shikijs/themes': 3.21.0 + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -9002,6 +9078,26 @@ snapshots: component-type: 1.2.2 join-component: 1.1.0 + '@shikijs/engine-oniguruma@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/themes@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/types@3.21.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -9075,6 +9171,10 @@ snapshots: '@types/hammerjs@2.0.46': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -9138,6 +9238,8 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/unist@3.0.3': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -12318,6 +12420,10 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + loader-runner@4.3.1: {} locate-path@3.0.0: @@ -12379,6 +12485,8 @@ snapshots: dependencies: yallist: 3.1.1 + lunr@2.3.9: {} + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -12394,6 +12502,15 @@ snapshots: dependencies: tmpl: 1.0.5 + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + marky@1.3.0: {} math-intrinsics@1.1.0: {} @@ -12410,6 +12527,8 @@ snapshots: mdn-data@2.0.14: {} + mdurl@2.0.0: {} + memoize-one@5.2.1: {} memoize-one@6.0.0: {} @@ -12681,12 +12800,12 @@ snapshots: napi-postinstall@0.3.4: {} - nativewind@4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)): + nativewind@4.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)): dependencies: comment-json: 4.5.1 debug: 4.4.3 - react-native-css-interop: 0.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)) - tailwindcss: 3.4.19(tsx@4.21.0) + react-native-css-interop: 0.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - react - react-native @@ -13016,13 +13135,14 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.6 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.6 tsx: 4.21.0 + yaml: 2.8.2 postcss-nested@6.2.0(postcss@8.5.6): dependencies: @@ -13092,6 +13212,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode.js@2.3.1: {} + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -13164,7 +13286,7 @@ snapshots: react-is@19.2.3: {} - react-native-css-interop@0.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)): + react-native-css-interop@0.2.1(react-native-reanimated@3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-svg@15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/helper-module-imports': 7.28.6 '@babel/traverse': 7.28.6 @@ -13175,7 +13297,7 @@ snapshots: react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1) react-native-reanimated: 3.16.7(@babel/core@7.28.6)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) semver: 7.7.3 - tailwindcss: 3.4.19(tsx@4.21.0) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: react-native-safe-area-context: 4.12.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) react-native-svg: 15.8.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) @@ -13960,7 +14082,7 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@3.4.19(tsx@4.21.0): + tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -13979,7 +14101,7 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.11 @@ -14178,12 +14300,27 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typedoc-plugin-markdown@4.9.0(typedoc@0.28.16(typescript@5.6.3)): + dependencies: + typedoc: 0.28.16(typescript@5.6.3) + + typedoc@0.28.16(typescript@5.6.3): + dependencies: + '@gerrit0/mini-shiki': 3.21.0 + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + typescript: 5.6.3 + yaml: 2.8.2 + typescript@5.6.3: {} ua-parser-js@0.7.41: {} ua-parser-js@1.0.41: {} + uc.micro@2.1.0: {} + uglify-js@3.19.3: optional: true @@ -14522,6 +14659,8 @@ snapshots: yallist@5.0.0: {} + yaml@2.8.2: {} + yargs-parser@21.1.1: {} yargs@17.7.2: From 7cdcfd1758b53230a2ec2e60ac515543f91941b7 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:23:10 -0600 Subject: [PATCH 06/12] fix(cd): properly skip EAS update steps when EXPO_TOKEN missing Use step outputs and conditionals to skip subsequent steps when EXPO_TOKEN is not configured, instead of just exiting the check step with code 0 (which doesn't stop subsequent steps). Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 99310574..0f572d15 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -60,28 +60,35 @@ jobs: steps: - name: Check for EXPO_TOKEN + id: check-token env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: | if [ -z "$EXPO_TOKEN" ]; then echo "::warning::EXPO_TOKEN secret is not configured. Skipping EAS update." echo "Learn more: https://docs.expo.dev/eas-update/github-actions" - exit 0 + echo "has_token=false" >> $GITHUB_OUTPUT + else + echo "has_token=true" >> $GITHUB_OUTPUT fi - name: Checkout code + if: steps.check-token.outputs.has_token == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup ThumbCode environment + if: steps.check-token.outputs.has_token == 'true' uses: ./.github/actions/setup-thumbcode - name: Setup Expo and EAS + if: steps.check-token.outputs.has_token == 'true' uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 with: eas-version: latest token: ${{ secrets.EXPO_TOKEN }} - name: Create preview update + if: steps.check-token.outputs.has_token == 'true' uses: expo/expo-github-action/preview@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 with: command: eas update --auto From 15edabe15746ce534b3f54bed8ca0d45535220a0 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:27:15 -0600 Subject: [PATCH 07/12] fix(cd): add package.json to PR path filters Dependency changes should trigger EAS Update PR previews to keep previews in sync with package changes. Addresses CodeRabbit review comment. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0f572d15..3462d62a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,6 +14,7 @@ on: - 'src/**' - 'app.json' - 'eas.json' + - 'package.json' workflow_dispatch: inputs: platform: From 04a9270f04dc4bf9b34397ff74af3b22e6b4485d Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:44:00 -0600 Subject: [PATCH 08/12] feat(ci): add EAS Workflows for automated CI/CD - development-builds.yml: PR builds for Android + iOS simulator - production-deploy.yml: Smart deployment with fingerprinting - Skips rebuilds when native code unchanged (OTA updates) - Parallel iOS/Android builds when needed - preview-update.yml: OTA updates for feature branches Enables full mobile CI/CD pipeline without GitHub Actions. Co-Authored-By: Claude Opus 4.5 --- .eas/workflows/development-builds.yml | 22 ++++++++++ .eas/workflows/preview-update.yml | 14 ++++++ .eas/workflows/production-deploy.yml | 63 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 .eas/workflows/development-builds.yml create mode 100644 .eas/workflows/preview-update.yml create mode 100644 .eas/workflows/production-deploy.yml diff --git a/.eas/workflows/development-builds.yml b/.eas/workflows/development-builds.yml new file mode 100644 index 00000000..541e5ff2 --- /dev/null +++ b/.eas/workflows/development-builds.yml @@ -0,0 +1,22 @@ +name: Create development builds + +on: + pull_request: + branches: + - main + - release/* + +jobs: + build_android: + name: Build Android (dev) + type: build + params: + platform: android + profile: development + + build_ios_simulator: + name: Build iOS Simulator (dev) + type: build + params: + platform: ios + profile: preview diff --git a/.eas/workflows/preview-update.yml b/.eas/workflows/preview-update.yml new file mode 100644 index 00000000..cb317dd6 --- /dev/null +++ b/.eas/workflows/preview-update.yml @@ -0,0 +1,14 @@ +name: Publish preview update + +on: + push: + branches: + - '*' + - '!main' + +jobs: + publish_preview: + name: Publish preview update + type: update + params: + branch: ${{ github.ref_name || 'preview' }} diff --git a/.eas/workflows/production-deploy.yml b/.eas/workflows/production-deploy.yml new file mode 100644 index 00000000..d003ef19 --- /dev/null +++ b/.eas/workflows/production-deploy.yml @@ -0,0 +1,63 @@ +name: Deploy to production + +on: + push: + branches: + - main + +jobs: + fingerprint: + name: Fingerprint + type: fingerprint + + get_android_build: + name: Check existing Android build + needs: [fingerprint] + type: get-build + params: + fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }} + profile: production + + get_ios_build: + name: Check existing iOS build + needs: [fingerprint] + type: get-build + params: + fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} + profile: production + + build_android: + name: Build Android + needs: [get_android_build] + if: ${{ !needs.get_android_build.outputs.build_id }} + type: build + params: + platform: android + profile: production + + build_ios: + name: Build iOS + needs: [get_ios_build] + if: ${{ !needs.get_ios_build.outputs.build_id }} + type: build + params: + platform: ios + profile: production + + publish_android_update: + name: Publish Android OTA update + needs: [get_android_build] + if: ${{ needs.get_android_build.outputs.build_id }} + type: update + params: + branch: production + platform: android + + publish_ios_update: + name: Publish iOS OTA update + needs: [get_ios_build] + if: ${{ needs.get_ios_build.outputs.build_id }} + type: update + params: + branch: production + platform: ios From e088254f952c558ed40244fe507b79e99b89ae79 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:48:24 -0600 Subject: [PATCH 09/12] docs: add market research and 1.0 roadmap to development log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added AI coding assistant market analysis ($4.91B → $30.1B market) - Documented competitive landscape (Copilot, Cursor, Claude, etc.) - Identified ThumbCode's unique positioning (mobile-first + BYOK) - Created phased 1.0 execution roadmap - Added 2026 market trends analysis Co-Authored-By: Claude Opus 4.5 --- docs/memory-bank/DEVELOPMENT-LOG.md | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/docs/memory-bank/DEVELOPMENT-LOG.md b/docs/memory-bank/DEVELOPMENT-LOG.md index 61f35858..eb1acc4f 100644 --- a/docs/memory-bank/DEVELOPMENT-LOG.md +++ b/docs/memory-bank/DEVELOPMENT-LOG.md @@ -275,6 +275,82 @@ All compiled into NativeWind-compatible Tailwind config. --- +## Market Research & Competitive Analysis (Jan 18, 2026) + +### AI Coding Assistant Market 2026 + +**Market Size**: $4.91B (2024) → $30.1B projected (2032) at 27.1% CAGR + +**Key Competitors**: +- GitHub Copilot (68% developer adoption) +- ChatGPT (82% usage for coding) +- Claude (41% developer usage) +- Cursor (leading "agentic IDE" with multi-platform integration) +- Windsurf (Codeium's "world's first agentic IDE") +- Various CLI tools: Claude Code, Gemini CLI + +**2026 Trends**: +1. **Quality > Speed** — 2025 was "year of AI speed"; 2026 focuses on quality/governance +2. **Multi-Platform Integration** — Winning pattern: terminal + IDE + web + desktop +3. **Agent Mode** — Autonomous multi-step workflows (refactoring, bug fixing, modules) +4. **Security Concerns** — 48% of AI-generated code has potential vulnerabilities +5. **Mobile "Vibe Coding"** — Emerging category with natural language prompts + +### ThumbCode's Competitive Advantage + +**CRITICAL INSIGHT**: NO major competitor is mobile-first. + +| Differentiator | ThumbCode | Competition | +|----------------|-----------|-------------| +| **Platform** | Mobile-first | Desktop/web-first | +| **Architecture** | Decentralized BYOK | Cloud-hosted | +| **Privacy** | User owns keys | Vendor manages keys | +| **Cost Model** | $0 infrastructure | Per-seat subscriptions | +| **Agent Model** | Multi-agent orchestration | Single assistant | + +ThumbCode addresses the **top market concerns**: +- Privacy/Security → BYOK model with SecureStore +- Cost → Zero per-user cost +- Mobile accessibility → Only mobile-first solution +- Agent capabilities → Full multi-agent orchestration + +--- + +## 1.0 Roadmap Execution Plan + +### Phase 1: Foundation (Current Sprint) +- [x] EAS Workflows (development, production, preview) +- [x] CI/CD pipeline with GitHub Actions +- [ ] Fix GitHub Pages loading issue (#63) +- [ ] Configure staging deployment (Render.com) +- [ ] Environment configuration (.env validation) + +### Phase 2: Core Services (Next Sprint) +- [ ] Credential management (expo-secure-store) +- [ ] Git service (isomorphic-git integration) +- [ ] Zustand state management stores +- [ ] Error handling and logging infrastructure + +### Phase 3: Agent System (Major Milestone) +- [ ] Agent orchestration engine +- [ ] Specialized agents (Architect, Implementer, Reviewer, Tester) +- [ ] Chat system with streaming +- [ ] MCP integration + +### Phase 4: UI/UX Polish +- [ ] Expand component library (40+ components) +- [ ] Implement all screens from Canva designs +- [ ] Guided onboarding flow +- [ ] Accessibility compliance (WCAG AA) + +### Phase 5: Production Hardening +- [ ] Security audit +- [ ] Performance optimization +- [ ] Analytics and monitoring (privacy-focused) +- [ ] App store submission + +--- + ## Open Questions / Future Decisions 1. **Domain registration** — thumbcode.app, thumbcode.dev not yet purchased From e73e8b451a1b64ef3956e12c93c9ddf5a9708eeb Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 20:57:47 -0600 Subject: [PATCH 10/12] fix: address CodeRabbit review findings - Link documentation pages to GitHub rendered markdown instead of raw .md files - Document intentional behavior of skipping in-progress builds in production-deploy.yml Co-Authored-By: Claude Opus 4.5 --- .eas/workflows/production-deploy.yml | 4 ++++ .github/workflows/deploy-gh-pages.yml | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.eas/workflows/production-deploy.yml b/.eas/workflows/production-deploy.yml index d003ef19..501d8d92 100644 --- a/.eas/workflows/production-deploy.yml +++ b/.eas/workflows/production-deploy.yml @@ -10,6 +10,10 @@ jobs: name: Fingerprint type: fingerprint + # NOTE: get-build intentionally uses default behavior (completed builds only). + # If a build is in-progress, we skip and let it complete on its own. + # This prevents duplicate builds and OTA updates that would race with ongoing builds. + # Set wait_for_in_progress: true if you want to wait for in-progress builds instead. get_android_build: name: Check existing Android build needs: [fingerprint] diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index 108d724d..163dd64b 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -108,17 +108,17 @@ jobs:

Brand Guidelines

From 784fbe2407c488f4a8413b3e64b7e4210497b047 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 21:04:28 -0600 Subject: [PATCH 11/12] feat(eas): configure EAS project with proper credentials - Add EAS project ID (36e52acc-b39a-4bfa-a9dc-4a1053e87032) - Update owner from thumbcode to jbcom - Set EXPO_PUBLIC_APP_ENV=production in PR preview workflow to match EAS project slug Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yml | 3 +++ app.config.ts | 4 ++-- app.json | 13 +++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3462d62a..f77b06bc 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -93,6 +93,9 @@ jobs: uses: expo/expo-github-action/preview@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8 with: command: eas update --auto + env: + # Use production slug to match EAS project configuration + EXPO_PUBLIC_APP_ENV: production # ============================================ # EAS Build - Android diff --git a/app.config.ts b/app.config.ts index b9536512..706f2dc1 100644 --- a/app.config.ts +++ b/app.config.ts @@ -136,7 +136,7 @@ export default ({ config }: ConfigContext): ExpoConfig => { // EAS configuration eas: { - projectId: process.env.EXPO_PROJECT_ID || '', + projectId: process.env.EXPO_PROJECT_ID || '36e52acc-b39a-4bfa-a9dc-4a1053e87032', }, // Public environment variables (accessible at runtime) @@ -144,6 +144,6 @@ export default ({ config }: ConfigContext): ExpoConfig => { githubClientId: process.env.EXPO_PUBLIC_GITHUB_CLIENT_ID || '', }, - owner: process.env.EXPO_OWNER || 'thumbcode', + owner: process.env.EXPO_OWNER || 'jbcom', }; }; diff --git a/app.json b/app.json index 1d140362..a4258653 100644 --- a/app.json +++ b/app.json @@ -13,7 +13,9 @@ "resizeMode": "contain", "backgroundColor": "#FEF8F0" }, - "assetBundlePatterns": ["**/*"], + "assetBundlePatterns": [ + "**/*" + ], "ios": { "supportsTablet": true, "bundleIdentifier": "com.thumbcode.app", @@ -30,7 +32,10 @@ }, "package": "com.thumbcode.app", "versionCode": 1, - "permissions": ["USE_BIOMETRIC", "USE_FINGERPRINT"] + "permissions": [ + "USE_BIOMETRIC", + "USE_FINGERPRINT" + ] }, "web": { "bundler": "metro", @@ -55,9 +60,9 @@ "origin": false }, "eas": { - "projectId": "${EXPO_PROJECT_ID:-your-project-id}" + "projectId": "36e52acc-b39a-4bfa-a9dc-4a1053e87032" } }, - "owner": "${EXPO_OWNER:-thumbcode}" + "owner": "jbcom" } } From 3e0684b7c361ae17b4028c38689d186f50de2c36 Mon Sep 17 00:00:00 2001 From: Jon B Date: Sun, 18 Jan 2026 21:06:49 -0600 Subject: [PATCH 12/12] feat(eas): add expo-updates for OTA support Required for EAS Update PR previews in CI workflow Co-Authored-By: Claude Opus 4.5 --- app.json | 9 +-- package.json | 3 +- pnpm-lock.yaml | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 8 deletions(-) diff --git a/app.json b/app.json index a4258653..9f16d81b 100644 --- a/app.json +++ b/app.json @@ -13,9 +13,7 @@ "resizeMode": "contain", "backgroundColor": "#FEF8F0" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "bundleIdentifier": "com.thumbcode.app", @@ -32,10 +30,7 @@ }, "package": "com.thumbcode.app", "versionCode": 1, - "permissions": [ - "USE_BIOMETRIC", - "USE_FINGERPRINT" - ] + "permissions": ["USE_BIOMETRIC", "USE_FINGERPRINT"] }, "web": { "bundler": "metro", diff --git a/package.json b/package.json index b6328eb4..ce1c447b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@anthropic-ai/sdk": "^0.32.0", "@react-native-async-storage/async-storage": "^2.0.0", "@react-native-community/netinfo": "^11.4.1", - "react-native-ssl-public-key-pinning": "^1.2.6", "@react-navigation/native": "^7.0.0", "@thumbcode/config": "workspace:*", "@thumbcode/core": "workspace:*", @@ -64,6 +63,7 @@ "expo-secure-store": "~14.0.0", "expo-splash-screen": "~0.29.0", "expo-status-bar": "~2.0.0", + "expo-updates": "^29.0.16", "expo-web-browser": "~14.0.0", "immer": "^10.2.0", "isomorphic-git": "^1.27.0", @@ -77,6 +77,7 @@ "react-native-reanimated": "~3.16.0", "react-native-safe-area-context": "~4.12.0", "react-native-screens": "~4.0.0", + "react-native-ssl-public-key-pinning": "^1.2.6", "react-native-svg": "~15.8.0", "react-native-web": "~0.19.13", "tailwindcss": "^3.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e20c1a8..742c9ce1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: expo-status-bar: specifier: ~2.0.0 version: 2.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + expo-updates: + specifier: ^29.0.16 + version: 29.0.16(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) expo-web-browser: specifier: ~14.0.0 version: 14.0.2(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)) @@ -1445,15 +1448,27 @@ packages: '@expo/code-signing-certificates@0.0.5': resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==} + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} + + '@expo/config-plugins@54.0.4': + resolution: {integrity: sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==} + '@expo/config-plugins@9.0.17': resolution: {integrity: sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg==} '@expo/config-types@52.0.5': resolution: {integrity: sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA==} + '@expo/config-types@54.0.10': + resolution: {integrity: sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA==} + '@expo/config@10.0.11': resolution: {integrity: sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww==} + '@expo/config@12.0.13': + resolution: {integrity: sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==} + '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} @@ -1494,6 +1509,9 @@ packages: '@expo/plist@0.2.2': resolution: {integrity: sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g==} + '@expo/plist@0.4.8': + resolution: {integrity: sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==} + '@expo/prebuild-config@8.2.0': resolution: {integrity: sha512-CxiPpd980s0jyxi7eyN3i/7YKu3XL+8qPjBZUCYtc0+axpGweqIkq2CslyLSKHyqVyH/zlPkbVgWdyiYavFS5Q==} @@ -1683,6 +1701,14 @@ packages: cpu: [x64] os: [win32] + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2508,6 +2534,9 @@ packages: arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + arg@4.1.0: + resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -3616,6 +3645,9 @@ packages: peerDependencies: expo: '*' + expo-eas-client@1.0.8: + resolution: {integrity: sha512-5or11NJhSeDoHHI6zyvQDW2cz/yFyE+1Cz8NTs5NK8JzC7J0JrkUgptWtxyfB6Xs/21YRNifd3qgbBN3hfKVgA==} + expo-file-system@18.0.12: resolution: {integrity: sha512-HAkrd/mb8r+G3lJ9MzmGeuW2B+BxQR1joKfeCyY4deLl1zoZ48FrAWjgZjHK9aHUVhJ0ehzInu/NQtikKytaeg==} peerDependencies: @@ -3644,6 +3676,9 @@ packages: react-native-web: optional: true + expo-json-utils@0.15.0: + resolution: {integrity: sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==} + expo-keep-awake@14.0.3: resolution: {integrity: sha512-6Jh94G6NvTZfuLnm2vwIpKe3GdOiVBuISl7FI8GqN0/9UOg9E0WXXp5cDcfAG8bn80RfgLJS8P7EPUGTZyOvhg==} peerDependencies: @@ -3661,6 +3696,11 @@ packages: peerDependencies: expo: '*' + expo-manifests@1.0.10: + resolution: {integrity: sha512-oxDUnURPcL4ZsOBY6X1DGWGuoZgVAFzp6PISWV7lPP2J0r8u1/ucuChBgpK7u1eLGFp6sDIPwXyEUCkI386XSQ==} + peerDependencies: + expo: '*' + expo-modules-autolinking@2.0.8: resolution: {integrity: sha512-DezgnEYFQYic8hKGhkbztBA3QUmSftjaNDIKNAtS2iGJmzCcNIkatjN2slFDSWjSTNo8gOvPQyMKfyHWFvLpOQ==} hasBin: true @@ -3710,6 +3750,22 @@ packages: react: '*' react-native: '*' + expo-structured-headers@5.0.0: + resolution: {integrity: sha512-RmrBtnSphk5REmZGV+lcdgdpxyzio5rJw8CXviHE6qH5pKQQ83fhMEcigvrkBdsn2Efw2EODp4Yxl1/fqMvOZw==} + + expo-updates-interface@2.0.0: + resolution: {integrity: sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg==} + peerDependencies: + expo: '*' + + expo-updates@29.0.16: + resolution: {integrity: sha512-E9/fxRz/Eurtc7hxeI/6ZPyHH3To9Xoccm1kXoICZTRojmuTo+dx0Xv53UHyHn4G5zGMezyaKF2Qtj3AKcT93w==} + hasBin: true + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + expo-web-browser@14.0.2: resolution: {integrity: sha512-Hncv2yojhTpHbP6SGWARBFdl7P6wBHc1O8IKaNsH0a/IEakq887o1eRhLxZ5IwztPQyRDhpqHdgJ+BjWolOnwA==} peerDependencies: @@ -3948,6 +4004,10 @@ packages: resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} engines: {node: '>=6'} + getenv@2.0.0: + resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} + engines: {node: '>=6'} + git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} @@ -3968,6 +4028,10 @@ packages: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} hasBin: true + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -4817,6 +4881,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -4986,6 +5054,10 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5332,6 +5404,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} @@ -8109,6 +8185,29 @@ snapshots: node-forge: 1.3.3 nullthrows: 1.1.1 + '@expo/code-signing-certificates@0.0.6': + dependencies: + node-forge: 1.3.3 + + '@expo/config-plugins@54.0.4': + dependencies: + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.0.8 + '@expo/plist': 0.4.8 + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.0 + resolve-from: 5.0.0 + semver: 7.7.3 + slash: 3.0.0 + slugify: 1.6.6 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + '@expo/config-plugins@9.0.17': dependencies: '@expo/config-types': 52.0.5 @@ -8130,6 +8229,8 @@ snapshots: '@expo/config-types@52.0.5': {} + '@expo/config-types@54.0.10': {} + '@expo/config@10.0.11': dependencies: '@babel/code-frame': 7.10.4 @@ -8148,6 +8249,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/config@12.0.13': + dependencies: + '@babel/code-frame': 7.10.4 + '@expo/config-plugins': 54.0.4 + '@expo/config-types': 54.0.10 + '@expo/json-file': 10.0.8 + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.0 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + resolve-workspace-root: 2.0.1 + semver: 7.7.3 + slugify: 1.6.6 + sucrase: 3.35.1 + transitivePeerDependencies: + - supports-color + '@expo/devcert@1.2.1': dependencies: '@expo/sudo-prompt': 9.3.2 @@ -8256,6 +8375,12 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 14.0.0 + '@expo/plist@0.4.8': + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + '@expo/prebuild-config@8.2.0': dependencies: '@expo/config': 10.0.11 @@ -8431,6 +8556,12 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -9617,6 +9748,8 @@ snapshots: arch@2.2.0: {} + arg@4.1.0: {} + arg@5.0.2: {} argparse@1.0.10: @@ -10981,6 +11114,8 @@ snapshots: expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) ua-parser-js: 0.7.41 + expo-eas-client@1.0.8: {} + expo-file-system@18.0.12(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)): dependencies: expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) @@ -11005,6 +11140,8 @@ snapshots: optionalDependencies: react-native-web: 0.19.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + expo-json-utils@0.15.0: {} + expo-keep-awake@14.0.3(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) @@ -11025,6 +11162,14 @@ snapshots: expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) invariant: 2.2.4 + expo-manifests@1.0.10(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)): + dependencies: + '@expo/config': 12.0.13 + expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + expo-json-utils: 0.15.0 + transitivePeerDependencies: + - supports-color + expo-modules-autolinking@2.0.8: dependencies: '@expo/spawn-async': 1.7.2 @@ -11100,6 +11245,34 @@ snapshots: react: 18.3.1 react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1) + expo-structured-headers@5.0.0: {} + + expo-updates-interface@2.0.0(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)): + dependencies: + expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + + expo-updates@29.0.16(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1): + dependencies: + '@expo/code-signing-certificates': 0.0.6 + '@expo/plist': 0.4.8 + '@expo/spawn-async': 1.7.2 + arg: 4.1.0 + chalk: 4.1.2 + debug: 4.4.3 + expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + expo-eas-client: 1.0.8 + expo-manifests: 1.0.10(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)) + expo-structured-headers: 5.0.0 + expo-updates-interface: 2.0.0(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1)) + getenv: 2.0.0 + glob: 13.0.0 + ignore: 5.3.2 + react: 18.3.1 + react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1) + resolve-from: 5.0.0 + transitivePeerDependencies: + - supports-color + expo-web-browser@14.0.2(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)): dependencies: expo: 52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1)))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) @@ -11380,6 +11553,8 @@ snapshots: getenv@1.0.0: {} + getenv@2.0.0: {} + git-raw-commits@4.0.0: dependencies: dargs: 8.1.0 @@ -11405,6 +11580,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@13.0.0: + dependencies: + minimatch: 10.1.1 + minipass: 7.1.2 + path-scurry: 2.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -12481,6 +12662,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.4: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -12744,6 +12927,10 @@ snapshots: min-indent@1.0.1: {} + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -13079,6 +13266,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.4 + minipass: 7.1.2 + path-to-regexp@3.3.0: {} path-type@4.0.0: {}