[
  {
    "path": ".github/CODEOWNERS",
    "content": "*  @bashbunni\n"
  },
  {
    "path": ".gitignore",
    "content": "kancli\n.DS_Store\ndebug.log\n"
  },
  {
    "path": "README.md",
    "content": "# Kancli\n\nWelcome to our demo repo for a kanban board for the command line.\n\nThere is a video to go along with this repo on our [YouTube\nchannel](https://youtube.com/c/charmcli) if you would like a full walk through\ntutorial on the topic.\n\n## Feedback\n\nWe'd love to hear your thoughts on this tutorial. Feel free to drop us a note!\n\n* [Twitter](https://twitter.com/charmcli)\n* [The Fediverse](https://mastodon.social/@charmcli)\n* [Discord](https://charm.sh/chat)\n\n## License\n\n[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)\n\n***\n\nPart of [Charm](https://charm.sh).\n\n<a href=\"https://charm.sh/\"><img alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"></a>\n\nCharm热爱开源 • Charm loves open source\n"
  },
  {
    "path": "column.go",
    "content": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/list\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\nconst APPEND = -1\n\ntype column struct {\n\tfocus  bool\n\tstatus status\n\tlist   list.Model\n\theight int\n\twidth  int\n}\n\nfunc (c *column) Focus() {\n\tc.focus = true\n}\n\nfunc (c *column) Blur() {\n\tc.focus = false\n}\n\nfunc (c *column) Focused() bool {\n\treturn c.focus\n}\n\nfunc newColumn(status status) column {\n\tvar focus bool\n\tif status == todo {\n\t\tfocus = true\n\t}\n\tdefaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0)\n\tdefaultList.SetShowHelp(false)\n\treturn column{focus: focus, status: status, list: defaultList}\n}\n\n// Init does initial setup for the column.\nfunc (c column) Init() tea.Cmd {\n\treturn nil\n}\n\n// Update handles all the I/O for columns.\nfunc (c column) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tc.setSize(msg.Width, msg.Height)\n\t\tc.list.SetSize(msg.Width/margin, msg.Height/2)\n\tcase tea.KeyMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, keys.Edit):\n\t\t\tif len(c.list.VisibleItems()) != 0 {\n\t\t\t\ttask := c.list.SelectedItem().(Task)\n\t\t\t\tf := NewForm(task.title, task.description)\n\t\t\t\tf.index = c.list.Index()\n\t\t\t\tf.col = c\n\t\t\t\treturn f.Update(nil)\n\t\t\t}\n\t\tcase key.Matches(msg, keys.New):\n\t\t\tf := newDefaultForm()\n\t\t\tf.index = APPEND\n\t\t\tf.col = c\n\t\t\treturn f.Update(nil)\n\t\tcase key.Matches(msg, keys.Delete):\n\t\t\treturn c, c.DeleteCurrent()\n\t\tcase key.Matches(msg, keys.Enter):\n\t\t\treturn c, c.MoveToNext()\n\t\t}\n\t}\n\tc.list, cmd = c.list.Update(msg)\n\treturn c, cmd\n}\n\nfunc (c column) View() string {\n\treturn c.getStyle().Render(c.list.View())\n}\n\nfunc (c *column) DeleteCurrent() tea.Cmd {\n\tif len(c.list.VisibleItems()) > 0 {\n\t\tc.list.RemoveItem(c.list.Index())\n\t}\n\n\tvar cmd tea.Cmd\n\tc.list, cmd = c.list.Update(nil)\n\treturn cmd\n}\n\nfunc (c *column) Set(i int, t Task) tea.Cmd {\n\tif i != APPEND {\n\t\treturn c.list.SetItem(i, t)\n\t}\n\treturn c.list.InsertItem(APPEND, t)\n}\n\nfunc (c *column) setSize(width, height int) {\n\tc.width = width / margin\n}\n\nfunc (c *column) getStyle() lipgloss.Style {\n\tif c.Focused() {\n\t\treturn lipgloss.NewStyle().\n\t\t\tPadding(1, 2).\n\t\t\tBorder(lipgloss.RoundedBorder()).\n\t\t\tBorderForeground(lipgloss.Color(\"62\")).\n\t\t\tHeight(c.height).\n\t\t\tWidth(c.width)\n\t}\n\treturn lipgloss.NewStyle().\n\t\tPadding(1, 2).\n\t\tBorder(lipgloss.HiddenBorder()).\n\t\tHeight(c.height).\n\t\tWidth(c.width)\n}\n\ntype moveMsg struct {\n\tTask\n}\n\nfunc (c *column) MoveToNext() tea.Cmd {\n\tvar task Task\n\tvar ok bool\n\t// If nothing is selected, the SelectedItem will return Nil.\n\tif task, ok = c.list.SelectedItem().(Task); !ok {\n\t\treturn nil\n\t}\n\t// move item\n\tc.list.RemoveItem(c.list.Index())\n\ttask.status = c.status.getNext()\n\n\t// refresh list\n\tvar cmd tea.Cmd\n\tc.list, cmd = c.list.Update(nil)\n\n\treturn tea.Sequence(cmd, func() tea.Msg { return moveMsg{task} })\n}\n"
  },
  {
    "path": "data.go",
    "content": "package main\n\nimport \"github.com/charmbracelet/bubbles/list\"\n\n// Provides the mock data to fill the kanban board\n\nfunc (b *Board) initLists() {\n\tb.cols = []column{\n\t\tnewColumn(todo),\n\t\tnewColumn(inProgress),\n\t\tnewColumn(done),\n\t}\n\t// Init To Do\n\tb.cols[todo].list.Title = \"To Do\"\n\tb.cols[todo].list.SetItems([]list.Item{\n\t\tTask{status: todo, title: \"buy milk\", description: \"strawberry milk\"},\n\t\tTask{status: todo, title: \"eat sushi\", description: \"negitoro roll, miso soup, rice\"},\n\t\tTask{status: todo, title: \"fold laundry\", description: \"or wear wrinkly t-shirts\"},\n\t})\n\t// Init in progress\n\tb.cols[inProgress].list.Title = \"In Progress\"\n\tb.cols[inProgress].list.SetItems([]list.Item{\n\t\tTask{status: inProgress, title: \"write code\", description: \"don't worry, it's Go\"},\n\t})\n\t// Init done\n\tb.cols[done].list.Title = \"Done\"\n\tb.cols[done].list.SetItems([]list.Item{\n\t\tTask{status: done, title: \"stay cool\", description: \"as a cucumber\"},\n\t})\n}\n"
  },
  {
    "path": "form.go",
    "content": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/textarea\"\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype Form struct {\n\thelp        help.Model\n\ttitle       textinput.Model\n\tdescription textarea.Model\n\tcol         column\n\tindex       int\n}\n\nfunc newDefaultForm() *Form {\n\treturn NewForm(\"task name\", \"\")\n}\n\nfunc NewForm(title, description string) *Form {\n\tform := Form{\n\t\thelp:        help.New(),\n\t\ttitle:       textinput.New(),\n\t\tdescription: textarea.New(),\n\t}\n\tform.title.Placeholder = title\n\tform.description.Placeholder = description\n\tform.title.Focus()\n\treturn &form\n}\n\nfunc (f Form) CreateTask() Task {\n\treturn Task{f.col.status, f.title.Value(), f.description.Value()}\n}\n\nfunc (f Form) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tvar cmd tea.Cmd\n\tswitch msg := msg.(type) {\n\tcase column:\n\t\tf.col = msg\n\t\tf.col.list.Index()\n\tcase tea.KeyMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, keys.Quit):\n\t\t\treturn f, tea.Quit\n\n\t\tcase key.Matches(msg, keys.Back):\n\t\t\treturn board.Update(nil)\n\t\tcase key.Matches(msg, keys.Enter):\n\t\t\tif f.title.Focused() {\n\t\t\t\tf.title.Blur()\n\t\t\t\tf.description.Focus()\n\t\t\t\treturn f, textarea.Blink\n\t\t\t}\n\t\t\t// Return the completed form as a message.\n\t\t\treturn board.Update(f)\n\t\t}\n\t}\n\tif f.title.Focused() {\n\t\tf.title, cmd = f.title.Update(msg)\n\t\treturn f, cmd\n\t}\n\tf.description, cmd = f.description.Update(msg)\n\treturn f, cmd\n}\n\nfunc (f Form) View() string {\n\treturn lipgloss.JoinVertical(\n\t\tlipgloss.Left,\n\t\t\"Create a new task\",\n\t\tf.title.View(),\n\t\tf.description.View(),\n\t\tf.help.View(keys))\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/charmbracelet/kancli\n\ngo 1.19\n\nrequire (\n\tgithub.com/charmbracelet/bubbles v0.16.1\n\tgithub.com/charmbracelet/bubbletea v0.24.2\n\tgithub.com/charmbracelet/lipgloss v0.7.1\n)\n\nrequire (\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/mattn/go-localereader v0.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.14 // indirect\n\tgithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/reflow v0.3.0 // indirect\n\tgithub.com/muesli/termenv v0.15.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.4 // indirect\n\tgithub.com/sahilm/fuzzy v0.1.0 // indirect\n\tgolang.org/x/sync v0.3.0 // indirect\n\tgolang.org/x/sys v0.9.0 // indirect\n\tgolang.org/x/term v0.9.0 // indirect\n\tgolang.org/x/text v0.10.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=\ngithub.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=\ngithub.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=\ngithub.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=\ngithub.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=\ngithub.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=\ngithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=\ngithub.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=\ngithub.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=\ngithub.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=\ngithub.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=\ngithub.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=\ngithub.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=\ngithub.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=\ngithub.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=\ngithub.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=\ngolang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=\ngolang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=\ngolang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=\ngolang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=\ngolang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\n"
  },
  {
    "path": "keys.go",
    "content": "package main\n\nimport \"github.com/charmbracelet/bubbles/key\"\n\n// ShortHelp returns keybindings to be shown in the mini help view. It's part\n// of the key.Map interface.\nfunc (k keyMap) ShortHelp() []key.Binding {\n\treturn []key.Binding{k.Help, k.Quit}\n}\n\n// FullHelp returns keybindings for the expanded help view. It's part of the\n// key.Map interface.\nfunc (k keyMap) FullHelp() [][]key.Binding {\n\treturn [][]key.Binding{\n\t\t{k.Up, k.Down, k.Left, k.Right}, // first column\n\t\t{k.Help, k.Quit},                // second column\n\t}\n}\n\ntype keyMap struct {\n\tNew    key.Binding\n\tEdit   key.Binding\n\tDelete key.Binding\n\tUp     key.Binding\n\tDown   key.Binding\n\tRight  key.Binding\n\tLeft   key.Binding\n\tEnter  key.Binding\n\tHelp   key.Binding\n\tQuit   key.Binding\n\tBack   key.Binding\n}\n\nvar keys = keyMap{\n\tNew: key.NewBinding(\n\t\tkey.WithKeys(\"n\"),\n\t\tkey.WithHelp(\"n\", \"new\"),\n\t),\n\tEdit: key.NewBinding(\n\t\tkey.WithKeys(\"e\"),\n\t\tkey.WithHelp(\"e\", \"edit\"),\n\t),\n\tDelete: key.NewBinding(\n\t\tkey.WithKeys(\"d\"),\n\t\tkey.WithHelp(\"d\", \"delete\"),\n\t),\n\tUp: key.NewBinding(\n\t\tkey.WithKeys(\"up\", \"k\"),\n\t\tkey.WithHelp(\"↑/k\", \"move up\"),\n\t),\n\tDown: key.NewBinding(\n\t\tkey.WithKeys(\"down\", \"j\"),\n\t\tkey.WithHelp(\"↓/j\", \"move down\"),\n\t),\n\tRight: key.NewBinding(\n\t\tkey.WithKeys(\"right\", \"l\"),\n\t\tkey.WithHelp(\"→/l\", \"move right\"),\n\t),\n\tLeft: key.NewBinding(\n\t\tkey.WithKeys(\"left\", \"h\"),\n\t\tkey.WithHelp(\"←/h\", \"move left\"),\n\t),\n\tEnter: key.NewBinding(\n\t\tkey.WithKeys(\"enter\"),\n\t\tkey.WithHelp(\"enter\", \"enter\"),\n\t),\n\tHelp: key.NewBinding(\n\t\tkey.WithKeys(\"?\"),\n\t\tkey.WithHelp(\"?\", \"toggle help\"),\n\t),\n\tQuit: key.NewBinding(\n\t\tkey.WithKeys(\"q\", \"ctrl+c\"),\n\t\tkey.WithHelp(\"q/ctrl+c\", \"quit\"),\n\t),\n\tBack: key.NewBinding(\n\t\tkey.WithKeys(\"esc\"),\n\t\tkey.WithHelp(\"esc\", \"back\"),\n\t),\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n)\n\ntype status int\n\nfunc (s status) getNext() status {\n\tif s == done {\n\t\treturn todo\n\t}\n\treturn s + 1\n}\n\nfunc (s status) getPrev() status {\n\tif s == todo {\n\t\treturn done\n\t}\n\treturn s - 1\n}\n\nconst margin = 4\n\nvar board *Board\n\nconst (\n\ttodo status = iota\n\tinProgress\n\tdone\n)\n\nfunc main() {\n\tf, err := tea.LogToFile(\"debug.log\", \"debug\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\tdefer f.Close()\n\n\tboard = NewBoard()\n\tboard.initLists()\n\tp := tea.NewProgram(board)\n\tif _, err := p.Run(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "model.go",
    "content": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss\"\n)\n\ntype Board struct {\n\thelp     help.Model\n\tloaded   bool\n\tfocused  status\n\tcols     []column\n\tquitting bool\n}\n\nfunc NewBoard() *Board {\n\thelp := help.New()\n\thelp.ShowAll = true\n\treturn &Board{help: help, focused: todo}\n}\n\nfunc (m *Board) Init() tea.Cmd {\n\treturn nil\n}\n\nfunc (m *Board) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n\tswitch msg := msg.(type) {\n\tcase tea.WindowSizeMsg:\n\t\tvar cmd tea.Cmd\n\t\tvar cmds []tea.Cmd\n\t\tm.help.Width = msg.Width - margin\n\t\tfor i := 0; i < len(m.cols); i++ {\n\t\t\tvar res tea.Model\n\t\t\tres, cmd = m.cols[i].Update(msg)\n\t\t\tm.cols[i] = res.(column)\n\t\t\tcmds = append(cmds, cmd)\n\t\t}\n\t\tm.loaded = true\n\t\treturn m, tea.Batch(cmds...)\n\tcase Form:\n\t\treturn m, m.cols[m.focused].Set(msg.index, msg.CreateTask())\n\tcase moveMsg:\n\t\treturn m, m.cols[m.focused.getNext()].Set(APPEND, msg.Task)\n\tcase tea.KeyMsg:\n\t\tswitch {\n\t\tcase key.Matches(msg, keys.Quit):\n\t\t\tm.quitting = true\n\t\t\treturn m, tea.Quit\n\t\tcase key.Matches(msg, keys.Left):\n\t\t\tm.cols[m.focused].Blur()\n\t\t\tm.focused = m.focused.getPrev()\n\t\t\tm.cols[m.focused].Focus()\n\t\tcase key.Matches(msg, keys.Right):\n\t\t\tm.cols[m.focused].Blur()\n\t\t\tm.focused = m.focused.getNext()\n\t\t\tm.cols[m.focused].Focus()\n\t\t}\n\t}\n\tres, cmd := m.cols[m.focused].Update(msg)\n\tif _, ok := res.(column); ok {\n\t\tm.cols[m.focused] = res.(column)\n\t} else {\n\t\treturn res, cmd\n\t}\n\treturn m, cmd\n}\n\n// Changing to pointer receiver to get back to this model after adding a new task via the form... Otherwise I would need to pass this model along to the form and it becomes highly coupled to the other models.\nfunc (m *Board) View() string {\n\tif m.quitting {\n\t\treturn \"\"\n\t}\n\tif !m.loaded {\n\t\treturn \"loading...\"\n\t}\n\tboard := lipgloss.JoinHorizontal(\n\t\tlipgloss.Left,\n\t\tm.cols[todo].View(),\n\t\tm.cols[inProgress].View(),\n\t\tm.cols[done].View(),\n\t)\n\treturn lipgloss.JoinVertical(lipgloss.Left, board, m.help.View(keys))\n}\n"
  },
  {
    "path": "task.go",
    "content": "package main\n\ntype Task struct {\n\tstatus      status\n\ttitle       string\n\tdescription string\n}\n\nfunc NewTask(status status, title, description string) Task {\n\treturn Task{status: status, title: title, description: description}\n}\n\nfunc (t *Task) Next() {\n\tif t.status == done {\n\t\tt.status = todo\n\t} else {\n\t\tt.status++\n\t}\n}\n\n// implement the list.Item interface\nfunc (t Task) FilterValue() string {\n\treturn t.title\n}\n\nfunc (t Task) Title() string {\n\treturn t.title\n}\n\nfunc (t Task) Description() string {\n\treturn t.description\n}\n"
  }
]