Skip to content

feat(tui): add cloud config form, ping command, and save flow#608

Open
mgaldamez wants to merge 7 commits into
Gentleman-Programming:mainfrom
mgaldamez:feat/tui-cloud-config-02-config
Open

feat(tui): add cloud config form, ping command, and save flow#608
mgaldamez wants to merge 7 commits into
Gentleman-Programming:mainfrom
mgaldamez:feat/tui-cloud-config-02-config

Conversation

@mgaldamez

@mgaldamez mgaldamez commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Adds the Cloud Config form: server URL input with ping-before-save validation. Pings the server's health endpoint, surfaces the result (reachable / unreachable / unauthorized), and on success persists only server_url to ~/.engram/cloud.json. The token is never written by the TUI.

Spec

  • REQ-CFG-01 (server URL config with ping validation)
  • REQ-CFG-02 (read-only token status display)

What's included

  • 309da7e feat(tui): add cloud config form scaffold and read-only token status
  • 19f7807 feat(tui): add ping command with injectable transport
  • 6200b80 feat(tui): wire ping result to cloud config save flow

Design notes

  • New internal/tui/cloud.go with loadCloudConfig, saveCloudConfig (merge-style, preserves token), tokenSource, pingCloudServer, validateCloudServerURL.
  • Ping uses an injectable http.RoundTripper so tests run with httptest.NewServer — no real network in CI.
  • 401 still persists the URL because server reachability and auth are independent concerns.

Test evidence

  • go test ./... passes (with and without ENGRAM_CLOUD_TOKEN set)
  • Coverage on changed files: ~90% in internal/tui/cloud.go
  • go vet ./... clean

Merge order

This is PR #2 of 6 in the tui-cloud-config feature delivery. Depends on PR #1 (merged first). After PR #1 is merged, please click "Update branch" on this PR so the diff shows only this PR's changes.

Order:

  1. PR 1 — nav + docs (already opened as feat(tui): add cloud settings screen and docs #607)
  2. PR 2 (this) — config form + ping + save feat(tui): add cloud config form, ping command, and save flow #608
  3. PR 3 — status view feat(tui): add cloud status view #609
  4. PR 4 — enrollment toggles feat(tui): add project enrollment toggles #610
  5. PR 5 — test fixes test(tui): close coverage gaps and harden env-var tests #611
  6. PR 6 — CLI parity feat(tui): add CLI parity fields to cloud status view #612

Issue link

Part of #577

Summary by CodeRabbit

Summary

  • New Features
    • Added Cloud sync settings to the dashboard, including a terminal flow to configure and validate a cloud server URL.
    • Added cloud connectivity checks with distinct reachable/unauthorized/error handling and clear save/pinging feedback.
    • Cloud authentication now supports environment-based tokens with an intentional fallback to the cloud configuration file.
  • Documentation
    • Updated troubleshooting guidance to clarify token precedence and fallback behavior.
  • Tests
    • Added unit and TUI coverage for token selection, config persistence, URL validation, and connectivity outcomes.
  • Chores
    • Added a store data directory accessor and supporting test.

Copilot AI review requested due to automatic review settings July 14, 2026 03:31
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4d33ea3a-957c-4ee3-a7f3-0c5069e064e7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cloud sync settings to the TUI, including persisted server configuration, token fallback handling, URL validation, asynchronous health checks, save behavior, navigation, rendering, and tests. Store data-directory access and troubleshooting documentation are also updated.

Changes

Cloud sync settings

Layer / File(s) Summary
Cloud config persistence and connectivity
internal/store/store.go, internal/store/store_test.go, internal/tui/cloud.go, internal/tui/cloud_test.go, docs/engram-cloud/troubleshooting.md
Adds data-directory access, cloud configuration loading and saving, token precedence, URL validation, health checks, and related documentation and tests.
Cloud screen state and loading command
internal/tui/model.go, internal/tui/model_test.go
Adds cloud screens, form state, asynchronous messages, initialization, and persisted configuration loading.
Cloud settings interaction and save flow
internal/tui/update.go, internal/tui/update_test.go
Adds dashboard navigation, keyboard handling, focus transitions, ping result processing, validation, persistence, and interaction tests.
Cloud settings rendering
internal/tui/view.go, internal/tui/view_test.go
Adds cloud settings and configuration views, shared menu rendering, status display, and rendering tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Gentleman-Programming/engram#577 — Covers the TUI cloud sync settings, token fallback documentation, server validation, and persistence flow implemented here.

Suggested labels: type:feature

Suggested reviewers: alan-thegentleman

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #611 required test-only coverage and env-var hardening, but this PR adds substantial source behavior changes in internal/tui and docs. Restrict this PR to *_test.go coverage fixes and ENGRAM_CLOUD_TOKEN hardening, or split the feature implementation into another PR.
Out of Scope Changes check ⚠️ Warning Several non-test source files add new cloud UI, persistence, and networking logic, which is outside #611's test-only scope. Remove the feature and docs changes from this PR, keeping only test coverage and env-var hardening edits.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main TUI cloud-config feature work: form, ping, and save flow.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI 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.

Pull request overview

This PR adds the TUI-side “Cloud sync settings” flow for configuring a cloud server URL, validating it by pinging the server health endpoint, and persisting the URL to cloud.json (while preserving any existing token fallback).

Changes:

  • Adds new TUI screens for Cloud Settings and a Cloud Config form, including token source display and ping-before-save flow.
  • Introduces internal/tui/cloud.go for loading/saving cloud.json, resolving token source, validating URLs, and pinging /health with an injectable transport.
  • Exposes Store.DataDir() for TUI access and adds tests + docs updates around cloud.json.token fallback behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/tui/view.go Adds Cloud Settings/Config views and shared menu rendering
internal/tui/view_test.go Adds view coverage for cloud config token source rendering
internal/tui/update.go Adds navigation + key handling + ping/save state machine for cloud config
internal/tui/update_test.go Adds navigation and save-flow tests (reachable/unreachable/401/preserve token)
internal/tui/model.go Adds new screens, cloud config state, and loadCloudConfig command
internal/tui/model_test.go Adds constants/command coverage for new cloud screens/config load
internal/tui/cloud.go Implements config IO, token source resolution, URL validation, and ping logic
internal/tui/cloud_test.go Adds unit tests for config IO, token source, ping, and URL validation
internal/store/store.go Adds DataDir() accessor used by the TUI
internal/store/store_test.go Adds a basic test for DataDir()
docs/engram-cloud/troubleshooting.md Documents the intentional cloud.json.token fallback with env precedence

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/tui/view.go
Comment on lines +222 to 231
if m.CloudConfigSaving {
b.WriteString("\n")
b.WriteString(m.SetupSpinner.View())
b.WriteString(" Pinging server...")
b.WriteString("\n")
} else if m.CloudConfigPingStatus != "" {
b.WriteString("\n")
b.WriteString(detailValueStyle.Render("Status: " + m.CloudConfigPingStatus))
b.WriteString("\n")
}
Comment thread internal/tui/cloud.go
Comment on lines +107 to +110
req, err := http.NewRequest(http.MethodGet, validatedURL+cloudHealthPath, nil)
if err != nil {
return "unreachable", err
}
Comment thread internal/tui/update.go
Comment on lines +668 to +686
case "enter", " ":
switch m.Cursor {
case 0: // Configure server
m.PrevScreen = ScreenCloudSettings
m.Screen = ScreenCloudConfig
m.Cursor = 0
m.CloudConfigFocus = cloudConfigFocusInput
m.CloudConfigError = ""
m.CloudConfigPingStatus = ""
m.CloudConfigSaving = false
m.CloudConfigTest = false
m.CloudConfigInput.SetValue("")
m.CloudConfigInput.Focus()
return m, loadCloudConfigCmd(m.store.DataDir())
case len(cloudSettingsMenuItems) - 1: // Back
m.Screen = ScreenDashboard
m.Cursor = 0
return m, loadStats(m.store)
}
Comment thread internal/tui/cloud.go
Comment on lines +61 to +66
cc.ServerURL = serverURL
b, err := json.MarshalIndent(cc, "", " ")
if err != nil {
return err
}
return os.WriteFile(cloudConfigPath(dataDir), b, 0o644)
Comment thread internal/tui/update.go
Comment on lines +140 to +164
if msg.err != nil {
m.CloudConfigError = msg.err.Error()
return m, nil
}
if m.CloudConfigTest {
// Test-only ping: just show the status.
return m, nil
}
if msg.status == "reachable" || msg.status == "unauthorized" {
validatedURL, err := validateCloudServerURL(m.CloudConfigInput.Value())
if err != nil {
m.CloudConfigError = err.Error()
return m, nil
}
if err := saveCloudConfig(m.store.DataDir(), validatedURL); err != nil {
m.CloudConfigError = err.Error()
return m, nil
}
m.Screen = ScreenCloudSettings
m.Cursor = 0
m.CloudConfigFocus = cloudConfigFocusInput
return m, nil
}
m.CloudConfigError = "server is unreachable"
return m, nil

@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: 5

🤖 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/tui/cloud.go`:
- Around line 53-67: Update saveCloudConfig to write cloud.json with owner-only
permissions by changing the os.WriteFile file mode from 0o644 to 0o600, while
preserving the existing configuration and token handling.
- Around line 101-154: Normalize the validated URL before constructing the
request in pingCloudServerStatus so appending cloudHealthPath cannot produce a
double slash when the server URL has a trailing slash. Use url.JoinPath or
equivalent trailing-slash trimming while preserving the existing URL validation
behavior.

In `@internal/tui/update.go`:
- Around line 126-135: Update the cloudConfigLoadedMsg handler to avoid
unconditionally focusing CloudConfigInput after loading. Preserve the user's
current navigation/focus state when the message arrives, or update
CloudConfigFocus consistently whenever the widget is focused so key routing
remains synchronized with CloudConfigInput.Focused().
- Around line 137-164: Update the cloud ping flow around cloudPingMsg so the
async result carries the exact URL that was pinged, and use that URL in the
saveCloudConfig branch instead of re-reading m.CloudConfigInput.Value(). Ensure
the ping request snapshots or validates the URL before dispatch, and prevent
cloud-config input edits or focus changes while CloudConfigSaving is true.

In `@internal/tui/view.go`:
- Around line 185-236: Update viewCloudConfig to render m.CloudConfigError when
it is non-empty, including validation failures that occur before any ping status
is set. Add the error display near the existing CloudConfigPingStatus feedback
while preserving the current saving and status rendering behavior.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 23defc48-1322-4bfb-8f6d-f80b7adce193

📥 Commits

Reviewing files that changed from the base of the PR and between be4b613 and 6200b80.

📒 Files selected for processing (11)
  • docs/engram-cloud/troubleshooting.md
  • internal/store/store.go
  • internal/store/store_test.go
  • internal/tui/cloud.go
  • internal/tui/cloud_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/update.go
  • internal/tui/update_test.go
  • internal/tui/view.go
  • internal/tui/view_test.go

Comment thread internal/tui/cloud.go
Comment on lines +53 to +67
func saveCloudConfig(dataDir, serverURL string) error {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return err
}
cc, err := loadCloudConfig(dataDir)
if err != nil {
return err
}
cc.ServerURL = serverURL
b, err := json.MarshalIndent(cc, "", " ")
if err != nil {
return err
}
return os.WriteFile(cloudConfigPath(dataDir), b, 0o644)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

cloud.json written with world/group-readable permissions.

saveCloudConfig preserves the existing Token field and writes the file with 0o644, which any local user can read. This file can hold a bearer credential (tuiCloudConfig.Token), so it should be written with owner-only permissions.

🔒 Proposed fix
-	return os.WriteFile(cloudConfigPath(dataDir), b, 0o644)
+	return os.WriteFile(cloudConfigPath(dataDir), b, 0o600)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func saveCloudConfig(dataDir, serverURL string) error {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return err
}
cc, err := loadCloudConfig(dataDir)
if err != nil {
return err
}
cc.ServerURL = serverURL
b, err := json.MarshalIndent(cc, "", " ")
if err != nil {
return err
}
return os.WriteFile(cloudConfigPath(dataDir), b, 0o644)
}
func saveCloudConfig(dataDir, serverURL string) error {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return err
}
cc, err := loadCloudConfig(dataDir)
if err != nil {
return err
}
cc.ServerURL = serverURL
b, err := json.MarshalIndent(cc, "", " ")
if err != nil {
return err
}
return os.WriteFile(cloudConfigPath(dataDir), b, 0o600)
}
🤖 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/tui/cloud.go` around lines 53 - 67, Update saveCloudConfig to write
cloud.json with owner-only permissions by changing the os.WriteFile file mode
from 0o644 to 0o600, while preserving the existing configuration and token
handling.

Comment thread internal/tui/cloud.go
Comment thread internal/tui/update.go
Comment on lines +126 to +135
case cloudConfigLoadedMsg:
if msg.err != nil {
m.CloudConfigError = msg.err.Error()
return m, nil
}
m.CloudConfigServerURL = msg.serverURL
m.CloudConfigTokenSource = msg.tokenSource
m.CloudConfigInput.SetValue(msg.serverURL)
m.CloudConfigInput.Focus()
return m, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unconditional Focus() on load can desync CloudConfigFocus from the widget's actual focus state.

If cloudConfigLoadedMsg arrives after the user has already tabbed away from the input (e.g., to Test/Save/Cancel), this handler still force-focuses CloudConfigInput without updating m.CloudConfigFocus. The Update key-routing check on line 31 requires both CloudConfigFocus == cloudConfigFocusInput and CloudConfigInput.Focused() to route typed keys to the input — so after this desync, the input looks focused but keystrokes are silently swallowed by handleCloudConfigKeys (which has no case for arbitrary characters).

As per coding guidelines, changes touching "update" and navigation logic in **/tui/**/*.go fall under the engram-tui-quality skill's scope, and this is exactly the kind of state-consistency bug that skill is meant to catch.

🐛 Proposed fix
 	case cloudConfigLoadedMsg:
 		if msg.err != nil {
 			m.CloudConfigError = msg.err.Error()
 			return m, nil
 		}
 		m.CloudConfigServerURL = msg.serverURL
 		m.CloudConfigTokenSource = msg.tokenSource
 		m.CloudConfigInput.SetValue(msg.serverURL)
-		m.CloudConfigInput.Focus()
+		if m.CloudConfigFocus == cloudConfigFocusInput {
+			m.CloudConfigInput.Focus()
+		}
 		return m, nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case cloudConfigLoadedMsg:
if msg.err != nil {
m.CloudConfigError = msg.err.Error()
return m, nil
}
m.CloudConfigServerURL = msg.serverURL
m.CloudConfigTokenSource = msg.tokenSource
m.CloudConfigInput.SetValue(msg.serverURL)
m.CloudConfigInput.Focus()
return m, nil
case cloudConfigLoadedMsg:
if msg.err != nil {
m.CloudConfigError = msg.err.Error()
return m, nil
}
m.CloudConfigServerURL = msg.serverURL
m.CloudConfigTokenSource = msg.tokenSource
m.CloudConfigInput.SetValue(msg.serverURL)
if m.CloudConfigFocus == cloudConfigFocusInput {
m.CloudConfigInput.Focus()
}
return m, 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/tui/update.go` around lines 126 - 135, Update the
cloudConfigLoadedMsg handler to avoid unconditionally focusing CloudConfigInput
after loading. Preserve the user's current navigation/focus state when the
message arrives, or update CloudConfigFocus consistently whenever the widget is
focused so key routing remains synchronized with CloudConfigInput.Focused().

Source: Coding guidelines

Comment thread internal/tui/update.go
Comment on lines +137 to +164
case cloudPingMsg:
m.CloudConfigSaving = false
m.CloudConfigPingStatus = msg.status
if msg.err != nil {
m.CloudConfigError = msg.err.Error()
return m, nil
}
if m.CloudConfigTest {
// Test-only ping: just show the status.
return m, nil
}
if msg.status == "reachable" || msg.status == "unauthorized" {
validatedURL, err := validateCloudServerURL(m.CloudConfigInput.Value())
if err != nil {
m.CloudConfigError = err.Error()
return m, nil
}
if err := saveCloudConfig(m.store.DataDir(), validatedURL); err != nil {
m.CloudConfigError = err.Error()
return m, nil
}
m.Screen = ScreenCloudSettings
m.Cursor = 0
m.CloudConfigFocus = cloudConfigFocusInput
return m, nil
}
m.CloudConfigError = "server is unreachable"
return m, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'type cloudPingMsg' -A 6 internal/tui/cloud.go
rg -n 'func pingCloudServer' -A 20 internal/tui/cloud.go

Repository: Gentleman-Programming/engram

Length of output: 1024


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- internal/tui/cloud.go outline ---'
ast-grep outline internal/tui/cloud.go --view expanded || true

echo
echo '--- internal/tui/update.go outline ---'
ast-grep outline internal/tui/update.go --view expanded || true

echo
echo '--- cloudPingMsg definition / ping / save / key handlers ---'
rg -n 'type cloudPingMsg|func pingCloudServer|func saveCloudConfig|handleCloudConfigKeys|handleCloudConfigInputKeys|CloudConfigSaving|CloudConfigInput.Value\(\)' -A 40 -B 8 internal/tui/*.go

Repository: Gentleman-Programming/engram

Length of output: 46396


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '760,820p' internal/tui/update.go | cat -n
printf '\n---\n'
sed -n '1,120p' internal/tui/model.go | cat -n
printf '\n--- tests around cloud config focus/save ---\n'
sed -n '355,560p' internal/tui/update_test.go | cat -n

Repository: Gentleman-Programming/engram

Length of output: 13678


Persist the URL that was pinged
cloudPingMsg only carries status/error, so the save branch re-reads m.CloudConfigInput.Value() after the async ping finishes. Since focus changes and typing are still allowed while CloudConfigSaving is true, the user can save a different URL than the one that was actually tested. Pass the validated URL through the ping result (or snapshot it in state) and block cloud-config edits/focus changes until the save completes.

🤖 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/tui/update.go` around lines 137 - 164, Update the cloud ping flow
around cloudPingMsg so the async result carries the exact URL that was pinged,
and use that URL in the saveCloudConfig branch instead of re-reading
m.CloudConfigInput.Value(). Ensure the ping request snapshots or validates the
URL before dispatch, and prevent cloud-config input edits or focus changes while
CloudConfigSaving is true.

Source: Coding guidelines

Comment thread internal/tui/view.go
Comment on lines +185 to +236
func (m Model) viewCloudConfig() string {
var b strings.Builder

b.WriteString(headerStyle.Render(" Configure cloud server"))
b.WriteString("\n\n")

b.WriteString(detailLabelStyle.Render("Server URL:"))
b.WriteString(" ")
if m.CloudConfigFocus == cloudConfigFocusInput {
b.WriteString(searchInputStyle.Render(m.CloudConfigInput.View()))
} else {
b.WriteString(detailValueStyle.Render(m.CloudConfigInput.View()))
}
b.WriteString("\n\n")

b.WriteString(detailLabelStyle.Render("Token:"))
b.WriteString(" ")
b.WriteString(detailValueStyle.Render(m.CloudConfigTokenSource))
b.WriteString("\n")
if m.CloudConfigTokenSource != TokenSourceEnv {
b.WriteString(timestampStyle.Render(" Set ENGRAM_CLOUD_TOKEN to override cloud.json.token"))
b.WriteString("\n")
}
b.WriteString("\n")

buttons := []string{"[Test]", "[Save]", "[Cancel]"}
for i, label := range buttons {
focus := i + 1 // input is 0
if focus == m.CloudConfigFocus {
b.WriteString(menuSelectedStyle.Render("▸ " + label))
} else {
b.WriteString(menuItemStyle.Render(" " + item))
b.WriteString(menuItemStyle.Render(" " + label))
}
b.WriteString(" ")
}
b.WriteString("\n")

if m.CloudConfigSaving {
b.WriteString("\n")
b.WriteString(m.SetupSpinner.View())
b.WriteString(" Pinging server...")
b.WriteString("\n")
} else if m.CloudConfigPingStatus != "" {
b.WriteString("\n")
b.WriteString(detailValueStyle.Render("Status: " + m.CloudConfigPingStatus))
b.WriteString("\n")
}

// Help
b.WriteString(helpStyle.Render("\n j/k navigate • enter select • s search • q quit"))
b.WriteString(helpStyle.Render("\n tab/shift+tab cycle • enter select • esc/q back"))

return b.String()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

CloudConfigError is set by the update flow but never rendered in this view.

m.CloudConfigError is populated for malformed-URL validation failures, ping transport errors, and post-ping re-validation failures, but viewCloudConfig() never writes it out. For the malformed-URL case specifically, no ping command is ever fired (see TestCloudConfigSaveInvalidURLNoPersist), so CloudConfigPingStatus also stays empty — the user sees no feedback at all after hitting Save on an invalid URL.

🐛 Proposed fix
 	b.WriteString(detailLabelStyle.Render("Token:"))
 	b.WriteString(" ")
 	b.WriteString(detailValueStyle.Render(m.CloudConfigTokenSource))
 	b.WriteString("\n")
+	if m.CloudConfigError != "" {
+		b.WriteString(errorStyle.Render("  " + m.CloudConfigError))
+		b.WriteString("\n")
+	}
 	if m.CloudConfigTokenSource != TokenSourceEnv {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m Model) viewCloudConfig() string {
var b strings.Builder
b.WriteString(headerStyle.Render(" Configure cloud server"))
b.WriteString("\n\n")
b.WriteString(detailLabelStyle.Render("Server URL:"))
b.WriteString(" ")
if m.CloudConfigFocus == cloudConfigFocusInput {
b.WriteString(searchInputStyle.Render(m.CloudConfigInput.View()))
} else {
b.WriteString(detailValueStyle.Render(m.CloudConfigInput.View()))
}
b.WriteString("\n\n")
b.WriteString(detailLabelStyle.Render("Token:"))
b.WriteString(" ")
b.WriteString(detailValueStyle.Render(m.CloudConfigTokenSource))
b.WriteString("\n")
if m.CloudConfigTokenSource != TokenSourceEnv {
b.WriteString(timestampStyle.Render(" Set ENGRAM_CLOUD_TOKEN to override cloud.json.token"))
b.WriteString("\n")
}
b.WriteString("\n")
buttons := []string{"[Test]", "[Save]", "[Cancel]"}
for i, label := range buttons {
focus := i + 1 // input is 0
if focus == m.CloudConfigFocus {
b.WriteString(menuSelectedStyle.Render("▸ " + label))
} else {
b.WriteString(menuItemStyle.Render(" " + item))
b.WriteString(menuItemStyle.Render(" " + label))
}
b.WriteString(" ")
}
b.WriteString("\n")
if m.CloudConfigSaving {
b.WriteString("\n")
b.WriteString(m.SetupSpinner.View())
b.WriteString(" Pinging server...")
b.WriteString("\n")
} else if m.CloudConfigPingStatus != "" {
b.WriteString("\n")
b.WriteString(detailValueStyle.Render("Status: " + m.CloudConfigPingStatus))
b.WriteString("\n")
}
// Help
b.WriteString(helpStyle.Render("\n j/k navigate • enter select • s search • q quit"))
b.WriteString(helpStyle.Render("\n tab/shift+tab cycle • enter select • esc/q back"))
return b.String()
}
func (m Model) viewCloudConfig() string {
var b strings.Builder
b.WriteString(headerStyle.Render(" Configure cloud server"))
b.WriteString("\n\n")
b.WriteString(detailLabelStyle.Render("Server URL:"))
b.WriteString(" ")
if m.CloudConfigFocus == cloudConfigFocusInput {
b.WriteString(searchInputStyle.Render(m.CloudConfigInput.View()))
} else {
b.WriteString(detailValueStyle.Render(m.CloudConfigInput.View()))
}
b.WriteString("\n\n")
b.WriteString(detailLabelStyle.Render("Token:"))
b.WriteString(" ")
b.WriteString(detailValueStyle.Render(m.CloudConfigTokenSource))
b.WriteString("\n")
if m.CloudConfigError != "" {
b.WriteString(errorStyle.Render(" " + m.CloudConfigError))
b.WriteString("\n")
}
if m.CloudConfigTokenSource != TokenSourceEnv {
b.WriteString(timestampStyle.Render(" Set ENGRAM_CLOUD_TOKEN to override cloud.json.token"))
b.WriteString("\n")
}
b.WriteString("\n")
buttons := []string{"[Test]", "[Save]", "[Cancel]"}
for i, label := range buttons {
focus := i + 1 // input is 0
if focus == m.CloudConfigFocus {
b.WriteString(menuSelectedStyle.Render("▸ " + label))
} else {
b.WriteString(menuItemStyle.Render(" " + label))
}
b.WriteString(" ")
}
b.WriteString("\n")
if m.CloudConfigSaving {
b.WriteString("\n")
b.WriteString(m.SetupSpinner.View())
b.WriteString(" Pinging server...")
b.WriteString("\n")
} else if m.CloudConfigPingStatus != "" {
b.WriteString("\n")
b.WriteString(detailValueStyle.Render("Status: " + m.CloudConfigPingStatus))
b.WriteString("\n")
}
b.WriteString(helpStyle.Render("\n tab/shift+tab cycle • enter select • esc/q back"))
return b.String()
}
🤖 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/tui/view.go` around lines 185 - 236, Update viewCloudConfig to
render m.CloudConfigError when it is non-empty, including validation failures
that occur before any ping status is set. Add the error display near the
existing CloudConfigPingStatus feedback while preserving the current saving and
status rendering behavior.

@mgaldamez

Copy link
Copy Markdown
Author

@coderabbitai re-review please — just added godoc comments to new exported functions (commit 6dde3ce on this branch's base, and b129225 on the enrollment branch). Want to confirm the docstring coverage warning is now resolved. Thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd cloud.go internal/tui
echo "---cloud.go---"
cat -n internal/tui/cloud.go 2>/dev/null | head -80
echo "---config load functions---"
rg -n "^func " internal/tui/cloud.go
echo "---check comments above exported funcs/consts---"
rg -n -B3 "^func (Load|Save|New|Ping|TokenSource|ValidateCloudServerURL)" internal/tui/cloud.go
rg -n -B3 "^func " internal/store/store.go

Length of output: 3885


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "---store.go DataDir---"
rg -n -B3 "^func \(s \*Store\) DataDir" internal/store/store.go
echo "---model.go new funcs---"
rg -n -B3 "^func " internal/tui/model.go | sed -n '1,100p'
echo "---exported funcs across tui---"
rg -n "^func [A-Z]" internal/tui/*.go
echo "---exported funcs store---"
rg -n "^func [A-Z]|^func \(.*\) [A-Z]" internal/store/store.go

Length of output: 16811


Confirmed — the new exported functions/constants introduced in this PR now have godoc comments in place:

  • internal/tui/cloud.go: TokenSourceEnv, TokenSourceFile, TokenSourceNone all have doc comments (Lines 21–31), as do cloudConfigPath, loadCloudConfig, and saveCloudConfig.
  • internal/store/store.go: (*Store).DataDir() now has a doc comment ("DataDir returns the configured data directory for the store.", Line 486).
  • internal/tui/model.go: New() and loadCloudConfigCmd() also have doc comments now.

This resolves the docstring coverage gap I flagged earlier. Thanks for the quick turnaround!

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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