diff --git a/.cursor/rules/asset_configuration.mdc b/.cursor/rules/asset_configuration.mdc
new file mode 100644
index 00000000..916ecdd6
--- /dev/null
+++ b/.cursor/rules/asset_configuration.mdc
@@ -0,0 +1,32 @@
+---
+alwaysApply: true
+---
+# Asset Configuration Directive
+*Scope: Assets Only (icons, splashes, image pipelines) — not overall build orchestration*
+
+## Intent
+- Version **asset configuration files** (optionally dev-time generated).
+- **Do not** version platform asset outputs (Android/iOS/Electron); generate them **at build-time** with standard tools.
+- Keep existing per-platform build scripts unchanged.
+
+## Source of Truth
+- **Preferred (Capacitor default):** `resources/` as the single master source.
+- **Alternative:** `assets/` is acceptable **only** if `capacitor-assets` is explicitly configured to read from it.
+- **Never** maintain both `resources/` and `assets/` as parallel sources. Migrate and delete the redundant folder.
+
+## Config Files
+- Live under: `config/assets/` (committed).
+- Examples:
+ - `config/assets/capacitor-assets.config.json` (or the path the tool expects)
+ - `config/assets/android.assets.json`
+ - `config/assets/ios.assets.json`
+ - `config/assets/common.assets.yaml` (optional shared layer)
+- **Dev-time generation allowed** for these configs; **build-time generation is forbidden**.
+
+## Build-Time Behavior
+- Build generates platform assets (not configs) using the standard chain:
+ ```bash
+ npm run build:capacitor # web build via Vite (.mts)
+ npx cap sync
+ npx capacitor-assets generate # produces platform assets; not committed
+ # then platform-specific build steps
diff --git a/.github/workflows/asset-validation.yml b/.github/workflows/asset-validation.yml
new file mode 100644
index 00000000..72cd2be0
--- /dev/null
+++ b/.github/workflows/asset-validation.yml
@@ -0,0 +1,142 @@
+name: Asset Validation & CI Safeguards
+
+on:
+ pull_request:
+ paths:
+ - 'resources/**'
+ - 'config/assets/**'
+ - 'capacitor-assets.config.json'
+ - 'capacitor.config.ts'
+ - 'capacitor.config.json'
+ push:
+ branches: [main, develop]
+ paths:
+ - 'resources/**'
+ - 'config/assets/**'
+ - 'capacitor-assets.config.json'
+ - 'capacitor.config.ts'
+ - 'capacitor.config.json'
+
+jobs:
+ asset-validation:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Validate asset configuration
+ run: npm run assets:validate
+
+ - name: Check for committed platform assets (Android)
+ run: |
+ if git ls-files -z android/app/src/main/res | grep -E '(AppIcon.*\.png|Splash.*\.png|mipmap-.*/ic_launcher.*\.png)' > /dev/null; then
+ echo "❌ Android platform assets found in VCS - these should be generated at build-time"
+ git ls-files -z android/app/src/main/res | grep -E '(AppIcon.*\.png|Splash.*\.png|mipmap-.*/ic_launcher.*\.png)'
+ exit 1
+ fi
+ echo "✅ No Android platform assets committed"
+
+ - name: Check for committed platform assets (iOS)
+ run: |
+ if git ls-files -z ios/App/App/Assets.xcassets | grep -E '(AppIcon.*\.png|Splash.*\.png)' > /dev/null; then
+ echo "❌ iOS platform assets found in VCS - these should be generated at build-time"
+ git ls-files -z ios/App/App/Assets.xcassets | grep -E '(AppIcon.*\.png|Splash.*\.png)'
+ exit 1
+ fi
+ echo "✅ No iOS platform assets committed"
+
+ - name: Test asset generation
+ run: |
+ echo "🧪 Testing asset generation workflow..."
+ npm run build:capacitor
+ npx cap sync
+ npx capacitor-assets generate --dry-run || npx capacitor-assets generate
+ echo "✅ Asset generation test completed"
+
+ - name: Verify clean tree after build
+ run: |
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "❌ Dirty tree after build - asset configs were modified"
+ git status
+ git diff
+ exit 1
+ fi
+ echo "✅ Build completed with clean tree"
+
+ schema-validation:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Validate schema compliance
+ run: |
+ echo "🔍 Validating schema compliance..."
+ node -e "
+ const fs = require('fs');
+ const config = JSON.parse(fs.readFileSync('capacitor-assets.config.json', 'utf8'));
+ const schema = JSON.parse(fs.readFileSync('config/assets/schema.json', 'utf8'));
+
+ // Basic schema validation
+ if (!config.icon || !config.splash) {
+ throw new Error('Missing required sections: icon and splash');
+ }
+
+ if (!config.icon.source || !config.splash.source) {
+ throw new Error('Missing required source fields');
+ }
+
+ if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) {
+ throw new Error('Icon source must be in resources/ directory');
+ }
+
+ if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) {
+ throw new Error('Splash source must be in resources/ directory');
+ }
+
+ console.log('✅ Schema validation passed');
+ "
+
+ - name: Check source file existence
+ run: |
+ echo "📁 Checking source file existence..."
+ node -e "
+ const fs = require('fs');
+ const config = JSON.parse(fs.readFileSync('capacitor-assets.config.json', 'utf8'));
+
+ const requiredFiles = [
+ config.icon.source,
+ config.splash.source
+ ];
+
+ if (config.splash.darkSource) {
+ requiredFiles.push(config.splash.darkSource);
+ }
+
+ const missingFiles = requiredFiles.filter(file => !fs.existsSync(file));
+
+ if (missingFiles.length > 0) {
+ console.error('❌ Missing source files:', missingFiles);
+ process.exit(1);
+ }
+
+ console.log('✅ All source files exist');
+ "
diff --git a/.gitignore b/.gitignore
index a9f37e49..4202ef2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,10 @@ icons
*.log
+# Build outputs
+dist/
+build/
+
# Generated Android assets and resources (should be generated during build)
android/app/src/main/assets/public/
@@ -64,6 +68,14 @@ android/app/src/main/res/drawable*/
android/app/src/main/res/mipmap*/
android/app/src/main/res/values/ic_launcher_background.xml
+# Android generated assets (deny-listed in CI)
+android/app/src/main/res/mipmap-*/ic_launcher*.png
+android/app/src/main/res/drawable*/splash*.png
+
+# iOS generated assets (deny-listed in CI)
+ios/App/App/Assets.xcassets/**/AppIcon*.png
+ios/App/App/Assets.xcassets/**/Splash*.png
+
# Keep these Android configuration files in version control:
# - android/app/src/main/assets/capacitor.plugins.json
# - android/app/src/main/res/values/strings.xml
diff --git a/.node-version b/.node-version
new file mode 100644
index 00000000..a9d08739
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+18.19.0
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000..a9d08739
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+18.19.0
diff --git a/README.md b/README.md
index 6992ba09..efc9b1ad 100644
--- a/README.md
+++ b/README.md
@@ -136,11 +136,50 @@ const apiUrl = `${APP_SERVER}/api/claim/123`;
See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
-## Icons
+## Asset Management
-Application icons are in the `assets` directory, processed by the `capacitor-assets` command.
+TimeSafari uses a standardized asset configuration system for consistent
+icon and splash screen generation across all platforms.
-To add a Font Awesome icon, add to fontawesome.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name.
+### Asset Sources
+
+- **Single source of truth**: `resources/` directory (Capacitor default)
+- **Source files**: `icon.png`, `splash.png`, `splash_dark.png`
+- **Format**: PNG or SVG files for optimal quality
+
+### Asset Generation
+
+- **Configuration**: `config/assets/capacitor-assets.config.json`
+- **Schema validation**: `config/assets/schema.json`
+- **Build-time generation**: Platform assets generated via `capacitor-assets`
+- **No VCS commits**: Generated assets are never committed to version control
+
+### Development Commands
+
+```bash
+# Generate/update asset configurations
+npm run assets:config
+
+# Validate asset configurations
+npm run assets:validate
+
+# Clean generated platform assets (local dev only)
+npm run assets:clean
+
+# Build with asset generation
+npm run build:native
+```
+
+### Platform Support
+
+- **Android**: Adaptive icons with foreground/background, monochrome support
+- **iOS**: LaunchScreen storyboard preferred, splash assets when needed
+- **Web**: PWA icons generated during build to `dist/` (not committed)
+
+### Font Awesome Icons
+
+To add a Font Awesome icon, add to `fontawesome.ts` and reference with
+`font-awesome` element and `icon` attribute with the hyphenated name.
## Other
diff --git a/android/app/src/main/assets/capacitor.config.json b/android/app/src/main/assets/capacitor.config.json
index 594ebca3..04ab8d0c 100644
--- a/android/app/src/main/assets/capacitor.config.json
+++ b/android/app/src/main/assets/capacitor.config.json
@@ -29,7 +29,7 @@
"splashFullScreen": true,
"splashImmersive": true
},
- "CapacitorSQLite": {
+ "CapSQLite": {
"iosDatabaseLocation": "Library/CapacitorDatabase",
"iosIsEncryption": false,
"iosBiometric": {
diff --git a/assets/README.md b/assets/README.md
deleted file mode 100644
index b9272ff0..00000000
--- a/assets/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-
-Application icons are here. They are processed for android & ios by the `capacitor-assets` command, as indicated in the BUILDING.md file.
diff --git a/capacitor-assets.config.json b/capacitor-assets.config.json
index d56533f4..92bd0414 100644
--- a/capacitor-assets.config.json
+++ b/capacitor-assets.config.json
@@ -1,36 +1,32 @@
{
"icon": {
- "ios": {
- "source": "resources/ios/icon/icon.png",
- "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset"
- },
"android": {
- "source": "resources/android/icon/icon.png",
+ "adaptive": {
+ "background": "#121212",
+ "foreground": "resources/icon.png",
+ "monochrome": "resources/icon.png"
+ },
"target": "android/app/src/main/res"
},
+ "ios": {
+ "padding": 0,
+ "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset"
+ },
+ "source": "resources/icon.png",
"web": {
- "source": "resources/web/icon/icon.png",
"target": "public/img/icons"
}
},
"splash": {
- "ios": {
- "source": "resources/ios/splash/splash.png",
- "target": "ios/App/App/Assets.xcassets/Splash.imageset"
- },
"android": {
- "source": "resources/android/splash/splash.png",
+ "scale": "cover",
"target": "android/app/src/main/res"
- }
- },
- "splashDark": {
+ },
+ "darkSource": "resources/splash_dark.png",
"ios": {
- "source": "resources/ios/splash/splash_dark.png",
- "target": "ios/App/App/Assets.xcassets/SplashDark.imageset"
+ "target": "ios/App/App/Assets.xcassets",
+ "useStoryBoard": true
},
- "android": {
- "source": "resources/android/splash/splash_dark.png",
- "target": "android/app/src/main/res"
- }
+ "source": "resources/splash.png"
}
-}
\ No newline at end of file
+}
diff --git a/capacitor.config.ts b/capacitor.config.ts
new file mode 100644
index 00000000..24ef38c6
--- /dev/null
+++ b/capacitor.config.ts
@@ -0,0 +1,116 @@
+import { CapacitorConfig } from '@capacitor/cli';
+
+const config: CapacitorConfig = {
+ appId: 'app.timesafari',
+ appName: 'TimeSafari',
+ webDir: 'dist',
+ server: {
+ cleartext: true
+ },
+ plugins: {
+ App: {
+ appUrlOpen: {
+ handlers: [
+ {
+ url: 'timesafari://*',
+ autoVerify: true
+ }
+ ]
+ }
+ },
+ SplashScreen: {
+ launchShowDuration: 3000,
+ launchAutoHide: true,
+ backgroundColor: '#ffffff',
+ androidSplashResourceName: 'splash',
+ androidScaleType: 'CENTER_CROP',
+ showSpinner: false,
+ androidSpinnerStyle: 'large',
+ iosSpinnerStyle: 'small',
+ spinnerColor: '#999999',
+ splashFullScreen: true,
+ splashImmersive: true
+ },
+ CapSQLite: {
+ iosDatabaseLocation: 'Library/CapacitorDatabase',
+ iosIsEncryption: false,
+ iosBiometric: {
+ biometricAuth: false,
+ biometricTitle: 'Biometric login for TimeSafari'
+ },
+ androidIsEncryption: false,
+ androidBiometric: {
+ biometricAuth: false,
+ biometricTitle: 'Biometric login for TimeSafari'
+ },
+ electronIsEncryption: false
+ }
+ },
+ ios: {
+ contentInset: 'never',
+ allowsLinkPreview: true,
+ scrollEnabled: true,
+ limitsNavigationsToAppBoundDomains: true,
+ backgroundColor: '#ffffff',
+ allowNavigation: [
+ '*.timesafari.app',
+ '*.jsdelivr.net',
+ 'api.endorser.ch'
+ ]
+ },
+ android: {
+ allowMixedContent: true,
+ captureInput: true,
+ webContentsDebuggingEnabled: false,
+ allowNavigation: [
+ '*.timesafari.app',
+ '*.jsdelivr.net',
+ 'api.endorser.ch',
+ '10.0.2.2:3000'
+ ]
+ },
+ electron: {
+ deepLinking: {
+ schemes: ['timesafari']
+ },
+ buildOptions: {
+ appId: 'app.timesafari',
+ productName: 'TimeSafari',
+ directories: {
+ output: 'dist-electron-packages'
+ },
+ files: [
+ 'dist/**/*',
+ 'electron/**/*'
+ ],
+ mac: {
+ category: 'public.app-category.productivity',
+ target: [
+ {
+ target: 'dmg',
+ arch: ['x64', 'arm64']
+ }
+ ]
+ },
+ win: {
+ target: [
+ {
+ target: 'nsis',
+ arch: ['x64']
+ }
+ ]
+ },
+ linux: {
+ target: [
+ {
+ target: 'AppImage',
+ arch: ['x64']
+ }
+ ],
+ category: 'Utility'
+ }
+ }
+ }
+};
+
+export default config;
diff --git a/config/assets/capacitor-assets.config.json b/config/assets/capacitor-assets.config.json
new file mode 100644
index 00000000..eb12403e
--- /dev/null
+++ b/config/assets/capacitor-assets.config.json
@@ -0,0 +1,32 @@
+{
+ "icon": {
+ "source": "resources/icon.png",
+ "android": {
+ "adaptive": {
+ "foreground": "resources/icon.png",
+ "background": "#121212",
+ "monochrome": "resources/icon.png"
+ },
+ "target": "android/app/src/main/res"
+ },
+ "ios": {
+ "padding": 0,
+ "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset"
+ },
+ "web": {
+ "target": "public/img/icons"
+ }
+ },
+ "splash": {
+ "source": "resources/splash.png",
+ "darkSource": "resources/splash_dark.png",
+ "android": {
+ "scale": "cover",
+ "target": "android/app/src/main/res"
+ },
+ "ios": {
+ "useStoryBoard": true,
+ "target": "ios/App/App/Assets.xcassets"
+ }
+ }
+}
diff --git a/config/assets/schema.json b/config/assets/schema.json
new file mode 100644
index 00000000..89e76c0c
--- /dev/null
+++ b/config/assets/schema.json
@@ -0,0 +1,119 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Capacitor Assets Configuration Schema",
+ "description": "Schema for validating capacitor-assets configuration files",
+ "type": "object",
+ "properties": {
+ "icon": {
+ "type": "object",
+ "properties": {
+ "source": {
+ "type": "string",
+ "pattern": "^resources/.*\\.(png|svg)$",
+ "description": "Source icon file path relative to project root"
+ },
+ "android": {
+ "type": "object",
+ "properties": {
+ "adaptive": {
+ "type": "object",
+ "properties": {
+ "foreground": {
+ "type": "string",
+ "pattern": "^resources/.*\\.(png|svg)$",
+ "description": "Foreground icon for Android adaptive icons"
+ },
+ "background": {
+ "type": ["string", "object"],
+ "description": "Background color or image for adaptive icons"
+ },
+ "monochrome": {
+ "type": "string",
+ "pattern": "^resources/.*\\.(png|svg)$",
+ "description": "Monochrome icon for Android 13+"
+ }
+ },
+ "required": ["foreground", "background"]
+ },
+ "target": {
+ "type": "string",
+ "description": "Android target directory for generated icons"
+ }
+ }
+ },
+ "ios": {
+ "type": "object",
+ "properties": {
+ "padding": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "description": "Padding ratio for iOS icons"
+ },
+ "target": {
+ "type": "string",
+ "description": "iOS target directory for generated icons"
+ }
+ }
+ },
+ "web": {
+ "type": "object",
+ "properties": {
+ "target": {
+ "type": "string",
+ "description": "Web target directory for generated icons"
+ }
+ }
+ }
+ },
+ "required": ["source"],
+ "additionalProperties": false
+ },
+ "splash": {
+ "type": "object",
+ "properties": {
+ "source": {
+ "type": "string",
+ "pattern": "^resources/.*\\.(png|svg)$",
+ "description": "Source splash screen file"
+ },
+ "darkSource": {
+ "type": "string",
+ "pattern": "^resources/.*\\.(png|svg)$",
+ "description": "Dark mode splash screen file"
+ },
+ "android": {
+ "type": "object",
+ "properties": {
+ "scale": {
+ "type": "string",
+ "enum": ["cover", "contain", "fill"],
+ "description": "Android splash screen scaling mode"
+ },
+ "target": {
+ "type": "string",
+ "description": "Android target directory for splash screens"
+ }
+ }
+ },
+ "ios": {
+ "type": "object",
+ "properties": {
+ "useStoryBoard": {
+ "type": "boolean",
+ "description": "Use LaunchScreen storyboard instead of splash assets"
+ },
+ "target": {
+ "type": "string",
+ "description": "iOS target directory for splash screens"
+ }
+ }
+ }
+ },
+ "required": ["source"],
+ "additionalProperties": false
+ }
+ },
+ "required": ["icon", "splash"],
+ "additionalProperties": false
+}
diff --git a/doc/asset-migration-plan.md b/doc/asset-migration-plan.md
new file mode 100644
index 00000000..3a05353c
--- /dev/null
+++ b/doc/asset-migration-plan.md
@@ -0,0 +1,214 @@
+# TimeSafari Asset Configuration Migration Plan
+
+**Author**: Matthew Raymer
+**Date**: 2025-08-14
+**Status**: 🎯 **IMPLEMENTATION** - Ready for Execution
+
+## Overview
+
+This document outlines the migration from the current mixed asset management
+system to a standardized, single-source asset configuration approach using
+`capacitor-assets` as the standard generator.
+
+## Current State Analysis
+
+### Asset Sources (Duplicated)
+
+- **`assets/` directory**: Contains `icon.png`, `splash.png`, `splash_dark.png`
+- **`resources/` directory**: Contains identical files in platform-specific subdirectories
+- **Result**: Duplicate storage, confusion about source of truth
+
+### Asset Generation (Manual)
+
+- **Custom scripts**: `generate-icons.sh`, `generate-ios-assets.sh`, `generate-android-icons.sh`
+- **Bypass capacitor-assets**: Manual ImageMagick-based generation
+- **Inconsistent outputs**: Different generation methods for each platform
+
+### Configuration (Scattered)
+
+- **`capacitor-assets.config.json`**: Basic configuration at root
+- **Platform-specific configs**: Mixed in various build scripts
+- **No validation**: No schema or consistency checks
+
+## Target State
+
+### Single Source of Truth
+
+- **`resources/` directory**: Capacitor default location for source assets
+- **Eliminate duplication**: Remove `assets/` directory after migration
+- **Standardized paths**: All tools read from `resources/`
+
+### Standardized Generation
+
+- **`capacitor-assets`**: Single tool for all platform asset generation
+- **Build-time generation**: Assets generated during build, not committed
+- **Deterministic outputs**: Same inputs → same outputs every time
+
+### Centralized Configuration
+
+- **`config/assets/`**: All asset-related configuration files
+- **Schema validation**: JSON schema for configuration validation
+- **CI safeguards**: Automated validation and compliance checks
+
+## Migration Steps
+
+### Phase 1: Foundation Setup ✅
+
+- [x] Create `config/assets/` directory structure
+- [x] Create asset configuration schema (`schema.json`)
+- [x] Create enhanced capacitor-assets configuration
+- [x] Convert `capacitor.config.json` to `capacitor.config.ts`
+- [x] Pin Node.js version (`.nvmrc`, `.node-version`)
+- [x] Create dev-time asset configuration generator
+- [x] Create asset configuration validator
+- [x] Add npm scripts for asset management
+- [x] Update `.gitignore` with proper asset exclusions
+- [x] Create CI workflow for asset validation
+
+### Phase 2: Validation & Testing
+
+- [ ] Run `npm run assets:config` to generate new configuration
+- [ ] Run `npm run assets:validate` to verify configuration
+- [ ] Test `npm run build:native` workflow
+- [ ] Verify CI workflow passes all checks
+- [ ] Confirm no platform assets are committed to VCS
+
+### Phase 3: Cleanup & Removal
+
+- [ ] Remove `assets/` directory (after validation)
+- [ ] Remove manual asset generation scripts
+- [ ] Remove asset checking scripts
+- [ ] Update documentation references
+- [ ] Final validation of clean state
+
+## Implementation Details
+
+### File Structure
+
+```
+resources/ # Image sources ONLY
+ icon.png
+ splash.png
+ splash_dark.png
+
+config/assets/ # Versioned config & schema
+ capacitor-assets.config.json
+ schema.json
+
+scripts/
+ assets-config.js # Dev-time config generator
+ assets-validator.js # Schema validator
+```
+
+### Configuration Schema
+
+The schema enforces:
+- Source files must be in `resources/` directory
+- Required fields for icon and splash sections
+- Android adaptive icon support (foreground/background/monochrome)
+- iOS LaunchScreen preferences
+- Target directory validation
+
+### CI Safeguards
+
+- **Schema validation**: Configuration must comply with schema
+- **Source file validation**: All referenced files must exist
+- **Platform asset denial**: Reject commits with generated assets
+- **Clean tree enforcement**: Build must not modify committed configs
+
+## Testing Strategy
+
+### Local Validation
+
+```bash
+# Generate configuration
+npm run assets:config
+
+# Validate configuration
+npm run assets:validate
+
+# Test build workflow
+npm run build:native
+
+# Clean generated assets
+npm run assets:clean
+```
+
+### CI Validation
+
+- **Asset validation workflow**: Runs on asset-related changes
+- **Schema compliance**: Ensures configuration follows schema
+- **Source file existence**: Verifies all referenced files exist
+- **Platform asset detection**: Prevents committed generated assets
+- **Build tree verification**: Ensures clean tree after build
+
+## Risk Mitigation
+
+### Data Loss Prevention
+
+- **Backup branch**: Create backup before removing `assets/`
+- **Validation checks**: Multiple validation steps before removal
+- **Gradual migration**: Phase-by-phase approach with rollback capability
+
+### Build Continuity
+
+- **Per-platform scripts unchanged**: All existing build orchestration preserved
+- **Standard toolchain**: Uses capacitor-assets, not custom scripts
+- **Fallback support**: Manual scripts remain until migration complete
+
+### Configuration Consistency
+
+- **Schema enforcement**: JSON schema prevents invalid configurations
+- **CI validation**: Automated checks catch configuration issues
+- **Documentation updates**: Clear guidance for future changes
+
+## Success Criteria
+
+### Technical Requirements
+
+- [ ] Single source of truth in `resources/` directory
+- [ ] All platform assets generated via `capacitor-assets`
+- [ ] No manual asset generation scripts
+- [ ] Configuration validation passes all checks
+- [ ] CI workflow enforces asset policies
+
+### Quality Metrics
+
+- [ ] Zero duplicate asset sources
+- [ ] 100% configuration schema compliance
+- [ ] No platform assets committed to VCS
+- [ ] Clean build tree after asset generation
+- [ ] Deterministic asset outputs
+
+### User Experience
+
+- [ ] Clear asset management documentation
+- [ ] Simple development commands
+- [ ] Consistent asset generation across platforms
+- [ ] Reduced confusion about asset sources
+
+## Next Steps
+
+1. **Execute Phase 2**: Run validation and testing steps
+2. **Verify CI workflow**: Ensure all checks pass
+3. **Execute Phase 3**: Remove duplicate assets and scripts
+4. **Update documentation**: Finalize README and BUILDING.md
+5. **Team training**: Ensure all developers understand new workflow
+
+## Rollback Plan
+
+If issues arise during migration:
+
+1. **Restore backup branch**: `git checkout backup-before-asset-migration`
+2. **Revert configuration changes**: Remove new config files
+3. **Restore manual scripts**: Re-enable previous asset generation
+4. **Investigate issues**: Identify and resolve root causes
+5. **Plan revised migration**: Adjust approach based on lessons learned
+
+---
+
+**Status**: Ready for Phase 2 execution
+**Priority**: High
+**Estimated Effort**: 2-3 hours
+**Dependencies**: CI workflow validation
+**Stakeholders**: Development team
diff --git a/package.json b/package.json
index 7b27258d..930cc8cf 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,10 @@
"auto-run:electron": "./scripts/auto-run.sh --platform=electron",
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
"build:capacitor:sync": "npm run build:capacitor && npx cap sync",
+ "build:native": "vite build && npx cap sync && npx capacitor-assets generate",
+ "assets:config": "node scripts/assets-config.js",
+ "assets:validate": "node scripts/assets-validator.js",
+ "assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true",
"build:ios": "./scripts/build-ios.sh",
"build:ios:dev": "./scripts/build-ios.sh --dev",
"build:ios:test": "./scripts/build-ios.sh --test",
diff --git a/assets/icon.png b/resources/icon.png
similarity index 100%
rename from assets/icon.png
rename to resources/icon.png
diff --git a/assets/splash.png b/resources/splash.png
similarity index 100%
rename from assets/splash.png
rename to resources/splash.png
diff --git a/assets/splash_dark.png b/resources/splash_dark.png
similarity index 100%
rename from assets/splash_dark.png
rename to resources/splash_dark.png
diff --git a/scripts/assets-config.js b/scripts/assets-config.js
new file mode 100644
index 00000000..926ff1fa
--- /dev/null
+++ b/scripts/assets-config.js
@@ -0,0 +1,174 @@
+#!/usr/bin/env node
+
+/**
+ * TimeSafari Asset Configuration Generator
+ * Generates capacitor-assets configuration files with deterministic outputs
+ * Author: Matthew Raymer
+ *
+ * Usage: node scripts/assets-config.js
+ */
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const PROJECT_ROOT = path.dirname(__dirname);
+
+/**
+ * Generate deterministic capacitor-assets configuration
+ * @returns {Object} Sorted, stable configuration object
+ */
+function generateAssetConfig() {
+ const config = {
+ icon: {
+ source: "resources/icon.png",
+ android: {
+ adaptive: {
+ foreground: "resources/icon.png",
+ background: "#121212",
+ monochrome: "resources/icon.png"
+ },
+ target: "android/app/src/main/res"
+ },
+ ios: {
+ padding: 0,
+ target: "ios/App/App/Assets.xcassets/AppIcon.appiconset"
+ },
+ web: {
+ target: "public/img/icons"
+ }
+ },
+ splash: {
+ source: "resources/splash.png",
+ darkSource: "resources/splash_dark.png",
+ android: {
+ scale: "cover",
+ target: "android/app/src/main/res"
+ },
+ ios: {
+ useStoryBoard: true,
+ target: "ios/App/App/Assets.xcassets"
+ }
+ }
+ };
+
+ return sortObjectKeys(config);
+}
+
+/**
+ * Sort object keys recursively for deterministic output
+ * @param {Object} obj - Object to sort
+ * @returns {Object} Object with sorted keys
+ */
+function sortObjectKeys(obj) {
+ if (obj === null || typeof obj !== 'object') {
+ return obj;
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map(sortObjectKeys);
+ }
+
+ const sorted = {};
+ Object.keys(obj)
+ .sort()
+ .forEach(key => {
+ sorted[key] = sortObjectKeys(obj[key]);
+ });
+
+ return sorted;
+}
+
+/**
+ * Validate that required source files exist
+ */
+function validateSourceFiles() {
+ const requiredFiles = [
+ 'resources/icon.png',
+ 'resources/splash.png',
+ 'resources/splash_dark.png'
+ ];
+
+ const missingFiles = requiredFiles.filter(file => {
+ const filePath = path.join(PROJECT_ROOT, file);
+ return !fs.existsSync(filePath);
+ });
+
+ if (missingFiles.length > 0) {
+ console.error('❌ Missing required source files:');
+ missingFiles.forEach(file => console.error(` ${file}`));
+ process.exit(1);
+ }
+
+ console.log('✅ All required source files found');
+}
+
+/**
+ * Write configuration to file with consistent formatting
+ * @param {Object} config - Configuration object
+ * @param {string} outputPath - Output file path
+ */
+function writeConfig(config, outputPath) {
+ const jsonString = JSON.stringify(config, null, 2);
+
+ // Ensure consistent line endings and no trailing whitespace
+ const cleanJson = jsonString
+ .split('\n')
+ .map(line => line.trimEnd())
+ .join('\n') + '\n';
+
+ fs.writeFileSync(outputPath, cleanJson, 'utf8');
+ console.log(`✅ Configuration written to: ${outputPath}`);
+}
+
+/**
+ * Main execution function
+ */
+function main() {
+ console.log('🔄 Generating TimeSafari asset configuration...');
+ console.log(`📁 Project root: ${PROJECT_ROOT}`);
+ console.log(`📅 Generated: ${new Date().toISOString()}`);
+
+ try {
+ // Validate source files exist
+ validateSourceFiles();
+
+ // Generate configuration
+ const config = generateAssetConfig();
+
+ // Ensure config directory exists
+ const configDir = path.join(PROJECT_ROOT, 'config', 'assets');
+ if (!fs.existsSync(configDir)) {
+ fs.mkdirSync(configDir, { recursive: true });
+ }
+
+ // Write configuration files
+ const capacitorAssetsConfigPath = path.join(configDir, 'capacitor-assets.config.json');
+ writeConfig(config, capacitorAssetsConfigPath);
+
+ // Copy to root for capacitor-assets discovery
+ const rootConfigPath = path.join(PROJECT_ROOT, 'capacitor-assets.config.json');
+ writeConfig(config, rootConfigPath);
+
+ console.log('🎉 Asset configuration generation completed successfully!');
+ console.log('');
+ console.log('📋 Next steps:');
+ console.log(' 1. Review the generated configuration');
+ console.log(' 2. Commit the configuration files');
+ console.log(' 3. Run "npm run assets:validate" to verify');
+ console.log(' 4. Use "npm run build:native" for builds');
+
+ } catch (error) {
+ console.error('❌ Configuration generation failed:', error.message);
+ process.exit(1);
+ }
+}
+
+// Run if called directly
+if (import.meta.url === `file://${process.argv[1]}`) {
+ main();
+}
+
+export { generateAssetConfig, sortObjectKeys, validateSourceFiles };
diff --git a/scripts/assets-validator.js b/scripts/assets-validator.js
new file mode 100644
index 00000000..fcdbcdff
--- /dev/null
+++ b/scripts/assets-validator.js
@@ -0,0 +1,218 @@
+#!/usr/bin/env node
+
+/**
+ * TimeSafari Asset Configuration Validator
+ * Validates capacitor-assets configuration against schema and source files
+ * Author: Matthew Raymer
+ *
+ * Usage: node scripts/assets-validator.js [config-path]
+ */
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const PROJECT_ROOT = path.dirname(__dirname);
+
+/**
+ * Load and parse JSON file
+ * @param {string} filePath - Path to JSON file
+ * @returns {Object} Parsed JSON object
+ */
+function loadJsonFile(filePath) {
+ try {
+ const content = fs.readFileSync(filePath, 'utf8');
+ return JSON.parse(content);
+ } catch (error) {
+ throw new Error(`Failed to load ${filePath}: ${error.message}`);
+ }
+}
+
+/**
+ * Validate configuration against schema
+ * @param {Object} config - Configuration object to validate
+ * @param {Object} schema - JSON schema for validation
+ * @returns {Array} Array of validation errors
+ */
+function validateAgainstSchema(config, schema) {
+ const errors = [];
+
+ // Basic structure validation
+ if (!config.icon || !config.splash) {
+ errors.push('Configuration must contain both "icon" and "splash" sections');
+ }
+
+ // Icon validation
+ if (config.icon) {
+ if (!config.icon.source) {
+ errors.push('Icon section must contain "source" field');
+ } else if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) {
+ errors.push('Icon source must be a PNG or SVG file in resources/ directory');
+ }
+
+ // Android adaptive icon validation
+ if (config.icon.android?.adaptive) {
+ const adaptive = config.icon.android.adaptive;
+ if (!adaptive.foreground || !adaptive.background) {
+ errors.push('Android adaptive icon must have both foreground and background');
+ }
+ if (adaptive.foreground && !/^resources\/.*\.(png|svg)$/.test(adaptive.foreground)) {
+ errors.push('Android adaptive foreground must be a PNG or SVG file in resources/ directory');
+ }
+ }
+ }
+
+ // Splash validation
+ if (config.splash) {
+ if (!config.splash.source) {
+ errors.push('Splash section must contain "source" field');
+ } else if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) {
+ errors.push('Splash source must be a PNG or SVG file in resources/ directory');
+ }
+
+ if (config.splash.darkSource && !/^resources\/.*\.(png|svg)$/.test(config.splash.darkSource)) {
+ errors.push('Dark splash source must be a PNG or SVG file in resources/ directory');
+ }
+ }
+
+ return errors;
+}
+
+/**
+ * Validate that source files exist
+ * @param {Object} config - Configuration object
+ * @returns {Array} Array of missing file errors
+ */
+function validateSourceFiles(config) {
+ const errors = [];
+ const requiredFiles = new Set();
+
+ // Collect all required source files
+ if (config.icon?.source) requiredFiles.add(config.icon.source);
+ if (config.icon?.android?.adaptive?.foreground) requiredFiles.add(config.icon.android.adaptive.foreground);
+ if (config.icon?.android?.adaptive?.monochrome) requiredFiles.add(config.icon.android.adaptive.monochrome);
+ if (config.splash?.source) requiredFiles.add(config.splash.source);
+ if (config.splash?.darkSource) requiredFiles.add(config.splash.darkSource);
+
+ // Check each file exists
+ requiredFiles.forEach(file => {
+ const filePath = path.join(PROJECT_ROOT, file);
+ if (!fs.existsSync(filePath)) {
+ errors.push(`Source file not found: ${file}`);
+ }
+ });
+
+ return errors;
+}
+
+/**
+ * Validate target directories are writable
+ * @param {Object} config - Configuration object
+ * @returns {Array} Array of directory validation errors
+ */
+function validateTargetDirectories(config) {
+ const errors = [];
+ const targetDirs = new Set();
+
+ // Collect all target directories
+ if (config.icon?.android?.target) targetDirs.add(config.icon.android.target);
+ if (config.icon?.ios?.target) targetDirs.add(config.icon.ios.target);
+ if (config.icon?.web?.target) targetDirs.add(config.icon.web.target);
+ if (config.splash?.android?.target) targetDirs.add(config.splash.android.target);
+ if (config.splash?.ios?.target) targetDirs.add(config.splash.ios.target);
+
+ // Check each target directory
+ targetDirs.forEach(dir => {
+ const dirPath = path.join(PROJECT_ROOT, dir);
+ const parentDir = path.dirname(dirPath);
+
+ if (!fs.existsSync(parentDir)) {
+ errors.push(`Parent directory does not exist: ${parentDir}`);
+ } else if (!fs.statSync(parentDir).isDirectory()) {
+ errors.push(`Parent path is not a directory: ${parentDir}`);
+ }
+ });
+
+ return errors;
+}
+
+/**
+ * Main validation function
+ * @param {string} configPath - Path to configuration file
+ * @returns {boolean} True if validation passes
+ */
+function validateConfiguration(configPath) {
+ console.log('🔍 Validating TimeSafari asset configuration...');
+ console.log(`📁 Config file: ${configPath}`);
+ console.log(`📁 Project root: ${PROJECT_ROOT}`);
+
+ try {
+ // Load configuration
+ const config = loadJsonFile(configPath);
+ console.log('✅ Configuration file loaded successfully');
+
+ // Load schema
+ const schemaPath = path.join(PROJECT_ROOT, 'config', 'assets', 'schema.json');
+ const schema = loadJsonFile(schemaPath);
+ console.log('✅ Schema file loaded successfully');
+
+ // Perform validations
+ const schemaErrors = validateAgainstSchema(config, schema);
+ const fileErrors = validateSourceFiles(config);
+ const dirErrors = validateTargetDirectories(config);
+
+ // Report results
+ const allErrors = [...schemaErrors, ...fileErrors, ...dirErrors];
+
+ if (allErrors.length === 0) {
+ console.log('🎉 All validations passed successfully!');
+ console.log('');
+ console.log('📋 Configuration summary:');
+ console.log(` Icon source: ${config.icon?.source || 'NOT SET'}`);
+ console.log(` Splash source: ${config.splash?.source || 'NOT SET'}`);
+ console.log(` Dark splash: ${config.splash?.darkSource || 'NOT SET'}`);
+ console.log(` Android adaptive: ${config.icon?.android?.adaptive ? 'ENABLED' : 'DISABLED'}`);
+ console.log(` iOS LaunchScreen: ${config.splash?.ios?.useStoryBoard ? 'ENABLED' : 'DISABLED'}`);
+ return true;
+ } else {
+ console.error('❌ Validation failed with the following errors:');
+ allErrors.forEach((error, index) => {
+ console.error(` ${index + 1}. ${error}`);
+ });
+ return false;
+ }
+
+ } catch (error) {
+ console.error('❌ Validation failed:', error.message);
+ return false;
+ }
+}
+
+/**
+ * Main execution function
+ */
+function main() {
+ const configPath = process.argv[2] || path.join(PROJECT_ROOT, 'capacitor-assets.config.json');
+
+ if (!fs.existsSync(configPath)) {
+ console.error(`❌ Configuration file not found: ${configPath}`);
+ console.log('');
+ console.log('💡 Available options:');
+ console.log(' - Use default: capacitor-assets.config.json');
+ console.log(' - Specify path: node scripts/assets-validator.js path/to/config.json');
+ console.log(' - Generate config: npm run assets:config');
+ process.exit(1);
+ }
+
+ const success = validateConfiguration(configPath);
+ process.exit(success ? 0 : 1);
+}
+
+// Run if called directly
+if (import.meta.url === `file://${process.argv[1]}`) {
+ main();
+}
+
+export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories };
diff --git a/scripts/check-android-resources.sh b/scripts/check-android-resources.sh
deleted file mode 100755
index cd994088..00000000
--- a/scripts/check-android-resources.sh
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/bin/bash
-
-# TimeSafari Android Resource Check Script
-# Checks for missing Android resources and automatically fixes common issues
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res"
-ASSETS_DIR="$PROJECT_ROOT/assets"
-
-echo "=== TimeSafari Android Resource Check ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Checking Android resources"
-
-# Function to check if a file exists
-check_file() {
- local file="$1"
- local description="$2"
- if [ -f "$file" ]; then
- echo "[✓] $description: $file"
- return 0
- else
- echo "[✗] $description: $file (MISSING)"
- return 1
- fi
-}
-
-# Function to check if a directory exists and has files
-check_directory() {
- local dir="$1"
- local description="$2"
- if [ -d "$dir" ] && [ "$(ls -A "$dir" 2>/dev/null)" ]; then
- echo "[✓] $description: $dir"
- return 0
- else
- echo "[✗] $description: $dir (MISSING OR EMPTY)"
- return 1
- fi
-}
-
-# Track issues
-issues_found=0
-fixes_applied=0
-
-echo "[INFO] Checking splash screen resources..."
-# Ensure drawable directory exists
-if [ ! -d "$ANDROID_RES_DIR/drawable" ]; then
- echo "[FIX] Creating drawable directory..."
- mkdir -p "$ANDROID_RES_DIR/drawable"
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check splash screen resources
-if ! check_file "$ANDROID_RES_DIR/drawable/splash.png" "Splash screen (light)"; then
- if [ -f "$ASSETS_DIR/splash.png" ]; then
- echo "[FIX] Copying splash.png to Android resources..."
- cp "$ASSETS_DIR/splash.png" "$ANDROID_RES_DIR/drawable/splash.png"
- fixes_applied=$((fixes_applied + 1))
- else
- issues_found=$((issues_found + 1))
- fi
-fi
-
-if ! check_file "$ANDROID_RES_DIR/drawable/splash_dark.png" "Splash screen (dark)"; then
- if [ -f "$ASSETS_DIR/splash_dark.png" ]; then
- echo "[FIX] Copying splash_dark.png to Android resources..."
- cp "$ASSETS_DIR/splash_dark.png" "$ANDROID_RES_DIR/drawable/splash_dark.png"
- fixes_applied=$((fixes_applied + 1))
- else
- issues_found=$((issues_found + 1))
- fi
-fi
-
-echo "[INFO] Checking launcher icon resources..."
-# Ensure mipmap directories exist
-mipmap_dirs=("mdpi" "hdpi" "xhdpi" "xxhdpi" "xxxhdpi" "anydpi-v26")
-for dir in "${mipmap_dirs[@]}"; do
- if [ ! -d "$ANDROID_RES_DIR/mipmap-$dir" ]; then
- echo "[FIX] Creating mipmap-$dir directory..."
- mkdir -p "$ANDROID_RES_DIR/mipmap-$dir"
- fixes_applied=$((fixes_applied + 1))
- fi
-done
-
-# Check launcher icon resources
-required_icons=(
- "mipmap-mdpi/ic_launcher.png"
- "mipmap-hdpi/ic_launcher.png"
- "mipmap-xhdpi/ic_launcher.png"
- "mipmap-xxhdpi/ic_launcher.png"
- "mipmap-xxxhdpi/ic_launcher.png"
- "mipmap-anydpi-v26/ic_launcher.xml"
- "mipmap-anydpi-v26/ic_launcher_round.xml"
-)
-
-missing_icons=0
-for icon in "${required_icons[@]}"; do
- if ! check_file "$ANDROID_RES_DIR/$icon" "Launcher icon: $icon"; then
- missing_icons=$((missing_icons + 1))
- fi
-done
-
-if [ $missing_icons -gt 0 ]; then
- echo "[FIX] Missing launcher icons detected. Running icon generation script..."
- if [ -f "$SCRIPT_DIR/generate-android-icons.sh" ]; then
- "$SCRIPT_DIR/generate-android-icons.sh"
- fixes_applied=$((fixes_applied + 1))
- else
- echo "[ERROR] Icon generation script not found: $SCRIPT_DIR/generate-android-icons.sh"
- issues_found=$((issues_found + 1))
- fi
-fi
-
-echo "[INFO] Checking Capacitor platform status..."
-# Check if Android platform is properly initialized
-if [ ! -d "$PROJECT_ROOT/android" ]; then
- echo "[ERROR] Android platform directory not found"
- issues_found=$((issues_found + 1))
-elif [ ! -f "$PROJECT_ROOT/android/app/src/main/AndroidManifest.xml" ]; then
- echo "[ERROR] AndroidManifest.xml not found - platform may be corrupted"
- issues_found=$((issues_found + 1))
-else
- echo "[✓] Android platform appears to be properly initialized"
-fi
-
-# Check for common build issues
-echo "[INFO] Checking for common build issues..."
-
-# Check for invalid resource names (dashes in filenames)
-invalid_resources=$(find "$ANDROID_RES_DIR" -name "*-*" -type f 2>/dev/null | grep -E '\.(png|jpg|jpeg|gif|xml)$' || true)
-if [ -n "$invalid_resources" ]; then
- echo "[WARNING] Found resources with invalid names (containing dashes):"
- echo "$invalid_resources" | while read -r file; do
- echo " - $file"
- done
- echo "[INFO] Android resource names must contain only lowercase a-z, 0-9, or underscore"
- issues_found=$((issues_found + 1))
-fi
-
-# Summary
-echo ""
-echo "=== Resource Check Summary ==="
-if [ $issues_found -eq 0 ] && [ $fixes_applied -eq 0 ]; then
- echo "[SUCCESS] All Android resources are present and valid"
- exit 0
-elif [ $fixes_applied -gt 0 ]; then
- echo "[SUCCESS] Fixed $fixes_applied resource issues automatically"
- if [ $issues_found -gt 0 ]; then
- echo "[WARNING] $issues_found issues remain that require manual attention"
- exit 1
- else
- exit 0
- fi
-else
- echo "[ERROR] Found $issues_found resource issues that require manual attention"
- exit 1
-fi
\ No newline at end of file
diff --git a/scripts/check-ios-resources.sh b/scripts/check-ios-resources.sh
deleted file mode 100755
index 70466fb2..00000000
--- a/scripts/check-ios-resources.sh
+++ /dev/null
@@ -1,294 +0,0 @@
-#!/bin/bash
-
-# TimeSafari iOS Resource Check Script
-# Checks for missing iOS resources and automatically fixes common issues
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-IOS_ASSETS_DIR="$PROJECT_ROOT/ios/App/App/Assets.xcassets"
-RESOURCES_DIR="$PROJECT_ROOT/resources/ios"
-
-echo "=== TimeSafari iOS Resource Check ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Checking iOS resources"
-
-# Function to check if a file exists
-check_file() {
- local file="$1"
- local description="$2"
- if [ -f "$file" ]; then
- echo "[✓] $description: $file"
- return 0
- else
- echo "[✗] $description: $file (MISSING)"
- return 1
- fi
-}
-
-# Function to check if a directory exists and has files
-check_directory() {
- local dir="$1"
- local description="$2"
- if [ -d "$dir" ] && [ "$(ls -A "$dir" 2>/dev/null)" ]; then
- echo "[✓] $description: $dir"
- return 0
- else
- echo "[✗] $description: $dir (MISSING OR EMPTY)"
- return 1
- fi
-}
-
-# Track issues
-issues_found=0
-fixes_applied=0
-
-echo "[INFO] Checking iOS asset catalog structure..."
-# Check if Assets.xcassets directory exists
-if ! check_directory "$IOS_ASSETS_DIR" "iOS Assets.xcassets directory"; then
- echo "[FIX] Creating iOS Assets.xcassets directory..."
- mkdir -p "$IOS_ASSETS_DIR"
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check main Contents.json
-if ! check_file "$IOS_ASSETS_DIR/Contents.json" "Main Assets.xcassets Contents.json"; then
- echo "[FIX] Creating main Assets.xcassets Contents.json..."
- cat > "$IOS_ASSETS_DIR/Contents.json" << 'EOF'
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
-EOF
- fixes_applied=$((fixes_applied + 1))
-fi
-
-echo "[INFO] Checking App Icon resources..."
-# Check App Icon directory
-if ! check_directory "$IOS_ASSETS_DIR/AppIcon.appiconset" "App Icon directory"; then
- echo "[FIX] Creating App Icon directory..."
- mkdir -p "$IOS_ASSETS_DIR/AppIcon.appiconset"
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check App Icon Contents.json
-if ! check_file "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" "App Icon Contents.json"; then
- echo "[FIX] Creating App Icon Contents.json..."
- cat > "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "60x60"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "60x60"
- },
- {
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "20x20"
- },
- {
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "29x29"
- },
- {
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "40x40"
- },
- {
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "76x76"
- },
- {
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "83.5x83.5"
- },
- {
- "idiom" : "ios-marketing",
- "scale" : "1x",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
- fixes_applied=$((fixes_applied + 1))
-fi
-
-echo "[INFO] Checking Splash Screen resources..."
-# Check Splash directory
-if ! check_directory "$IOS_ASSETS_DIR/Splash.imageset" "Splash screen directory"; then
- echo "[FIX] Creating Splash screen directory..."
- mkdir -p "$IOS_ASSETS_DIR/Splash.imageset"
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check Splash Contents.json
-if ! check_file "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" "Splash screen Contents.json"; then
- echo "[FIX] Creating Splash screen Contents.json..."
- cat > "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check SplashDark directory
-if ! check_directory "$IOS_ASSETS_DIR/SplashDark.imageset" "Dark splash screen directory"; then
- echo "[FIX] Creating Dark splash screen directory..."
- mkdir -p "$IOS_ASSETS_DIR/SplashDark.imageset"
- fixes_applied=$((fixes_applied + 1))
-fi
-
-# Check SplashDark Contents.json
-if ! check_file "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" "Dark splash screen Contents.json"; then
- echo "[FIX] Creating Dark splash screen Contents.json..."
- cat > "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
- fixes_applied=$((fixes_applied + 1))
-fi
-
-echo "[INFO] Checking source resource files..."
-# Check if source resources exist
-if ! check_file "$RESOURCES_DIR/icon/icon.png" "iOS icon source"; then
- issues_found=$((issues_found + 1))
-fi
-
-if ! check_file "$RESOURCES_DIR/splash/splash.png" "iOS splash source"; then
- issues_found=$((issues_found + 1))
-fi
-
-if ! check_file "$RESOURCES_DIR/splash/splash_dark.png" "iOS dark splash source"; then
- issues_found=$((issues_found + 1))
-fi
-
-echo "[INFO] Checking iOS platform status..."
-# Check if iOS platform is properly initialized
-if [ ! -d "$PROJECT_ROOT/ios" ]; then
- echo "[ERROR] iOS platform directory not found"
- issues_found=$((issues_found + 1))
-elif [ ! -f "$PROJECT_ROOT/ios/App/App/Info.plist" ]; then
- echo "[ERROR] Info.plist not found - platform may be corrupted"
- issues_found=$((issues_found + 1))
-else
- echo "[✓] iOS platform appears to be properly initialized"
-fi
-
-# Summary
-echo ""
-echo "=== iOS Resource Check Summary ==="
-if [ $issues_found -eq 0 ] && [ $fixes_applied -eq 0 ]; then
- echo "[SUCCESS] All iOS resources are present and valid"
- exit 0
-elif [ $fixes_applied -gt 0 ]; then
- echo "[SUCCESS] Fixed $fixes_applied resource issues automatically"
- if [ $issues_found -gt 0 ]; then
- echo "[WARNING] $issues_found issues remain that require manual attention"
- echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux"
- exit 1
- else
- exit 0
- fi
-else
- echo "[ERROR] Found $issues_found resource issues that require manual attention"
- echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux"
- exit 1
-fi
diff --git a/scripts/generate-android-icons.sh b/scripts/generate-android-icons.sh
deleted file mode 100755
index aa2775ca..00000000
--- a/scripts/generate-android-icons.sh
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-# TimeSafari Android Icon Generation Script
-# Generates all required Android launcher icon sizes from assets/icon.png
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-ASSETS_DIR="$PROJECT_ROOT/assets"
-ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res"
-
-echo "=== TimeSafari Android Icon Generation ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting Android icon generation"
-
-# Check if source icon exists
-if [ ! -f "$ASSETS_DIR/icon.png" ]; then
- echo "[ERROR] Source icon not found: $ASSETS_DIR/icon.png"
- exit 1
-fi
-
-# Check if ImageMagick is available and determine the correct command
-IMAGEMAGICK_CMD=""
-if command -v magick &> /dev/null; then
- IMAGEMAGICK_CMD="magick"
- echo "[INFO] Using ImageMagick v7+ (magick command)"
-elif command -v convert &> /dev/null; then
- IMAGEMAGICK_CMD="convert"
- echo "[INFO] Using ImageMagick v6 (convert command)"
-else
- echo "[ERROR] ImageMagick not found. Please install ImageMagick."
- echo " Arch: sudo pacman -S imagemagick"
- echo " Ubuntu: sudo apt-get install imagemagick"
- echo " macOS: brew install imagemagick"
- exit 1
-fi
-
-# Create mipmap directories if they don't exist
-mkdir -p "$ANDROID_RES_DIR/mipmap-hdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-mdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xhdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xxhdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xxxhdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-anydpi-v26"
-
-echo "[INFO] Generating launcher icons..."
-
-# Function to resize image using the appropriate ImageMagick command
-resize_image() {
- local input="$1"
- local output="$2"
- local size="$3"
-
- if [ "$IMAGEMAGICK_CMD" = "magick" ]; then
- # ImageMagick v7+ syntax
- magick "$input" -resize "${size}x${size}" "$output"
- else
- # ImageMagick v6 syntax
- convert "$input" -resize "${size}x${size}" "$output"
- fi
-}
-
-# Generate launcher icons for different densities
-# Android launcher icon sizes: mdpi=48, hdpi=72, xhdpi=96, xxhdpi=144, xxxhdpi=192
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" 48
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" 72
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" 96
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" 144
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" 192
-
-# Generate round launcher icons
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher_round.png" 48
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher_round.png" 72
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher_round.png" 96
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png" 144
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png" 192
-
-# Create simple launcher XML files (no adaptive icons for now)
-cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher.xml" << 'EOF'
-
-
-
-
-
-EOF
-
-cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_round.xml" << 'EOF'
-
-
-
-
-
-EOF
-
-# Create foreground mipmap files for adaptive icons
-resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_foreground.png" 108
-
-echo "[SUCCESS] Generated Android launcher icons:"
-echo " - mipmap-mdpi/ic_launcher.png (48x48)"
-echo " - mipmap-hdpi/ic_launcher.png (72x72)"
-echo " - mipmap-xhdpi/ic_launcher.png (96x96)"
-echo " - mipmap-xxhdpi/ic_launcher.png (144x144)"
-echo " - mipmap-xxxhdpi/ic_launcher.png (192x192)"
-echo " - mipmap-anydpi-v26/ic_launcher_foreground.png (108x108)"
-echo " - Updated mipmap-anydpi-v26 XML files"
-echo "[SUCCESS] Android icon generation completed successfully!"
\ No newline at end of file
diff --git a/scripts/generate-icons.sh b/scripts/generate-icons.sh
deleted file mode 100755
index 3f0062ed..00000000
--- a/scripts/generate-icons.sh
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/bin/bash
-
-# TimeSafari Android Icon Generation Script (Placeholder Icons)
-# Generates placeholder Android launcher icons with "TS" text
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res"
-
-echo "=== TimeSafari Android Placeholder Icon Generation ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting Android placeholder icon generation"
-
-# Check if ImageMagick is available and determine the correct command
-IMAGEMAGICK_CMD=""
-if command -v magick &> /dev/null; then
- IMAGEMAGICK_CMD="magick"
- echo "[INFO] Using ImageMagick v7+ (magick command)"
-elif command -v convert &> /dev/null; then
- IMAGEMAGICK_CMD="convert"
- echo "[INFO] Using ImageMagick v6 (convert command)"
-else
- echo "[ERROR] ImageMagick not found. Please install ImageMagick."
- echo " Arch: sudo pacman -S imagemagick"
- echo " Ubuntu: sudo apt-get install imagemagick"
- echo " macOS: brew install imagemagick"
- exit 1
-fi
-
-# Create directories if they don't exist
-mkdir -p "$ANDROID_RES_DIR/mipmap-mdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-hdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xhdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xxhdpi"
-mkdir -p "$ANDROID_RES_DIR/mipmap-xxxhdpi"
-
-# Function to generate placeholder icon using the appropriate ImageMagick command
-generate_placeholder_icon() {
- local size="$1"
- local output="$2"
- local pointsize="$3"
-
- if [ "$IMAGEMAGICK_CMD" = "magick" ]; then
- # ImageMagick v7+ syntax
- magick -size "${size}x${size}" xc:blue -gravity center -pointsize "$pointsize" -fill white -annotate 0 "TS" "$output"
- else
- # ImageMagick v6 syntax
- convert -size "${size}x${size}" xc:blue -gravity center -pointsize "$pointsize" -fill white -annotate 0 "TS" "$output"
- fi
-}
-
-echo "[INFO] Generating placeholder launcher icons..."
-
-# Generate placeholder icons using ImageMagick
-generate_placeholder_icon 48 "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" 20
-generate_placeholder_icon 72 "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" 30
-generate_placeholder_icon 96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" 40
-generate_placeholder_icon 144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" 60
-generate_placeholder_icon 192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" 80
-
-echo "[INFO] Copying to round versions..."
-
-# Copy to round versions
-cp "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher_round.png"
-cp "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher_round.png"
-cp "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher_round.png"
-cp "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png"
-cp "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png"
-
-echo "[SUCCESS] Generated Android placeholder launcher icons:"
-echo " - mipmap-mdpi/ic_launcher.png (48x48)"
-echo " - mipmap-hdpi/ic_launcher.png (72x72)"
-echo " - mipmap-xhdpi/ic_launcher.png (96x96)"
-echo " - mipmap-xxhdpi/ic_launcher.png (144x144)"
-echo " - mipmap-xxxhdpi/ic_launcher.png (192x192)"
-echo " - All round versions created"
-echo "[SUCCESS] Android placeholder icon generation completed successfully!"
\ No newline at end of file
diff --git a/scripts/generate-ios-assets.sh b/scripts/generate-ios-assets.sh
deleted file mode 100755
index 17131d2d..00000000
--- a/scripts/generate-ios-assets.sh
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/bin/bash
-
-# TimeSafari iOS Asset Generation Script
-# Manually generates iOS assets using ImageMagick when capacitor-assets fails
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-IOS_ASSETS_DIR="$PROJECT_ROOT/ios/App/App/Assets.xcassets"
-RESOURCES_DIR="$PROJECT_ROOT/resources/ios"
-
-echo "=== TimeSafari iOS Asset Generation ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Generating iOS assets manually"
-
-# Check if ImageMagick is available
-if ! command -v convert &> /dev/null; then
- echo "[ERROR] ImageMagick 'convert' command not found. Please install ImageMagick."
- exit 1
-fi
-
-# Check if source files exist
-if [ ! -f "$RESOURCES_DIR/icon/icon.png" ]; then
- echo "[ERROR] Source icon not found: $RESOURCES_DIR/icon/icon.png"
- exit 1
-fi
-
-if [ ! -f "$RESOURCES_DIR/splash/splash.png" ]; then
- echo "[ERROR] Source splash not found: $RESOURCES_DIR/splash/splash.png"
- exit 1
-fi
-
-if [ ! -f "$RESOURCES_DIR/splash/splash_dark.png" ]; then
- echo "[ERROR] Source dark splash not found: $RESOURCES_DIR/splash/splash_dark.png"
- exit 1
-fi
-
-echo "[INFO] Generating iOS app icons..."
-
-# Generate app icons for different sizes
-# iPhone icons
-convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 60x60 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@3x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 58x58 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 87x87 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@3x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 80x80 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 120x120 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@3x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 120x120 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-60@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 180x180 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-60@3x.png"
-
-# iPad icons
-convert "$RESOURCES_DIR/icon/icon.png" -resize 20x20 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@1x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 29x29 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@1x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 58x58 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@1x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 80x80 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 152x152 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-76@2x.png"
-convert "$RESOURCES_DIR/icon/icon.png" -resize 167x167 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-83.5@2x.png"
-
-# App Store icon
-convert "$RESOURCES_DIR/icon/icon.png" -resize 1024x1024 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-1024@1x.png"
-
-echo "[INFO] Generating iOS splash screens..."
-
-# Generate splash screens for different scales
-convert "$RESOURCES_DIR/splash/splash.png" -resize 320x480 "$IOS_ASSETS_DIR/Splash.imageset/splash@1x.png"
-convert "$RESOURCES_DIR/splash/splash.png" -resize 640x960 "$IOS_ASSETS_DIR/Splash.imageset/splash@2x.png"
-convert "$RESOURCES_DIR/splash/splash.png" -resize 960x1440 "$IOS_ASSETS_DIR/Splash.imageset/splash@3x.png"
-
-# Generate dark splash screens
-convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 320x480 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@1x.png"
-convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 640x960 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@2x.png"
-convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 960x1440 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@3x.png"
-
-echo "[INFO] Updating Contents.json files to reference generated images..."
-
-# Update AppIcon Contents.json to reference the generated files
-cat > "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "filename" : "AppIcon-20@2x.png",
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "filename" : "AppIcon-20@3x.png",
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "20x20"
- },
- {
- "filename" : "AppIcon-29@2x.png",
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "filename" : "AppIcon-29@3x.png",
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "29x29"
- },
- {
- "filename" : "AppIcon-40@2x.png",
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "filename" : "AppIcon-40@3x.png",
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "40x40"
- },
- {
- "filename" : "AppIcon-60@2x.png",
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "60x60"
- },
- {
- "filename" : "AppIcon-60@3x.png",
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "60x60"
- },
- {
- "filename" : "AppIcon-20@1x.png",
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "20x20"
- },
- {
- "filename" : "AppIcon-20@2x.png",
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "filename" : "AppIcon-29@1x.png",
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "29x29"
- },
- {
- "filename" : "AppIcon-29@2x.png",
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "filename" : "AppIcon-40@1x.png",
- "idiom" : "ipad",
- "scale" : "1x",
- "size" : "40x40"
- },
- {
- "filename" : "AppIcon-40@2x.png",
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "filename" : "AppIcon-76@2x.png",
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "76x76"
- },
- {
- "filename" : "AppIcon-83.5@2x.png",
- "idiom" : "ipad",
- "scale" : "2x",
- "size" : "83.5x83.5"
- },
- {
- "filename" : "AppIcon-1024@1x.png",
- "idiom" : "ios-marketing",
- "scale" : "1x",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
-
-# Update Splash Contents.json to reference the generated files
-cat > "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "filename" : "splash@1x.png",
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "filename" : "splash@2x.png",
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "filename" : "splash@3x.png",
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
-
-# Update SplashDark Contents.json to reference the generated files
-cat > "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" << 'EOF'
-{
- "images" : [
- {
- "filename" : "splash@1x.png",
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "filename" : "splash@2x.png",
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "filename" : "splash@3x.png",
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
-
-echo "[SUCCESS] iOS assets generated successfully!"
-echo "[INFO] Generated files:"
-find "$IOS_ASSETS_DIR" -name "*.png" | sort
-
-echo ""
-echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux"
-echo "[INFO] Assets are now ready for when you build on macOS"
diff --git a/scripts/purge-generated-assets.sh b/scripts/purge-generated-assets.sh
deleted file mode 100755
index 3be21db6..00000000
--- a/scripts/purge-generated-assets.sh
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/bash
-
-# TimeSafari Generated Assets Purge Script
-# Removes generated Android assets and resources from git history
-# Author: Matthew Raymer
-
-set -e
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
-
-echo "=== TimeSafari Generated Assets Purge ==="
-echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting git history cleanup"
-
-# Check if we're in a git repository
-if [ ! -d ".git" ]; then
- echo "[ERROR] Not in a git repository. Please run this script from the project root."
- exit 1
-fi
-
-# Check if we have uncommitted changes
-if [ -n "$(git status --porcelain)" ]; then
- echo "[ERROR] You have uncommitted changes. Please commit or stash them first."
- echo "Current status:"
- git status --short
- exit 1
-fi
-
-# Create backup branch
-BACKUP_BRANCH="backup-before-asset-purge-$(date +%Y%m%d-%H%M%S)"
-echo "[INFO] Creating backup branch: $BACKUP_BRANCH"
-git branch "$BACKUP_BRANCH"
-
-echo "[INFO] Starting git filter-branch to remove generated assets..."
-
-# Use git filter-branch to remove the directories from history
-git filter-branch --force --index-filter '
- # Remove generated Android assets directory
- git rm -rf --cached --ignore-unmatch android/app/src/main/assets/public/ 2>/dev/null || true
-
- # Remove generated Android resources (but keep config files)
- git rm -rf --cached --ignore-unmatch android/app/src/main/res/drawable*/ 2>/dev/null || true
- git rm -rf --cached --ignore-unmatch android/app/src/main/res/mipmap*/ 2>/dev/null || true
- git rm -rf --cached --ignore-unmatch android/app/src/main/res/values/ic_launcher_background.xml 2>/dev/null || true
-
- # Keep configuration files
- git add android/app/src/main/res/values/strings.xml 2>/dev/null || true
- git add android/app/src/main/res/values/styles.xml 2>/dev/null || true
- git add android/app/src/main/res/layout/activity_main.xml 2>/dev/null || true
- git add android/app/src/main/res/xml/config.xml 2>/dev/null || true
- git add android/app/src/main/res/xml/file_paths.xml 2>/dev/null || true
-' --prune-empty --tag-name-filter cat -- --all
-
-echo "[INFO] Cleaning up git filter-branch temporary files..."
-rm -rf .git/refs/original/
-git reflog expire --expire=now --all
-git gc --prune=now --aggressive
-
-echo "[SUCCESS] Generated assets purged from git history!"
-echo "[INFO] Backup branch created: $BACKUP_BRANCH"
-echo "[INFO] Repository size should be significantly reduced"
-echo ""
-echo "Next steps:"
-echo "1. Test that the repository works correctly"
-echo "2. Force push to remote: git push --force-with-lease origin "
-echo "3. Inform team members to re-clone or reset their local repositories"
-echo "4. Delete backup branch when confident: git branch -D $BACKUP_BRANCH"
\ No newline at end of file