Ta-Da! is a personal lifelogger — celebrating what you've accomplished rather than dreading what remains. The name is an inversion of the todo list.
Ta-Da! unifies activity tracking, rhythm discovery, journaling, and accomplishment capture. It answers:
- "What have I done?" — The accomplishment record
- "Who am I becoming?" — The pattern recognition
- "What do I want to remember?" — The memory keeper
It's not about productivity — it's about noticing your own life while you're living it.
| Page | Route | Purpose | Entry Type |
|---|---|---|---|
| Timeline | / |
Chronological feed of all entries | All |
| Ta-Da! | /tada |
Celebrate wins & accomplishments | tada |
| Moments | /moments |
Dreams, reflections, magic moments | moment |
| Sessions | /sessions |
Timed activities (meditation, practice) | timed |
| Tally | /tally |
Count-based (reps, glasses of water) | tally |
| Rhythms | /rhythms |
Habit tracking with graceful chains | Views |
Everything is an Entry with flexible metadata:
- Type = capture behavior (
timed,tada,moment,tally) - Category = life domain (
mindfulness,accomplishment,creative,movement) - Subcategory = specific activity (
sitting,work,piano,push-ups) - Data = type-specific JSONB payload (open schema)
Types and categories are open strings — just use them, no enum changes needed.
- Timers count UP — Celebrate "you did 47 minutes" not "you need 20 more"
- Chains bend, not break — Miss a day? Suggest easier tier, don't show broken streak
- Minimize friction — One tap starts recording, no confirmation dialogs
- Identity over behavior — "You're becoming a meditator" not "47 sessions logged"
| Document | Purpose |
|---|---|
| design/SDR.md | Software requirements — THE source of truth |
| design/philosophy.md | Vision, principles, tone |
| design/ontology.md | Entry types, categories, emojis |
| design/roadmap.md | Feature roadmap by version |
See design/roadmap.md for full details. Key features:
- Voice input across all entry pages (Ta-Da!, Moments, Tally, Sessions)
- Magic moments capture
- Practice links for sessions
- 361+ unit tests passing
- ❌ NEVER write large files in one go — LLM output limits cause truncation. Break into sections: create file with first section, then append remaining sections with separate edits.
- ❌ NEVER run
bun run dev— The user controls the dev server. It runs on :3000. Don't start, restart, or modify it. - ❌ NEVER run
bun run test— It blocks the terminal waiting for interactive quit. Use VS Code Test Explorer,runTeststool, or add--runflag for non-interactive mode. - ❌ NEVER run
sqlite3— sqlite3 CLI is not installed. Usebun run db:studiofor DB UI or inspect via drizzle migrations. - ❌ NEVER commit automatically — Wait for the user to explicitly say "commit" or "commit this" before running git commit.
- ❌ Create documentation sparingly — Only create documentation when there are major changes in function. Strongly favour updating existing files in /docs folder.
- ❌ NEVER use interactive commands — Always use
--run,--reporter=dot, or similar flags to prevent commands from waiting for user input.
Changing these paths WILL cause permanent data loss in production:
- ❌ NEVER change
DATABASE_URLin Dockerfile — Must befile:/data/db.sqlite - ❌ NEVER change the data directory path — Must be
/data, not/app/dataor anything else - ❌ NEVER modify persistent volume paths without checking
docs/DEPLOY_CAPROVER.md
Why /data and not /app/data?
- CapRover mounts host directory
/var/lib/caprover/appsdata/tadatato container path/data - The
/appdirectory is ephemeral (rebuilt on each deploy) - The
/datadirectory persists across container rebuilds
Before ANY Dockerfile changes:
- Check
docs/DEPLOY_CAPROVER.mdfor current production configuration - Verify the DATABASE_URL path matches CapRover's "Path in App" setting
- If unsure, ASK the user before making changes
If you accidentally change database paths:
- Data may still exist on host at
/var/lib/caprover/appsdata/tadata/db.sqlite - Revert the path change and redeploy to recover
- Quotes:
"not' - Semicolons: Required
- Types: Never
any(useunknown+ guards) $fetchcalls: Always use explicit type:$fetch<Type>("/api/...")— untyped calls cause CI failures- Strictness: All code must pass TypeScript strict mode (no ts-ignore, proper null checks)
- Logging:
createLogger()notconsole.log
- ❌ Launch dev server (user has it on :3000)
- ❌ Use
anytype - ❌ Use Bun APIs in server code (production is Node 20)
- ❌ Create test scripts in root - use
scripts/ - ❌ Create excessive documentation
- ❌ Auto-commit changes (wait for explicit instruction)
- ❌ Create markdown summary files in root
- Stack: Nuxt 3 + Vue 3 + TypeScript + Bun + SQLite/Drizzle
- Dir: Always
cd appbefore commands - Docs:
design/SDR.md(requirements),design/ontology.md(entry types) - New docs: If explicitly requested, put in
docs/folder, not root
| File | Purpose |
|---|---|
app/server/db/schema.ts |
Database schema (entries, users, rhythms) |
app/utils/categoryDefaults.ts |
Ontology: categories, subcategories, emojis |
app/layouts/default.vue |
Main layout with navigation |
app/composables/useEntryEngine.ts |
Entry CRUD operations |
app/composables/useEntrySave.ts |
Voice/batch entry creation |
tada/
├── app/ # Nuxt 3 application
│ ├── pages/ # File-based routing
│ │ ├── index.vue # Timeline (/)
│ │ ├── tada/ # Ta-Da! (/tada, /tada/history)
│ │ ├── moments.vue # Moments (/moments)
│ │ ├── sessions.vue # Sessions (/sessions)
│ │ ├── tally.vue # Tally (/tally)
│ │ └── rhythms.vue # Rhythms (/rhythms)
│ ├── components/ # Vue components
│ │ └── voice/ # Voice recording components
│ ├── composables/ # Vue composables (shared logic)
│ ├── server/ # Nitro server
│ │ ├── api/ # REST endpoints
│ │ └── db/ # Drizzle schema & migrations
│ └── utils/ # Client utilities
├── design/ # Design documents (SDR, philosophy, ontology)
├── docs/ # Developer documentation
└── specs/ # Feature specifications
# Always start here
cd app
# Check/fix single file (fast!)
bun run lint:fix path/to/file.ts
eslint --fix path/to/file.vue
# Project-wide (only when needed)
bun run lint:fix # All files
bun run db:generate # After schema change
bun run db:migrate # Apply migrations
bun run db:studio # DB UI on :4983
# ⚠️ TYPECHECK STRATEGY (slow - use sparingly!)
# Full typecheck takes 30-60 seconds. Use this strategy:
#
# 1. RELY ON DEV SERVER: The running Nuxt dev server shows type errors in terminal
# 2. USE VS CODE: Red squiggles show errors in real-time
# 3. USE get_errors TOOL: Check specific files for errors without running typecheck
# 4. BATCH TYPECHECKS: Run once per phase/commit, not after every file
#
# When to run full typecheck:
# - Before committing (required)
# - After schema.ts changes
# - After creating new API endpoints
# - When VS Code errors seem stale
#
# DO NOT run typecheck:
# - After every file edit
# - Multiple times in a row
# - Just to "verify" - trust VS Code and dev server
# ⚠️ IMPORTANT: Never run `bun run test` in CLI - it blocks terminal!
# Use VS Code Test Explorer or runTests tool with limits insteadThe VS Code Problems panel has been configured for clarity:
- Errors (❌) block compilation - fix these first
- Warnings (
⚠️ ) are suggestions - fix when convenient - Info (ℹ️) are hints - optional improvements
Filtering noise:
skipLibCheck: truesuppresses third-party module errors- PWA modules have type stubs in
types/pwa-icons.d.ts - Node modules excluded from diagnostics
Using the Problems panel:
- Click error to jump to location
- Right-click → "Copy" to copy error text
- Right-click → "Copy Message" for just the message
- Filter by severity using the toolbar icons
- Use search box to filter by keyword
Cmd/Ctrl+Shift+Mto toggle panel
When errors are overwhelming:
- Run
bun run typecheckto see actual TS errors - Fix errors in production code first (not node_modules)
- Use
// @ts-expect-errorwith explanation for unavoidable issues
Current status: 361+ unit tests passing. Integration tests with @nuxt/test-utils/e2e in tests/api/ and tests/integration/. Full auth/import integration tests planned for v0.4.0.
- Unit tests: Co-locate with source (
utils/*.test.ts) - Integration tests:
tests/api/*.test.ts(use @nuxt/test-utils/e2e) - Never create test scripts in root
- See
app/tests/README.mdfor examples
cd app
bun run test # Run all tests
bun run test --watch # Watch mode
bun run test:ui # Visual UI
bun run test:coverage # Coverage report- Unit tests:
*.test.tsco-located with source - E2E tests:
tests/e2e/*.spec.ts
Example locations:
server/api/entries.get.test.ts— API endpoint testscomposables/useTimer.test.ts— Composable logic teststests/e2e/timer-flow.spec.ts— Full user flows
- 80%+ coverage target for unit tests
- Test critical user flows with E2E (timer, entry creation)
- Co-locate tests with implementation when possible
- Test behavior, not implementation details
The CI pipeline runs in .github/workflows/ci.yml:
bun install --frozen-lockfile— Reproducible installsbun run lint— Must passbunx nuxt prepare— Generate types (critical!)bun run typecheck— Must passmkdir -p data— Create SQLite directorybun run test:run— Non-interactive test runbun run build— Must succeed
Why CI fails when local passes:
-
Missing
.nuxt/types — Local dev server runsnuxt prepareautomatically. CI starts fresh.- Fix: CI runs
bunx nuxt preparebefore typecheck
- Fix: CI runs
-
Missing
data/directory — SQLite can't create test.db if parent directory doesn't exist.- Fix: CI runs
mkdir -p databefore tests
- Fix: CI runs
-
$fetchtype inference — Nuxt infers route types, causing "excessive stack depth" errors.- Fix: Always use explicit types:
$fetch<ResponseType>("/api/...")or$fetch<unknown>(...)for fire-and-forget
- Fix: Always use explicit types:
To test like CI locally:
cd app
rm -rf .nuxt node_modules
bun install --frozen-lockfile
bunx nuxt prepare
bun run lint && bun run typecheck && bun run test:runGolden rule: If adding new $fetch calls, always add explicit type parameter.
Use conventional commits:
feat: add entry CRUD API endpoints
fix: handle null timestamps
docs: update testing guide
design/SDR.md— Software requirements (THE source of truth)design/philosophy.md— Vision and principlesdesign/decisions.md— Technical decisionsdesign/roadmap.md— Feature roadmap
Voice input enables friction-free entry creation across all main pages.
| Page | Voice Feature | Component |
|---|---|---|
| Ta-Da! | Speak accomplishments → LLM extracts multiple ta-das | VoiceRecorder |
| Moments | Quick voice capture for reflections | VoiceRecorder |
| Tally | Speak counts: "10 push-ups, 12 squats" | VoiceTallyRecorder |
| Sessions | Post-session voice reflection | VoiceRecorder |
components/voice/
├── VoiceRecorder.vue # Main recording UI (green mic → red stop)
├── VoiceTallyRecorder.vue # Tally-specific wrapper
composables/
├── useVoiceCapture.ts # MediaRecorder + audio levels
├── useTranscription.ts # Web Speech API + Whisper fallback
├── useLLMStructure.ts # Extract structured data from text
└── useVoiceSettings.ts # API keys, preferences
POST /api/voice/transcribe # Whisper transcription (uses GROQ_API_KEY)
POST /api/structure/tadas # LLM extraction of ta-das from text
POST /api/voice/validate-key # Validate user-provided API keys
- Green mic button in header (hidden when recording)
- Click → Shows voice panel AND starts recording (red stop button)
- Stop → Transcription + LLM processing
- Review → Edit extracted data before saving
Status: 73% complete. See docs/CSV_IMPORT_COMPLETION_REVIEW.md for detailed analysis.
Quick Reference:
- User Entry: Visit
/importor Settings → Import Data - Built-in Recipe: Insight Timer automatically configured, just upload your CSV export
- Custom Recipe: Save your column mappings to reuse on future imports
- Workflow: File Upload → Column Mapping → Data Validation → Import
API Endpoints:
POST /api/import/entries # Perform bulk import (rate limited)
GET /api/import/recipes # List recipes (auto-creates Insight Timer)
POST /api/import/recipes # Save new recipe
GET /api/import/recipes/[id] # Get recipe details
DELETE /api/import/recipes/[id] # Delete recipe
POST /api/import/recipes/[id]/restore # Restore previous version
GET /api/import/logs # Import audit trail
Parser Utilities:
csvParser.ts: parseCSV, detectDateFormat, parseDuration, parseDateTime, generateExternalIdcolumnDetection.ts: Auto-detect entry fields with confidence scoring
Database Tables:
import_recipes(id, userId, name, columnMapping, isBuiltIn, previousVersions)import_logs(id, userId, recipeId, status, totalRows, successfulRows, errors)
Known Limitations:
⚠️ Task 3.5: Timezone/date format selector UI not yet implemented (backend ready)⚠️ Task 4.4: Recipe rollback UI controls missing (backend supports it)⚠️ Task 5.4: E2E tests not yet written (recommend@nuxt/test-utils)
New API endpoint: Create app/server/api/path.{get|post}.ts, import createLogger, use types from ~/server/db/schema
New page: Create app/pages/name.vue (route auto-generated)
Schema change: Edit schema.ts → bun run db:generate → bun run db:migrate → commit both
New entry type: Just use it! Types are open strings. Put type-specific data in data field.
CSV import (v0.2.0+): See docs/CSV_IMPORT_COMPLETION_REVIEW.md for full feature status. Quick facts:
- Page:
/importwith recipe selector and wizard - Built-in Insight Timer recipe auto-created on first access
- Custom recipes saved with version history (up to 3 rollbacks)
- API:
POST /api/import/entrieswith batching (500 rows/txn) - Limits: 50MB files, 1 import/10 seconds per user, auto-deduplication
- Auto-detection: Date formats, durations, column mapping with confidence scoring
- Database:
import_recipesandimport_logstables for recipes and audit trail - See Task 3.5 status: Timezone/format selectors UI still needed
- Module errors:
bun install - DB errors:
bun run db:migrateor deleteapp/data/db.sqlite* - Type errors:
bun run typecheck - Lint errors:
bun run lint:fix
- Ask a clarifying question with specific context
- Propose a short plan before making large changes
- Open discussion rather than making speculative changes
- Check
design/docs for architectural guidance