7.6 KiB
Daily Timer - AI Assistant Instructions
Project Overview
- Purpose: Desktop meeting timer application with speaker management
- Framework: Wails v2.11.0 (Go backend + Svelte frontend)
- Status: Active development
Technology Stack
| Component | Technology |
|---|---|
| Backend | Go 1.21+ |
| Framework | Wails v2.11.0 |
| Frontend | Svelte 4 + Vite |
| Database | SQLite via GORM |
| Audio | Web Audio API (oscillators) |
| Linter | golangci-lint |
Project Structure
daily-timer/
├── main.go # App entry point
├── wails.json # Wails configuration
├── internal/
│ ├── app/
│ │ └── app.go # Main App struct, Wails bindings
│ ├── models/
│ │ └── models.go # Data models (Participant, Meeting, etc.)
│ ├── storage/
│ │ └── storage.go # GORM database operations
│ └── timer/
│ └── timer.go # Timer logic, event emission
├── frontend/
│ ├── src/
│ │ ├── App.svelte # Main component, event handlers
│ │ ├── components/
│ │ │ ├── Timer.svelte
│ │ │ ├── ParticipantList.svelte
│ │ │ ├── Controls.svelte
│ │ │ ├── Settings.svelte
│ │ │ ├── History.svelte
│ │ │ └── Setup.svelte
│ │ └── lib/
│ │ └── i18n.js # Translations (RU/EN)
│ └── wailsjs/ # Auto-generated Go bindings
└── build/ # Build output
Critical Rules - MUST FOLLOW
1. Wails Terminal Directory
ALWAYS run wails dev from project root, not from subdirectories:
# ✅ CORRECT
cd /Users/admin-msk/git/daily-timer && wails dev
# ❌ WRONG - causes "wails.json not found" error
cd /Users/admin-msk/git/daily-timer/internal/timer && wails dev
2. TypeScript Model Casing
Go structs use PascalCase, but Wails generates TypeScript with camelCase:
// Go model
type Session struct {
ID uint
StartTime time.Time
}
// Generated TypeScript - use lowercase!
session.id; // ✅ CORRECT
session.ID; // ❌ WRONG - undefined
session.startTime; // ✅ CORRECT
session.StartTime; // ❌ WRONG - undefined
3. GORM Partial Updates
NEVER use db.Save() for partial updates - it overwrites ALL fields with zero values:
// ❌ WRONG - overwrites Active, Order, etc. with zero values
func (s *Storage) UpdateParticipant(p *models.Participant) error {
return s.db.Save(p).Error
}
// ✅ CORRECT - updates only specified fields
func (s *Storage) UpdateParticipant(p *models.Participant) error {
return s.db.Model(p).Updates(map[string]interface{}{
"name": p.Name,
"email": p.Email,
"time_limit": p.TimeLimit,
}).Error
}
4. Timer Event Flags
Use separate flags for different event types to prevent blocking:
// ❌ WRONG - one flag blocks both events
speakerWarned bool // After warning, timeup never fires
// ✅ CORRECT - separate flags
speakerWarned bool // For warning event
speakerTimeUpEmitted bool // For timeup event
5. Sound in Wails (macOS)
- Use Web Audio API oscillators, not audio files
- macOS "Do Not Disturb" mode blocks sounds
- Always add test buttons in Settings for verification
function playBeep(frequency, duration) {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = frequency;
gain.gain.setValueAtTime(0.3, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + duration);
osc.start();
osc.stop(ctx.currentTime + duration);
}
6. Svelte Event Dispatching
When adding interactivity to list items:
<!-- Parent component -->
<ChildList on:customEvent={handleEvent} />
<!-- Child component -->
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleClick(id) {
dispatch('customEvent', { id })
}
</script>
7. i18n Translations
Always update BOTH languages (RU and EN) in frontend/src/lib/i18n.js:
translations: {
ru: {
history: {
deleteAll: 'Удалить историю',
}
},
en: {
history: {
deleteAll: 'Delete History',
}
}
}
8. Go Linter (golangci-lint)
Run before committing:
/opt/homebrew/bin/golangci-lint run ./...
Common fixes:
- Remove explicit types when inferring:
idx := -1notvar idx int = -1 - Remove unused functions
- Handle all error returns
Common Bugs & Solutions
| Bug | Cause | Solution |
|---|---|---|
wails.json not found |
Wrong directory | Run from project root |
| Data disappears on edit | db.Save() overwrites |
Use Updates(map[string]interface{}) |
| Event never fires | Shared flag blocks | Use separate flags per event |
| Sounds don't play | DND mode or no AudioContext | Add test buttons, check DND |
undefined in template |
Wrong case (ID vs id) | Check generated TS types |
| Drag-drop doesn't save | Missing backend call | Call ReorderParticipants() |
| "App is damaged" error | macOS Gatekeeper | Run xattr -cr "Daily Timer.app" |
Development Commands
# Run in development mode (ALWAYS run manually from clean shell!)
cd /Users/admin-msk/git/daily-timer && wails dev
# Build for production
wails build
# Run linter
/opt/homebrew/bin/golangci-lint run ./...
# Generate bindings (automatic in wails dev)
wails generate module
Release Workflow
Building Release
# Build and package for current architecture
make release
# Build for all macOS architectures (arm64 + amd64)
make release-all
Output: dist/Daily-Timer-vX.X.X-macos-arm64.zip
Publishing Release to Gitea
# Create and push tag
git tag -a v0.2.0 -m "Release description"
git push origin v0.2.0
# Build and upload to Gitea (requires GITEA_TOKEN)
GITEA_TOKEN=your_token make release-publish
macOS Gatekeeper Fix
IMPORTANT: Built app is not signed. macOS shows "app is damaged" error.
Fix for users after downloading:
xattr -cr "Daily Timer.app"
Or: Right-click → Open → Open (first launch only)
For apps in /Applications:
xattr -cr "/Applications/Daily Timer.app"
Event System
Timer emits events via Wails runtime:
timer:tick- Every 100ms during active timertimer:speaker_warning- When speaker time is running lowtimer:speaker_timeup- When speaker time expiredtimer:meeting_warning- When meeting time running lowtimer:meeting_ended- When meeting endstimer:speaker_changed- When switching speakers
Frontend listens in App.svelte:
EventsOn('timer:speaker_warning', handleWarning);
Debugging
Open DevTools in Wails app: Cmd+Option+I
Add console.log for event debugging:
function handleWarning(state) {
console.log('=== handleWarning EVENT RECEIVED ===');
console.log('settings:', settings);
// ...
}
Last Updated: February 2026