Skip to content

fix(companion): move step detection from Init() to NewModel()#65

Merged
xlanor merged 1 commit into
xlanor:masterfrom
ThyagoTrofeh:fix/companion-init-step-detection
Jul 25, 2026
Merged

fix(companion): move step detection from Init() to NewModel()#65
xlanor merged 1 commit into
xlanor:masterfrom
ThyagoTrofeh:fix/companion-init-step-detection

Conversation

@ThyagoTrofeh

Copy link
Copy Markdown
Contributor

Bug

In bubbletea, Init() uses a value receiver — it receives a copy of the model. Any mutations made inside Init() are silently discarded; only the returned tea.Cmd is used by the runtime.

The step-detection logic was placed in Init():

func (m Model) Init() tea.Cmd {
    if m.state.GetDUID() != "" {
        m.currentStep = StepLogin   // ← mutates a copy, discarded
        tokenInfo := m.state.GetTokenInfo()
        if tokenInfo.HasAccessToken && !tokenInfo.IsExpired {
            m.currentStep = StepServer  // ← discarded
        }
    }
    return tea.Batch(m.currentStepInit(), detectNAT)
}

Because the mutations are discarded, the app always starts at StepDUID regardless of saved state — even when a valid DUID and unexpired tokens are already present in state.json.

Fix

Move the step-detection logic into NewModel() where it can set the field on the struct value being returned:

func NewModel(s *state.AppState) Model {
    currentStep := StepDUID
    if s.GetDUID() != "" {
        currentStep = StepLogin
        tokenInfo := s.GetTokenInfo()
        if tokenInfo.HasAccessToken && !tokenInfo.IsExpired {
            currentStep = StepServer
        }
    }
    return Model{
        state:       s,
        currentStep: currentStep,
        ...
    }
}

func (m Model) Init() tea.Cmd {
    return tea.Batch(m.currentStepInit(), detectNAT)
}

Result

The companion app now opens directly at the correct step:

  • StepServer when DUID + valid tokens are saved → server starts immediately
  • StepLogin when DUID is saved but tokens are missing/expired
  • StepDUID when no state is saved (first run)

🤖 Generated with Claude Code

bubbletea uses value receivers — Init() receives a copy of the model
and any mutations to that copy are silently discarded. Only the returned
tea.Cmd is used; the modified model is never seen by the runtime.

Because of this, the step-detection logic placed in Init() had no effect:
the app always started at StepDUID even when a valid DUID and unexpired
tokens were already saved in state.json.

Moving the same logic into NewModel() fixes the initialization so the
app opens directly at StepServer when credentials are already complete,
or at StepLogin when only the DUID is present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@xlanor

xlanor commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Can you provide a repro scenario of what led you to this bug?

@ThyagoTrofeh

Copy link
Copy Markdown
Contributor Author

Repro steps:

  1. Run akira-companion for the first time and complete the full flow: enter the DUID, log in via PSN, let the server start
  2. Quit the app (q)
  3. Run akira-companion again

Expected: the app opens directly at Step 3 (Server) since state.json already contains a valid DUID and non-expired tokens.

Actual: the app always opens at Step 1 (DUID) regardless of saved state, forcing the user to navigate through steps they already completed.

Root cause: bubbletea calls Init() with a value receiver, so any mutations inside Init() are made on a copy that is immediately discarded — only the returned tea.Cmd is kept. The step-detection logic was inside Init(), so currentStep was always reset to StepDUID by the time the model was rendered.

@xlanor
xlanor merged commit 7363f79 into xlanor:master Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants