feat(tui): add cloud config form, ping command, and save flow#608
feat(tui): add cloud config form, ping command, and save flow#608mgaldamez wants to merge 7 commits into
Conversation
|
Important Review skippedNo new commits to review since the last review. ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds 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. ChangesCloud sync settings
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.gofor loading/savingcloud.json, resolving token source, validating URLs, and pinging/healthwith an injectable transport. - Exposes
Store.DataDir()for TUI access and adds tests + docs updates aroundcloud.json.tokenfallback 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.
| 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") | ||
| } |
| req, err := http.NewRequest(http.MethodGet, validatedURL+cloudHealthPath, nil) | ||
| if err != nil { | ||
| return "unreachable", err | ||
| } |
| 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) | ||
| } |
| cc.ServerURL = serverURL | ||
| b, err := json.MarshalIndent(cc, "", " ") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return os.WriteFile(cloudConfigPath(dataDir), b, 0o644) |
| 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 |
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
docs/engram-cloud/troubleshooting.mdinternal/store/store.gointernal/store/store_test.gointernal/tui/cloud.gointernal/tui/cloud_test.gointernal/tui/model.gointernal/tui/model_test.gointernal/tui/update.gointernal/tui/update_test.gointernal/tui/view.gointernal/tui/view_test.go
| 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) | ||
| } |
There was a problem hiding this comment.
🔒 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.
| 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.
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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
| 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 |
There was a problem hiding this comment.
🗄️ 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.goRepository: 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/*.goRepository: 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 -nRepository: 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
| 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() | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
|
@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! |
🧩 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.goLength 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.goLength of output: 16811 Confirmed — the new exported functions/constants introduced in this PR now have godoc comments in place:
This resolves the docstring coverage gap I flagged earlier. Thanks for the quick turnaround! ✅ Action performedReview finished.
|
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_urlto~/.engram/cloud.json. The token is never written by the TUI.Spec
What's included
309da7efeat(tui): add cloud config form scaffold and read-only token status19f7807feat(tui): add ping command with injectable transport6200b80feat(tui): wire ping result to cloud config save flowDesign notes
internal/tui/cloud.gowithloadCloudConfig,saveCloudConfig(merge-style, preserves token),tokenSource,pingCloudServer,validateCloudServerURL.http.RoundTripperso tests run withhttptest.NewServer— no real network in CI.Test evidence
go test ./...passes (with and withoutENGRAM_CLOUD_TOKENset)internal/tui/cloud.gogo vet ./...cleanMerge order
This is PR #2 of 6 in the
tui-cloud-configfeature 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:
Issue link
Part of #577
Summary by CodeRabbit
Summary