Skip to content

Architecture

Daylight is built with a local-first architecture that prioritizes data ownership, offline capability, and cross-platform support.

┌─────────────────────────────────────────────────────┐
│ UI Layer │
│ Svelte 5 + Runes │
├─────────────────────────────────────────────────────┤
│ Bridge Layer │
│ Tauri 2 Commands │
├─────────────────────────────────────────────────────┤
│ Core Layer │
│ Rust + Filesystem │
├─────────────────────────────────────────────────────┤
│ Storage Layer │
│ Markdown + YAML Files │
└─────────────────────────────────────────────────────┘
TechnologyPurpose
Svelte 5Reactive UI framework with fine-grained updates
RunesSvelte’s new reactivity primitives ($state, $derived, $effect)
Tailwind CSSUtility-first styling with theme support
TypeScriptType safety throughout the codebase
TechnologyPurpose
Tauri 2Native app shell with minimal footprint
RustSystem-level file operations and parsing
BunJavaScript runtime and package manager
TechnologyPurpose
MarkdownHuman-readable task content (notes, descriptions)
YAMLStructured metadata in frontmatter
FilesystemDirect file access—no database layer

Daylight uses Svelte 5’s runes for reactive state management:

// Reactive state - tasks stored in a Map
let taskFiles = $state(new Map());
// Derived computations - filtered views
let activeTasks = $derived(
[...taskFiles.values()].filter(t => t.status === 'active')
);
// Effects - side effects on state changes
$effect(() => {
console.log(`${activeTasks.length} active tasks`);
});

[!note] Map reactivity in Svelte 5 requires creating a new Map on mutation:

// This triggers reactivity
taskFiles = new Map(taskFiles).set(filename, newData);
ComponentPurpose
ViewTaskRowTask row display with actions
TaskEditModalFull task editor (notes, tags, projects, recurrence)
TaskContextMenuQuick actions (reschedule, delete, edit)
RecurrenceEditorUI for setting up recurring tasks

The ViewService transforms raw task data into grouped views:

  • Past: Scheduled before today, still active
  • Now: Scheduled today or no date
  • Upcoming: Scheduled for future dates
  • Wrapped: Completed today

The RecurringInstanceService expands recurring task series into individual instance rows.

Tauri provides a secure bridge between the web frontend and Rust backend:

#[tauri::command]
async fn read_tasks(path: String) -> Result<Vec<Task>, Error> {
// Read .md files from directory
// Parse YAML frontmatter
// Return task data to frontend
}
#[tauri::command]
async fn write_task(path: String, task: Task) -> Result<(), Error> {
// Serialize task to YAML + Markdown
// Write to filesystem
}

Tauri 2 uses a capability system for security. Daylight’s permissions include:

  • fs:scope — Access to configured task directories
  • path:all — Path resolution for cross-platform compatibility
  • dialog:open — Directory picker for data path selection

On Android, additional permissions are required:

  • MANAGE_EXTERNAL_STORAGE — Access files outside app sandbox (for Syncthing folders)

Each task is a single Markdown file:

---
YAML frontmatter (metadata)
---
Markdown content (notes)

This format is:

  • Human-readable — Open in any text editor
  • Version-control friendly — Works with git
  • Parser-friendly — Standard YAML + Markdown

Reading tasks:

App startup
Tauri reads task directory
Rust parses each .md file
Tasks sent to Svelte via IPC
Store updates, UI renders

Writing tasks:

User edits task
Svelte dispatches save
Tauri command invoked
Rust serializes to YAML + Markdown
File written to disk
Syncthing picks up change (if configured)
PlatformStatusNotes
LinuxSupportedPrimary development platform; AppImage distribution
AndroidSupportedAPK distribution; requires storage permissions
WindowsPlannedTauri supports Windows; needs testing
macOSPlannedTauri supports macOS; needs testing
  • Uses custom Kotlin plugins for storage permissions
  • Default path assumes Syncthing at /storage/emulated/0/syncthing/
  • Data path override stored in localStorage for persistence
  1. Offline by default — No network required for any operation
  2. Data ownership — Files live on your device, not a server
  3. Sync flexibility — Use any file sync tool (Syncthing, Dropbox, etc.)
  4. Longevity — Plain text files outlive any app
  1. No real-time collaboration — File sync has inherent latency
  2. Manual conflict resolution — When sync conflicts occur
  3. Scale limits — File-per-task doesn’t scale to millions of tasks