Skip to content

Test pr 1 rebased#3086

Open
JackyRen wants to merge 2 commits intowavetermdev:mainfrom
JackyRen:test-pr-1-rebased
Open

Test pr 1 rebased#3086
JackyRen wants to merge 2 commits intowavetermdev:mainfrom
JackyRen:test-pr-1-rebased

Conversation

@JackyRen
Copy link

No description provided.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Codex seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

This pull request introduces a "saved commands" feature for the AI panel. The implementation adds UI components to save, display, edit, and manage code blocks from AI responses as reusable commands. Users can save commands, insert them into prompts, run them in focused terminals, or delete them. The feature includes validation logic to determine which code blocks are saveable (based on language and content patterns), persistent storage via object metadata, and integration with the AI model's state management.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Test pr 1 rebased' is vague and generic, using a test/temporary naming convention that does not meaningfully describe the actual changeset of implementing saved AI commands functionality. Rename the title to clearly describe the main feature, such as 'Add saved commands functionality to AI panel' or similar.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a pull request description explaining the purpose and scope of the saved commands feature being introduced.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

@JackyRen
Copy link
Author

@codex review and find possible issue

return nextCommand.id;
}

updateSavedCommand(id: string, text: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Inconsistent text normalization - addSavedCommand normalizes text by removing trailing newlines (line 319), but updateSavedCommand does not. This can cause duplicate commands if a user updates an existing command with trailing whitespace/newlines.

Consider normalizing the text in updateSavedCommand as well:

updateSavedCommand(id: string, text: string) {
    const normalizedText = text.replace(/\n$/, "");
    // ...

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 19, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
frontend/app/aipanel/waveai-model.tsx 344 Inconsistent text normalization in updateSavedCommand
Files Reviewed (8 files)
  • frontend/app/aipanel/aimessage.tsx
  • frontend/app/aipanel/aipanel.tsx
  • frontend/app/aipanel/aitypes.ts
  • frontend/app/aipanel/savedcommandspanel.tsx
  • frontend/app/aipanel/waveai-model.tsx
  • frontend/app/element/streamdown.test.ts
  • frontend/app/element/streamdown.tsx
  • pkg/waveobj/objrtinfo.go

Note: The inline comment was posted directly on the code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/element/streamdown.tsx (1)

356-356: ⚠️ Potential issue | 🟡 Minor

Missing onClickSaveCommand in useMemo dependency array.

The components memo uses onClickSaveCommand in the pre component (line 276), but it's not included in the dependency array. This can cause stale closures where the save button uses an outdated callback reference.

🐛 Proposed fix
         }),
-        [onClickExecute, codeBlockMaxWidthAtom]
+        [onClickExecute, onClickSaveCommand, codeBlockMaxWidthAtom]
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/element/streamdown.tsx` at line 356, The memoized "components"
(created with useMemo) references onClickSaveCommand inside the pre component
but omits it from the dependency array ([onClickExecute,
codeBlockMaxWidthAtom]), which can lead to stale closures; update the dependency
array for the useMemo that defines components to include onClickSaveCommand so
the pre component always captures the latest callback (i.e., add
onClickSaveCommand alongside onClickExecute and codeBlockMaxWidthAtom).
🧹 Nitpick comments (2)
frontend/app/aipanel/savedcommandspanel.tsx (1)

53-55: Consider debouncing command text updates.

The onChange handler calls model.updateSavedCommand on every keystroke. The implementation directly calls persistSavedCommands without any debouncing, which means every character typed triggers a persistence operation. This could cause unnecessary storage writes or backend calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/aipanel/savedcommandspanel.tsx` around lines 53 - 55, The
textarea's onChange directly calls model.updateSavedCommand each keystroke
causing immediate persistence; debounce those updates instead. Implement a
debounced wrapper (e.g., using lodash.debounce or a custom debounce) around
model.updateSavedCommand inside the component (or create a new method like
debouncedUpdateSavedCommand) and call that from the textarea onChange; ensure
you cancel/flush the debounce on unmount. Alternatively modify
model.updateSavedCommand to batch/debounce calls before calling
persistSavedCommands so persistSavedCommands is not invoked on every keystroke.
frontend/app/aipanel/waveai-model.tsx (1)

357-388: Inconsistent use of trimmed vs. original text.

The method validates using commandText (trimmed), but sends using text (original) at line 385. This means leading/trailing whitespace is stripped for validation but preserved when sending to the terminal. If intentional (to preserve original formatting), consider documenting this behavior. If not, use commandText consistently.

Additionally, the method is marked async but contains no await expressions—the sendDataToController call is synchronous per the codebase.

♻️ Proposed refactor for consistency
-    async runSavedCommand(text: string) {
+    runSavedCommand(text: string) {
         const commandText = text.trim();
         if (!commandText) {
             return;
         }
         if (this.inBuilder) {
             this.setError("Running saved commands in the terminal is not available from the builder.");
             return;
         }

         const focusedBlockId = getFocusedBlockId();
         if (!focusedBlockId) {
             this.setError("Focus a terminal block, then run the saved command again.");
             return;
         }
         const blockAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", focusedBlockId));
         const blockData = globalStore.get(blockAtom);
         if (blockData?.meta?.view !== "term") {
             this.setError("Focus a terminal block, then run the saved command again.");
             return;
         }
         const blockComponentModel = getBlockComponentModel(focusedBlockId);
         const termViewModel = blockComponentModel?.viewModel as { sendDataToController?: (data: string) => void };
         if (typeof termViewModel?.sendDataToController !== "function") {
             this.setError("The focused terminal is not ready to receive commands.");
             return;
         }

-        const commandWithNewline = text.endsWith("\n") ? text : `${text}\n`;
+        const commandWithNewline = commandText.endsWith("\n") ? commandText : `${commandText}\n`;
         termViewModel.sendDataToController(commandWithNewline);
         this.requestNodeFocus();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/aipanel/waveai-model.tsx` around lines 357 - 388, The method
runSavedCommand uses a trimmed commandText for validation but sends the original
text to termViewModel.sendDataToController, causing inconsistent whitespace
handling; change the send to use commandText (append newline to commandText as
needed) so validation and sent data match, and since there are no awaits remove
the async modifier from runSavedCommand to avoid misleading async semantics;
update references to commandWithNewline to be derived from commandText and
ensure requestNodeFocus() remains called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/aipanel/waveai-model.tsx`:
- Around line 318-342: The addSavedCommand function currently allows empty or
whitespace-only text because the find predicate prevents matching but doesn’t
prevent creating a new empty command; add an early validation in addSavedCommand
to trim the input and if trimmed text length is 0 return an empty string (or
null per project convention) without persisting; use the normalizedText (or
trimmed variable) to check and bail out before accessing globalStore or calling
persistSavedCommands, referencing addSavedCommand, savedCommandsAtom, and
persistSavedCommands to locate the change.

In `@pkg/waveobj/objrtinfo.go`:
- Around line 6-11: setFieldValue currently ignores slice targets so
WaveAISavedCommands (type []WaveAISavedCommand) is never persisted; update
setFieldValue in pkg/wstore/wstore_rtinfo.go to handle reflect.Slice: detect
when dest.Kind() == reflect.Slice, and if src is a string or []byte JSON
payload, unmarshal that JSON into a new slice of the destination element type
(e.g., []WaveAISavedCommand) and use reflect.Value.Set to assign it; ensure the
code path integrates with existing cases and will be used by SetRTInfoCommand
when persisting WaveAISavedCommands.

---

Outside diff comments:
In `@frontend/app/element/streamdown.tsx`:
- Line 356: The memoized "components" (created with useMemo) references
onClickSaveCommand inside the pre component but omits it from the dependency
array ([onClickExecute, codeBlockMaxWidthAtom]), which can lead to stale
closures; update the dependency array for the useMemo that defines components to
include onClickSaveCommand so the pre component always captures the latest
callback (i.e., add onClickSaveCommand alongside onClickExecute and
codeBlockMaxWidthAtom).

---

Nitpick comments:
In `@frontend/app/aipanel/savedcommandspanel.tsx`:
- Around line 53-55: The textarea's onChange directly calls
model.updateSavedCommand each keystroke causing immediate persistence; debounce
those updates instead. Implement a debounced wrapper (e.g., using
lodash.debounce or a custom debounce) around model.updateSavedCommand inside the
component (or create a new method like debouncedUpdateSavedCommand) and call
that from the textarea onChange; ensure you cancel/flush the debounce on
unmount. Alternatively modify model.updateSavedCommand to batch/debounce calls
before calling persistSavedCommands so persistSavedCommands is not invoked on
every keystroke.

In `@frontend/app/aipanel/waveai-model.tsx`:
- Around line 357-388: The method runSavedCommand uses a trimmed commandText for
validation but sends the original text to termViewModel.sendDataToController,
causing inconsistent whitespace handling; change the send to use commandText
(append newline to commandText as needed) so validation and sent data match, and
since there are no awaits remove the async modifier from runSavedCommand to
avoid misleading async semantics; update references to commandWithNewline to be
derived from commandText and ensure requestNodeFocus() remains called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ae5bd66e-4985-455e-8793-200fa48b89b2

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee003a and cd31239.

📒 Files selected for processing (9)
  • frontend/app/aipanel/aimessage.tsx
  • frontend/app/aipanel/aipanel.tsx
  • frontend/app/aipanel/aitypes.ts
  • frontend/app/aipanel/savedcommandspanel.tsx
  • frontend/app/aipanel/waveai-model.tsx
  • frontend/app/element/streamdown.test.ts
  • frontend/app/element/streamdown.tsx
  • frontend/types/gotypes.d.ts
  • pkg/waveobj/objrtinfo.go

Comment on lines +318 to +342
addSavedCommand(text: string): string {
const normalizedText = text.replace(/\n$/, "");
const now = Date.now();
const currentCommands = globalStore.get(this.savedCommandsAtom);
const existing = currentCommands.find(
(command) => command.text.trim() === normalizedText.trim() && normalizedText.trim().length > 0
);
if (existing) {
this.persistSavedCommands(
currentCommands.map((command) =>
command.id === existing.id ? { ...command, updatedts: now } : command
)
);
return existing.id;
}

const nextCommand: SavedCommand = {
id: crypto.randomUUID(),
text: normalizedText,
createdts: now,
updatedts: now,
};
this.persistSavedCommands([nextCommand, ...currentCommands]);
return nextCommand.id;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add validation for empty or whitespace-only input.

If text is empty or contains only whitespace, existing will never match (due to the normalizedText.trim().length > 0 check in the find predicate), and the code will proceed to create a new command with empty text. This could result in multiple empty commands being saved.

🛡️ Proposed fix to add early validation
 addSavedCommand(text: string): string {
     const normalizedText = text.replace(/\n$/, "");
+    if (!normalizedText.trim()) {
+        return "";
+    }
     const now = Date.now();
📝 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
addSavedCommand(text: string): string {
const normalizedText = text.replace(/\n$/, "");
const now = Date.now();
const currentCommands = globalStore.get(this.savedCommandsAtom);
const existing = currentCommands.find(
(command) => command.text.trim() === normalizedText.trim() && normalizedText.trim().length > 0
);
if (existing) {
this.persistSavedCommands(
currentCommands.map((command) =>
command.id === existing.id ? { ...command, updatedts: now } : command
)
);
return existing.id;
}
const nextCommand: SavedCommand = {
id: crypto.randomUUID(),
text: normalizedText,
createdts: now,
updatedts: now,
};
this.persistSavedCommands([nextCommand, ...currentCommands]);
return nextCommand.id;
}
addSavedCommand(text: string): string {
const normalizedText = text.replace(/\n$/, "");
if (!normalizedText.trim()) {
return "";
}
const now = Date.now();
const currentCommands = globalStore.get(this.savedCommandsAtom);
const existing = currentCommands.find(
(command) => command.text.trim() === normalizedText.trim() && normalizedText.trim().length > 0
);
if (existing) {
this.persistSavedCommands(
currentCommands.map((command) =>
command.id === existing.id ? { ...command, updatedts: now } : command
)
);
return existing.id;
}
const nextCommand: SavedCommand = {
id: crypto.randomUUID(),
text: normalizedText,
createdts: now,
updatedts: now,
};
this.persistSavedCommands([nextCommand, ...currentCommands]);
return nextCommand.id;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/aipanel/waveai-model.tsx` around lines 318 - 342, The
addSavedCommand function currently allows empty or whitespace-only text because
the find predicate prevents matching but doesn’t prevent creating a new empty
command; add an early validation in addSavedCommand to trim the input and if
trimmed text length is 0 return an empty string (or null per project convention)
without persisting; use the normalizedText (or trimmed variable) to check and
bail out before accessing globalStore or calling persistSavedCommands,
referencing addSavedCommand, savedCommandsAtom, and persistSavedCommands to
locate the change.

Comment on lines +6 to +11
type WaveAISavedCommand struct {
Id string `json:"id"`
Text string `json:"text"`
CreatedTs int64 `json:"createdts,omitempty"`
UpdatedTs int64 `json:"updatedts,omitempty"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that setFieldValue lacks slice handling
rg -n 'reflect\.Slice' pkg/wstore/wstore_rtinfo.go

# Show the setFieldValue function to confirm missing slice case
ast-grep --pattern $'func setFieldValue($_, $_) {
  $$$
}'

Repository: wavetermdev/waveterm

Length of output: 3466


Add reflect.Slice handling to setFieldValue function.

The WaveAISavedCommands field (added to ObjRTInfo) is a slice type, but setFieldValue in pkg/wstore/wstore_rtinfo.go (lines 19-79) only handles string, bool, int, map, and interface kinds. When SetRTInfoCommand attempts to persist saved commands via this function, the slice assignment is silently skipped due to the missing case, resulting in data loss across sessions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/waveobj/objrtinfo.go` around lines 6 - 11, setFieldValue currently
ignores slice targets so WaveAISavedCommands (type []WaveAISavedCommand) is
never persisted; update setFieldValue in pkg/wstore/wstore_rtinfo.go to handle
reflect.Slice: detect when dest.Kind() == reflect.Slice, and if src is a string
or []byte JSON payload, unmarshal that JSON into a new slice of the destination
element type (e.g., []WaveAISavedCommand) and use reflect.Value.Set to assign
it; ensure the code path integrates with existing cases and will be used by
SetRTInfoCommand when persisting WaveAISavedCommands.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd31239a37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

WaveAIChatId string `json:"waveai:chatid,omitempty"`
WaveAIMode string `json:"waveai:mode,omitempty"`
WaveAIMaxOutputTokens int `json:"waveai:maxoutputtokens,omitempty"`
WaveAISavedCommands []WaveAISavedCommand `json:"waveai:savedcommands,omitempty"`

Choose a reason for hiding this comment

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

P1 Badge Handle slice RTInfo fields before adding saved commands

Adding waveai:savedcommands as []WaveAISavedCommand makes persistence silently fail because SetRTInfo currently routes updates through setFieldValue, which does not support reflect.Slice (it only handles nil/string/bool/int/map/interface in pkg/wstore/wstore_rtinfo.go). As a result, frontend calls to SetRTInfoCommand with non-empty saved command arrays are ignored, so commands appear in-memory but are lost after reload/restart.

Useful? React with 👍 / 👎.

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