# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

**IMPORTANT**: Always read the global project instructions at `/home/subdomains/livetimelapse/public_html/ai/CLAUDE.md` first, as it contains system-wide conventions, file permissions, testing requirements, and deployment procedures that apply to all projects.

## Project Overview

This is a Google Chat bot that provides remote control of DSLR cameras via SSH connections. The bot responds to slash commands and interactive card buttons to capture images, view camera settings, and check error logs on remote Raspberry Pi devices connected to DSLR cameras.

## Architecture

### Entry Points
- `app.php` - Main Google Chat webhook handler that processes all incoming requests from Google Chat
- `app-home.php` - App Home page handler for APP_HOME interaction events (provides dashboard interface)

### Core Components
- `capture.php` - Connects via SSH to camera device and executes image capture command
- `settings.php` - Retrieves and formats camera settings/information via SSH  
- `errors.php` - Fetches latest error logs from the remote camera device
- `input.php` - Updates camera IP address and Google Chat space ID configuration

### Configuration Files
- `camera.txt` - Stores the last two octets of camera IP (192.168.x format)
- `space.txt` - Stores Google Chat space ID for notifications

### SSH Connection Pattern
All PHP files follow a consistent pattern:
1. Read camera IP from `camera.txt` (format: `192.168.{contents}`)
2. Connect using hardcoded credentials (`timelapseptyltd` / `TimeLapse123`)
3. Execute remote commands with sudo privileges
4. Format and return results with emoji-based status indicators

### Google Chat Integration
- **Slash Commands**: `/capture`, `/settings`, `/errors`, `/setcamera`, `/list`, `/clear`
- **@DSLR help**: Responds to mentions with comprehensive help information
- **Interactive Cards**: Clickable buttons for mobile-optimized interface
- **App Home Page**: Comprehensive dashboard accessible via Home tab in DM spaces
- **Message Types**: `ADDED_TO_SPACE`, `APP_HOME`, `CARD_CLICKED`, and slash commands
- **Camera Selection**: Dropdown (`/setcamera`) or thumbnail gallery (`/inputthumbs`)

## Core Features

### Space-Based Camera Management
Each chat space maintains independent camera assignments using a flat file database system.

**Database Structure**:
```
db/
├── {SANITIZED_SPACE_ID}/
│   ├── VPN.txt (contains "10.22")
│   └── NAME.txt (contains "Abyss Mtgravatt1 JN1093")
└── ...
```

**Key Functions**:
- `getSpaceCamera($spaceId)` - Get VPN number for space
- `setSpaceCamera($spaceId, $vpn)` - Store VPN number for space
- `spaceHasCamera($spaceId)` - Boolean check for camera assignment
- `getSpaceCameraIP($spaceId)` - Get full IP address (192.168.x.x format)
- `getSpaceCameraName($spaceId)` - Retrieve formatted camera name
- `sanitizeSpaceId($spaceId)` - Convert space ID to filesystem-safe directory name

**Space-Specific IP Parameter Passing**:
ALL camera operations (capture, settings, errors, test_relay, test_storage) use space-specific camera IPs with NO hardcoded fallbacks. When a user triggers any action, the system:
1. Retrieves the space's assigned camera VPN from `db/{SPACE_ID}/VPN.txt` (e.g., "12.50")
2. **CRITICAL**: `getSpaceCameraIP()` constructs full IP address ("192.168.12.50")
3. Passes `cameraIP=192.168.{VPN}` parameter to ALL endpoint scripts (except camera configuration operations)
4. Endpoint scripts use the provided IP or show "No Camera Configured" error
5. This ensures 100% accuracy - ALL operations target the user's selected camera with proper error handling

**Universal Implementation**: Every endpoint script uses this pattern:
```php
if (isset($_GET['cameraIP']) && !empty($_GET['cameraIP'])) {
    $ip = $_GET['cameraIP'];  // Space-specific camera (full IP like "192.168.12.50")
} else {
    // No hardcoded fallback - require proper camera selection
    echo "❌ No Camera Configured\n";
    echo "Please configure a camera for this chat space first:\n";
    echo "• Use /setcamera command to select a camera\n";
    exit;
}
```

**Key Function Implementation**:
```php
function getSpaceCameraIP($spaceId) {
    $vpn = getSpaceCamera($spaceId);  // Gets "12.50" from VPN.txt
    if ($vpn) {
        return '192.168.' . $vpn;     // Returns "192.168.12.50"
    }
    return null;
}
```

**Camera Configuration Operation Exclusions**:
Certain operations must work WITHOUT a camera configured (used TO configure cameras):
- `camera_selection_submit` - Camera dropdown selection submission
- `camera_thumbnail_submit` - Thumbnail gallery selection submission  
- `camera_clear_filter` - Clear search filter in camera selection
- `camera_selection_filter` - Apply search filter in camera selection

These operations are excluded from both spaceHasCamera requirements AND IP parameter passing to prevent catch-22 scenarios where cameras are required to configure cameras.

### Concurrent Request Protection
Lock file system prevents conflicts when multiple users trigger operations simultaneously.

**Lock File Architecture**:
- **Directory**: `locks/` contains operation-specific lock files
- **Format**: `{SPACE_ID}_{OPERATION}.lock`
- **Timeout**: 30-second automatic expiration with cleanup
- **Protected Operations**: capture, settings, errors, test_relay, test_storage (all operations now use space-specific cameras)

**Key Functions**:
- `acquireLock($spaceId, $operation, $userInfo)` - Create lock file
- `releaseLock($spaceId, $operation)` - Remove lock file
- `isLocked($spaceId, $operation)` - Check if operation is locked
- `cleanupExpiredLocks()` - Remove stale locks

### User Interaction and Welcome System

**Smart @Mention Response**:
- **Configured spaces**: Shows options card with camera control buttons
- **Unconfigured spaces**: Shows welcome message with setup instructions
- **@DSLR help**: Provides comprehensive help card with commands and usage tips

**New Space Setup Flow**:
1. User adds bot to new chat space
2. Bot checks for existing camera assignment
3. If none found, displays welcome message with setup instructions
4. User runs `/setcamera` to configure camera for the space
5. Future operations use space-specific camera configuration

## Camera Selection and Management

### Camera Selection Methods
Two methods for selecting cameras from the company's master database:

1. **Dropdown Selection (`/setcamera`)**:
   - Formatted camera names with integrated search functionality
   - Search by client name, location, project code, or partial matches
   - Dynamic result counts and filtering capabilities
   - Alphabetically sorted with auto-refresh cache

2. **Thumbnail Gallery (`/inputthumbs`)**:
   - Visual thumbnail gallery with clickable camera images
   - Mobile-optimized touch-friendly interface
   - Preview images from `https://www.livetimelapse.com.au/clients/{client}/{location}/latest.jpg`

### Camera Status Display
The options card shows intelligent camera status text:
- **Enhanced Display**: `"Current camera: {Formatted Name} on IP {VPN}"`
- **Backward Compatibility**: `"Current camera: {VPN}"` for existing spaces
- **Unconfigured Spaces**: `"Type /setcamera and press enter to set camera"`

### Mobile-Optimized Interface
- **Row 1**: 📸 Capture + 🔌 Test Relay (primary actions)
- **Row 2**: ⚠️ + 💾 + ⚙️ (icon-only quick access buttons)
- **Button Functions**:
  - 📸 Capture (`how_many_response`) - Image capture
  - 🔌 Test Relay (`test_relay_response`) - Connection testing
  - ⚠️ (`how_long_response`) - Error logs
  - 💾 (`test_storage_response`) - Storage testing
  - ⚙️ (`how_big_response`) - Camera settings

### Camera Configuration Success Response
When a user successfully selects and configures a camera (via `/setcamera` dropdown or `/inputthumbs` thumbnail gallery), the bot now responds with an interactive card containing:

**Success Message**: `"{User} selected {Camera Name} (VPN {XX.XX})"`

**Action Button Rows**:
- **Row 1**: 📸 Capture + 🔌 Test Relay
- **Row 2**: ⚠️ Errors + 💾 Storage + ⚙️ Settings

**Implementation Details**:
- Uses `createCameraConfigSuccessCard()` helper function in `app.php:1971`
- Returns Google Chat `cards` format (not `cardsV2`) with `buttons` arrays and `textButton` wrappers
- Replaces simple text responses for successful camera configurations
- Error cases still return simple text responses for clarity
- Supports both thumbnail and regular camera selection flows

**Important Format Notes**:
- Uses legacy `cards` format for compatibility with existing implementations
- Button structure: `buttons` array with `textButton` wrapper for each button
- Avoid `cardsV2` with `buttonList` as it causes "Could not load dialog" errors

## App Home Page Dashboard

**Configuration**: 
- URL: `https://www.livetimelapse.com.au/ai/google-chat/dslr/app-home.php`
- Enable "Support App Home" checkbox in Google Cloud Console

**Dashboard Sections**:
- **Header**: App title and personalized welcome
- **Camera Status**: Current camera name, IP address, and configuration status
- **Quick Actions**: Capture, Settings, Errors, Refresh buttons
- **Camera Selection**: Access to dropdown and thumbnail selection methods
- **Diagnostics**: Storage and relay testing tools
- **Help**: Slash commands reference and usage tips

**Technical Features**:
- Cache-busting headers to prevent Google Chat response caching
- Mobile-responsive design with touch-friendly buttons
- Error handling with comprehensive logging to `app-home-debug.log`

## Help System and Commands

### Available Commands
- `/capture` - Image capture
- `/settings` - Camera settings
- `/errors` - Error logs
- `/setcamera` - Dropdown camera selection with search
- `/list` - Camera selection from list
- `/clear` - Remove camera assignment from current space

### /clear Command
Provides easy way to remove camera assignments:
- Removes VPN.txt and NAME.txt files from space database
- Shows confirmation before removal
- Displays welcome message after successful clearing
- Automatic cleanup of empty space directories

## Google Chat API Integration

### Essential Documentation
- **Primary Resource**: https://developers.google.com/workspace/chat/dialogs
- **Card Reference**: https://developers.google.com/workspace/add-ons/reference/rpc/google.apps.card.v1
- **Main API Hub**: https://developers.google.com/workspace/chat

### Dialog Structure
```json
{
  "actionResponse": {
    "type": "DIALOG",
    "dialogAction": {
      "dialog": {
        "body": {
          "sections": [
            {
              "header": "Section Title",
              "widgets": [...]
            }
          ]
        }
      }
    }
  }
}
```

### Implementation Notes
- Slash commands opening dialogs require "Open a dialog" checkbox in Google Cloud Console
- Dialog responses use `actionResponse.type = "DIALOG"`
- Button actions use `function` field, not `actionMethodName`
- Form inputs accessed via `message.action.formInputs[fieldName].stringInputs.value[0]`

## Remote Camera Commands
- **Image capture**: `/usr/bin/captureImageDSLRInstall` (auto-downloads if missing)
- **Settings sync**: `syncCameraInformation` command
- **Error retrieval**: `/tmp/gPhotoOutput.txt` file

## Image Capture Processing

### Architecture Overview
The image capture system supports both **synchronous** and **background** processing modes for different use cases and testing scenarios.

### Current Implementation: Synchronous Processing

**Synchronous Processing Approach:**
1. **Direct Execution**: `capture.php` runs `captureImageDSLRInstall` directly and waits for completion
2. **Full Output Display**: Complete script output shown to user for debugging
3. **Google Chat Waits**: Webhook response sent only after complete process finishes
4. **Real-time Feedback**: Users see actual capture progress and results

**Current Command Execution:**
```php
// Direct synchronous execution with debugging
$fullCommand = "echo '$sudoPassword' | sudo -S -s -- bash -c 'bash -x $captureFile 2>&1'";
$stream = ssh2_exec($connection, $fullCommand);
$output = stream_get_contents($stream); // Wait for completion
```

**Debugging Features Added:**
- SSH connection test before running capture
- Script format and permissions check
- First 5 lines of script displayed
- Basic execution test with exit code
- Full bash -x debugging output
- Stderr capture for error messages

**User Experience Flow:**
1. User clicks 📸 Capture button
2. **Processing display**: "EXECUTING IMAGE CAPTURE" with real-time status
3. **Synchronous processing**: Script runs and user waits for completion
4. **Complete output**: Full script output displayed including image posting results
5. **Final status**: Success/failure clearly indicated

### Background Processing (Alternative)

**Background Processing Solution (when enabled):**
1. **Immediate Response**: `capture.php` returns instant success message to Google Chat
2. **Background Execution**: Shell script runs asynchronously on camera system  
3. **Automated Delivery**: Shell script posts processed image directly to chat space via built-in relay

**Shell Script Integration:**
- `captureImageDSLRInstall` includes built-in Google Chat relay functionality
- Uses curl to post images directly to chat spaces
- Handles error scenarios and retry logic
- Provides comprehensive logging to `/tmp/capture_output.log`

### Benefits by Mode

**Synchronous Mode:**
✅ **Complete Visibility**: Full process output visible for debugging  
✅ **Immediate Feedback**: Clear success/failure status  
✅ **Testing Friendly**: Easy to identify issues in the pipeline  
✅ **Direct Control**: User sees exactly what happens  

**Background Mode:**
✅ **No Timeouts**: Google Chat operations complete within seconds  
✅ **Reliable Delivery**: Leverages existing, tested image posting mechanism  
✅ **User Feedback**: Clear status messages and delivery expectations  
✅ **Scalability**: Multiple concurrent captures don't block the system

## Multi-Space Image Relay Architecture

### Overview
The image relay system supports multiple Google Chat spaces with space-specific image delivery. Each space receives images only from their assigned cameras, ensuring proper isolation and delivery.

### Core Components

**imageRelay.php (php-tools):**
- **Location**: `/home/subdomains/livetimelapse/public_html/ai/google-chat/php-tools/imageRelay.php`
- **Function**: Receives images from shell scripts and posts to Google Chat spaces
- **Dependencies**: Google Apps Chat client library, service account authentication
- **Multi-Space Support**: Accepts `space_id` POST parameter for targeted delivery

**Space ID Priority System:**
```php
// Priority 1: POST parameter (multi-space support)
$_POST['space_id'] = 'AAAAW5ncoDw'

// Priority 2: Fallback file (backward compatibility)  
/dslr/space.txt = 'spaces/AAAAW5ncoDw'

// Priority 3: Default fallback
'AAAAW5ncoDw'
```

### Parameter Flow Architecture

**Current Flow:**
1. **User Action**: User in Space A clicks 📸 Capture button
2. **App.php Processing**: Adds `spaceId` parameter to endpoint call
3. **Capture.php**: Sets GOOGLE_CHAT_SPACE_ID environment variable
4. **Shell Script Execution**: Reads environment variable and adds space_id to curl
5. **ImageRelay.php Delivery**: Routes image to specified space

**Parameter Passing Chain:**
```
User (Space A) → app.php → capture.php → captureImageDSLRInstall → imageRelay.php → Google Chat (Space A)
              spaceId     spaceId       GOOGLE_CHAT_SPACE_ID    space_id
```

**Implementation Details:**
- capture.php sets the environment variable: `export GOOGLE_CHAT_SPACE_ID="AAAAW5ncoDw"`
- captureImageDSLRInstall checks for the variable and adds `-F "space_id=$GOOGLE_CHAT_SPACE_ID"` to curl
- imageRelay.php receives the space_id parameter and routes to the correct space

### Multi-Space Considerations

**Current Implementation:**
- `capture.php` receives the space ID parameter from app.php
- Sets GOOGLE_CHAT_SPACE_ID environment variable for the shell script
- `captureImageDSLRInstall` script checks for the environment variable
- If present, adds `space_id` parameter to the curl command
- Images are routed to the requesting space via imageRelay.php

**Space ID Processing:**
```php
// capture.php
if (!empty($spaceId)) {
    $cleanSpaceId = str_replace('spaces/', '', $spaceId);
    $fullCommand = "... export GOOGLE_CHAT_SPACE_ID=\"$cleanSpaceId\"; $captureFile'";
}

// captureImageDSLRInstall
if [ ! -z "$GOOGLE_CHAT_SPACE_ID" ]; then
    curl -F "space_id=$GOOGLE_CHAT_SPACE_ID" ...
fi
```

**Multi-Space Benefits:**
✅ **Space Isolation**: Each space receives only their camera's images  
✅ **Concurrent Support**: Multiple spaces can capture simultaneously  
✅ **Backward Compatibility**: Single-space setups continue working  
✅ **Automatic Targeting**: Images delivered to the requesting space  
✅ **Lock System**: Prevents conflicts between concurrent captures

**Note**: This implementation requires imageRelay.php to support the space_id parameter for proper routing.

## Google Chat Webhook Limitations

### Programmatic Access Not Available
Google Chat webhooks cannot be accessed, created, or managed programmatically through any API:
- **No API endpoints** for webhook management exist
- **Manual creation only** through Google Chat web interface
- **No programmatic listing** of existing webhooks
- **No URL retrieval** via API calls

### Manual Webhook Process
1. Open Google Chat space in web browser
2. Click space menu → Apps & integrations
3. Navigate to Webhooks → Add webhooks
4. Enter webhook name and avatar URL
5. Save and manually copy webhook URL
6. Store URL in application configuration

### Implications for Multi-Space Delivery
- Each space requires manual webhook setup
- Webhook URLs must be stored externally
- No automatic provisioning for new spaces
- Cannot validate webhooks programmatically

### Alternative Approaches
- **Service Account API**: Use Chat API with service account to post messages directly
- **Hybrid Model**: Bot receives commands, posts responses via API (not webhooks)
- **Pre-configuration**: Document manual webhook setup process for users

## Development Notes

### Error Handling
- All files include SSH2 extension checks
- Connection timeouts set to 10-30 seconds
- Comprehensive error messages with emoji status indicators
- Local error logging to `google-chat-errors.log`

### Security Considerations
- SSH credentials hardcoded in source files
- Uses password authentication (not key-based)
- Sudo operations require password input via echo piping

### Responsive Design
Google Chat API does not provide device detection. Use mobile-first design:
- Column layouts automatically wrap at breakpoints (Web: ≤480px, iOS: ≤300pt, Android: ≤320dp)
- Touch-friendly button sizes and spacing
- Test across multiple screen sizes

### Dialog State Management
- Google Chat dialogs don't preserve state between steps
- Multi-step dialogs require manual data reconstruction
- Extract user context from message metadata
- Use proper session/database storage for complex flows

## Error Logging and Debugging

### Local Error Logging
- **PHP Error Log**: Standard PHP errors via `error_log()`
- **google-chat-errors.log**: Dedicated Chat app error log

### Enhanced Error Functions
- `logChatError($context, $error, $data)` - Logs errors with context
- `validateJsonResponse($data, $context)` - Validates JSON before sending
- `safeJsonResponse($data, $context)` - Safe JSON response with fallback

### Viewing Logs
```bash
# View real-time errors
tail -f /path/to/project/google-chat-errors.log

# Search for specific errors
grep "GOOGLE_CHAT_ERROR" /var/log/php_errors.log
```

## Troubleshooting

### Dialog Server Errors
**Problem**: "Could not load dialog" or "Server error occurred"

**Common Causes**:
- Incorrect dialog structure (must follow Google Apps Card V1 schema)
- JSON syntax errors or malformed widgets array
- Using `cardsV2` format instead of legacy `cards` format
- Using `buttonList` widgets instead of `buttons` arrays with `textButton` wrappers
- Using `actionMethodName` instead of `function` for dialog buttons
- Missing required dialog response format

**Solution**: Use legacy `cards` format with `buttons` arrays and validate dialog structure.

**Card Format Fix**: For interactive buttons, use:
```php
'cards' => [
    [
        'sections' => [
            [
                'widgets' => [
                    [
                        'buttons' => [
                            [
                                'textButton' => [
                                    'text' => '📸 Capture',
                                    'onClick' => ['action' => [...]]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
]
```
**Avoid**: `cardsV2` with `buttonList` as it causes dialog errors.

### App Not Responding After Code Changes
**Problem**: App stops responding completely after modifications

**Common Causes**:
- PHP syntax errors (unmatched braces, missing semicolons)
- Over-broad exclusion removal breaking camera configuration operations
- Broken try-catch-finally structures
- Parameter passing logic errors

**Troubleshooting Steps**:
1. **Check PHP syntax**: `php -l app.php`
2. **Check error logs**: Review `google-chat-errors.log` for recent errors
3. **Verify exclusions**: Ensure camera configuration operations can work without cameras
4. **Test parameter passing**: Confirm space-specific IP parameters are passed correctly

**Camera Configuration Exclusions Pattern**:
```php
// Operations that need to work WITHOUT cameras configured
$configOps = ['camera_selection_submit', 'camera_thumbnail_submit', 'camera_clear_filter', 'camera_selection_filter'];

// Exclude from camera requirement check
if (!in_array($operation, $configOps) && !spaceHasCamera($spaceId)) {

// Exclude from IP parameter passing
if (!in_array($operation, $configOps)) {
    $spaceCameraIP = getSpaceCameraIP($spaceId);
}
```

### Slash Command Not Responding
**Problem**: `/setcamera` command doesn't respond

**Root Cause**: Missing "Open a dialog" checkbox in Google Cloud Console

**Solution**: 
1. Go to Google Cloud Console → Chat API → Configuration
2. Find the slash command and enable "Open a dialog" checkbox
3. Save configuration

### Dialog Form Submission Issues
**Problem**: Form shows "No camera selected" despite selection

**Root Cause**: Incorrect form data extraction

**Solution**: Use correct Google Chat form input structure:
```php
$selectedCamera = $message['action']['formInputs']['camera_selection']['stringInputs']['value'][0];
```

### Connection Issues
**Problem**: App doesn't respond at all

**Check**:
- HTTP endpoint URL is publicly accessible
- Webhook returns HTTP 200 status
- Check server error logs for PHP errors
- Verify Google Chat API configuration

### VPN IP Parameter Issues
**Problem**: Camera operations timeout or fail to connect despite camera being online

**Root Cause**: Invalid IP address format being passed to endpoint scripts

**Common Symptoms**:
- Capture/Settings/Errors operations timeout consistently
- TestRelay shows connection failures to valid cameras
- SSH connection attempts to incomplete IP addresses (e.g., "12.50" instead of "192.168.12.50")

**Critical Fix Required**:
Ensure `getSpaceCameraIP()` function returns full IP address format:
```php
function getSpaceCameraIP($spaceId) {
    $vpn = getSpaceCamera($spaceId);
    if ($vpn) {
        return '192.168.' . $vpn;  // MUST return full IP
    }
    return null;
}
```

**Common Mistake**:
```php
// WRONG - returns incomplete IP
function getSpaceCameraIP($spaceId) {
    $vpn = getSpaceCamera($spaceId);
    return $vpn;  // Returns "12.50" instead of "192.168.12.50"
}
```

**Verification Test**:
```php
// Test that getSpaceCameraIP returns correct format
$ip = getSpaceCameraIP('spaces/AAAAW5ncoDw');
echo $ip;  // Should output "192.168.12.50", not "12.50"
```

**Prevention**: Never use hardcoded camera.txt fallbacks - require proper camera selection for all operations.

### Capture Processing Issues

#### Synchronous Mode Issues
**Problem**: Capture process fails or doesn't complete in synchronous mode

**Common Symptoms**:
- Google Chat shows "EXECUTING IMAGE CAPTURE" but times out
- Script output shows errors during image processing
- Image not posted to Google Chat despite successful capture

**Troubleshooting Steps**:
1. **Check Script Output**:
   - Review complete output shown in Google Chat response
   - Look for specific error messages in capture process
   - Verify image file creation and processing steps

2. **Verify Camera Connection**:
   ```bash
   # Test camera connectivity manually
   gphoto2 --summary
   ```

3. **Check Google Chat Relay**:
   - Verify imageRelay.php is accessible at relay URL
   - Test manual image posting to relay endpoint
   - Check service account authentication

**Common Causes**:
- **Camera Hardware Issues**: DSLR not connected or responding
- **Network Issues**: Cannot reach imageRelay.php endpoint
- **Authentication Problems**: Service account key issues
- **Image Processing Failures**: gphoto2 or conversion errors

#### Background Mode Issues (Alternative Implementation)
**Problem**: Background capture process starts but images never arrive

**Common Symptoms**:
- User receives "✅ Capture process started successfully!" message
- No image appears in chat space after expected delivery time (30-60 seconds)
- No error messages or failures reported

**Troubleshooting Steps**:
1. **Check Background Process Status**:
   ```bash
   # On camera system, check if capture script is running
   ps aux | grep captureImageDSLRInstall
   ```

2. **Review Background Process Logs**:
   ```bash
   # Check capture output log on camera system
   tail -f /tmp/capture_output.log
   ```

**Recovery Actions**:
- Switch to synchronous mode for better visibility
- Monitor complete script output for specific error identification
- Verify camera system network connectivity to relay endpoints
- Check available disk space and system resources

**Prevention**:
- Use synchronous mode during testing and troubleshooting
- Regular monitoring of background process logs when using background mode
- Automated health checks for relay connectivity

### Multi-Space Image Relay Issues
**Problem**: Images appear in wrong chat space or not delivered to any space

**Common Symptoms**:
- Images posted to default space instead of originating space
- Images not appearing in any space despite successful capture
- Multiple spaces receiving same images

**Troubleshooting Steps**:
1. **Check Space ID Parameter Flow**:
   ```bash
   # Check app.php logs for space ID passing
   tail -f /path/to/google-chat-errors.log | grep "Passing space ID"
   ```

2. **Verify Space ID Reception**:
   ```bash
   # Check capture.php output for space ID display
   # Look for "Requested by Space: AAAAW5ncoDw" in the output
   ```

3. **Test ImageRelay.php Space Parameter**:
   ```bash
   # Test manual POST with space_id parameter
   curl -X POST "https://www.livetimelapse.com.au/ai/google-chat/php-tools/imageRelay.php" \
        -F "space_id=AAAAW5ncoDw" \
        -F "image=@test.jpg" \
        -F "caption=Test image"
   ```

4. **Check Google Chat API Authentication**:
   - Verify service account key exists: `/php-tools/chat-space-tool-key.json`
   - Ensure service account has permissions for target spaces
   - Check Google Apps Chat client library dependencies

**Common Causes**:
- **Missing Space ID**: app.php not passing spaceId parameter
- **ImageRelay Configuration**: php-tools/imageRelay.php not configured for multi-space support
- **Authentication Issues**: Service account lacks permissions for target space
- **Single Space Limitation**: Current setup delivers all images to one configured space

**Recovery Actions**:
- Check `$_GET['spaceId']` reception in capture.php
- Verify space ID is displayed in capture output
- Test imageRelay.php directly with manual curl command
- Consider updating imageRelay.php to accept space_id parameter for multi-space support

**Space ID Format Validation**:
```php
// Correct formats handled by imageRelay.php:
$_POST['space_id'] = 'AAAAW5ncoDw';      // ✅ Preferred
$_POST['space_id'] = 'spaces/AAAAW5ncoDw'; // ✅ Also supported
```