▍ humdrum codex / ticktock v0.0.2

feat(cmd): add `ticktock tui` grid subcommand; document in README

e95af753c387281918516c7ddb913c8e6fe5d2ac
Kevin Kortum <kevinkortum@me.com> · 2026-07-07 21:48

parent 6cab7d55

3 files changed

README.md +7 −0
@@ -13,5 +13,12 @@ ## Commands
 
 - `ticktock day [date|today|yesterday]` — a day view (the tock-list layout) with
   per-project totals and Enter-to-edit. `←/→` change day, `q` quits.
+- `ticktock tui [date|today|yesterday]` — a spatial day grid. Each slot is a
+  fixed row (default 30 min). `←/→` switch column, `↑/↓` move slot, `space`
+  in ACTIVITY starts a selection and a second `space` opens the add form for
+  that range, `enter` edits the logged session under the cursor, `d` deletes
+  it (with confirm), `n/p/t` change day, `r` reloads, `q` quits.
+  Reads `~/.config/ticktock/config.json` (`slot_minutes`, `grid_start`,
+  `grid_end`, `project`) if present.
 
 Requires `tock` on PATH.
cmd/ticktock/main.go +20 −0
@@ -8,8 +8,10 @@
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/spf13/cobra"
 
+	"ticktock/internal/config"
 	"ticktock/internal/store"
 	"ticktock/internal/tui/day"
+	"ticktock/internal/tui/grid"
 )
 
 func newRootCmd() *cobra.Command {
@@ -18,7 +20,25 @@ 		Use:   "ticktock",
 		Short: "Charm-native front-end for the tock time tracker",
 	}
 	root.AddCommand(newDayCmd())
+	root.AddCommand(newTuiCmd())
 	return root
+}
+
+func newTuiCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "tui [date|today|yesterday]",
+		Short: "Spatial day grid; range-select to add entries",
+		Args:  cobra.MaximumNArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			arg := ""
+			if len(args) == 1 {
+				arg = args[0]
+			}
+			m := grid.New(store.New(store.ExecRunner{}), config.Load(), resolveDate(arg))
+			_, err := tea.NewProgram(m, tea.WithAltScreen()).Run()
+			return err
+		},
+	}
 }
 
 func newDayCmd() *cobra.Command {
cmd/ticktock/main_test.go +13 −0
@@ -15,6 +15,19 @@ 		t.Fatalf("root command is missing the 'day' subcommand")
 	}
 }
 
+func TestTuiCommandRegistered(t *testing.T) {
+	root := newRootCmd()
+	var found bool
+	for _, c := range root.Commands() {
+		if c.Name() == "tui" {
+			found = true
+		}
+	}
+	if !found {
+		t.Fatal("expected a `tui` subcommand")
+	}
+}
+
 func TestRootUse(t *testing.T) {
 	if got := newRootCmd().Use; got != "ticktock" {
 		t.Fatalf("root Use = %q, want %q", got, "ticktock")