Files
daily-timer/.github/copilot-instructions.md
2026-02-10 02:48:33 +03:00

321 lines
8.2 KiB
Markdown

# 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
<!-- 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`:
```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
```
### Rebuilding Existing Release
If you need to rebuild a release (e.g., after fixing icon or bugs):
```bash
# 1. Delete old dist folder
rm -rf dist
# 2. Delete local and remote tag
git tag -d v0.1.0
git push origin :refs/tags/v0.1.0
# 3. Delete old release on Gitea (get release ID from web UI or API)
curl -s -X DELETE \
-H "Authorization: token $GITEA_TOKEN" \
"https://git.movida.biz/api/v1/repos/bell/daily-timer/releases/RELEASE_ID"
# 4. Create new tag
git tag -a v0.1.0 -m "Release description"
git push origin v0.1.0
# 5. Build and upload
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