Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.9 KiB
Spaces Feature Documentation
Overview
Spaces in Memoro allow users to organize their memories and memos into logical containers. This feature provides a way to categorize content, manage access, and facilitate organization of user data.
Architecture
The Spaces feature uses a centralized approach for data management with fast local access:
- Express Backend API: Serves as the source of truth for space management
- Memoro App (Client): Interfaces with the Express Backend API
- Supabase Database: Used by both the Express Backend and the app for data storage and retrieval
This architecture allows the app to perform operations through the Express Backend API, which then directly updates the Supabase database. This ensures a single source of truth while still enabling fast data access.
Implementation Details
Database Schema
In Memoro's local Supabase database:
-- Spaces table
CREATE TABLE public.spaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT DEFAULT '',
color TEXT DEFAULT '#4CAF50',
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
is_default BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Junction table linking memos to spaces
CREATE TABLE public.memo_spaces (
memo_id UUID NOT NULL REFERENCES public.memos(id) ON DELETE CASCADE,
space_id UUID NOT NULL REFERENCES public.spaces(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (memo_id, space_id)
);
The schema includes foreign key constraints to ensure referential integrity between the spaces, memos, and user tables.
API Integration
The app integrates with the Memoro API:
- API Endpoints:
GET /api/memoro/spaces- List all spaces for the authenticated userGET /api/memoro/spaces/:id- Get space detailsPOST /api/memoro/spaces- Create a new spacePUT /api/memoro/spaces/:id- Update a spaceDELETE /api/memoro/spaces/:id- Delete a spaceGET /api/memoro/spaces/:spaceId/memos- Get all memos for a specific spacePOST /api/memoro/spaces/memos/link- Link a memo to a spaceDELETE /api/memoro/spaces/memos/unlink- Unlink a memo from a space
Space-Memo Relationship
Spaces can contain multiple memos, and memos can belong to multiple spaces. This is a many-to-many relationship implemented through the memo_spaces junction table. The API provides specific endpoints to manage these relationships:
- Retrieving memos in a space: Using
GET /api/memoro/spaces/:spaceId/memos - Linking a memo to a space: Using
POST /api/memoro/spaces/memos/link - Unlinking a memo from a space: Using
DELETE /api/memoro/spaces/memos/unlink
Data Structures
The Space interface reflects the updated API response structure:
export interface Space {
id: string;
name: string;
description?: string;
memoCount?: number;
isDefault?: boolean;
color?: string;
created_at?: string;
updated_at?: string;
owner_id?: string;
app_id?: string;
credits?: number;
roles?: {
members: {
[userId: string]: {
role: string;
added_at: string;
added_by: string;
};
};
};
apps?: {
name: string;
slug: string;
};
}
And the Memo interface for space-specific memos:
export interface Memo {
id: string;
title: string;
user_id: string;
source?: any;
style?: any;
is_pinned?: boolean;
is_archived?: boolean;
is_public?: boolean;
metadata?: any;
created_at?: string;
updated_at?: string;
}
Authentication
Custom JWT-based authentication is used throughout:
- User authenticates with the Memoro middleware (mana-core)
- JWT token contains a
subclaim with the user ID - This token is used for all API requests
- All API endpoints require a valid JWT token
Client Implementation
The app uses:
spaceService.ts: Service for space operations, including memo managementuseSpaces.ts: Hook for React componentsSpaceContext.tsx: Context provider for the appSpacesScreen.tsx: Main UI for space management[id].tsx: Space detail view showing space info and all associated memos
User Interface
The Spaces UI includes:
Spaces List Screen
- List of spaces with customizable colors
- Create/edit/delete functionality
- Space selection for filtering memos
- Visual indication of the active space
Space Detail Screen
- Shows space details (name, description, color, etc.)
- Displays all memos associated with the space
- Supports adding new memos to the space
- Handles error cases gracefully with retry mechanisms
Error Handling
The system handles several error cases:
- Network failures during API requests
- Authentication errors
- Permission issues
- Database constraints
Error handling improvements:
- Error messages are properly displayed in the UI
- Retry mechanisms for failed requests
- Cleanup functions to prevent memory leaks and state updates after component unmount
- Mock data support for development/testing
Error Handling Example
The Space detail view implements robust error handling:
// Error handling for memo loading
useEffect(() => {
// Create a flag to prevent state updates after unmount
let isMounted = true;
const fetchSpaceMemos = async () => {
if (!id) return;
try {
setLoadingMemos(true);
setMemosError(null);
// Fetch memos for this space
const memos = await spaceContext.getSpaceMemos(id);
// Only update state if component is still mounted
if (isMounted) {
setSpaceMemos(memos);
}
} catch (err) {
console.error('Error fetching space memos:', err);
// Only update state if component is still mounted
if (isMounted) {
setMemosError('Failed to load memos for this space');
}
} finally {
// Only update state if component is still mounted
if (isMounted) {
setLoadingMemos(false);
}
}
};
fetchSpaceMemos();
// Cleanup function to prevent state updates after unmount
return () => {
isMounted = false;
};
}, [id]);
UI for error states:
{loadingMemos ? (
<LoadingIndicator />
) : memosError ? (
<ErrorView
message={memosError}
onRetry={() => {
setLoadingMemos(true);
setMemosError(null);
// Re-render triggers the useEffect again
}}
/>
) : (
<MemoList memos={spaceMemos} spaceId={id} />
)}
Development and Testing Support
The implementation includes development aids:
// Mock data support for development
async getSpaceMemos(spaceId: string): Promise<Memo[]> {
// For development/testing, use mock data if API is not available
const USE_MOCK_DATA = process.env.EXPO_PUBLIC_USE_MOCK_DATA === 'true';
if (USE_MOCK_DATA) {
console.debug('Using mock data for space memos');
return [];
}
// Regular API call implementation
// ...
}
Navigation
Navigation between spaces-related screens:
- From spaces list to space detail view via
router.push(/(protected)/(space)/${spaceId}) - Add memo to space via navigation to memo creation with space ID in params
- Back to spaces list via built-in back navigation
Future Enhancements
Potential improvements for the spaces feature:
- Offline Support: Caching space data for offline use
- Batch Operations: Moving multiple memos between spaces
- Advanced Filtering: Filter memos by multiple spaces or other criteria
- Space Sharing: Allowing users to share spaces with other users
- Nested Spaces: Implementing hierarchical space organization
Maintenance and Troubleshooting
Common issues and solutions:
- API Communication Errors: Check console logs for detailed error messages
- Authentication Issues: Verify JWT token format and claims
- Performance Concerns: Monitor API call performance
- Mock Data: Use
EXPO_PUBLIC_USE_MOCK_DATA=truefor testing without API