Skip to content

refactor(terminal): split panel features#74

Merged
omarluq merged 3 commits into
mainfrom
refactor/terminal-panel-split
Jun 1, 2026
Merged

refactor(terminal): split panel features#74
omarluq merged 3 commits into
mainfrom
refactor/terminal-panel-split

Conversation

@omarluq

@omarluq omarluq commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary\n- split generic panel framework from feature-specific panel implementations\n- move model, session, settings, scoped model, and tree panel helpers into dedicated files\n- add focused panel tests without changing behavior\n\n## Validation\n- mise exec -- task ci\n

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 06acac39-0a17-400d-8fda-28db700d5927

📥 Commits

Reviewing files that changed from the base of the PR and between 9307f6b and aabf186.

📒 Files selected for processing (4)
  • internal/terminal/panel_actions_test.go
  • internal/terminal/panel_session_selection_test.go
  • internal/terminal/panel_session_test.go
  • internal/terminal/panel_settings_selection_test.go
💤 Files with no reviewable changes (1)
  • internal/terminal/panel_session_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/terminal/panel_settings_selection_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added in-terminal panels: Model selection, Scoped Models, Resume Session, Session Tree, Settings, Hotkeys, and Changelog for interactive selection and controls.
    • Model cycling and scoped-model toggles; settings toggles (theme, thinking, tool output); resume session and session-tree selection flows.
  • Tests

    • Added extensive unit tests covering models, scoped models, sessions, settings, tree panel flows, and panel selection behaviors.
  • Refactor

    • Centralized and reorganized panel open/close and selection handling for more consistent behavior.

Walkthrough

This PR splits terminal panel functionality into focused modules: panel action dispatch, model selection, scoped models, session resume, settings UI, session tree navigation, and command wiring, with tests and helpers for each feature.

Changes

Panel System Refactoring

Layer / File(s) Summary
Panel control foundation
internal/terminal/panel_actions.go
openPanel switches to panel mode and records active panel state, closePanel resets to chat mode, and applyPanelSelection dispatches selections to handler methods by panel kind.
Model selection panel
internal/terminal/panel_model.go, internal/terminal/panel_model_test.go
openModelPanel shows available models; modelItems marks current model; availableModels/ensureCurrentModel prepare the list; applyModelSelection parses provider/modelID or modelID and updates model; cycleModel cycles scoped/available models.
Scoped models panel
internal/terminal/panel_scoped_models.go, internal/terminal/panel_scoped_models_test.go
openScopedModelsPanel builds per-model checkbox items from orderedAvailableModels; toggleScopedModel flips enable state and persists; refreshScopedModelsPanel rebuilds when active.
Session resume panel
internal/terminal/panel_session.go, internal/terminal/panel_session_test.go, internal/terminal/panel_test_helpers_test.go, internal/terminal/panel_session_selection_test.go
openSessionPanel lists resumable sessions; sessionItems filters and formats sessions (timestamp, optional path); applySessionSelection loads session state/messages and resumes the session. Tests and helpers validate filtering, ordering, and panel wiring.
Settings panel
internal/terminal/panel_settings.go, internal/terminal/panel_settings_test.go, internal/terminal/panel_settings_selection_test.go
openSettingsPanel renders theme, thinking level, and boolean toggles; applySettingSelection applies toggles/cycles and persists settings; toggleTheme swaps dark/light; openHotkeysPanel/openChangelogPanel open auxiliary panels.
Session tree navigation
internal/terminal/panel_tree.go, internal/terminal/panel_tree_test.go
openTreePanel renders session tree as selectable ASCII-prefixed items; treeDescription derives node descriptions from content/summary/model/DataJSON; applyTreeSelection loads entry and prepares branching/editing context; emptyParentID normalizes nil parent IDs.
Command wiring & constants
internal/terminal/auth_commands.go, internal/terminal/autocomplete.go, internal/terminal/commands.go, internal/terminal/panel.go
Replaced string literals with hotkeysCommandName/changelogCommandName constants in command handlers and autocomplete; expanded non-auth cases to include hotkeys/changelog.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A tiny rabbit hops and sings,

panels split with careful springs.
Models, sessions, settings prance,
Trees and scopes join in the dance.
Tests applaud the tidy change.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main refactoring: splitting panel features into separate files.
Description check ✅ Passed The description is directly related to the changeset, outlining the refactoring goals and validation approach without being off-topic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/terminal-panel-split

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented May 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.96703% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.96%. Comparing base (812dde4) to head (aabf186).

Files with missing lines Patch % Lines
internal/terminal/panel_actions.go 46.66% 16 Missing ⚠️
internal/terminal/panel_tree.go 77.61% 11 Missing and 4 partials ⚠️
internal/terminal/panel_session.go 71.05% 6 Missing and 5 partials ⚠️
internal/terminal/panel_model.go 89.47% 5 Missing and 3 partials ⚠️
internal/terminal/panel_settings.go 93.42% 5 Missing ⚠️
internal/terminal/panel_scoped_models.go 93.84% 2 Missing and 2 partials ⚠️
internal/terminal/commands.go 77.77% 1 Missing and 1 partial ⚠️
internal/terminal/auth_commands.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #74      +/-   ##
==========================================
+ Coverage   64.20%   65.96%   +1.76%     
==========================================
  Files         200      205       +5     
  Lines       17822    17828       +6     
==========================================
+ Hits        11443    11761     +318     
+ Misses       5299     4976     -323     
- Partials     1080     1091      +11     
Flag Coverage Δ
unittests 65.96% <82.96%> (+1.76%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/terminal/panel_actions.go`:
- Around line 17-35: The switch in applyPanelSelection currently falls through
to return nil for unknown app.selectedPanelKind; change it to return an explicit
error when no case matches (e.g., construct and return fmt.Errorf("unknown panel
kind: %v", app.selectedPanelKind) or use errors.New with context) so callers can
observe the bad state; update imports (fmt or errors) and ensure functions that
previously returned nil still return errors where applicable
(applyPanelSelection signature remains the same, so add the error return at the
end of the function).

In `@internal/terminal/panel_session.go`:
- Around line 48-52: Move the "resumed session" system message so it is only
posted after the resume actually completes successfully: call
app.loadInitialMessages(ctx) first and if that returns nil then call
app.addSystemMessage("resumed session: "+value) and app.closePanel(); otherwise
return the error without emitting the success message. Update the sequence
around the existing addSystemMessage, loadInitialMessages, and closePanel calls
in the panel resume logic to ensure the success message is only logged on
success.

In `@internal/terminal/panel_settings.go`:
- Line 30: openHotkeysPanel and openChangelogPanel currently call
newSelectionPanel with panelSettings which makes applyPanelSelection treat
selections as settings and rebuild the Settings panel; update those callers to
use the correct panel kind (e.g. panelHotkeys for openHotkeysPanel and
panelChangelog for openChangelogPanel) so applyPanelSelection does not route the
selection to applySettingSelection, or alternatively adjust applyPanelSelection
to dispatch based on the panel identity created by newSelectionPanel; target
symbols: openHotkeysPanel, openChangelogPanel, newSelectionPanel,
applyPanelSelection, applySettingSelection, panelSettings (replace with
panelHotkeys/panelChangelog).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e5e0a06-7fd9-4d79-b0b7-bce2b2d4e2bb

📥 Commits

Reviewing files that changed from the base of the PR and between 812dde4 and f1f69e1.

📒 Files selected for processing (13)
  • internal/terminal/panel_actions.go
  • internal/terminal/panel_model.go
  • internal/terminal/panel_model_test.go
  • internal/terminal/panel_scoped_models.go
  • internal/terminal/panel_scoped_models_test.go
  • internal/terminal/panel_session.go
  • internal/terminal/panel_session_test.go
  • internal/terminal/panel_settings.go
  • internal/terminal/panel_settings_test.go
  • internal/terminal/panel_test_helpers_test.go
  • internal/terminal/panel_tree.go
  • internal/terminal/panel_tree_test.go
  • internal/terminal/panels.go
💤 Files with no reviewable changes (1)
  • internal/terminal/panels.go

Comment thread internal/terminal/panel_actions.go
Comment thread internal/terminal/panel_session.go Outdated
Comment thread internal/terminal/panel_settings.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
internal/terminal/auth_commands.go (1)

193-206: 💤 Low value

Redundant switch case: explicit non-auth panel handling duplicates default.

The switch case at line 201 explicitly returns nil for non-auth panel kinds, but the default case at line 205 also returns nil. This creates redundancy without adding clarity or safety.

Consider one of these options:

  • Option 1 (simpler): Remove the explicit case for non-auth panels and rely on the default.
  • Option 2 (safer): Make the default case return an error for truly unknown panel kinds, keeping the explicit case as documentation of expected non-auth panels.
♻️ Option 2 example: explicit error for unknown kinds
 func (app *App) applyAuthSelection(ctx context.Context, value string) error {
 	switch app.selectedPanelKind {
 	case panelAuthLogin:
 		app.closePanel()
 		return app.loginCommand(ctx, value)
 	case panelAuthLogout:
 		app.closePanel()
 		return app.logoutCommand(ctx, value)
 	case panelModel, panelScopedModels, panelSettings, panelHotkeys, panelChangelog, panelSessions, panelTree:
 		return nil
+	default:
+		return fmt.Errorf("applyAuthSelection called with unexpected panel kind: %q", app.selectedPanelKind)
 	}
-
-	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/auth_commands.go` around lines 193 - 206, In
applyAuthSelection, keep the explicit case listing known non-auth panels
(panelModel, panelScopedModels, panelSettings, panelHotkeys, panelChangelog,
panelSessions, panelTree) but remove the trailing unconditional return nil;
instead make the switch's default branch return an error like
fmt.Errorf("unknown panel kind: %v", app.selectedPanelKind) and ensure fmt is
imported; this preserves documented non-auth handling while surfacing truly
unexpected panel kinds.
internal/terminal/panel.go (1)

13-24: 💤 Low value

Inconsistent constant pattern: mix of string literals and forward-referenced command name constants.

Most panelKind constants use direct string literals ("model", "scoped_models", etc.), but panelHotkeys and panelChangelog reference hotkeysCommandName and changelogCommandName, which are defined later in the same const block.

While Go allows forward-references within const blocks, this inconsistency makes the code harder to follow:

  • The actual string values for hotkeys/changelog are obscured.
  • It's unclear why only these two panel kinds need this indirection.
  • The pattern suggests coupling between panel kinds and command names that may not hold for all panels (e.g., panelAuthLogin vs command "login").

Consider using string literals consistently unless there's a documented reason to share command names across multiple contexts.

♻️ Example: consistent string literals
 const (
 	panelModel           panelKind = "model"
 	panelScopedModels    panelKind = "scoped_models"
 	panelAuthLogin       panelKind = "auth_login"
 	panelAuthLogout      panelKind = "auth_logout"
 	panelSettings        panelKind = "settings"
-	panelHotkeys         panelKind = hotkeysCommandName
-	panelChangelog       panelKind = changelogCommandName
+	panelHotkeys         panelKind = "hotkeys"
+	panelChangelog       panelKind = "changelog"
 	panelSessions        panelKind = "sessions"
 	panelTree            panelKind = "tree"
-	hotkeysCommandName             = "hotkeys"
-	changelogCommandName           = "changelog"
 )

Then define command name constants separately if they're needed for the command handler map:

+const (
+	hotkeysCommandName   = "hotkeys"
+	changelogCommandName = "changelog"
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/panel.go` around lines 13 - 24, The panelKind constants mix
direct string literals and forward-referenced command name constants; make this
consistent by replacing panelHotkeys and panelChangelog with their literal
string values (e.g., "hotkeys" and "changelog") or by moving hotkeysCommandName
and changelogCommandName above the panelKind const block and using them
everywhere; update the const block containing panelModel, panelScopedModels,
panelAuthLogin, panelAuthLogout, panelSettings, panelHotkeys, panelChangelog,
panelSessions, panelTree (and the hotkeysCommandName/changelogCommandName
identifiers) so all panelKind values are defined consistently and clearly
reference either literals or shared named constants.
internal/terminal/panel_settings.go (1)

88-94: ⚡ Quick win

applySettingSelection can assign app.panel directly (no extra openPanel bookkeeping)

  • openPanel in internal/terminal/panel_actions.go only sets app.mode = modePanel, app.selectedPanelKind = panel.kind, and app.panel = panel; there’s no additional state tracking that applySettingSelection would bypass.
  • Since applySettingSelection is reached via the panelSettings branch of applyPanelSelection, the existing selectedPanelKind/mode fields remain consistent.
  • Optional: extract a small helper to build the settings selectionPanel and reuse it from both openSettingsPanel and applySettingSelection to avoid duplicate construction.
app.panel = newSelectionPanel(
	panelSettings,
	"Settings",
	"Enter cycles values; Esc returns",
	app.settingsItems(),
	false,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/panel_settings.go` around lines 88 - 94, The current
applySettingSelection constructs a selection panel then calls openPanel, but
openPanel only sets mode and panel fields, so simplify by assigning app.panel
directly in applySettingSelection instead of calling openPanel; locate
applySettingSelection and replace the openPanel(...) invocation with app.panel =
newSelectionPanel(panelSettings, "Settings", "Enter cycles values; Esc returns",
app.settingsItems(), false). Optionally extract a helper (e.g.,
buildSettingsPanel) that returns newSelectionPanel(...) and use it from both
openSettingsPanel and applySettingSelection to remove duplication while
preserving app.mode and app.selectedPanelKind assignments in
openPanel/openSettingsPanel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/terminal/panel_session_test.go`:
- Around line 74-88: The test currently mutates secondSession.UpdatedAt
(secondSession.UpdatedAt = firstSession.UpdatedAt.Add(time.Minute)) but
CreateSession returns an in-memory entity so that change is never persisted and
openSessionPanel re-reads from the repository; either persist the updated
timestamp before calling openSessionPanel (e.g. call the repository update
method for the session returned by CreateSession so that the repository's
ordering reflects the new UpdatedAt and then assert deterministic ordering of
app.panel.items[0].Value) or remove the no-op mutation and its unused time
import and keep the existing non-deterministic assertion, ensuring you reference
secondSession, firstSession, CreateSession and openSessionPanel when making the
change.

---

Nitpick comments:
In `@internal/terminal/auth_commands.go`:
- Around line 193-206: In applyAuthSelection, keep the explicit case listing
known non-auth panels (panelModel, panelScopedModels, panelSettings,
panelHotkeys, panelChangelog, panelSessions, panelTree) but remove the trailing
unconditional return nil; instead make the switch's default branch return an
error like fmt.Errorf("unknown panel kind: %v", app.selectedPanelKind) and
ensure fmt is imported; this preserves documented non-auth handling while
surfacing truly unexpected panel kinds.

In `@internal/terminal/panel_settings.go`:
- Around line 88-94: The current applySettingSelection constructs a selection
panel then calls openPanel, but openPanel only sets mode and panel fields, so
simplify by assigning app.panel directly in applySettingSelection instead of
calling openPanel; locate applySettingSelection and replace the openPanel(...)
invocation with app.panel = newSelectionPanel(panelSettings, "Settings", "Enter
cycles values; Esc returns", app.settingsItems(), false). Optionally extract a
helper (e.g., buildSettingsPanel) that returns newSelectionPanel(...) and use it
from both openSettingsPanel and applySettingSelection to remove duplication
while preserving app.mode and app.selectedPanelKind assignments in
openPanel/openSettingsPanel.

In `@internal/terminal/panel.go`:
- Around line 13-24: The panelKind constants mix direct string literals and
forward-referenced command name constants; make this consistent by replacing
panelHotkeys and panelChangelog with their literal string values (e.g.,
"hotkeys" and "changelog") or by moving hotkeysCommandName and
changelogCommandName above the panelKind const block and using them everywhere;
update the const block containing panelModel, panelScopedModels, panelAuthLogin,
panelAuthLogout, panelSettings, panelHotkeys, panelChangelog, panelSessions,
panelTree (and the hotkeysCommandName/changelogCommandName identifiers) so all
panelKind values are defined consistently and clearly reference either literals
or shared named constants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba7f27c2-3a8d-48a0-97bc-ad1aaacf47e2

📥 Commits

Reviewing files that changed from the base of the PR and between f1f69e1 and 9307f6b.

📒 Files selected for processing (12)
  • internal/terminal/auth_commands.go
  • internal/terminal/autocomplete.go
  • internal/terminal/commands.go
  • internal/terminal/panel.go
  • internal/terminal/panel_actions.go
  • internal/terminal/panel_model_test.go
  • internal/terminal/panel_scoped_models_test.go
  • internal/terminal/panel_session.go
  • internal/terminal/panel_session_test.go
  • internal/terminal/panel_settings.go
  • internal/terminal/panel_settings_test.go
  • internal/terminal/panel_tree_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/terminal/autocomplete.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/terminal/panel_actions.go
  • internal/terminal/panel_session.go

Comment thread internal/terminal/panel_session_test.go Outdated
@sonarqubecloud

sonarqubecloud Bot commented Jun 1, 2026

Copy link
Copy Markdown

@omarluq
omarluq merged commit af77032 into main Jun 1, 2026
13 checks passed
@omarluq
omarluq deleted the refactor/terminal-panel-split branch June 1, 2026 04:34
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