Skip to content

Commit d71c470

Browse files
feat: implement task API architecture and core UI component structure
1 parent 39612d4 commit d71c470

13 files changed

Lines changed: 423 additions & 353 deletions

File tree

CHANGELOG.md

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,45 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.5.1] - 2026-05-06
9-
10-
### Fixed
11-
- Due date time corruption bug: time was incorrectly overwritten by MM-dd being parsed as HH:mm
12-
- Edit mode field labels were duplicated (plain label + icon label both rendered)
13-
- Auto-cleanup plugin commands now apply live (no restart required) because `app_start` hooks are emitted after plugins load
14-
- Tag cleanup now updates live: orphaned tags removed by cleanup/prune no longer require a restart to disappear from the UI
15-
- Deleting a just-created task now immediately prunes orphaned tags, so tag lists update without waiting for periodic cleanup
16-
17-
### Changed
18-
- Edit mode: all field labels are now fully highlighted regardless of active state
19-
- Edit mode: active input fields have a brighter background for clearer focus indication
20-
- Default task list right-side field order changed to: tags due priority
21-
22-
### Added
23-
- `config.toml` support for `[list.order] right = [...]` to configure right-side field display order
24-
- New task field `wait_until`: hides a task (and its recurrences) until the specified datetime
25-
- New task field `until`: stops recurrence generation after the specified datetime
8+
## [1.5.2] - 2026-05-06
9+
10+
### Added
11+
- Dashboard-style Empty State: Transformed the empty home screen into a modular 'Momentum Dashboard' with productivity velocity metrics and clean, bordered layout.
12+
- Parent Selection Dialog: Replaced manual task ID entry with an interactive task palette for parent task selection.
13+
- Full API/MCP Field Coverage: Exposed all task fields (WaitUntil, Recurrence, Until, ParentID) to the API and AI Assistant.
14+
15+
### Changed
16+
- Minimalist UI Overhaul: Replaced heavy borders with whitespace for a cleaner, Apple-inspired aesthetic.
17+
- Global Spacing: Standardized padding and layout margins across all views.
18+
- Typography: Refined font weights and hierarchy for better visual focus.
19+
- Unified Powerline Pills: Standardized all pill caps across tabs, status badges, priority tags, and tag filters for cohesive design.
20+
- AI Assistant Panel: Refined layout with multi-line input box and full-height divider for a premium feel.
21+
22+
### Fixed
23+
- AI task creation reliability: Resolved race condition in message routing and improved date parsing robustness in the API.
24+
- AI context persistence: Fixed bug where conversation history was lost between prompts.
25+
- Editor compilation errors: Fixed syntax and scope issues in parent selection logic.
26+
- UI Redundancy: Removed duplicate selection indicators in task list.
27+
- AI System Prompt: Enforced strict schema adherence for AI tool calls, enabling reliable persistence of all task attributes via natural language.
28+
29+
## [1.5.1] - 2026-05-06
30+
31+
### Fixed
32+
- Due date time corruption bug: time was incorrectly overwritten by MM-dd being parsed as HH:mm
33+
- Edit mode field labels were duplicated (plain label + icon label both rendered)
34+
- Auto-cleanup plugin commands now apply live (no restart required) because `app_start` hooks are emitted after plugins load
35+
- Tag cleanup now updates live: orphaned tags removed by cleanup/prune no longer require a restart to disappear from the UI
36+
- Deleting a just-created task now immediately prunes orphaned tags, so tag lists update without waiting for periodic cleanup
37+
38+
### Changed
39+
- Edit mode: all field labels are now fully highlighted regardless of active state
40+
- Edit mode: active input fields have a brighter background for clearer focus indication
41+
- Default task list right-side field order changed to: tags due priority
42+
43+
### Added
44+
- `config.toml` support for `[list.order] right = [...]` to configure right-side field display order
45+
- New task field `wait_until`: hides a task (and its recurrences) until the specified datetime
46+
- New task field `until`: stops recurrence generation after the specified datetime
2647

2748
## [1.5.0]
2849

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
**The terminal task manager for developers who live in their editor.**
88

9-
No browser tabs. No subscriptions. No mouse. Just your tasks — exactly where your brain already is.
9+
A premium, minimalist task manager designed for focus. Kairo strips away the noise, relying on structured whitespace and refined typography to keep you in your flow.
1010

1111
<br/>
1212

1313
[![Release](https://img.shields.io/github/v/release/programmersd21/kairo?style=for-the-badge&logo=github&color=7c3aed)](https://github.com/programmersd21/kairo/releases)
1414
[![CI](https://img.shields.io/github/actions/workflow/status/programmersd21/kairo/ci.yml?branch=main&style=for-the-badge&logo=github&color=2563eb)](https://github.com/programmersd21/kairo/actions)
15-
[![Go Report](https://img.shields.io/badge/go%20report-A%2B-brightgreen?style=for-the-badge&logo=go&logoColor=white)](https://goreportcard.com/report/github.com/programmersd21/kairo)
16-
[![Downloads](https://img.shields.io/github/downloads/programmersd21/kairo/total?style=for-the-badge&logo=github)](https://github.com/programmersd21/kairo/releases)
1715
[![License: MIT](https://img.shields.io/badge/License-MIT-f59e0b?style=for-the-badge)](https://opensource.org/licenses/MIT)
1816

1917
<br/>
@@ -26,14 +24,15 @@ No browser tabs. No subscriptions. No mouse. Just your tasks — exactly where y
2624

2725
## Why Kairo?
2826

29-
Most task managers make you context-switch out of your flow. Kairo doesn't.
27+
Kairo is built on the philosophy of "Calm Tech." We focus on your data, not our UI.
3028

31-
| Pain point | What Kairo does |
29+
| Premium Minimalist Feature | Benefit |
3230
|---|---|
33-
| GUI apps break your focus | Lives entirely in your terminal |
34-
| Cloud tools own your data | Everything local, stored in SQLite |
35-
| Plain-text tools lack structure | Full tagging, filtering, and fuzzy search |
36-
| Legacy TUIs feel dated | Modern, animated, keyboard-first UX |
31+
| **Momentum Dashboard** | Empty states are now data-rich, bordered modules. |
32+
| **White-Space-First UI** | No borders, no clutter — just content. |
33+
| **Monochrome Design** | Neutral core, semantic-only color highlights. |
34+
| **Typography Hierarchy** | Clear visual focus through font weight and scale. |
35+
| **Fluid Motion** | Sub-300ms transitions that feel "alive." |
3736

3837
---
3938

VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.1
1+
1.5.2

internal/ai/gemini.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ type StreamChunk struct {
1717
Done bool
1818
Err error
1919
ToolUse *ToolUseEvent
20-
Refresh bool // Signals the UI to reload data live
20+
Refresh bool // Signals the UI to reload data live
21+
History []*genai.Content // Updated history to persist context
2122
}
2223

2324
type AppContext struct {
@@ -60,19 +61,29 @@ func (c *Client) ChatStream(ctx context.Context, history []*genai.Content, userM
6061
}
6162
}()
6263

63-
systemPrompt := fmt.Sprintf(`You are Kairo Assistant, an expert productivity AI embedded in Kairo, a terminal-based task manager.
64+
systemPrompt := fmt.Sprintf(`You are Kairo Assistant, an expert productivity AI.
6465
Current View: %s
6566
Context Data: %s
6667
67-
You have TOTAL control over the user's tasks, projects, UI themes, and Lua plugins through tool calls.
68-
You can:
69-
- Manage tasks (create, update, delete, list, tags, priority, status, deadline).
70-
- Recurring tasks: use 'recurrence' (none|weekly|monthly), 'recurrence_weekly' (e.g. ["mon", "wed"]), 'recurrence_monthly' (e.g. 15).
71-
- Change the UI theme (e.g. catppuccin, dracula, nord, midnight, etc.) using 'set_theme'.
72-
- Manage Lua plugins (list, read, write, delete) using 'plugin_*' actions.
73-
- Configure AI settings and export data.
74-
75-
Be concise, direct, and action-oriented. Format output for a terminal: use plain text, avoid markdown headers, use simple bullet points (- ) only.`, appCtx.ViewName, appCtx.Data)
68+
You have TOTAL control over tasks via the 'kairo_api' tool.
69+
70+
SCHEMA FOR 'kairo_api' PAYLOAD:
71+
For 'create' and 'update' actions, the 'payload' must be a JSON object with these fields:
72+
- title (string, REQUIRED for create)
73+
- description (string)
74+
- tags (array of strings)
75+
- priority (int: 0=P0, 1=P1, 2=P2, 3=P3)
76+
- status (string: "todo", "doing", "done")
77+
- deadline (string: RFC3339 format)
78+
- wait_until (string: RFC3339 format)
79+
- until (string: RFC3339 format)
80+
- recurrence (string: "none", "weekly", "monthly")
81+
- recurrence_weekly (array of strings: e.g. ["mon", "wed"])
82+
- recurrence_monthly (int: 1-31)
83+
- parent_id (string: ID of parent task)
84+
- collapsed (bool)
85+
86+
Follow this structure strictly for tool calls. Be concise, direct, and action-oriented. Format output for a terminal: plain text, bullet points only.`, appCtx.ViewName, appCtx.Data)
7687

7788
kairoTools := GetKairoTools()
7889

@@ -149,7 +160,7 @@ Be concise, direct, and action-oriented. Format output for a terminal: use plain
149160
}
150161

151162
if len(toolCalls) == 0 {
152-
send(StreamChunk{Done: true})
163+
send(StreamChunk{Done: true, History: history})
153164
return
154165
}
155166

internal/api/api.go

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/programmersd21/kairo/internal/config"
1313
"github.com/programmersd21/kairo/internal/core"
1414
"github.com/programmersd21/kairo/internal/core/codec"
15+
"github.com/programmersd21/kairo/internal/core/nlp"
1516
"github.com/programmersd21/kairo/internal/service"
1617
)
1718

@@ -104,6 +105,8 @@ type TaskDTO struct {
104105
Priority int `json:"priority"`
105106
Status string `json:"status"`
106107
Deadline *string `json:"deadline,omitempty"`
108+
WaitUntil *string `json:"wait_until,omitempty"`
109+
Until *string `json:"until,omitempty"`
107110
Recurrence string `json:"recurrence,omitempty"`
108111
RecurrenceWeekly []string `json:"recurrence_weekly,omitempty"`
109112
RecurrenceMonthly int `json:"recurrence_monthly,omitempty"`
@@ -134,6 +137,14 @@ func toDTO(t core.Task) TaskDTO {
134137
s := t.Deadline.Format("2006-01-02T15:04:05Z")
135138
dto.Deadline = &s
136139
}
140+
if t.WaitUntil != nil {
141+
s := t.WaitUntil.Format("2006-01-02T15:04:05Z")
142+
dto.WaitUntil = &s
143+
}
144+
if t.Until != nil {
145+
s := t.Until.Format("2006-01-02T15:04:05Z")
146+
dto.Until = &s
147+
}
137148
return dto
138149
}
139150

@@ -146,6 +157,8 @@ func (api *TaskAPI) handleCreate(ctx context.Context, payload json.RawMessage) R
146157
Priority *int `json:"priority,omitempty"`
147158
Status string `json:"status,omitempty"`
148159
Deadline *string `json:"deadline,omitempty"`
160+
WaitUntil *string `json:"wait_until,omitempty"`
161+
Until *string `json:"until,omitempty"`
149162
Recurrence string `json:"recurrence,omitempty"`
150163
RecurrenceWeekly []string `json:"recurrence_weekly,omitempty"`
151164
RecurrenceMonthly *int `json:"recurrence_monthly,omitempty"`
@@ -173,13 +186,17 @@ func (api *TaskAPI) handleCreate(ctx context.Context, payload json.RawMessage) R
173186
Description: p.Description,
174187
Tags: p.Tags,
175188
Status: core.StatusTodo,
176-
Recurrence: core.RecurrenceType(p.Recurrence),
189+
Recurrence: core.RecurrenceNone,
177190
RecurrenceWeekly: p.RecurrenceWeekly,
178191
RecurrenceMonthly: 0,
179192
ParentID: p.ParentID,
180193
Collapsed: p.Collapsed,
181194
}
182195

196+
if p.Recurrence != "" {
197+
task.Recurrence = core.RecurrenceType(p.Recurrence)
198+
}
199+
183200
if p.RecurrenceMonthly != nil {
184201
task.RecurrenceMonthly = *p.RecurrenceMonthly
185202
}
@@ -191,9 +208,21 @@ func (api *TaskAPI) handleCreate(ctx context.Context, payload json.RawMessage) R
191208
task.Priority = core.Priority(*p.Priority)
192209
}
193210
if p.Deadline != nil {
194-
t, err := time.Parse(time.RFC3339, *p.Deadline)
195-
if err == nil {
196-
task.Deadline = &t
211+
t, err := nlp.ParseDeadline(*p.Deadline, time.Now())
212+
if err == nil && t != nil {
213+
task.Deadline = t
214+
}
215+
}
216+
if p.WaitUntil != nil {
217+
t, err := nlp.ParseDeadline(*p.WaitUntil, time.Now())
218+
if err == nil && t != nil {
219+
task.WaitUntil = t
220+
}
221+
}
222+
if p.Until != nil {
223+
t, err := nlp.ParseDeadline(*p.Until, time.Now())
224+
if err == nil && t != nil {
225+
task.Until = t
197226
}
198227
}
199228

@@ -256,6 +285,8 @@ func (api *TaskAPI) handleUpdate(ctx context.Context, payload json.RawMessage) R
256285
Priority *int `json:"priority,omitempty"`
257286
Status *string `json:"status,omitempty"`
258287
Deadline *string `json:"deadline,omitempty"`
288+
WaitUntil *string `json:"wait_until,omitempty"`
289+
Until *string `json:"until,omitempty"`
259290
Recurrence *string `json:"recurrence,omitempty"`
260291
RecurrenceWeekly []string `json:"recurrence_weekly,omitempty"`
261292
RecurrenceMonthly *int `json:"recurrence_monthly,omitempty"`
@@ -315,14 +346,40 @@ func (api *TaskAPI) handleUpdate(ctx context.Context, payload json.RawMessage) R
315346
var nilTime *time.Time
316347
patch.Deadline = &nilTime
317348
} else {
318-
t, err := time.Parse(time.RFC3339, *p.Deadline)
319-
if err == nil {
320-
timePtr := &t
349+
t, err := nlp.ParseDeadline(*p.Deadline, time.Now())
350+
if err == nil && t != nil {
351+
timePtr := t
321352
patch.Deadline = &timePtr
322353
}
323354
}
324355
}
325356

357+
if p.WaitUntil != nil {
358+
if *p.WaitUntil == "" {
359+
var nilTime *time.Time
360+
patch.WaitUntil = &nilTime
361+
} else {
362+
t, err := nlp.ParseDeadline(*p.WaitUntil, time.Now())
363+
if err == nil && t != nil {
364+
timePtr := t
365+
patch.WaitUntil = &timePtr
366+
}
367+
}
368+
}
369+
370+
if p.Until != nil {
371+
if *p.Until == "" {
372+
var nilTime *time.Time
373+
patch.Until = &nilTime
374+
} else {
375+
t, err := nlp.ParseDeadline(*p.Until, time.Now())
376+
if err == nil && t != nil {
377+
timePtr := t
378+
patch.Until = &timePtr
379+
}
380+
}
381+
}
382+
326383
updated, err := api.service.Update(ctx, p.ID, patch)
327384
if err != nil {
328385
return Response{

0 commit comments

Comments
 (0)