diff --git a/opal-task/cmd/root.go b/opal-task/cmd/root.go index 00bf4cd..40a6951 100644 --- a/opal-task/cmd/root.go +++ b/opal-task/cmd/root.go @@ -12,9 +12,10 @@ import ( // ParsedArgs represents preprocessed command arguments type ParsedArgs struct { - Command string - Filters []string - Modifiers []string + Command string + Filters []string + Modifiers []string + CmdArgIndex int // position of command in os.Args[1:], -1 if not found } // Context key for parsed args @@ -84,28 +85,29 @@ func Execute() error { if len(os.Args) > 1 { firstArg := os.Args[1] if firstArg == "-h" || firstArg == "--help" || firstArg == "help" { - // Let Cobra handle help - skip preprocessing return rootCmd.Execute() } } - // Preprocess arguments BEFORE Cobra routing + // Preprocess arguments (read-only scan — os.Args is never mutated) if len(os.Args) > 1 { parsed := preprocessArgs(os.Args[1:]) - - // Store in context for commands to use ctx := context.WithValue(context.Background(), parsedArgsKey, parsed) rootCmd.SetContext(ctx) - // Rewrite os.Args for Cobra based on parsed command - // This allows Cobra to route to the correct command - if parsed.Command != "list" || len(parsed.Filters) > 0 || len(parsed.Modifiers) > 0 { - // Reconstruct args: [command, ...filters, ...modifiers] - newArgs := []string{os.Args[0], parsed.Command} - newArgs = append(newArgs, parsed.Filters...) - newArgs = append(newArgs, parsed.Modifiers...) - os.Args = newArgs + // Build clean args for Cobra via SetArgs (os.Args stays untouched). + if parsed.CmdArgIndex >= 0 { + i := parsed.CmdArgIndex + 1 // offset for binary name in os.Args + cmdAndAfter := os.Args[i:] // command + subcommands + their flags + preCmdFlags := collectFlags(os.Args[1:i]) // persistent flags before command + + cobraArgs := make([]string, 0, len(cmdAndAfter)+len(preCmdFlags)) + cobraArgs = append(cobraArgs, cmdAndAfter...) + cobraArgs = append(cobraArgs, preCmdFlags...) + rootCmd.SetArgs(cobraArgs) } + // CmdArgIndex == -1: no command found, don't call SetArgs. + // Cobra processes os.Args naturally → root command → default report. } return rootCmd.Execute() @@ -127,9 +129,10 @@ func getParsedArgs(cmd *cobra.Command) *ParsedArgs { func preprocessArgs(args []string) *ParsedArgs { if len(args) == 0 { return &ParsedArgs{ - Command: "list", // Default command - Filters: []string{}, - Modifiers: []string{}, + Command: "list", // Default command + Filters: []string{}, + Modifiers: []string{}, + CmdArgIndex: -1, } } @@ -167,9 +170,10 @@ func preprocessArgs(args []string) *ParsedArgs { // If no command found, treat as filters for default list command if cmdIdx == -1 { return &ParsedArgs{ - Command: "list", - Filters: stripFlags(args), - Modifiers: []string{}, + Command: "list", + Filters: stripFlags(args), + Modifiers: []string{}, + CmdArgIndex: -1, } } @@ -183,16 +187,18 @@ func preprocessArgs(args []string) *ParsedArgs { // Determine how to interpret right args if commandsWithModifiers[cmdName] { return &ParsedArgs{ - Command: cmdName, - Filters: leftArgs, - Modifiers: rightArgs, + Command: cmdName, + Filters: leftArgs, + Modifiers: rightArgs, + CmdArgIndex: cmdIdx, } } else { allFilters := append(leftArgs, rightArgs...) return &ParsedArgs{ - Command: cmdName, - Filters: allFilters, - Modifiers: []string{}, + Command: cmdName, + Filters: allFilters, + Modifiers: []string{}, + CmdArgIndex: cmdIdx, } } } @@ -208,6 +214,35 @@ func stripFlags(args []string) []string { return result } +// collectFlags extracts flag arguments (with their values) from a slice. +// Uses Cobra's persistent flag registry to determine if a flag takes a value. +func collectFlags(args []string) []string { + var flags []string + for i := 0; i < len(args); i++ { + if !strings.HasPrefix(args[i], "-") { + continue + } + flags = append(flags, args[i]) + // If flag uses = syntax, value is already included + if strings.Contains(args[i], "=") { + continue + } + // Check if this flag takes a value argument + if i+1 < len(args) { + name := strings.TrimLeft(args[i], "-") + f := rootCmd.PersistentFlags().Lookup(name) + if f == nil && len(name) == 1 { + f = rootCmd.PersistentFlags().ShorthandLookup(name) + } + if f != nil && f.Value.Type() != "bool" { + i++ + flags = append(flags, args[i]) + } + } + } + return flags +} + func init() { // Add persistent flags for directory overrides rootCmd.PersistentFlags().StringVar(&configDirFlag, "config-dir", "", diff --git a/opal-web/BUGS.md b/opal-web/BUGS.md index c47be92..9341df4 100644 --- a/opal-web/BUGS.md +++ b/opal-web/BUGS.md @@ -2,3 +2,39 @@ `Buy milk due:8d wait:5d` still showing up # Missing uncomplete feat +Undo / uncomplete fails in web-ui. task still has checked box and strikethrough. + +# Cycling priority - Web +Trying to edit a task priority results in following console error: +GQLaRcBw.js:1 PUT https://opal.jnss.me/api/tasks/8814798c-97af-4134-9786-47e027d164c8 400 (Bad Request) +window.fetch @ GQLaRcBw.js:1 +n @ nlPNz7OE.js:1 +update @ nlPNz7OE.js:1 +updateTask @ 2.qiXA3Mmu.js:1 +X @ 2.qiXA3Mmu.js:3 +Q @ 2.qiXA3Mmu.js:1 +(anonymous) @ idxpmzXF.js:1 +Qe @ jWcw5lls.js:1 +n @ idxpmzXF.js:1 +nlPNz7OE.js:1 API Error [/tasks/8814798c-97af-4134-9786-47e027d164c8]: Error: HTTP 400: + at n (nlPNz7OE.js:1:1556) + at async Object.updateTask (2.qiXA3Mmu.js:1:4502) + at async X (2.qiXA3Mmu.js:3:3519) + at async HTMLDivElement.Q (2.qiXA3Mmu.js:1:58579) +n @ nlPNz7OE.js:1 +await in n +update @ nlPNz7OE.js:1 +updateTask @ 2.qiXA3Mmu.js:1 +X @ 2.qiXA3Mmu.js:3 +Q @ 2.qiXA3Mmu.js:1 +(anonymous) @ idxpmzXF.js:1 +Qe @ jWcw5lls.js:1 +n @ idxpmzXF.js:1 +2.qiXA3Mmu.js:3 Failed to update task: Error: HTTP 400: + at n (nlPNz7OE.js:1:1556) + at async Object.updateTask (2.qiXA3Mmu.js:1:4502) + at async X (2.qiXA3Mmu.js:3:3519) + at async HTMLDivElement.Q (2.qiXA3Mmu.js:1:58579) + +# Ambiguity complete and details tap. +Pressing the task description completes the task. only the checkbox click should complete task, otherwise open details view. to complete swipe left