🔧 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
- Slug from URL: Space slug is set from URL parameter, not generated from space name
- No Settings UI: Users cannot edit space name, slug, or configuration
- No Validation: Space creation doesn't validate or regenerate slugs
- 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
- View/edit space name
- View/edit space slug (with validation)
- View/edit space settings:
- Default theme
- AI provider
- Storage location
- Auto-save
- Auto-backup
- Space information display:
- Space ID
- Owner
- Created date
- Universe count
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)
- Run console script to fix current user's space slug
- Navigate to correct URL:
/my-space/dashboard
Phase 2: SpaceService Updates (30 minutes)
- Add
updateSpace()method - Add
slugify()helper method - Update
createSpace()to generate slug from name - Export methods from barrel file
Phase 3: Space Settings Component (1-2 hours)
- Create
SpaceSettingsComponent - Add form with all fields
- Implement save/cancel logic
- Add validation
- Style the component
Phase 4: Route Integration (15 minutes)
- Add route to app.routes.ts
- Add link in navigation menu
- Test navigation
Phase 5: Testing (30 minutes)
- Test space name editing
- Test slug generation
- Test settings updates
- Test URL changes
- Verify persistence
🎯 Expected Results
After Quick Fix
Space: {
name: 'My space',
spaceSlug: 'my-space' // ✅ Correct!
}
URL: /my-space/dashboard // ✅ Correct!
After Full Implementation
- ✅ Users can edit space name
- ✅ Slug auto-generates from name
- ✅ Slug can be manually edited
- ✅ Space settings are configurable
- ✅ URL updates when slug changes
- ✅ All changes persist correctly
🔗 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
- Space Deletion: Allow users to delete their space
- Space Export: Export space data (universes, pages, etc.)
- Space Import: Import space from backup
- Space Duplication: Clone entire space
- Space Collaboration: Invite other users to space
- Custom Domain: Set custom domain for space
🚨 Important Notes
Slug Validation
- Must be lowercase
- Must use hyphens for spaces
- No special characters
- Must be unique (future: check against other users' spaces)
URL Updates
- When slug changes, update all navigation
- Redirect from old slug to new slug
- Update bookmarks/favorites
Backwards Compatibility
- Support both old and new slugs temporarily
- Redirect old URLs to new URLs
- Show migration notice if needed
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!" 🌌⚙️