docs: add release workflow and Gatekeeper fix
This commit is contained in:
295
.github/copilot-instructions.md
vendored
Normal file
295
.github/copilot-instructions.md
vendored
Normal file
@@ -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
|
||||
<!-- 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
|
||||
```
|
||||
|
||||
### 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
|
||||
Reference in New Issue
Block a user