Skip to content

big simplifications to the preview mocks for rendering blocks#3082

Merged
sawka merged 6 commits intomainfrom
sawka/mock1
Mar 19, 2026
Merged

big simplifications to the preview mocks for rendering blocks#3082
sawka merged 6 commits intomainfrom
sawka/mock1

Conversation

@sawka
Copy link
Member

@sawka sawka commented Mar 18, 2026

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: afddd382-db14-4718-b6c4-b696f5722e87

📥 Commits

Reviewing files that changed from the base of the PR and between 9b7af02 and b94538e.

📒 Files selected for processing (4)
  • frontend/preview/mock/mock-node-model.ts
  • frontend/preview/mock/mockwaveenv.ts
  • frontend/preview/mock/use-rpc-override.ts
  • frontend/preview/preview.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/preview/mock/use-rpc-override.ts
  • frontend/preview/mock/mock-node-model.ts
  • frontend/preview/preview.tsx

Walkthrough

The PR moves many globalStore imports from @/store/global to @/app/store/jotaiStore across multiple frontend files. It adds new preview-test utilities: makeMockNodeModel, mock environment enhancements in mockwaveenv (separating regular and streaming RPC overrides and runtime registration), and two hooks useRpcOverride/useRpcStreamOverride. Several preview components (AiFileDiffPreview, SysinfoPreview, WebPreview) are refactored to use the new mock node model and RPC override hooks, removing prior WaveEnv/TabModel scaffolding and simplifying their render logic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 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.
Description check ❓ Inconclusive No description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a description explaining the rationale and benefits of these simplifications to help reviewers understand the motivation behind these changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: significant refactoring to simplify preview mock structures for block rendering, reflected in all modified preview components and mock utilities.

✏️ 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 sawka/mock1
📝 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.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 18, 2026

Deploying waveterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: b94538e
Status: ✅  Deploy successful!
Preview URL: https://010bc6cd.waveterm.pages.dev
Branch Preview URL: https://sawka-mock1.waveterm.pages.dev

View logs

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: 3

Caution

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

⚠️ Outside diff range comments (1)
frontend/preview/mock/mockwaveenv.ts (1)

311-320: ⚠️ Potential issue | 🟡 Minor

Hardcoded stream handler detection may miss custom stream commands.

The override registration checks only for "filereadstream" and "fileliststream" by name to decide whether to use setStreamHandler. If a user registers a custom stream override via RpcOverrides (e.g., "CustomStreamCommand"), it would incorrectly be registered as a call handler instead of a stream handler.

Consider using the handler's type or a more extensible pattern to determine if it's a stream handler.

🔧 Suggested fix using function type detection
     if (overrides) {
         for (const key of Object.keys(overrides) as (keyof RpcOverrides)[]) {
             const cmdName = key.slice(0, -"Command".length).toLowerCase();
-            if (cmdName === "filereadstream" || cmdName === "fileliststream") {
-                setStreamHandler(cmdName, overrides[key] as (...args: any[]) => AsyncGenerator<any, void, boolean>);
+            const handler = overrides[key];
+            // Check if handler is an async generator function
+            if (handler?.constructor?.name === "AsyncGeneratorFunction") {
+                setStreamHandler(cmdName, handler as (...args: any[]) => AsyncGenerator<any, void, boolean>);
             } else {
-                setCallHandler(cmdName, overrides[key] as (...args: any[]) => Promise<any>);
+                setCallHandler(cmdName, handler as (...args: any[]) => Promise<any>);
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/preview/mock/mockwaveenv.ts` around lines 311 - 320, The current
override registration uses hardcoded names ("filereadstream"/"fileliststream")
to pick setStreamHandler vs setCallHandler; change it to detect the override
function type instead: for each overrides[key] (from RpcOverrides) inspect the
function's constructor.name (or prototype) to see if it's an
"AsyncGeneratorFunction" (and optionally "GeneratorFunction") and call
setStreamHandler(cmdName, ...) when true, otherwise call setCallHandler(cmdName,
...); update the loop around overrides, and ensure you reference the existing
symbols overrides, RpcOverrides, setStreamHandler, setCallHandler and cmdName
when implementing the type check so custom stream commands are correctly
registered.
🧹 Nitpick comments (3)
frontend/preview/preview.tsx (1)

101-106: Unnecessary fragment wrapper.

The <></> fragment is redundant since TabModelContext.Provider can directly accept multiple children.

🧹 Remove unnecessary fragment
             <TabModelContext.Provider value={getTabModelByTabId(PreviewTabId, waveEnvRef.current)}>
-                    <>
-                        <PreviewApp />
-                        <PreviewContextMenu />
-                    </>
+                <PreviewApp />
+                <PreviewContextMenu />
             </TabModelContext.Provider>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/preview/preview.tsx` around lines 101 - 106, The fragment wrapper
around the children of TabModelContext.Provider is redundant—remove the <>...</>
and pass PreviewApp and PreviewContextMenu directly as children of
TabModelContext.Provider (the provider with
value={getTabModelByTabId(PreviewTabId, waveEnvRef.current)}). Ensure the JSX
now reads TabModelContext.Provider with PreviewApp and PreviewContextMenu nested
directly inside, preserving the same order and props.
frontend/preview/mock/mockwaveenv.ts (1)

18-23: **Consider documenting ID stability characteristics.**These IDs are generated once at module load time using crypto.randomUUID(). This feature has been available across browsers since March 2022, so browser compatibility is not a concern.

However, developers should be aware that these IDs will change on each page reload (or HMR in development), which is expected behavior for the preview environment but worth documenting for debugging purposes.

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

In `@frontend/preview/mock/mockwaveenv.ts` around lines 18 - 23, Add a concise
comment above the PreviewTabId, PreviewWindowId, PreviewWorkspaceId,
PreviewClientId, WebBlockId, and SysinfoBlockId exports explaining these values
are created once at module load time via crypto.randomUUID() and therefore will
change on each full page reload or during HMR in development; state they are
intentionally ephemeral for the preview environment and should not be relied on
for long-term persistence or cross-session debugging so developers know to
expect ID churn when investigating issues.
frontend/preview/mock/use-rpc-override.ts (1)

8-15: Missing cleanup could leak handlers if component unmounts and remounts with different handler.

The hook registers the override but never unregisters it. If a component using this hook unmounts and a new instance mounts with a different handler for the same command, the old handler remains registered. Consider returning a cleanup function or using useEffect with cleanup.

Additionally, the generic constraint K extends keyof RpcOverrides suggests type safety, but the handler parameter is always RpcHandlerType rather than the specific type for command K. This weakens the type guarantee.

♻️ Suggested approach with cleanup
-export function useRpcOverride<K extends keyof RpcOverrides>(command: K, handler: RpcHandlerType): void {
+export function useRpcOverride<K extends keyof RpcOverrides>(command: K, handler: RpcHandlerType): void {
     const mockEnv = useWaveEnv() as MockWaveEnv;
-    const registeredRef = React.useRef(false);
-    if (!registeredRef.current) {
-        registeredRef.current = true;
+    React.useEffect(() => {
         mockEnv.addRpcOverride(command, handler);
-    }
+        // If MockWaveEnv supported removal, cleanup would go here:
+        // return () => mockEnv.removeRpcOverride(command);
+    }, [mockEnv, command, handler]);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/preview/mock/use-rpc-override.ts` around lines 8 - 15, The hook
useRpcOverride currently registers a handler via mockEnv.addRpcOverride but
never unregisters it and also types the handler too loosely; update
useRpcOverride to register/unregister inside a React.useEffect that calls
mockEnv.addRpcOverride(command, handler) in the effect body and returns a
cleanup that calls mockEnv.removeRpcOverride(command, handler) (or remove by
command if API requires) so handlers are removed on unmount or when handler
changes, and tighten the handler type from RpcHandlerType to the specific
handler type for the command (e.g., use the RpcOverrides[K] or a
RpcHandlerFor<K> generic) so the handler parameter matches the command-specific
signature; keep the existing MockWaveEnv and addRpcOverride/removeRpcOverride
symbols to locate the changes.
🤖 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/view/aifilediff/aifilediff.tsx`:
- Line 9: The import currently referencing "@/store/jotaiStore" should be
updated to the actual module location used elsewhere so the module resolves;
replace the import statement that brings in globalStore from
"@/store/jotaiStore" with the correct module specifier used in the migration
(the module that exports globalStore from frontend/app/store/jotaiStore.ts) so
the named import globalStore continues to be used unchanged.

In `@frontend/preview/mock/mock-node-model.ts`:
- Line 17: The mock defines isMagnifiedAtom and a separate anyMagnified atom
pinned to false, so calling toggleMagnify (which flips isMagnifiedAtom) does not
update anyMagnified and consumers that read anyMagnified never see
magnification; fix by mirroring isMagnifiedAtom into anyMagnified in the mock
(e.g., have anyMagnified derive from or subscribe to isMagnifiedAtom so its
value reflects changes made by toggleMagnify), updating the mock-node-model.ts
atoms for the existing declarations around isMagnifiedAtom and the anyMagnified
definition (also apply same change to the other occurrences noted at lines
31–37).

In `@frontend/preview/previews/aifilediff.preview.tsx`:
- Around line 29-42: The useEffect calling env.createBlock currently ignores
rejection which can leave the component stuck; update the React.useEffect to
handle promise rejections from env.createBlock by adding a .catch or using
try/catch in an async IIFE, and on error call a recovery path such as setting an
error state or logging via console.error/processLogger and avoid leaving blockId
unset; target the createBlock call and setBlockId usage inside the
React.useEffect (or convert to an async function) so failures are surfaced and
the component can render an error or retry UI.

---

Outside diff comments:
In `@frontend/preview/mock/mockwaveenv.ts`:
- Around line 311-320: The current override registration uses hardcoded names
("filereadstream"/"fileliststream") to pick setStreamHandler vs setCallHandler;
change it to detect the override function type instead: for each overrides[key]
(from RpcOverrides) inspect the function's constructor.name (or prototype) to
see if it's an "AsyncGeneratorFunction" (and optionally "GeneratorFunction") and
call setStreamHandler(cmdName, ...) when true, otherwise call
setCallHandler(cmdName, ...); update the loop around overrides, and ensure you
reference the existing symbols overrides, RpcOverrides, setStreamHandler,
setCallHandler and cmdName when implementing the type check so custom stream
commands are correctly registered.

---

Nitpick comments:
In `@frontend/preview/mock/mockwaveenv.ts`:
- Around line 18-23: Add a concise comment above the PreviewTabId,
PreviewWindowId, PreviewWorkspaceId, PreviewClientId, WebBlockId, and
SysinfoBlockId exports explaining these values are created once at module load
time via crypto.randomUUID() and therefore will change on each full page reload
or during HMR in development; state they are intentionally ephemeral for the
preview environment and should not be relied on for long-term persistence or
cross-session debugging so developers know to expect ID churn when investigating
issues.

In `@frontend/preview/mock/use-rpc-override.ts`:
- Around line 8-15: The hook useRpcOverride currently registers a handler via
mockEnv.addRpcOverride but never unregisters it and also types the handler too
loosely; update useRpcOverride to register/unregister inside a React.useEffect
that calls mockEnv.addRpcOverride(command, handler) in the effect body and
returns a cleanup that calls mockEnv.removeRpcOverride(command, handler) (or
remove by command if API requires) so handlers are removed on unmount or when
handler changes, and tighten the handler type from RpcHandlerType to the
specific handler type for the command (e.g., use the RpcOverrides[K] or a
RpcHandlerFor<K> generic) so the handler parameter matches the command-specific
signature; keep the existing MockWaveEnv and addRpcOverride/removeRpcOverride
symbols to locate the changes.

In `@frontend/preview/preview.tsx`:
- Around line 101-106: The fragment wrapper around the children of
TabModelContext.Provider is redundant—remove the <>...</> and pass PreviewApp
and PreviewContextMenu directly as children of TabModelContext.Provider (the
provider with value={getTabModelByTabId(PreviewTabId, waveEnvRef.current)}).
Ensure the JSX now reads TabModelContext.Provider with PreviewApp and
PreviewContextMenu nested directly inside, preserving the same order and props.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f080e79e-4e9a-49bf-ae5b-ef6d2ac4f81d

📥 Commits

Reviewing files that changed from the base of the PR and between 134b388 and 9b7af02.

📒 Files selected for processing (22)
  • .kilocode/skills/create-view/SKILL.md
  • aiprompts/newview.md
  • frontend/app/app.tsx
  • frontend/app/modals/modalsrenderer.tsx
  • frontend/app/view/aifilediff/aifilediff.tsx
  • frontend/app/view/preview/preview-edit.tsx
  • frontend/app/view/preview/preview-markdown.tsx
  • frontend/app/view/preview/preview-model.tsx
  • frontend/app/view/preview/preview-streaming.tsx
  • frontend/app/view/preview/preview.tsx
  • frontend/app/view/term/term.tsx
  • frontend/app/view/waveai/waveai.tsx
  • frontend/app/view/webview/webview.tsx
  • frontend/builder/app-selection-modal.tsx
  • frontend/builder/builder-app.tsx
  • frontend/preview/mock/mock-node-model.ts
  • frontend/preview/mock/mockwaveenv.ts
  • frontend/preview/mock/use-rpc-override.ts
  • frontend/preview/preview.tsx
  • frontend/preview/previews/aifilediff.preview.tsx
  • frontend/preview/previews/sysinfo.preview.tsx
  • frontend/preview/previews/web.preview.tsx

import { DiffViewer } from "@/app/view/codeeditor/diffviewer";
import type { WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
import { globalStore, WOS } from "@/store/global";
import { globalStore } from "@/store/jotaiStore";
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "# candidate jotaiStore modules"
fd 'jotaiStore\.(ts|tsx)$'

echo
echo "# globalStore exports / re-exports"
fd 'jotaiStore\.(ts|tsx)$' -x rg -n 'globalStore|export .* from'

echo
echo "# import under review"
sed -n '1,20p' frontend/app/view/aifilediff/aifilediff.tsx

Repository: wavetermdev/waveterm

Length of output: 907


Update import path to match the actual module location.

The import on line 9 uses @/store/jotaiStore, but the only jotaiStore module exists at frontend/app/store/jotaiStore.ts. This import will fail module resolution. Update it to match the path used throughout the rest of the migration:

Required change
-import { globalStore } from "@/store/jotaiStore";
+import { globalStore } from "@/app/store/jotaiStore";
📝 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
import { globalStore } from "@/store/jotaiStore";
import { globalStore } from "@/app/store/jotaiStore";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/aifilediff/aifilediff.tsx` at line 9, The import currently
referencing "@/store/jotaiStore" should be updated to the actual module location
used elsewhere so the module resolves; replace the import statement that brings
in globalStore from "@/store/jotaiStore" with the correct module specifier used
in the migration (the module that exports globalStore from
frontend/app/store/jotaiStore.ts) so the named import globalStore continues to
be used unchanged.

Copy link
Member Author

Choose a reason for hiding this comment

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

in tsconfig.json:

        "@/store/*": ["frontend/app/store/*"],

Copy link
Contributor

Choose a reason for hiding this comment

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

@sawka, you're absolutely right — my mistake! The @/store/* alias in tsconfig.json maps to frontend/app/store/*, so @/store/jotaiStore correctly resolves to frontend/app/store/jotaiStore.ts. The import is perfectly valid. Sorry for the noise.


✏️ Learnings added
Learnt from: sawka
Repo: wavetermdev/waveterm PR: 3082
File: frontend/app/view/aifilediff/aifilediff.tsx:9-9
Timestamp: 2026-03-18T23:02:25.531Z
Learning: In the wavetermdev/waveterm repository, the TypeScript path alias `"@/store/*"` maps to `"frontend/app/store/*"` (configured in tsconfig.json). So imports like `import { globalStore } from "@/store/jotaiStore"` correctly resolve to `frontend/app/store/jotaiStore.ts` and should NOT be flagged as incorrect import paths.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +29 to +42
React.useEffect(() => {
env.createBlock(
{
meta: {
view: "aifilediff",
file: DefaultAiFileDiffFileName,
"aifilediff:chatid": DefaultAiFileDiffChatId,
"aifilediff:toolcallid": DefaultAiFileDiffToolCallId,
},
},
});
}, [baseEnv]);
false,
false
).then((id) => setBlockId(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

Missing error handling for createBlock promise.

If env.createBlock rejects, the error will be swallowed silently, leaving the component in a perpetual loading state. Consider adding error handling.

🛡️ Add error handling
     React.useEffect(() => {
         env.createBlock(
             {
                 meta: {
                     view: "aifilediff",
                     file: DefaultAiFileDiffFileName,
                     "aifilediff:chatid": DefaultAiFileDiffChatId,
                     "aifilediff:toolcallid": DefaultAiFileDiffToolCallId,
                 },
             },
             false,
             false
-        ).then((id) => setBlockId(id));
+        ).then((id) => setBlockId(id))
+         .catch((err) => console.error("[AiFileDiffPreview] createBlock failed:", err));
     }, []);
📝 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
React.useEffect(() => {
env.createBlock(
{
meta: {
view: "aifilediff",
file: DefaultAiFileDiffFileName,
"aifilediff:chatid": DefaultAiFileDiffChatId,
"aifilediff:toolcallid": DefaultAiFileDiffToolCallId,
},
},
});
}, [baseEnv]);
false,
false
).then((id) => setBlockId(id));
}, []);
React.useEffect(() => {
env.createBlock(
{
meta: {
view: "aifilediff",
file: DefaultAiFileDiffFileName,
"aifilediff:chatid": DefaultAiFileDiffChatId,
"aifilediff:toolcallid": DefaultAiFileDiffToolCallId,
},
},
false,
false
).then((id) => setBlockId(id))
.catch((err) => console.error("[AiFileDiffPreview] createBlock failed:", err));
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/preview/previews/aifilediff.preview.tsx` around lines 29 - 42, The
useEffect calling env.createBlock currently ignores rejection which can leave
the component stuck; update the React.useEffect to handle promise rejections
from env.createBlock by adding a .catch or using try/catch in an async IIFE, and
on error call a recovery path such as setting an error state or logging via
console.error/processLogger and avoid leaving blockId unset; target the
createBlock call and setBlockId usage inside the React.useEffect (or convert to
an async function) so failures are surfaced and the component can render an
error or retry UI.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 18, 2026

Code Review Summary

Status: No New Issues Found | Recommendation: Merge

Overview

The PR introduces a simplified mock environment system for preview testing. After reviewing the diff, no new issues were identified beyond those already flagged in existing comments.

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Existing Comments Status
File Line Issue Status
frontend/app/view/aifilediff/aifilediff.tsx 9 Import path (@/store/jotaiStore) ✅ Resolved - alias works correctly
frontend/preview/mock/mock-node-model.ts 17 anyMagnified not derived from isMagnified ✅ Resolved - now properly derived
frontend/preview/previews/aifilediff.preview.tsx 42 Missing error handling for createBlock ⚠️ Still present
Files Reviewed (23 files)
  • .kilocode/skills/create-view/SKILL.md
  • aiprompts/newview.md
  • frontend/app/app.tsx
  • frontend/app/modals/modalsrenderer.tsx
  • frontend/app/view/aifilediff/aifilediff.tsx
  • frontend/app/view/preview/preview-edit.tsx
  • frontend/app/view/preview/preview-markdown.tsx
  • frontend/app/view/preview/preview-model.tsx
  • frontend/app/view/preview/preview-streaming.tsx
  • frontend/app/view/preview/preview.tsx
  • frontend/app/view/term/term.tsx
  • frontend/app/view/waveai/waveai.tsx
  • frontend/app/view/webview/webview.tsx
  • frontend/builder/app-selection-modal.tsx
  • frontend/builder/builder-app.tsx
  • frontend/preview/mock/mock-node-model.ts
  • frontend/preview/mock/mockwaveenv.ts
  • frontend/preview/mock/use-rpc-override.ts
  • frontend/preview/preview.tsx
  • frontend/preview/previews/aifilediff.preview.tsx
  • frontend/preview/previews/sysinfo.preview.tsx
  • frontend/preview/previews/web.preview.tsx

Note: The existing comment about missing error handling for createBlock promise in aifilediff.preview.tsx line 41-42 remains unaddressed. Consider adding a .catch() handler for robustness, though this is a minor issue for mock/test code.

@sawka sawka merged commit 4ee003a into main Mar 19, 2026
8 checks passed
@sawka sawka deleted the sawka/mock1 branch March 19, 2026 01:49
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.

1 participant