Code Structure
This guide provides a detailed walkthrough of TimeLock's codebase, explaining how files are organized and how different parts work together.
Table of Contents
- Directory Structure
 - Application Bootstrap
 - Component Architecture
 - Service Layer
 - Models and Types
 - Routing System
 - Styling Architecture
 - Testing Structure
 
Directory Structure
src/
├── app/
│   ├── components/              # Reusable UI components
│   │   ├── auto-plan-dialog/
│   │   ├── confirmation-dialog/
│   │   ├── import-dialog/
│   │   ├── parent-selection-dialog/
│   │   ├── task-editor/
│   │   ├── todo-filters/
│   │   ├── todo-form/
│   │   └── todo-item/
│   ├── pages/                   # Route-level components
│   │   ├── calendar/
│   │   ├── project-detail/
│   │   ├── projects/
│   │   ├── search/
│   │   ├── timetable/
│   │   └── upcoming/
│   ├── services/                # Business logic and data
│   │   ├── confirmation.service.ts
│   │   ├── indexeddb.service.ts
│   │   ├── project.service.ts
│   │   ├── recurring-tasks.service.ts
│   │   ├── tags.service.ts
│   │   └── todo.service.ts
│   ├── models/                  # TypeScript interfaces
│   │   ├── project.model.ts
│   │   └── todo.model.ts
│   ├── task-item/              # Legacy components (being refactored)
│   ├── task-list/
│   ├── app.config.ts           # Application configuration
│   ├── app.routes.ts           # Route definitions
│   ├── app.ts                  # Root component
│   └── app.css                 # Root component styles
├── assets/                     # Static assets
│   ├── calendar_logo.svg
│   ├── logo.svg
│   ├── timetable_logo.svg
│   └── upcoming_logo.svg
├── index.html                  # Main HTML file
├── main.ts                     # Application entry point
└── styles.css                  # Global styles
Application Bootstrap
Entry Point: main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
  .catch((err) => console.error(err));
Key Points:
- Uses 
bootstrapApplication()for standalone components - No 
AppModuleneeded (modern Angular approach) - Configuration is externalized to 
app.config.ts 
Application Configuration: app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes)
  ]
};
Configuration Features:
- Global error handling - Catches unhandled errors
 - Zone.js optimization - Event coalescing for performance
 - Router configuration - Enables routing functionality
 
Root Component: app.ts
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, RouterModule],
  template: `
    <div class="app-container">
      <!-- Sidebar Toggle Button -->
      <button class="sidebar-toggle" (click)="toggleSidebar()">
        <!-- Hamburger menu -->
      </button>
      <!-- Sidebar Navigation -->
      <aside class="sidebar" [class.open]="sidebarOpen()">
        <div class="sidebar-header">
          <h1 class="app-title">TimeLock</h1>
        </div>
        
        <nav class="sidebar-nav">
          <!-- Navigation links -->
        </nav>
        
        <!-- Projects section -->
        <div class="projects-section">
          <!-- Project list -->
        </div>
      </aside>
      <!-- Main Content -->
      <main class="main-content">
        <router-outlet></router-outlet>
      </main>
    </div>
  `
})
export class App {
  private projectService = inject(ProjectService);
  
  sidebarOpen = signal(false);
  projects = this.projectService.activeProjects;
  currentProject = this.projectService.currentProject;
}
Root Component Features:
- Layout structure - Sidebar + main content
 - Navigation management - Sidebar toggle and routing
 - Project integration - Displays project list
 - Responsive design - Mobile-friendly sidebar
 
Component Architecture
Component Categories
1. Page Components (src/app/pages/)
- Route-level components
 - Container components that manage state
 - Coordinate multiple child components
 
2. Feature Components (src/app/components/)
- Reusable UI components
 - Presentation components with inputs/outputs
 - Focused on specific functionality
 
3. Dialog Components
- Modal dialogs for user interactions
 - Confirmation dialogs, forms, selection dialogs
 
Example: TodoItem Component
// src/app/components/todo-item/todo-item.component.ts
@Component({
  selector: 'app-todo-item',
  standalone: true,
  imports: [CommonModule, FormsModule, TaskEditorComponent],
  templateUrl: './todo-item.component.html',
  styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent {
  @Input() todo!: Todo;
  @Input() depth: number = 0;
  @Input() showProject: boolean = false;
  
  @Output() todoUpdated = new EventEmitter<Todo>();
  @Output() todoDeleted = new EventEmitter<string>();
  @Output() subtaskAdded = new EventEmitter<{parentId: string, subtask: Omit<Todo, 'id'>}>();
  
  // Local state
  isEditing = signal(false);
  isExpanded = signal(false);
  showSubtasks = signal(true);
  
  // Computed properties
  hasSubtasks = computed(() => this.todo.subtasks.length > 0);
  completionPercentage = computed(() => {
    if (!this.hasSubtasks()) return 0;
    const completed = this.todo.subtasks.filter(t => t.completed).length;
    return (completed / this.todo.subtasks.length) * 100;
  });
  
  // Methods
  toggleComplete() {
    this.todoUpdated.emit({
      ...this.todo,
      completed: !this.todo.completed,
      updatedAt: new Date()
    });
  }
  
  startEditing() {
    this.isEditing.set(true);
  }
  
  saveEdit(updatedTodo: Todo) {
    this.isEditing.set(false);
    this.todoUpdated.emit(updatedTodo);
  }
}
Component Structure:
- Inputs - Data from parent components
 - Outputs - Events to parent components
 - Local state - Component-specific state with signals
 - Computed properties - Derived state
 - Methods - Event handlers and business logic
 
Component Communication Patterns
1. Parent-Child Communication
// Parent template
<app-todo-item 
  [todo]="todo" 
  [depth]="0"
  (todoUpdated)="handleTodoUpdate($event)"
  (todoDeleted)="handleTodoDelete($event)">
</app-todo-item>
// Parent component
handleTodoUpdate(updatedTodo: Todo) {
  this.todoService.updateTodo(updatedTodo.id, updatedTodo);
}
2. Service-Based Communication
// Components communicate through shared services
export class ComponentA {
  private todoService = inject(TodoService);
  
  addTodo(todo: Todo) {
    this.todoService.addTodo(todo);  // Updates signal
  }
}
export class ComponentB {
  private todoService = inject(TodoService);
  
  todos = this.todoService.todos;  // Reactive to changes
}
Service Layer
Service Organization
Core Services:
TodoService- Task managementProjectService- Project managementIndexedDBService- Data persistence
Utility Services:
TagsService- Tag managementRecurringTasksService- Recurring task logicConfirmationService- Dialog management
Example: TodoService Structure
@Injectable({ providedIn: 'root' })
export class TodoService {
  private indexedDBService = inject(IndexedDBService);
  
  // Private signals (writable)
  private todosSignal = signal<Todo[]>([]);
  private filterSignal = signal<FilterType>('all');
  private searchTermSignal = signal<string>('');
  private sortTypeSignal = signal<SortType>('created');
  
  // Public signals (readonly)
  todos = this.todosSignal.asReadonly();
  filter = this.filterSignal.asReadonly();
  searchTerm = this.searchTermSignal.asReadonly();
  sortType = this.sortTypeSignal.asReadonly();
  
  // Computed signals
  filteredTodos = computed(() => {
    let todos = this.todosSignal();
    const filter = this.filterSignal();
    const searchTerm = this.searchTermSignal().toLowerCase();
    
    // Apply filters
    todos = this.applyFilter(todos, filter);
    todos = this.applySearch(todos, searchTerm);
    todos = this.applySorting(todos, this.sortTypeSignal());
    
    return todos;
  });
  
  activeTodos = computed(() => 
    this.todosSignal().filter(todo => !todo.completed && !todo.archived)
  );
  
  completedTodos = computed(() => 
    this.todosSignal().filter(todo => todo.completed)
  );
  
  // CRUD operations
  async addTodo(todoData: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>): Promise<string> {
    const newTodo: Todo = {
      ...todoData,
      id: this.generateId(),
      createdAt: new Date(),
      updatedAt: new Date(),
      subtasks: [],
      isExpanded: false,
      order: this.getNextOrder()
    };
    
    this.todosSignal.update(todos => [...todos, newTodo]);
    await this.saveTodos();
    return newTodo.id;
  }
  
  async updateTodo(id: string, updates: Partial<Todo>): Promise<void> {
    this.todosSignal.update(todos =>
      todos.map(todo =>
        todo.id === id
          ? { ...todo, ...updates, updatedAt: new Date() }
          : todo
      )
    );
    await this.saveTodos();
  }
  
  async deleteTodo(id: string): Promise<void> {
    this.todosSignal.update(todos => 
      this.removeTodoRecursively(todos, id)
    );
    await this.saveTodos();
  }
  
  // Business logic methods
  async toggleComplete(id: string): Promise<void> {
    const todo = this.findTodoById(id);
    if (todo) {
      await this.updateTodo(id, { completed: !todo.completed });
    }
  }
  
  async addSubtask(parentId: string, subtaskData: Omit<Todo, 'id'>): Promise<string> {
    // Implementation for adding subtasks
  }
  
  // Utility methods
  private generateId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }
  
  private async saveTodos(): Promise<void> {
    await this.indexedDBService.saveTodos(this.todosSignal());
  }
}
Service Patterns:
- Signal-based state - Reactive state management
 - Computed derived state - Automatic updates
 - Async operations - Promise-based API
 - Error handling - Try-catch blocks
 - Data persistence - Automatic saving
 
IndexedDB Service
@Injectable({ providedIn: 'root' })
export class IndexedDBService {
  private dbName = 'TimeLockDB';
  private version = 1;
  private db: IDBDatabase | null = null;
  
  async initDB(): Promise<void> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve();
      };
      
      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        this.createObjectStores(db);
      };
    });
  }
  
  private createObjectStores(db: IDBDatabase): void {
    // Create todos store
    if (!db.objectStoreNames.contains('todos')) {
      const todosStore = db.createObjectStore('todos', { keyPath: 'id' });
      todosStore.createIndex('projectId', 'projectId', { unique: false });
      todosStore.createIndex('dueDate', 'dueDate', { unique: false });
    }
    
    // Create projects store
    if (!db.objectStoreNames.contains('projects')) {
      db.createObjectStore('projects', { keyPath: 'id' });
    }
  }
  
  async saveTodos(todos: Todo[]): Promise<void> {
    if (!this.db) await this.initDB();
    
    const transaction = this.db!.transaction(['todos'], 'readwrite');
    const store = transaction.objectStore('todos');
    
    // Clear existing data
    await store.clear();
    
    // Add all todos
    for (const todo of todos) {
      await store.add(todo);
    }
  }
  
  async loadTodos(): Promise<Todo[]> {
    if (!this.db) await this.initDB();
    
    const transaction = this.db!.transaction(['todos'], 'readonly');
    const store = transaction.objectStore('todos');
    const request = store.getAll();
    
    return new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result || []);
      request.onerror = () => reject(request.error);
    });
  }
}
Models and Types
Todo Model
// src/app/models/todo.model.ts
export interface Todo {
  id: string;
  title: string;
  description?: string;
  completed: boolean;
  archived: boolean;
  priority: 'low' | 'medium' | 'high';
  dueDate?: Date;
  startDateTime?: Date;
  endDateTime?: Date;
  duration?: number;
  createdAt: Date;
  updatedAt: Date;
  category?: string;
  parentId?: string;
  projectId?: string;
  subtasks: Todo[];
  isExpanded: boolean;
  order: number;
  isAutoPlanned?: boolean;
  tags?: string[];
  isRecurring?: boolean;
  recurrencePattern?: RecurrencePattern;
  originalTaskId?: string;
  nextDueDate?: Date;
}
export interface RecurrencePattern {
  type: 'daily' | 'weekly' | 'monthly';
  interval: number;
  endDate?: Date;
  maxOccurrences?: number;
  currentOccurrences?: number;
}
export interface Tag {
  name: string;
  color: string;
  count?: number;
}
export type FilterType = 'all' | 'active' | 'completed' | 'archived';
export type SortType = 'created' | 'priority' | 'dueDate' | 'alphabetical';
Project Model
// src/app/models/project.model.ts
export interface Project {
  id: string;
  name: string;
  description?: string;
  color: string;
  icon?: string;
  createdAt: Date;
  updatedAt: Date;
  isArchived: boolean;
  order: number;
}
export interface ProjectStats {
  totalTasks: number;
  completedTasks: number;
  activeTasks: number;
  overdueTasks: number;
  highPriorityTasks: number;
}
Model Design Principles:
- Immutable updates - Always create new objects
 - Optional properties - Use 
?for optional fields - Union types - Restrict values with type unions
 - Date objects - Use Date type for temporal data
 - Nested structures - Support complex hierarchies
 
Routing System
Route Configuration
// src/app/app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
  {
    path: '',
    redirectTo: '/projects',
    pathMatch: 'full'
  },
  {
    path: 'projects',
    loadComponent: () => import('./pages/projects/projects.component')
      .then(m => m.ProjectsComponent)
  },
  {
    path: 'project/:id',
    loadComponent: () => import('./pages/project-detail/project-detail.component')
      .then(m => m.ProjectDetailComponent)
  },
  {
    path: 'calendar',
    loadComponent: () => import('./pages/calendar/calendar.component')
      .then(m => m.CalendarComponent)
  },
  {
    path: 'timetable',
    loadComponent: () => import('./pages/timetable/timetable.component')
      .then(m => m.TimetableComponent)
  },
  {
    path: 'upcoming',
    loadComponent: () => import('./pages/upcoming/upcoming.component')
      .then(m => m.UpcomingComponent)
  },
  {
    path: 'search',
    loadComponent: () => import('./pages/search/search.component')
      .then(m => m.SearchComponent)
  },
  {
    path: '**',
    redirectTo: '/projects'
  }
];
Routing Features:
- Lazy loading - Components loaded on demand
 - Route parameters - Dynamic routes with parameters
 - Redirects - Default and fallback routes
 - Wildcard routes - Handle unknown routes
 
Route Parameters
// In ProjectDetailComponent
export class ProjectDetailComponent {
  private route = inject(ActivatedRoute);
  private projectService = inject(ProjectService);
  
  projectId = signal<string | null>(null);
  project = computed(() => {
    const id = this.projectId();
    return id ? this.projectService.getProjectById(id) : null;
  });
  
  ngOnInit() {
    // Get route parameter
    const id = this.route.snapshot.paramMap.get('id');
    this.projectId.set(id);
    
    // Set current project
    if (id) {
      this.projectService.setCurrentProject(id);
    }
  }
}
Styling Architecture
Global Styles
/* src/styles.css */
:root {
  /* Color variables */
  --primary-color: #3b82f6;
  --success-color: #10b981;
  --warning-color: #f59e0b;
  --danger-color: #ef4444;
  
  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  /* Typography */
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
}
/* Reset and base styles */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.6;
  color: #374151;
  background-color: #f9fafb;
}
Component Styles
/* Component-specific styles */
.todo-item {
  display: flex;
  align-items: center;
  padding: var(--spacing-md);
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  background: white;
  margin-bottom: var(--spacing-sm);
  transition: all 0.2s ease;
}
.todo-item:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.todo-item.completed {
  opacity: 0.6;
  text-decoration: line-through;
}
Styling Patterns:
- CSS Custom Properties - Consistent design tokens
 - BEM methodology - Clear class naming
 - Component scoping - Styles scoped to components
 - Responsive design - Mobile-first approach
 
Testing Structure
Unit Tests
// Component test example
describe('TodoItemComponent', () => {
  let component: TodoItemComponent;
  let fixture: ComponentFixture<TodoItemComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TodoItemComponent]
    }).compileComponents();
    fixture = TestBed.createComponent(TodoItemComponent);
    component = fixture.componentInstance;
    component.todo = mockTodo;
    fixture.detectChanges();
  });
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should emit todoUpdated when toggling complete', () => {
    spyOn(component.todoUpdated, 'emit');
    component.toggleComplete();
    expect(component.todoUpdated.emit).toHaveBeenCalled();
  });
});
E2E Tests
// e2e/app.spec.ts
import { test, expect } from '@playwright/test';
test('should create a new todo', async ({ page }) => {
  await page.goto('/');
  
  // Navigate to projects
  await page.click('[data-testid="projects-link"]');
  
  // Add new todo
  await page.fill('[data-testid="todo-input"]', 'New test todo');
  await page.click('[data-testid="add-todo-button"]');
  
  // Verify todo appears
  await expect(page.locator('[data-testid="todo-item"]')).toContainText('New test todo');
});
Next Steps
Now that you understand the code structure:
- Start developing - Development Guide
 - Learn the features - Features Overview
 - Understand architecture - Project Architecture
 
The codebase follows modern Angular patterns and best practices, making it an excellent learning resource for Angular development.