Skip to main content

Overview

The SpacesController manages spaces - isolated workspaces that help users organize tabs by context (work, personal, research, etc.). Each space belongs to a profile and maintains its own set of tabs.
Import: import { spacesController } from '@/controllers/spaces-controller'The controller is a singleton instance exported as spacesController.

Key Concepts

What is a Space?

A space is a container for tabs within a profile. Think of it as a virtual workspace:
  • Each space has a unique ID and name
  • Spaces belong to a specific profile
  • Tabs can be organized into different spaces
  • Users can switch between spaces to change context
  • Each space tracks when it was last used

Work Space

Email, documents, team chat

Personal Space

Social media, shopping, entertainment

Research Space

Reference materials, documentation

Development Space

GitHub, Stack Overflow, documentation

Data Structure

interface SpaceData {
  profileId: string;    // Parent profile ID
  name: string;         // Display name
  order: number;        // Display order in UI
  lastUsed: number;     // Timestamp of last access
  createdAt: number;    // Creation timestamp
}

type SpaceDataWithId = SpaceData & { id: string };

Caching Strategy

The SpacesController uses an intelligent caching system:
  • Spaces are loaded on-demand from the database
  • Once loaded, they’re cached in memory
  • Cache is automatically updated on modifications
  • requestedAllSpaces tracks which profiles are fully loaded
The controller maintains a RawSpacesController internally for database operations, while providing a cached API for performance.

Core Methods

Creating Spaces

create()

Create a new space in a profile.
public async create(profileId: string, spaceName: string): Promise<boolean>
profileId
string
required
The profile ID to create the space in
spaceName
string
required
Display name for the space
return
Promise<boolean>
true if space was created successfully
Example:
const success = await spacesController.create(
  'main-profile',
  'Work Projects'
);

if (success) {
  console.log('Space created successfully');
}

Retrieving Spaces

get()

Get a space by ID, searching across all profiles.
public async get(spaceId: string): Promise<SpaceData | null>
Example:
const space = await spacesController.get('space-123');
if (space) {
  console.log(`Space: ${space.name}`);
  console.log(`Profile: ${space.profileId}`);
}

getWithProfile()

Get a space by ID within a specific profile (faster than get()).
public async getWithProfile(
  profileId: string,
  spaceId: string
): Promise<SpaceData | null>
Example:
const space = await spacesController.getWithProfile(
  'main-profile',
  'space-123'
);

getAllFromProfile()

Get all spaces in a profile, sorted by order.
public async getAllFromProfile(profileId: string): Promise<SpaceDataWithId[]>
Example:
const spaces = await spacesController.getAllFromProfile('main-profile');

spaces.forEach(space => {
  console.log(`${space.name} (ID: ${space.id})`);
});

getAll()

Get all spaces across all profiles, sorted by order.
public async getAll(): Promise<SpaceDataWithId[]>
Example:
const allSpaces = await spacesController.getAll();
console.log(`Total spaces: ${allSpaces.length}`);

Updating Spaces

update()

Update space properties.
public async update(
  profileId: string,
  spaceId: string,
  spaceData: Partial<SpaceData>
): Promise<boolean>
profileId
string
required
Profile ID containing the space
spaceId
string
required
Space ID to update
spaceData
Partial<SpaceData>
required
Fields to update (name, order, lastUsed, etc.)
Example:
// Rename a space
await spacesController.update(
  'main-profile',
  'space-123',
  { name: 'Personal Projects' }
);

// Update last used time
await spacesController.update(
  'main-profile',
  'space-123',
  { lastUsed: Date.now() }
);

Deleting Spaces

delete()

Delete a space permanently.
public async delete(profileId: string, spaceId: string): Promise<boolean>
Deleting a space does not automatically delete its tabs. Handle tab cleanup separately.
Example:
const deleted = await spacesController.delete('main-profile', 'space-123');
if (deleted) {
  console.log('Space deleted');
}

Last Used Tracking

Spaces track when they were last accessed for smart sorting and restoration.

setLastUsed()

Update the last used timestamp for a space.
public async setLastUsed(profileId: string, spaceId: string): Promise<boolean>
Example:
// Mark space as active
await spacesController.setLastUsed('main-profile', 'space-123');

getLastUsedFromProfile()

Get the most recently used space in a profile.
public async getLastUsedFromProfile(
  profileId: string
): Promise<SpaceDataWithId | null>
Example:
const lastSpace = await spacesController.getLastUsedFromProfile('main-profile');
if (lastSpace) {
  console.log(`Last used space: ${lastSpace.name}`);
}

getLastUsed()

Get the most recently used space across all profiles.
public async getLastUsed(): Promise<SpaceDataWithId | null>
Example:
const lastSpace = await spacesController.getLastUsed();
if (lastSpace) {
  // Restore the last active space on startup
  console.log(`Restoring space: ${lastSpace.name}`);
}

Space Ordering

reorder()

Reorder spaces in the UI by updating their order values.
public async reorder(orderMap: SpaceOrderMap): Promise<boolean>
SpaceOrderMap Type:
type SpaceOrderMap = {
  profileId: string;
  spaceId: string;
  order: number;
}[];
Example:
// Reorder spaces
const newOrder = [
  { profileId: 'main', spaceId: 'space-1', order: 0 },
  { profileId: 'main', spaceId: 'space-2', order: 1 },
  { profileId: 'main', spaceId: 'space-3', order: 2 }
];

await spacesController.reorder(newOrder);

Events

The SpacesController extends TypedEventEmitter with these events:
space-created
[profileId: string, spaceId: string, spaceData: SpaceData]
Emitted when a new space is created
space-updated
[profileId: string, spaceId: string, updatedFields: Partial<SpaceData>]
Emitted when a space is modified
space-deleted
[profileId: string, spaceId: string]
Emitted when a space is deleted
requested-all-spaces-from-profile
[profileId: string]
Emitted when all spaces from a profile are loaded into cache
Example:
spacesController.on('space-created', (profileId, spaceId, spaceData) => {
  console.log(`New space created: ${spaceData.name}`);
  console.log(`Profile: ${profileId}, Space ID: ${spaceId}`);
});

spacesController.on('space-updated', (profileId, spaceId, updatedFields) => {
  console.log(`Space ${spaceId} updated:`, updatedFields);
});

spacesController.on('space-deleted', (profileId, spaceId) => {
  console.log(`Space ${spaceId} deleted from profile ${profileId}`);
});

Cache Management

hasRequestedAllSpaces()

Check if all spaces from a profile are loaded in cache.
public hasRequestedAllSpaces(profileId: string): boolean
Example:
if (spacesController.hasRequestedAllSpaces('main-profile')) {
  // All spaces are cached, no database query needed
  const spaces = await spacesController.getAllFromProfile('main-profile');
} else {
  // First call will load from database and cache
  const spaces = await spacesController.getAllFromProfile('main-profile');
}

Usage Patterns

Session Restoration

// On app startup, restore last used space
const lastSpace = await spacesController.getLastUsed();

if (lastSpace) {
  // Restore tabs in this space
  const tabs = await tabsController.getTabsInSpace(lastSpace.id);
  
  // Activate the space in a window
  tabsController.setCurrentWindowSpace(windowId, lastSpace.id);
}

Creating a New Workspace

// Create space and switch to it
const success = await spacesController.create(
  currentProfileId,
  'New Project'
);

if (success) {
  // Get the newly created space
  const spaces = await spacesController.getAllFromProfile(currentProfileId);
  const newSpace = spaces[spaces.length - 1];
  
  // Mark as used
  await spacesController.setLastUsed(currentProfileId, newSpace.id);
  
  // Switch to it
  tabsController.setCurrentWindowSpace(windowId, newSpace.id);
}

Space Switcher UI

// Get all spaces for switcher
const spaces = await spacesController.getAllFromProfile(profileId);

// Render space list sorted by order
spaces.forEach(space => {
  renderSpaceButton({
    id: space.id,
    name: space.name,
    isActive: currentSpaceId === space.id,
    onClick: () => switchToSpace(space.id)
  });
});

function switchToSpace(spaceId: string) {
  tabsController.setCurrentWindowSpace(windowId, spaceId);
  spacesController.setLastUsed(profileId, spaceId);
}

Data Validation

The controller uses Zod schemas for data validation:
import { SpaceDataSchema } from '@/controllers/spaces-controller';

// Validate space data
const result = SpaceDataSchema.safeParse(spaceData);
if (result.success) {
  console.log('Valid space data');
} else {
  console.error('Invalid space data:', result.error);
}

Profiles Controller

Manage profiles that contain spaces

Tabs Controller

Manage tabs within spaces

Windows Controller

Window management for displaying spaces

Build docs developers (and LLMs) love