Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ unic --checklist ./checklists/readiness.yaml # optional: pre-load a checklist
unic --verbose
```

The TUI shows a short retro bootup splash after a new install or version update, then records the version so later launches go straight to the context picker. Press `Enter`, `Esc`, or `Space` to skip it. Press `S` to open Settings, where the splash can be toggled on or off for every launch.

### Config/bootstrap

```bash
Expand Down Expand Up @@ -146,6 +148,10 @@ favorites:
- Bedrock
- ECS

ui:
boot_splash: false
last_boot_splash_version: 0.1.3

contexts:
- name: dev-sso
order: 10
Expand Down Expand Up @@ -321,6 +327,7 @@ checks:
| `H` | Jump to service list |
| `i` | Enter Inspector mode from the service list |
| `C` | Open context picker |
| `S` | Open settings |
| `/` | Toggle filter mode on supported screens |
| `f` | Favorite/unfavorite the selected service on the service list |
| `?` | Toggle context-aware shortcut help |
Expand Down Expand Up @@ -348,6 +355,7 @@ checks:
| Inspector Mode | `i` open mode from the service list, `Enter` open the selected workflow, `l` open the checklist file picker |
| Security Inspector | `r` run/rescan, `1`-`5` severity filter, `Enter` finding detail |
| Checklist Inspector | `l` load or switch checklist files, `r` run/rerun the loaded checklist, `Enter` result detail |
| Settings | `Enter`/`Space` toggle selected setting, `Esc`/`q` back |
| Context Picker | `a` add context, type or `/` filter, `s` setup selected context and quit, `y` copy selected exports and quit, filter-mode `Ctrl+S` setup selected filtered context, filter-mode `Ctrl+Y` copy selected filtered exports, `u` clear shell context and quit with a final confirmation message |
| ECR | `Enter` images, `d` repository detail, `/` filter, `r` refresh, image detail `c` copy digest, `t` copy tag |
| Lambda | `Enter` invoke, `d` detail, `l` view CloudWatch Logs, `/` filter, `r` refresh |
Expand Down
108 changes: 100 additions & 8 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/table"
Expand All @@ -19,7 +20,8 @@ import (
type screen int

const (
screenServiceList screen = iota
screenBootup screen = iota
screenServiceList
screenFeatureList
screenInstanceList
screenEC2InstanceBrowserList
Expand Down Expand Up @@ -104,19 +106,23 @@ const (
screenContextAdd
screenContextSSOAccountList
screenContextSSORoleList
screenSettings
screenLoading
screenError
screenExitNotice
)

// Model is the root Bubbletea model.
type Model struct {
cfg *config.Config
awsRepo *awsservice.AwsRepository
screen screen
quitting bool
exitMessage string
exitTitle string
cfg *config.Config
awsRepo *awsservice.AwsRepository
screen screen
quitting bool
exitMessage string
exitTitle string
bootFrame int
settingsIdx int
settingsPrevScreen screen

// App-shell state stays root-owned because it coordinates global navigation,
// context/session setup, shared chrome, and cross-feature transitions.
Expand Down Expand Up @@ -193,6 +199,7 @@ type Model struct {
currentVersion string
updateAvailable string // non-empty = new version available
installMethod update.InstallMethod
bootSplash bool

// Error display
errMsg string
Expand Down Expand Up @@ -231,6 +238,7 @@ func New(cfg *config.Config, configPath string, version string, checklistPath ..
ctxPrevScreen: screenServiceList,
services: services,
favoriteServices: favoriteServiceSet(favoriteServiceNames),
bootSplash: cfg != nil && cfg.BootSplash,
loadingSpinner: newLoadingSpinner(),
filterTI: filterTI,
filters: make(map[filterTarget]string),
Expand Down Expand Up @@ -280,7 +288,49 @@ func (m Model) checkForUpdate() tea.Cmd {
}

func (m Model) Init() tea.Cmd {
return tea.Batch(m.loadContexts(), m.checkForUpdate(), m.loadCallerIdentity())
cmds := []tea.Cmd{m.loadContexts(), m.checkForUpdate(), m.loadCallerIdentity()}
if m.shouldShowBootup() {
cmds = append(cmds, bootupTickCmd(), m.markBootupSeen())
return tea.Sequence(
func() tea.Msg { return bootupStartMsg{} },
tea.Batch(cmds...),
)
}
return tea.Sequence(
func() tea.Msg { return screenReadyMsg{} },
tea.Batch(cmds...),
)
}

func (m Model) shouldShowBootup() bool {
if m.bootSplash {
return true
}
if m.cfg == nil {
return false
}
return strings.TrimSpace(m.currentVersion) != "" && m.cfg.BootSplashSeen != m.currentVersion
}

func bootupTickCmd() tea.Cmd {
return tea.Tick(120*time.Millisecond, func(time.Time) tea.Msg {
return bootupTickMsg{}
})
}

func (m Model) markBootupSeen() tea.Cmd {
return func() tea.Msg {
if m.configPath == "" || strings.TrimSpace(m.currentVersion) == "" {
return nil
}
if err := configSetBootSplashSeenVersionFn(m.configPath, m.currentVersion); err != nil {
return nil
}
if m.cfg != nil {
m.cfg.BootSplashSeen = m.currentVersion
}
return nil
}
}

func (m Model) loadCallerIdentity() tea.Cmd {
Expand Down Expand Up @@ -326,6 +376,23 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case callerIdentityMsg:
m.callerIdentity = msg.identity
return m, nil
case screenReadyMsg:
m.screen = screenContextPicker
return m, nil
case bootupStartMsg:
m.screen = screenBootup
m.bootFrame = 0
return m, nil
case bootupTickMsg:
if m.screen != screenBootup {
return m, nil
}
m.bootFrame++
if m.bootFrame >= bootupFrameCount {
m.screen = screenContextPicker
return m, nil
}
return m, bootupTickCmd()
case updateAvailableMsg:
m.installMethod = msg.method
if msg.version != "" {
Expand Down Expand Up @@ -369,6 +436,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.quitting = true
return m, tea.Quit
}
if m.screen == screenBootup {
switch msg.String() {
case "q":
m.quitting = true
return m, tea.Quit
case "enter", "esc", " ":
m.screen = screenContextPicker
}
return m, nil
}
if m.screen == screenExitNotice {
m.quitting = true
return m, tea.Quit
Expand Down Expand Up @@ -402,6 +479,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ctxPrevScreen = m.screen
return m, m.loadContexts()
}
if msg.String() == "S" && !m.filterTI.Focused() && m.screen != screenSettings &&
m.screen != screenSecurityGroupAddRule && m.screen != screenSecurityGroupDeleteConfirm &&
m.screen != screenLambdaInvokeInput && m.screen != screenBedrockKeyCreate &&
m.screen != screenBedrockKeyConfirm {
m.deactivateFilter()
m.settingsPrevScreen = m.screen
m.screen = screenSettings
return m, nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

for _, submodel := range m.featureSubmodels() {
if newM, cmd, handled := submodel.HandleKey(&m, msg); handled {
Expand All @@ -424,6 +510,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.updateContextSSOAccountList(msg)
case screenContextSSORoleList:
return m.updateContextSSORoleList(msg)
case screenSettings:
return m.updateSettings(msg)
case screenError:
return m.updateError(msg)
}
Expand Down Expand Up @@ -564,6 +652,8 @@ func (m Model) View() string {
}
}
switch m.screen {
case screenBootup:
v = m.viewBootup()
case screenServiceList:
v = m.viewServiceList()
case screenFeatureList:
Expand All @@ -578,6 +668,8 @@ func (m Model) View() string {
v = m.viewContextSSOAccountList()
case screenContextSSORoleList:
v = m.viewContextSSORoleList()
case screenSettings:
v = m.viewSettings()
case screenLoading:
v = m.viewLoading()
case screenError:
Expand Down
Loading
Loading