🔧 Space Configuration & Settings Fix

Add space settings UI and fix slug generation

🔧 Space Configuration & Settings Fix

Date: October 31, 2025
Status: 🚨 CRITICAL
Priority: HIGH


🚨 Current Problem

What's Happening

// User's actual space:
Space: {
  id: 'space_68fbe3ac4740d821e041f9df',
  name: 'My space',          // ✅ Correct
  spaceSlug: 'demo'          // ❌ WRONG - from URL!
}

// URL being used:
/demo/dashboard              // ❌ WRONG slug!

// Should be:
/my-space/dashboard          // ✅ Correct slug from name

Root Causes

  1. Slug from URL: Space slug is set from URL parameter, not generated from space name
  2. No Settings UI: Users cannot edit space name, slug, or configuration
  3. No Validation: Space creation doesn't validate or regenerate slugs
  4. Hardcoded Logic: createSpace() uses parameter or defaults to 'my-space'

🎯 What We Need

1. Space Settings Component (Priority 1)

UI to view and edit space configuration

2. Slug Generation from Name (Priority 2)

Automatically generate slug from space name

3. Update Existing Space (Priority 3)

Fix user's current space to have correct slug


🛠️ Solution 1: Space Settings Component

Location

apps/gograb-space/src/app/components/space-settings/

Features

Component Structure

import { Component, inject, signal } from '@angular/core';
import { SpaceService } from '@libs-foundational/core-angular';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-space-settings',
  standalone: true,
  imports: [CommonModule, FormsModule],
  template: `
    <div class="space-settings">
      <h1>Space Settings</h1>
      
      @if (loading()) {
        <div class="loading">Loading...</div>
      } @else if (space()) {
        <form (ngSubmit)="saveSettings()">
          <!-- Space Name -->
          <div class="form-group">
            <label>Space Name</label>
            <input 
              type="text" 
              [(ngModel)]="spaceName" 
              name="spaceName"
              (blur)="generateSlugFromName()"
              class="form-control">
            <small>Your workspace name</small>
          </div>
          
          <!-- Space Slug -->
          <div class="form-group">
            <label>Space Slug</label>
            <input 
              type="text" 
              [(ngModel)]="spaceSlug" 
              name="spaceSlug"
              pattern="[a-z0-9-]+"
              class="form-control">
            <small>URL-friendly identifier (lowercase, hyphens only)</small>
            <div class="slug-preview">
              URL: <code>/{{ spaceSlug() }}/dashboard</code>
            </div>
          </div>
          
          <!-- Space ID (read-only) -->
          <div class="form-group">
            <label>Space ID</label>
            <input 
              type="text" 
              [value]="space().id" 
              readonly 
              class="form-control">
          </div>
          
          <!-- Settings -->
          <h2>Space Settings</h2>
          
          <div class="form-group">
            <label>Default Theme</label>
            <select [(ngModel)]="defaultTheme" name="defaultTheme" class="form-control">
              <option value="light">Light</option>
              <option value="dark">Dark</option>
              <option value="system">System</option>
            </select>
          </div>
          
          <div class="form-group">
            <label>AI Provider</label>
            <select [(ngModel)]="aiProvider" name="aiProvider" class="form-control">
              <option value="anthropic">Anthropic (Claude)</option>
              <option value="openai">OpenAI</option>
              <option value="local">Local</option>
            </select>
          </div>
          
          <div class="form-group">
            <label>
              <input type="checkbox" [(ngModel)]="autoSave" name="autoSave">
              Auto-save changes
            </label>
          </div>
          
          <div class="form-group">
            <label>
              <input type="checkbox" [(ngModel)]="autoBackup" name="autoBackup">
              Auto-backup (daily)
            </label>
          </div>
          
          <!-- Actions -->
          <div class="form-actions">
            <button type="submit" class="btn btn-primary" [disabled]="saving()">
              @if (saving()) {
                Saving...
              } @else {
                Save Changes
              }
            </button>
            <button type="button" class="btn btn-secondary" (click)="cancel()">
              Cancel
            </button>
          </div>
        </form>
        
        <!-- Space Info -->
        <div class="space-info">
          <h2>Space Information</h2>
          <div class="info-grid">
            <div class="info-item">
              <strong>Owner:</strong> {{ space().ownerId }}
            </div>
            <div class="info-item">
              <strong>Created:</strong> {{ space().createdAt | date:'medium' }}
            </div>
            <div class="info-item">
              <strong>Universes:</strong> {{ space().universeIds?.length || 0 }}
            </div>
          </div>
        </div>
      }
    </div>
  `,
  styles: [`
    .space-settings {
      max-width: 800px;
      margin: 0 auto;
      padding: 2rem;
    }
    
    .form-group {
      margin-bottom: 1.5rem;
    }
    
    .form-control {
      width: 100%;
      padding: 0.5rem;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    
    .slug-preview {
      margin-top: 0.5rem;
      padding: 0.5rem;
      background: #f5f5f5;
      border-radius: 4px;
    }
    
    .form-actions {
      display: flex;
      gap: 1rem;
      margin-top: 2rem;
    }
    
    .btn {
      padding: 0.5rem 1rem;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    
    .btn-primary {
      background: #007bff;
      color: white;
    }
    
    .btn-secondary {
      background: #6c757d;
      color: white;
    }
    
    .space-info {
      margin-top: 3rem;
      padding: 1.5rem;
      background: #f8f9fa;
      border-radius: 8px;
    }
    
    .info-grid {
      display: grid;
      gap: 1rem;
      margin-top: 1rem;
    }
  `]
})
export class SpaceSettingsComponent {
  private spaceService = inject(SpaceService);
  
  space = this.spaceService.currentSpace;
  loading = signal(false);
  saving = signal(false);
  
  // Editable fields
  spaceName = signal('');
  spaceSlug = signal('');
  defaultTheme = signal('dark');
  aiProvider = signal('anthropic');
  autoSave = signal(true);
  autoBackup = signal(false);
  
  ngOnInit() {
    this.loadSpaceSettings();
  }
  
  loadSpaceSettings() {
    const space = this.space();
    if (!space) return;
    
    this.spaceName.set(space.name);
    this.spaceSlug.set(space.spaceSlug || this.slugify(space.name));
    this.defaultTheme.set(space.settings?.defaultTheme || 'dark');
    this.aiProvider.set(space.settings?.aiProvider || 'anthropic');
    this.autoSave.set(space.settings?.autoSave ?? true);
    this.autoBackup.set(space.settings?.autoBackup ?? false);
  }
  
  generateSlugFromName() {
    const name = this.spaceName();
    if (name) {
      const slug = this.slugify(name);
      this.spaceSlug.set(slug);
    }
  }
  
  slugify(text: string): string {
    return text
      .toLowerCase()
      .trim()
      .replace(/[^\w\s-]/g, '')  // Remove special chars
      .replace(/\s+/g, '-')       // Replace spaces with hyphens
      .replace(/-+/g, '-');       // Remove duplicate hyphens
  }
  
  async saveSettings() {
    const space = this.space();
    if (!space) return;
    
    this.saving.set(true);
    
    try {
      await this.spaceService.updateSpace(space.id, {
        name: this.spaceName(),
        spaceSlug: this.spaceSlug(),
        settings: {
          defaultTheme: this.defaultTheme(),
          aiProvider: this.aiProvider(),
          storageLocation: 'local',
          autoSave: this.autoSave(),
          autoBackup: this.autoBackup()
        }
      });
      
      alert('Space settings saved successfully!');
    } catch (error) {
      console.error('Failed to save space settings:', error);
      alert('Failed to save settings. Please try again.');
    } finally {
      this.saving.set(false);
    }
  }
  
  cancel() {
    this.loadSpaceSettings();
  }
}

🛠️ Solution 2: Update SpaceService

Add updateSpace Method

// In libs/core-angular/src/lib/services/space.service.ts

/**
 * Update space configuration
 */
async updateSpace(spaceId: string, updates: Partial<Space>): Promise<Space> {
  const currentSpace = this._currentSpace();
  
  if (!currentSpace || currentSpace.id !== spaceId) {
    throw new Error('Cannot update space: not current space');
  }
  
  const updatedSpace: Space = {
    ...currentSpace,
    ...updates,
    updatedAt: new Date().toISOString()
  };
  
  await this.storage.put(spaceId, updatedSpace);
  this._currentSpace.set(updatedSpace);
  
  this.logger.info('[SpaceService] Space updated', {
    spaceId,
    updates: Object.keys(updates)
  });
  
  return updatedSpace;
}

🛠️ Solution 3: Fix Space Creation

Update createSpace to Generate Slug from Name

// In libs/core-angular/src/lib/services/space.service.ts

private async createSpace(userId: string, spaceName?: string): Promise<Space> {
  // Default space name
  const name = spaceName || 'My Space';
  
  // Generate slug from name
  const slug = this.slugify(name);
  
  const space: Space = {
    id: `space_${userId}`,
    name: name,
    spaceSlug: slug,  // ✅ Generated from name!
    ownerId: userId,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    settings: {
      defaultTheme: 'dark',
      aiProvider: 'anthropic',
      storageLocation: 'local',
      autoSave: true,
      autoBackup: false
    },
    universeIds: []
  };
  
  await this.storage.put(`space_${userId}`, space);
  this.logger.info('[SpaceService] Created new space', {
    spaceId: space.id,
    name,
    slug
  });
  
  return space;
}

/**
 * Generate URL-friendly slug from text
 */
private slugify(text: string): string {
  return text
    .toLowerCase()
    .trim()
    .replace(/[^\w\s-]/g, '')  // Remove special chars
    .replace(/\s+/g, '-')       // Replace spaces with hyphens
    .replace(/-+/g, '-');       // Remove duplicate hyphens
}

🛠️ Solution 4: Add Route for Space Settings

Update app.routes.ts

// In apps/gograb-space/src/app/app.routes.ts

children: [
  // ... existing routes ...
  
  {
    path: 'settings/space',
    loadComponent: () => import('./components/space-settings/space-settings.component').then(m => m.SpaceSettingsComponent),
    title: 'Space Settings',
    data: {
      icon: 'fas fa-cog',
      description: 'Configure your space',
      category: 'settings',
      order: 999
    }
  }
]

🛠️ Solution 5: Quick Fix for Current User

Script to Fix Existing Space

// Run in browser console

async function fixMySpace() {
  const storage = window.__ggStorage__;  // Access GgStorage
  const userId = '68fbe3ac4740d821e041f9df';  // Your user ID
  const spaceId = `space_${userId}`;
  
  // Load current space
  const space = await storage.get(spaceId);
  
  console.log('Current space:', space);
  
  // Update with correct slug
  const updatedSpace = {
    ...space,
    spaceSlug: 'my-space',  // Correct slug from name "My space"
    updatedAt: new Date().toISOString()
  };
  
  // Save
  await storage.put(spaceId, updatedSpace);
  
  console.log('Updated space:', updatedSpace);
  console.log('Please refresh the page and navigate to /my-space/dashboard');
}

// Run it
fixMySpace();

📋 Implementation Checklist

Phase 1: Quick Fix (5 minutes)

Phase 2: SpaceService Updates (30 minutes)

Phase 3: Space Settings Component (1-2 hours)

Phase 4: Route Integration (15 minutes)

Phase 5: Testing (30 minutes)


🎯 Expected Results

After Quick Fix

Space: {
  name: 'My space',
  spaceSlug: 'my-space'  // ✅ Correct!
}

URL: /my-space/dashboard  // ✅ Correct!

After Full Implementation


🔗 Navigation Integration

Add to Settings Menu

// In main-sidenav.component.ts or similar

const settingsItems = [
  {
    id: 'space-settings',
    label: 'Space Settings',
    icon: 'fas fa-cog',
    route: '/my-space/settings/space',  // Will use correct slug
    order: 1
  },
  // ... other settings
];

💡 Future Enhancements

  1. Space Deletion: Allow users to delete their space
  2. Space Export: Export space data (universes, pages, etc.)
  3. Space Import: Import space from backup
  4. Space Duplication: Clone entire space
  5. Space Collaboration: Invite other users to space
  6. Custom Domain: Set custom domain for space

🚨 Important Notes

Slug Validation

URL Updates

Backwards Compatibility


Last Updated: October 31, 2025, 8:30 PM
Status: 🚨 READY TO IMPLEMENT
Priority: HIGH
Estimated Time: 2-3 hours for full implementation

"Configure your space, configure your universe!" 🌌⚙️