Extract CreateRecurringTask into engine package for reuse by both CLI
and API. Add POST /tasks/parse endpoint for CLI-style input parsing.
Remove FK constraint on change_log to preserve history after task
deletion. Update web frontend to filter completed tasks from view and
add mock mode support for development.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace cobra.OnInitialize with PersistentPreRun pattern to fix initialization
issues with setup and server commands. This follows Cobra best practices and
allows subcommands to properly override initialization behavior.
Problem:
- OnInitialize runs globally before command parsing
- os.Args check for 'setup' was fragile and broke with flags
- Setup wizard failed on server: 'unable to open database file: no such file
or directory'
Solution:
- Use rootCmd.PersistentPreRun for initialization (inherited by all commands)
- setup and server commands override with their own PersistentPreRun
- Directory overrides still applied correctly in all cases
- Removes fragile os.Args parsing
Benefits:
- Works regardless of flag order
- Follows Cobra's intended design patterns
- Only 3 files modified (root.go, setup.go, server.go)
- Commands that need custom init (setup/server) simply override
- All other commands get automatic initialization
- Cleaner, more maintainable code
Testing:
- ✓ opal setup works without initialization errors
- ✓ opal list initializes database correctly
- ✓ First-run detection still works
- ✓ Directory overrides work with flags in any position
- ✓ Server command handles its own initialization
Display the default config and data directories before prompting
the user if they want to customize them. This gives better context
for the decision.
Before: Only showed directories if user chose not to customize
After: Shows directories first, then asks if user wants to change them
Implement a comprehensive setup wizard to improve onboarding and
configuration experience for both personal and server deployments.
Key features:
- Interactive wizard with profile selection (personal/server/custom)
- Quick setup mode with sensible defaults
- First-run detection with helpful welcome message
- Directory configuration with validation
- Server OAuth/JWT configuration with auto-generation
- Environment file creation for server deployments
- Template generators for systemd service and env files
New commands:
- opal setup # Interactive wizard
- opal setup --quick # Quick setup with defaults
- opal setup --profile # Use specific profile
- opal setup --show-systemd # Show systemd template
- opal setup --show-env # Show environment file template
Implementation:
- internal/wizard/prompts.go: Reusable prompt utilities
- internal/wizard/profiles.go: Profile definitions and templates
- cmd/setup.go: Main setup command implementation
- cmd/root.go: First-run detection and welcome message
- internal/engine/config.go: ConfigExists() and IsFirstRun() helpers
User experience:
- On first run, shows welcome message suggesting 'opal setup'
- Non-intrusive - creates defaults automatically if skipped
- Wizard guides through all configuration options
- Server setup includes OAuth/JWT configuration
- Environment file created with proper permissions (0600)
- Clear next steps displayed after completion
Separate configuration from data storage and make paths configurable
via environment variables and command-line flags. This improves
Unix/Linux compliance and supports both development and production
deployments.
Key changes:
- Separate config dir (opal.yml) from data dir (database, logs)
- Support XDG Base Directory specification
- Add --config-dir and --data-dir flags
- Environment variables: OPAL_CONFIG_DIR, OPAL_DATA_DIR, OPAL_DB_PATH
- Smart fallback: /etc/opal, /var/lib/opal -> ~/.config/opal, ~/.local/share/opal
- Server mode validates required OAuth/JWT environment variables
- Update naming from 'jade' to 'opal' throughout
- Update systemd service name to 'opal.service'
- Add migration guide in README
Default paths:
- Config: /etc/opal (fallback: ~/.config/opal)
- Data: /var/lib/opal (fallback: ~/.local/share/opal)
Files modified:
- internal/engine/config.go: New directory resolution logic
- internal/engine/database.go: Auto-create data directory
- cmd/root.go: Add global flags for directory overrides
- cmd/server.go: Add configuration validation
- cmd/sync.go, internal/sync/*: Use new path helper functions
- tests: Update to use directory overrides
- docs: Update deployment guide and README
- Add OAuth2 client for Authentik integration
- Implement JWT token generation and validation
- Add refresh token support with database storage
- Update database schema with oauth_subject, oauth_provider, and refresh_tokens table
- Create auth package with config, jwt, oauth, and token management
- Add OAuth endpoints: /auth/login, /auth/callback, /auth/refresh, /auth/logout
- Update AuthMiddleware to support both JWT and API key authentication
- Add user helper functions for OAuth user creation and retrieval
- Add .env.example with OAuth configuration template
API keys still work for CLI compatibility while JWT tokens support web/mobile clients.
BREAKING CHANGE: BuildWorkingSet() now accepts []*Task instead of *Filter
Problem:
- Working set IDs were assigned based on database query order (unsorted)
- Reports displayed tasks in sorted order (by urgency)
- Result: IDs didn't match displayed task positions (ID 1 wasn't first task)
Solution:
- Changed BuildWorkingSet() to accept pre-sorted task slice
- Reports now pass sorted tasks to BuildWorkingSet()
- IDs are assigned sequentially to match display order (1, 2, 3...)
Behavior:
- Reports rebuild working set on every execution with fresh IDs
- Task operations (done, modify, info) use saved working set IDs
- After completing a task, re-running report renumbers remaining tasks
Example:
Before: opal list shows ID 1 = low urgency task (wrong)
After: opal list shows ID 1 = highest urgency task (correct)
Tested scenarios:
✓ List report: IDs 1-N match urgency order
✓ Next report: IDs 1-5 match top urgent tasks
✓ Task completion: IDs renumber correctly after removal
✓ Multiple operations: Use saved working set (correct behavior)
✓ Different reports: Each builds own sequential IDs
- Add urgency calculation based on multiple factors:
* Due date (linear scale: overdue=12.0, today=10.0, week=6.0, 2weeks=2.0)
* Priority (H=6.0, M=3.9, D=1.8, L=0.0)
* Age (0-2.0 over 365 days)
* Active status (+4.0 boost)
* Waiting status (-3.0 penalty)
* Tags (+1.0 with count modifier)
* Project assignment (+1.0)
* Configurable urgent tag (default 'next', +15.0)
- Replace priority column with urgency in all reports
* Display as decimal with 1 decimal place
* 4-tier color coding: ≥10 (bright red), ≥5 (red), ≥2 (yellow), <2 (cyan)
* Minimal format color-coded by urgency
- Add default urgency sorting to all reports
* list, minimal, active, ready, overdue reports sort by urgency
* newest/oldest keep date-based sorting
- Implement 'next' report
* Shows most urgent ready tasks
* Configurable limit (default 5)
* Only includes tasks ready to work on (no future wait/scheduled)
- Add urgency display to info command
* Shows urgency score alongside priority
- All urgency coefficients configurable via config
* Adjusted defaults for Opal's simpler model (no blocking/annotations)
* Configurable urgent tag name (not hardcoded to 'next')
Priority order maintained: High > Medium > Default > Low
- Implement ProgressReporter interface with InteractiveProgress and NoOpProgress
- Add real-time progress bars using go-pretty/progress library
- Track 6 sync phases: connection test, queue push, pull, parse, apply, and server push
- Add --quiet flag to suppress progress output
- Auto-detect TTY to disable progress when piped/redirected
- Show task-level progress during apply phase with descriptions
- Display percentage complete and elapsed time for each phase
- Fix template task filtering bug: templates now hidden from all reports
except 'template' and 'all' reports, even when using custom filters
- Add support for status:template filter to explicitly show templates
- Implement comprehensive report system with 12 predefined reports:
* active - Started tasks
* all - All tasks including templates
* completed - Completed tasks
* list - Pending tasks (default)
* minimal - Pending tasks in minimal format
* newest - Most recent pending tasks
* oldest - Oldest pending tasks
* overdue - Overdue tasks
* ready - Tasks ready to work on
* recurring - Pending recurring instances
* template - Recurring template tasks
* waiting - Hidden/waiting tasks
- Replace list command with report-based architecture
- Add configurable default_report option (defaults to 'list')
- Add minimal display format (ID + description only)
- Support flexible syntax: 'opal <report> [filters]' or 'opal [filters] <report>'
- Add 'opal reports' command to list all available reports
Implement complete key:value format parsing for change log entries and fix
critical tag synchronization issue from server to client.
Key Changes:
1. Shared Key:Value Parser (NEW: internal/engine/parser.go)
- Created ParseKeyValueFormat() for both edit and sync operations
- Supports flexible whitespace: 'key:value' and 'key: value'
- Handles comment skipping for edit files
- Consolidates parsing logic (DRY principle)
2. Database Triggers - Tags Support (internal/engine/database.go)
- Added tags to track_task_create trigger
- Added tags to track_task_update trigger
- Tags sorted alphabetically via SQL ORDER BY
- Format: 'tags: alpha,bravo,charlie'
3. Task Creation - Tag Update Fix (internal/engine/task.go)
- CreateTaskWithModifier() now triggers update after adding tags
- Ensures tags appear in change log (UPDATE entry)
- Fixes missing tags in initial CREATE entries
4. Edit Command - Use Shared Parser (cmd/edit.go)
- Replaced custom parseEditedFile() with shared ParseKeyValueFormat()
- Added tag sorting in parseTags()
- Removed ~30 lines, improved maintainability
5. Sync Client - Complete Implementation (internal/sync/client.go)
- NEW: applyChangeDataToTask() - parses all fields from change log
- NEW: Helper functions for status, priority, tag parsing
- FIXED: parseChanges() - sort by timestamp+ID before grouping
- Added parent/child task ordering (prevents FK violations)
- Enhanced tag sync in merge loop with task reload
- Specific validation error messages per field
Critical Bug Fix:
- When CREATE and UPDATE have same timestamp, old code kept CREATE (no tags)
- New code sorts by ID as tiebreaker, ensuring UPDATE (with tags) is used
- Verified: Server->client tag sync now works correctly
Validation:
- Description must not be empty (both edit and sync)
- Recurrence validated (not negative, max 100 years)
- All timestamps parsed correctly (Unix epoch)
- Tags sorted alphabetically in all contexts
Testing:
- Fresh pull from server: ✅ All tags present
- API-created tasks: ✅ Tags sync correctly
- Local->server->client round-trip: ✅ No data loss
- Same-second CREATE+UPDATE: ✅ Correct entry processed
- Parent/child tasks: ✅ Correct ordering
Files Changed:
- NEW: internal/engine/parser.go (+44 lines)
- Modified: internal/engine/database.go (+10 lines)
- Modified: internal/engine/task.go (+8 lines)
- Modified: cmd/edit.go (-25 lines net)
- Modified: internal/sync/client.go (+280 lines)
- Modified: srv/README.md (+1 line)
Total: +318 lines added, -25 removed, net +293 lines
This completes Phase 6: Full bidirectional sync with complete tag support.
- Added 'opal sync merge' command for initial database merge
- Support for merge strategies: prefer-local, prefer-server, smart (default)
- Offline queue already implemented in Phase 2
- Conflict warning display already implemented in Phase 2-3
- Full offline mode support with automatic queueing when server unreachable
- Created comprehensive sync command suite:
- 'opal sync init' - Configure sync with server (URL, API key)
- 'opal sync status' - Show sync configuration and queue status
- 'opal sync now' - Bidirectional sync with conflict resolution
- 'opal sync up' - Push local changes to server
- 'opal sync down' - Pull server changes to local
- 'opal sync log' - View conflict resolution log
- Added interactive prompts for init (URL and API key)
- Automatic client ID generation (UUID)
- Display user-friendly sync results with emojis
- Support for viewing queued offline changes
- Integration with config system for persistent sync settings
- Created sync client for communicating with API server
- Implemented conflict resolution strategies (last-write-wins, server-wins, client-wins)
- Added offline change queue for queuing changes when server is unreachable
- Implemented merge logic for local and remote task lists
- Added conflict logging to sync_conflicts.log
- Created bidirectional sync with pull/push operations
- Extended Config struct with sync settings (URL, API key, client ID, strategy, offline queue)
- Added SyncResult display with user-friendly output
- Sync handlers already implemented in Phase 1 (GetChanges, PushChanges)
Replace manual string-based table formatting with jedib0t/go-pretty/v6/table
library to fix alignment issues with Norwegian characters (æøå) and ANSI
color codes.
Changes:
- Migrate FormatTaskList() to use go-pretty table
- Migrate FormatTaskDetail() to use clean table format
- Migrate FormatProjects() and FormatTagCounts() for consistency
- Remove truncate() function (no longer needed)
- Configure StyleLight with Unicode box-drawing characters
- Set proper column widths and alignment
Fixes:
- Priority column now center-aligned under 'Pri'
- Norwegian characters (æøå) display and align correctly
- Tags column properly aligned
- Description field truncates at 40 chars with proper UTF-8 handling
- All ANSI color codes handled automatically
- Consistent formatting across all table views
All existing color functions work unchanged. UTF-8 and ANSI codes are
handled automatically by go-pretty's width calculation.
When creating recurring tasks, modifiers (due, wait, priority, etc) were
not being applied because AttributeOrder was not copied to the temporary
modifier. This caused all date attributes to be ignored.
Refactored addRecurringTask to:
- Create task structs directly instead of using CreateTask (avoiding
premature saves)
- Use ApplyToNew() instead of Apply() for modifiers before first save
- Properly copy AttributeOrder when building the temporary modifier
- Save template and instance once with all fields correctly set
This ensures recurring tasks now properly have due dates, wait dates,
and other modifiers applied when created via 'opal add' or batch import.
- Add 'opal info' command to display detailed task information
- Shows all task attributes including UUID, timestamps, and metadata
- Supports flexible syntax (opal info 2 or opal 2 info)
- Displays recurrence information and parent UUID for recurring tasks
- Add 'opal edit' command to edit tasks in $EDITOR
- Opens task in text editor with human-readable format
- Supports all editable fields with smart date formatting
- Special handling for recurring tasks (updates parent template)
- Status changes trigger appropriate methods (Complete/Delete)
- Auto-saves changes on editor exit without confirmation
- Clear validation and error messages
- Register new commands in root command dispatcher
Previously, only numeric forms (1d, 1w, 1m, 1y) and plural word forms
(days, weeks, months, years) were supported for recurrence patterns.
The common adverbial forms (daily, weekly, monthly, yearly) would fail
with 'invalid recurrence pattern' error.
Now users can use natural language recurrence patterns:
- recur:daily -> 1 day interval
- recur:weekly -> 7 day interval
- recur:monthly -> 30 day interval
- recur:yearly -> 365 day interval
Added test coverage for all four new patterns.
Previously, 'opal -h', 'opal --help', and 'opal help' would show the
list command help instead of the root command help. This was because
the argument preprocessing logic would rewrite these as 'opal list -h'
before Cobra could handle them.
Now we detect help flags/command before preprocessing and let Cobra
handle them naturally. This ensures:
- 'opal -h' and 'opal --help' show root help
- 'opal help' shows root help
- 'opal help <command>' shows command-specific help
- 'opal <command> -h' still shows command-specific help
- All existing flexible syntax continues to work
- Mark all features as implemented ✅
- Add comprehensive examples for all date formats
- Document configuration options
- Add examples for time of day, period boundaries, and relative expressions
- Include chaining examples and use cases
- Add parseRelativeExpression() to detect pattern: attr+/-duration
- Add resolveDateValue() to resolve absolute or relative dates
- Add applyDateAttribute() helper for date attributes with relative support
- Track attribute order in Modifier struct (AttributeOrder field)
- Refactor Apply() and ApplyToNew() to process attrs in order
- Support chaining: due:mon scheduled:due-3d wait:scheduled-1d
- Support addition and subtraction: due+1y, wait-2d
- Add comprehensive test suite for relative expressions
- Error if referencing undefined date attribute
- All 38+ tests passing
- Add month name parsing (jan, january, feb, etc.) with year logic
- Add day+month parsing (21jan, Jan21, 21January, etc.) in all case variations
- Add period boundaries (sod, eod, sow, eow, som, eom, soy, eoy)
- Add special keywords (later, someday -> 2150-01-01)
- Add duration-as-date offset (2d, 3w, etc. means X from now)
- Add named duration aliases (daily, weekly, monthly, yearly)
- Enhance ParseRecurrencePattern to support min, sec, hrs and word forms
- Implement time-of-day parsing (mon:15:35, tomorrow:0800, 15:35)
- Add comprehensive test suite (50+ tests total)
- All tests passing
- Add WeekStartDay and DefaultDueTime fields to Config struct
- Set defaults: week_start_day=monday, default_due_time=empty
- Add GetWeekStart() and GetDefaultDueTime() helper methods
- Update ParseDate() to use configured week start day
- All tests passing
- Create DateParser struct with configurable base time and week start
- Add placeholder methods for new parsing features
- Implement time-of-day parsing foundation (splitDateTime, parseTimeOfDay)
- Maintain backward compatibility with existing ParseDate() function
- Update tests to use new DateParser API
- All 33 existing tests passing
Issue 1: Fix recurrence calculation for overdue tasks
- Use completion date (End) as base for next instance, not original due date
- If task due Monday completed Wednesday, next is Wednesday+7d not Monday+7d
- Fallback to Due date if End is not set
- Update test to verify new behavior
Issue 2: Fix description parsing to work without quotes
- Add parseAddArgs() to extract description from non-modifier words
- Description = all words that don't start with +, -, or contain :
- Enables: opal add buy groceries +shop carrots → 'buy groceries carrots'
- Validate description is required (error if only modifiers)
- Validate recurring tasks require due date
Issue 3: Implement flexible command syntax
- Add preprocessArgs() to parse arguments before Cobra routing
- Detect command position and split filters (left) from modifiers (right)
- Rewrite os.Args so Cobra routes correctly
- Enable both 'opal 2 done' and 'opal done 2' syntax
- Commands without modifiers accept filters on either side
- Commands with modifiers enforce [filters] command [modifiers]
- Add confirmation for modify without filters (modifies all tasks)
All commands updated to use preprocessed ParsedArgs from context.
All tests passing (33 tests).
Phase 6: Display and Basic Commands
- Add display.go with colored formatting for tasks, projects, tags
- Implement cmd/root.go with Cobra command structure
- Implement cmd/list.go for listing and filtering tasks
- Implement cmd/add.go with support for regular and recurring tasks
- Implement cmd/done.go with bulk completion and confirmation
Phase 7: Advanced Commands
- Implement cmd/modify.go for updating task attributes
- Implement cmd/delete.go with soft delete confirmation
- Implement cmd/start.go and cmd/stop.go for task timing
- Implement cmd/count.go for counting filtered tasks
- Implement cmd/projects.go and cmd/tags.go for aggregation
Phase 8: Integration and Polish
- Update main.go to use CLI commands
- Add colored output with fatih/color
- Format task lists with proper alignment
- Highlight overdue tasks in red, upcoming in yellow
- Test end-to-end workflow: add, list, done, recurring tasks
- Verify recurrence spawning works correctly
All CLI commands functional and tested!
- Complete SpawnNextInstance() for creating recurring task instances
- Implement automatic next instance spawning on task completion
- Add support for 'until' date to expire recurrences
- Copy tags from template to new instances
- Add comprehensive recurrence tests (6 tests, all passing)
- Test pattern parsing, formatting, next due calculation
- Test end-to-end recurring task workflow
- Test expiration with until dates
- Implement BuildWorkingSet() to create ephemeral display ID mappings
- Implement LoadWorkingSet() to restore from database
- Add GetTaskByDisplayID() for ID resolution
- Add GetTasks() and Size() helper methods
- Persist working set to SQLite working_set table
- Add comprehensive tests (5 tests, all passing)
- Support filtered working sets