diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..2a4d121 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,295 @@ +# 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: + +```bash +# ✅ 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 +// Go model +type Session struct { + ID uint + StartTime time.Time +} +``` + +```typescript +// 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: + +```go +// ❌ 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: + +```go +// ❌ 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 + +```javascript +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: + +```svelte + + + + + +``` + +### 7. i18n Translations + +Always update BOTH languages (RU and EN) in `frontend/src/lib/i18n.js`: + +```javascript +translations: { + ru: { + history: { + deleteAll: 'Удалить историю', + } + }, + en: { + history: { + deleteAll: 'Delete History', + } + } +} +``` + +### 8. Go Linter (golangci-lint) + +Run before committing: + +```bash +/opt/homebrew/bin/golangci-lint run ./... +``` + +Common fixes: + +- Remove explicit types when inferring: `idx := -1` not `var 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# 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: + +```bash +xattr -cr "Daily Timer.app" +``` + +Or: Right-click → Open → Open (first launch only) + +For apps in /Applications: + +```bash +xattr -cr "/Applications/Daily Timer.app" +``` + +## Event System + +Timer emits events via Wails runtime: + +- `timer:tick` - Every 100ms during active timer +- `timer:speaker_warning` - When speaker time is running low +- `timer:speaker_timeup` - When speaker time expired +- `timer:meeting_warning` - When meeting time running low +- `timer:meeting_ended` - When meeting ends +- `timer:speaker_changed` - When switching speakers + +Frontend listens in App.svelte: + +```javascript +EventsOn('timer:speaker_warning', handleWarning); +``` + +## Debugging + +Open DevTools in Wails app: `Cmd+Option+I` + +Add console.log for event debugging: + +```javascript +function handleWarning(state) { + console.log('=== handleWarning EVENT RECEIVED ==='); + console.log('settings:', settings); + // ... +} +``` + +--- + +**Last Updated**: February 2026