@@ -32,6 +32,7 @@ import (
3232 "github.com/programmersd21/kairo/internal/ui/help"
3333 "github.com/programmersd21/kairo/internal/ui/import_export_menu"
3434 "github.com/programmersd21/kairo/internal/ui/keymap"
35+ "github.com/programmersd21/kairo/internal/ui/onboarding"
3536 "github.com/programmersd21/kairo/internal/ui/palette"
3637 "github.com/programmersd21/kairo/internal/ui/plugin_menu"
3738 "github.com/programmersd21/kairo/internal/ui/render"
@@ -94,6 +95,7 @@ const (
9495 ModeConfirmQuit
9596 ModeSettings
9697 ModeImportExport
98+ ModeOnboarding
9799)
98100
99101type Model struct {
@@ -122,6 +124,7 @@ type Model struct {
122124 det detail.Model
123125 edit * editor.Model
124126 hlp help.Model
127+ onb onboarding.Model
125128 tm theme_menu.Model
126129 pm plugin_menu.Model
127130 set settings.Model
@@ -222,6 +225,7 @@ func New(ctx context.Context, cfg config.Config, svc service.TaskService) (tea.M
222225 m .pal = palette .New (m .s )
223226 m .det = detail .New (m .s )
224227 m .hlp = help .New (m .s , m .km )
228+ m .onb = onboarding .New (m .s , m .km )
225229 m .tm = theme_menu .New (m .s , nil )
226230 m .pm = plugin_menu .New (m .s )
227231 m .set = settings .New (m .s , cfg )
@@ -316,6 +320,11 @@ func New(ctx context.Context, cfg config.Config, svc service.TaskService) (tea.M
316320
317321 m .rebuildViews ()
318322 m .activeIdx = 0
323+
324+ if ! m .cfg .App .OnboardingCompleted {
325+ m .mode = ModeOnboarding
326+ }
327+
319328 return m , nil
320329}
321330
@@ -357,6 +366,9 @@ func (m *Model) Init() tea.Cmd {
357366 if m .aiChan != nil {
358367 cmds = append (cmds , m .listenAICmd ())
359368 }
369+ if m .mode == ModeOnboarding {
370+ cmds = append (cmds , m .onb .Init ())
371+ }
360372 if m .cfg .App .MCPEnabled {
361373 cmds = append (cmds , m .startMCPCmd ())
362374 }
@@ -391,6 +403,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
391403 case tea.WindowSizeMsg :
392404 m .width , m .height = x .Width , x .Height
393405 m .list .SetSize (x .Width , x .Height )
406+ m .onb .SetSize (x .Width , x .Height )
394407 m .pal .SetSize (x .Width , x .Height )
395408 m .det .SetSize (x .Width , x .Height )
396409 m .tm .SetSize (x .Width , x .Height )
@@ -436,6 +449,20 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
436449 m .rebuildPaletteIndex ()
437450 return m , nil
438451
452+ case onboarding.CloseMsg :
453+ m .mode = ModeList
454+ if ! x .Skipped {
455+ m .cfg .App .OnboardingCompleted = true
456+ _ = m .cfg .Save ()
457+ }
458+ if m .cfg .App .Animations {
459+ m .transitioning = true
460+ m .transitionStarted = time .Now ()
461+ m .animationGen ++
462+ return m , m .viewTransitionTickCmd ()
463+ }
464+ return m , nil
465+
439466 case palette.CloseMsg :
440467 if m .mode == ModePalette {
441468 m .mode = ModeList
@@ -491,6 +518,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
491518 if m .aiKey != "" {
492519 m .aiClient , _ = ai .NewClient (m .ctx , m .aiKey , m .cfg .App .AIModel )
493520 ai .SetService (m .svc )
521+ } else {
522+ m .aiClient = nil
494523 }
495524 m .km = keymap .FromConfig (m .cfg .Keymap )
496525 m .thBuiltin = theme .FindBuiltin (m .cfg .App .Theme )
@@ -980,6 +1009,20 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9801009 return m , nil
9811010 }
9821011
1012+ if km .String () == "ctrl+d" {
1013+ m .onb = onboarding .New (m .s , m .km )
1014+ m .onb .SetSize (m .width , m .height )
1015+ m .mode = ModeOnboarding
1016+ var animCmd tea.Cmd
1017+ if m .cfg .App .Animations {
1018+ m .transitioning = true
1019+ m .transitionStarted = time .Now ()
1020+ m .animationGen ++
1021+ animCmd = m .viewTransitionTickCmd ()
1022+ }
1023+ return m , tea .Batch (m .onb .Init (), animCmd )
1024+ }
1025+
9831026 if keymapMatch (m .km .AIPanelToggle , km ) {
9841027 m .aiPanel .Toggle ()
9851028 m .aiPanel .SetSize (m .width , m .height )
@@ -1294,6 +1337,10 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
12941337 var cmd tea.Cmd
12951338 m .iem , cmd = m .iem .Update (msg )
12961339 return m , cmd
1340+ case ModeOnboarding :
1341+ var cmd tea.Cmd
1342+ m .onb , cmd = m .onb .Update (msg )
1343+ return m , cmd
12971344 }
12981345
12991346 return m , nil
@@ -1360,6 +1407,7 @@ func (m *Model) renderMainUI() string {
13601407 m .pm .SetSize (mainW , availableHeight )
13611408 m .set .SetSize (mainW , availableHeight )
13621409 m .hlp .SetSize (mainW , availableHeight )
1410+ m .hlp .AIEnabled = m .aiKey != ""
13631411 m .tm .SetSize (mainW , availableHeight )
13641412 m .iem .SetSize (mainW , availableHeight )
13651413 if m .edit != nil {
@@ -1403,6 +1451,8 @@ func (m *Model) renderMainUI() string {
14031451 }
14041452 case ModeImportExport :
14051453 body = m .iem .View ()
1454+ case ModeOnboarding :
1455+ body = m .list .View ()
14061456 default :
14071457 body = m .list .View ()
14081458 }
@@ -1438,6 +1488,15 @@ func (m *Model) renderMainUI() string {
14381488 }
14391489
14401490 content := lipgloss .JoinVertical (lipgloss .Left , head , body , foot )
1491+
1492+ if m .mode == ModeOnboarding {
1493+ content = lipgloss .Place (m .width , m .height , lipgloss .Center , lipgloss .Center ,
1494+ m .onb .View (),
1495+ lipgloss .WithWhitespaceChars (" " ),
1496+ lipgloss .WithWhitespaceBackground (m .s .Theme .Bg ),
1497+ )
1498+ }
1499+
14411500 if m .aiPanel .Visible {
14421501 return lipgloss .JoinHorizontal (lipgloss .Top , content , m .aiPanel .View ())
14431502 }
@@ -1780,9 +1839,11 @@ func (m *Model) renderFooter() string {
17801839 makePill (fk (m .km .ToggleStrike ) + " " + styles .IconStrike + "done" ),
17811840 makePill (fk (m .km .DeleteTask ) + " " + styles .IconDelete + "delete" ),
17821841 makePill (fk (m .km .Settings ) + " settings" ),
1783- makePill (fk (m .km .AIPanelToggle ) + " assistant" ),
1784- makePill (fk (m .km .Help ) + " " + styles .IconHelp + "help" ),
17851842 }
1843+ if m .aiKey != "" {
1844+ items = append (items , makePill (fk (m .km .AIPanelToggle )+ " assistant" ))
1845+ }
1846+ items = append (items , makePill (fk (m .km .Help )+ " " + styles .IconHelp + "help" ))
17861847 left = " " + strings .Join (items , sep )
17871848 }
17881849 }
@@ -2090,8 +2151,13 @@ func (m *Model) rebuildPaletteIndex() {
20902151 search.Item {ID : "cmd:view:tag" , Kind : search .KindCommand , Title : "View: By Tag" , Hint : "f" },
20912152 search.Item {ID : "cmd:view:priority" , Kind : search .KindCommand , Title : "View: By Priority" , Hint : "5" },
20922153 search.Item {ID : "cmd:import-export" , Kind : search .KindCommand , Title : "Import/Export" , Hint : "x" },
2154+ search.Item {ID : "cmd:onboarding" , Kind : search .KindCommand , Title : "Welcome Tour" , Hint : "ctrl+d" },
20932155 )
20942156
2157+ if m .aiKey != "" {
2158+ items = append (items , search.Item {ID : "cmd:ai" , Kind : search .KindCommand , Title : "AI Assistant" , Hint : "ctrl+a" })
2159+ }
2160+
20952161 for _ , t := range m .tags {
20962162 items = append (items , search.Item {ID : t , Kind : search .KindTag , Title : "#" + t , Hint : "tag" })
20972163 }
@@ -2194,6 +2260,11 @@ func (m *Model) runCommand(id string) tea.Cmd {
21942260 case "cmd:import-export" :
21952261 m .mode = ModeImportExport
21962262 return nil
2263+ case "cmd:onboarding" :
2264+ m .onb = onboarding .New (m .s , m .km )
2265+ m .onb .SetSize (m .width , m .height )
2266+ m .mode = ModeOnboarding
2267+ return m .onb .Init ()
21972268 }
21982269
21992270 if strings .HasPrefix (id , "cmd:view:plugin:" ) {
0 commit comments