Always respond to the user in Spanish (Castilian). Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.
Wails v2 · Go 1.24 · SQLite (mattn/go-sqlite3 CGO) · sqlc · golang-migrate · adrg/xdg · Vue 3 + TS
main.go # embeds frontend/dist + db/migrations
app.go # App facade: startup/shutdown + DI composition
internal/
config/
database.go # DBPath(), ImagesDir()
db.go # OpenDB() — opens SQLite, runs migrations
controllers/ # auth.go notebook.go note.go image.go
services/ # auth.go notebook.go note.go image.go
repositories/ # only when extra logic beyond sqlc is needed
dto/ # auth.go notebook.go note.go image.go
errors/ # ErrNotFound ErrConflict ErrForbidden ErrUnauthenticated ErrInvalidInput
db/
migrations/ # YYYYMMDDHHMMSS_name.up/down.sql
queries/ # settings.sql notebooks.sql notes.sql images.sql
generated/ # DO NOT EDIT — run: cd db && sqlc generate
sqlc.yaml # paths relative to db/; emit_interface emit_json_tags emit_pointers_for_null_types
OpenDB → db.New(sqlDB) → NewXxxService(querier) → NewXxxController(svc) — no framework.
App binds to Wails and delegates to controllers. Controllers initialized in startup(), not NewApp() (Bind runs before startup).
settings(key PK, value)— generic k/v; used forauth.password_hashnotebooks(id, parent_id nullable FK self-ref CASCADE, title, position, created_at, updated_at)notes(id, notebook_id nullable FK CASCADE, title, content JSON TipTap, position, created_at, updated_at)images(id, note_id FK CASCADE, filename, mime_type, created_at)NULL parent_id / notebook_id= root levelpositionis per-type (notebooks among notebooks, notes among notes) for drag & drop- List queries return only
id, title, position, timestamps— content only in GetNote
- DB:
<XDG_DATA_HOME>/gvnotes/gvnotes.db - Images:
<XDG_DATA_HOME>/gvnotes/images/<uuid>.<ext> - macOS XDG_DATA_HOME =
~/Library/Application Support
- Single app password, no users
- Stored as
base64(salt):base64(argon2id hash)insettingsunder keyauth.password_hash - argon2id params: time=1, memory=64MB, threads=4, keyLen=32, saltLen=16 (OWASP minimums)
IsPasswordSet→ checks row exists;SetPassword→ errors if already set (ErrConflict);VerifyPassword→ constant-time compare- Wails methods:
GetAuthStatus(),SetPassword(password),VerifyPassword(password)
Auth: GetAuthStatus SetPassword VerifyPassword
Notebooks: ListNotebooks(parentID) GetNotebook CreateNotebook UpdateNotebookTitle UpdateNotebookPosition DeleteNotebook
Notes: ListNotes(notebookID) GetNote CreateNote UpdateNoteTitle UpdateNoteContent UpdateNotePosition DeleteNote
Images: SaveImage(noteID, mimeType, []byte) GetImage GetImagePath(filename) ListImagesByNote DeleteImage
— ListNotebooks("") / ListNotes("") → root level (converts empty string to nil)
- All TypeScript/JavaScript functions must be written as arrow functions
vee-validateis auto-imported — useuseForm+useFieldfor all forms; define validation rules insrc/composables/useValidation.ts- vee-validate: the global
configure()inmain.tsdoes NOT reliably suppress eager validation — always pass options explicitly per form/field:useForm({ validateOnMount: false })—validateOnModelUpdatedoes NOT exist in vee-validate v4, omit ituseField('name', rule, { validateOnValueUpdate: false })- This makes validation trigger only on submit (
handleSubmit), which is the desired UX
- When adding new generic directories under
src/, register them invite.config.ts:AutoImport.dirsfor composables/utils/stores/services/types/plugins,Components.dirsfor components/views - Vue SFC tag order:
<script lang="ts" setup>first, then<template>— never use<style>in components - All SCSS goes in
src/css/ - No vue-router — use
<component :is="xxx">for view switching
- Use CoreUI components (
CButton,CModal,CDropdown…) only when they provide interactive logic - For purely structural/styling elements (
CCardBody,CFormInput,CRow…) use plain Bootstrap HTML + classes directly
- Archivo:
seed.goen la raíz (build tag//go:build ignore, no afecta al binario) - Embebe
db/migrationspara abrir la DB igual que la app - Genera: 21 notebooks raíz · 15 notas sueltas raíz · 1 notebook con 50 notas · 3 notebooks con sub-notebooks anidados
go run seed.go— añade datos (acumulativo)go run seed.go --reset— limpia notebooks/notas y vuelve a poblar
- Title in English, imperative, conventional commits format:
feat:,fix:,refactor:, etc. - Short body (2-4 lines) describing what and why, in English
- Always add:
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> - NEVER commit unless the user explicitly asks for it
- When asked to commit: stage ALL modified/untracked files (
git add -A) and push to remote immediately after
sqlc generatemust be run fromdb/directory- Controllers never receive
*sql.DB - Never compare error strings — use
errors.Isagainst typed errors - Repositories only when sqlc querier is insufficient
- Images: file written to disk first, DB insert after; file removed on DB failure (best-effort)