Skip to content

Add block rename and preview follow terminal features#3235

Open
cowhen wants to merge 2 commits intowavetermdev:mainfrom
cowhen:feature/block-rename-and-preview-follow
Open

Add block rename and preview follow terminal features#3235
cowhen wants to merge 2 commits intowavetermdev:mainfrom
cowhen:feature/block-rename-and-preview-follow

Conversation

@cowhen
Copy link
Copy Markdown

@cowhen cowhen commented Apr 18, 2026

Summary

  • Add inline block renaming via context menu with frame:title metadata support
  • Add preview follow terminal functionality to automatically sync directory navigation between terminal and preview pane
  • Configure dev build as separate Wave Dev app to avoid single-instance lock conflicts
  • Improve accessibility, UX, and cross-platform compatibility

Detailed Changes

Block Rename Improvements

  • Restrict rename menu to terminal blocks only (matches badge visibility)
  • Disable rename for preview/ephemeral blocks
  • Add race condition protection for Escape/blur handling
  • Auto-select text on focus for better UX
  • Await RPC calls and log errors instead of fire-and-forget

Preview Follow Terminal Enhancements

  • Add keyboard accessibility (Escape key, Enter/Space navigation)
  • Add ARIA roles and labels for screen readers
  • Implement focus management (capture and restore active element)
  • Fix effect dependencies to prevent stale closures
  • Improve bidirectional suppression with timeout cleanup
  • Make sendCdToTerminal shell-aware (POSIX, PowerShell, cmd.exe)
  • Clear bidir state when linking new terminal to avoid inheriting previous settings
  • Fix terminal numbering to use actual tab order instead of filtered index
  • Move helper functions after imports for better code organization

Dev Build Improvements

  • Update CLAUDE.md with explicit WAVETERM_CONFIG_HOME and WAVETERM_DATA_HOME env variables
  • Make launch_wave_dev.command portable with dynamic script directory detection
  • Document why explicit overrides are needed for clean installs

Test plan

  • TypeScript build passes with no errors
  • Test block renaming via context menu works correctly and persists
  • Verify rename is disabled for non-terminal/preview/ephemeral blocks
  • Test Escape key cancels rename without saving
  • Verify preview follow terminal syncs directory changes from terminal to preview
  • Test bidirectional following between terminal and preview
  • Confirm keyboard navigation works in follow terminal dropdown
  • Test shell-aware cd commands on different shells (bash, zsh, PowerShell, cmd)
  • Confirm dev build launches independently from production build with isolated data
  • Test that frame:title metadata is properly saved and restored

🤖 Generated with Claude Code

- Add inline block renaming via context menu with frame:title metadata
- Add preview follow terminal functionality to sync directory navigation
- Configure dev build as separate Wave Dev app to avoid conflicts
- Add launch_wave_dev.command helper script for dev builds

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 18, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 18, 2026

Walkthrough

This PR adds block renaming UI and state (context-menu rename, inline editable header, Jotai atoms, RPC persistence), implements a "Follow Terminal" feature for directory previews (atoms, dropdown UI, shell-aware cd dispatch, bidirectional sync metadata and behavior), extends TypeScript metadata types, adds corresponding Go metadata constants and struct fields, updates Electron dev packaging identifiers and adds a macOS dev launch script, and documents a dev build & packaging workflow in CLAUDE.md.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the two main features added: block renaming and preview follow terminal functionality, matching the primary changes across the changeset.
Description check ✅ Passed The description is directly related to the changeset, providing a structured summary of block rename improvements, preview follow terminal enhancements, and dev build improvements with detailed implementation notes.

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

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

Copy link
Copy Markdown
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: 8

🧹 Nitpick comments (5)
frontend/app/block/blockframe-header.tsx (1)

86-91: Minor: every block header re-renders on any rename start/stop.

renamingBlockIdAtom is a single global atom, so every mounted HeaderTextElems (one per block) re-subscribes and re-renders whenever any block enters/exits rename mode. For typical layouts this is negligible, but a derived per-block atom keeps the subscription scoped:

♻️ Optional refactor
// in blockrenamestate.ts
+export const isRenamingBlockAtomFamily = (blockId: string) =>
+    jotai.atom((get) => get(renamingBlockIdAtom) === blockId);

Then consume useAtomValue(isRenamingBlockAtomFamily(blockId)) instead of comparing the raw id. (Wrap in a memoized atomFamily if you'd rather not re-create the derived atom per render.)

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

In `@frontend/app/block/blockframe-header.tsx` around lines 86 - 91, The header
components subscribe to the global renamingBlockIdAtom so every HeaderTextElems
re-renders on any rename toggle; fix this by introducing a per-block derived
atom (e.g., create an atomFamily or a memoized function like
isRenamingBlockAtom(blockId) that returns jotai.atom(get =>
get(renamingBlockIdAtom) === blockId)) and then have HeaderTextElems consume
useAtomValue of that per-block atom (replace the renamingBlockId === blockId
check with util.useAtomValueSafe(isRenamingBlockAtom(blockId))). If you create
the derived atom inside a factory, memoize it (or use an atomFamily helper) to
avoid recreating it each render.
frontend/app/view/preview/preview.tsx (2)

143-151: Consider also clearing bidir when switching to a different terminal via linkTerm.

linkTerm only writes preview:followtermid; preview:followterm:bidir is preserved. If a user had bidir enabled on terminal A and then switches to terminal B through this menu, the new link silently inherits bidir — which will immediately push a cd into terminal B on the next directory change. If that's intentional, fine; otherwise consider resetting bidir on link to match the unlink semantics at L143–151.

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

In `@frontend/app/view/preview/preview.tsx` around lines 143 - 151, linkTerm
currently only updates "preview:followtermid" and leaves
"preview:followterm:bidir" intact, causing a new link to inherit bidir state;
update the linkTerm implementation (the same area interacting with
model.env.services.object.UpdateObjectMeta / WOS.makeORef and model.blockId) to
also clear or explicitly set "preview:followterm:bidir" (e.g., null/false) when
writing the new followterm id so its behavior matches unlink which clears both
keys; ensure any client-side atoms like model.followTermMenuDataAtom are updated
consistently after the UpdateObjectMeta call.

7-28: Move top-level helpers below the import block.

shellEscapePath and sendCdToTerminal are declared between two groups of import statements (lines 7–20 vs 21–28). ES module import hoisting makes this work at runtime, but it breaks the conventional top-of-file import layout and trips most linters/formatters. Group all imports first, then define the helpers — or move them into a small helper module.

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

In `@frontend/app/view/preview/preview.tsx` around lines 7 - 28, Move the
top-level helper functions out of the middle of the import section: relocate
shellEscapePath and sendCdToTerminal so they appear after all import statements
(or extract them into a new helper module and import them), ensuring you
preserve their signatures and update any imports/exports if you extract them;
this restores a single contiguous import block and keeps the functions
(shellEscapePath, sendCdToTerminal) in their original file or a small helper
module referenced by preview.tsx.
frontend/app/view/preview/preview-model.tsx (2)

173-176: Extract the dropdown menu data type for readability.

The inline anonymous type on followTermMenuDataAtom is long and is duplicated conceptually in preview.tsx when destructuring menuData. Naming it (e.g. FollowTermMenuData) and exporting it simplifies future consumers and keeps the shape in one place.

♻️ Proposed extraction
+type FollowTermMenuData = {
+    pos: { x: number; y: number };
+    terms: { blockId: string; title: string }[];
+    currentFollowId: string | null;
+    bidir: boolean;
+};
+
 ...
-    followTermMenuDataAtom: PrimitiveAtom<{ pos: { x: number; y: number }; terms: { blockId: string; title: string }[]; currentFollowId: string | null; bidir: boolean } | null>;
+    followTermMenuDataAtom: PrimitiveAtom<FollowTermMenuData | null>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/preview/preview-model.tsx` around lines 173 - 176, Extract
the long anonymous menu-data shape into a named exported type (e.g.
FollowTermMenuData) and replace the inline type on followTermMenuDataAtom with
that alias so the shape is defined once; update any consumers (like preview.tsx
where menuData is destructured) to import and use FollowTermMenuData instead of
repeating the inline type. Ensure the new type is exported from the module that
defines followTermMenuDataAtom and keep the exact field names/structures (pos,
terms array with blockId/title, currentFollowId, bidir) to maintain
compatibility.

520-544: Minor: menu fallback title index is based on the filtered list, not tab order.

The i in .map(..., i) => ... || \Terminal ${i + 1}`is the index *after* filtering out non-terminal blocks and the current preview block. Users with mixed blocks will see "Terminal 1"/"Terminal 2" numbers that don't align with tab position. Givenframe:title` is usually present, this is a cosmetic fallback only — flagging as nitpick.

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

In `@frontend/app/view/preview/preview-model.tsx` around lines 520 - 544, The
fallback "Terminal N" index is being taken from the filtered/map iteration index
in showFollowTermMenu, which reflects the post-filtered order rather than the
tab order; to fix, build a termBlockIds array from the original tabData.blockids
in the same order (filtering out this.blockId and non-term blocks using
WOS.getObjectValue and block.meta.view === "term"), then when constructing terms
use that ordered termBlockIds to derive the fallback index (e.g., Terminal
${termBlockIds.indexOf(bid) + 1}) instead of the map's i; update the logic that
sets followTermMenuDataAtom accordingly so titles use the tab-order-based index
while keeping frame:title when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Around line 26-30: Update the CLAUDE.md launch docs to explicitly document the
config/data env overrides used by the launcher: replace the single WAVETERM_HOME
example with a multi-line example that sets WAVETERM_HOME, WAVETERM_CONFIG_HOME,
and WAVETERM_DATA_HOME (e.g. WAVETERM_HOME=~/.waveterm-dev,
WAVETERM_CONFIG_HOME=~/.waveterm-dev/config,
WAVETERM_DATA_HOME=~/.waveterm-dev/data) and change the explanatory sentence to
state these variables create isolated config and data dirs for the dev build;
also mention that getWaveHomeDir() only honors WAVETERM_HOME after wave.lock
exists so the explicit CONFIG/DATA overrides are needed for clean
installs/launched dev instances.

In `@frontend/app/block/blockframe-header.tsx`:
- Around line 46-49: The "Rename Block" menu item currently always calls
startBlockRename(blockId); update the generation/handler in
handleHeaderContextMenu (or where the menu array with label "Rename Block" is
built) to either omit the menu item when preview is true or to guard the click
with a check like if (!ephemeral && !preview) then call
startBlockRename(blockId); reference startBlockRename, handleHeaderContextMenu,
the "Rename Block" menu entry, and the ephemeral/preview flags when making the
change so renames are disabled for preview/ephemeral blocks.
- Around line 131-142: The "Rename Block" context-menu item is shown
unconditionally while the inline rename badge only renders when useTermHeader &&
frameTitle; update the context-menu entry for "Rename Block" to respect the same
condition by adding a visible or enabled property driven by useTermHeader (or
the same terminal-block check used to render the badge) so the menu item is
hidden or disabled for non-terminal/ephemeral blocks; locate the context-menu
items array or builder that defines the "Rename Block" entry (the item with
label "Rename Block") and gate it on useTermHeader (or the terminal/frame-type
predicate) to match the badge rendering logic.
- Around line 95-127: The input rename UX should guard against Escape/blur
races, auto-select text on focus, and surface RPC errors: add a ref (e.g.,
cancelRef) that you set when Escape is pressed (inside the onKeyDown handler)
and check in the onBlur handler and saveRename to no-op if cancellation is set;
change saveRename to await the waveEnv.rpc.SetMetaCommand call (or attach a
.catch) and log errors instead of fire-and-forget; in the input's onFocus call
select() on the target to auto-select the current frameTitle; ensure
stopBlockRename still clears the ref when appropriate so subsequent opens are
not cancelled. Reference saveRename, stopBlockRename,
waveEnv.rpc.SetMetaCommand, and the input's onBlur/onKeyDown/onFocus handlers
when making the changes.

In `@frontend/app/view/preview/preview.tsx`:
- Around line 116-218: FollowTermDropdown currently only closes on backdrop
mousedown and lacks keyboard/accessibility features; update the component
(FollowTermDropdown) to add an Escape key listener (in a useEffect) that calls
closeMenu and is cleaned up on unmount, assign role="menu" to the portal inner
container and role="menuitem" plus tabIndex={0} and descriptive aria-labels to
each clickable row (the term rows, the bidir toggle in toggleBidir, and Stop
Following via unlink), add onKeyDown handlers on those rows to trigger the same
actions on Enter/Space, and implement focus management by capturing
document.activeElement before opening, moving focus to the first menu item on
mount (use refs or a ref array for the mapped terms), and restoring focus to the
original opener when closeMenu runs; ensure all event listeners and refs are
properly cleaned up.
- Around line 249-265: Add the missing effect dependencies and make the bidir
suppression robust: update the first useEffect to include followTermId and model
(it currently references them but only depends on followTermCwd) and update the
second useEffect to include followTermId, followTermBidir, and env in its
dependency array (keep loadableFileInfo too) so toggling bidir immediately
triggers a cd; change the suppressBidirRef logic around model.goHistory so you
only set suppressBidirRef.current = true when model.goHistory(followTermCwd)
actually produces an update (i.e., returns a non-null/changed value) or
alternatively clear the suppressor on a short timer or in a cleanup to avoid
suppressing the next genuine navigation; references: followTermId,
followTermCwd, model.goHistory, suppressBidirRef, loadableFileInfo,
followTermBidir, sendCdToTerminal, env.
- Around line 8-20: The current sendCdToTerminal and shellEscapePath assume
POSIX shells and send a raw Ctrl-U + single-quoted cd payload which breaks on
PowerShell, cmd.exe, and when a foreground program is running; update the logic
to first obtain the terminal's shell type via PreviewEnv (extend PreviewEnv to
expose a method like getTerminalMeta or getShellType or add term metadata
access) and skip or use a shell-aware code path if the shell is unknown/unsafe;
implement per-shell quoting helpers (e.g. posixQuote in shellEscapePath,
powershellQuote, cmdQuote) and route sendCdToTerminal to use the appropriate
quoter or to avoid sending Ctrl-U (remove the leading "\x15" for non-POSIX
shells) and to avoid sending keystrokes into foreground processes by only
sending when term metadata indicates a normal shell prompt; also rename or
document shellEscapePath to posixEscapePath if you keep POSIX-only behavior and
update references (shellEscapePath, sendCdToTerminal, PreviewEnv, TabRpcClient)
accordingly.

In `@launch_wave_dev.command`:
- Line 4: Line 4 hard-codes the app binary and only sets WAVETERM_HOME (which
can be ignored on a clean install); change the launcher to compute its own
directory and use that to set portable dev paths and explicit overrides: derive
SCRIPT_DIR from the script location (e.g., dirname of $0 or ${BASH_SOURCE[0]}),
set WAVETERM_HOME to "$SCRIPT_DIR/.waveterm-dev" and also set explicit
WAVETERM_CONFIG and WAVETERM_DATA to "$WAVETERM_HOME/config" and
"$WAVETERM_HOME/data" (or similar) so clean installs are isolated, then exec the
Wave Dev binary using the computed SCRIPT_DIR path instead of the hard-coded
absolute path.

---

Nitpick comments:
In `@frontend/app/block/blockframe-header.tsx`:
- Around line 86-91: The header components subscribe to the global
renamingBlockIdAtom so every HeaderTextElems re-renders on any rename toggle;
fix this by introducing a per-block derived atom (e.g., create an atomFamily or
a memoized function like isRenamingBlockAtom(blockId) that returns
jotai.atom(get => get(renamingBlockIdAtom) === blockId)) and then have
HeaderTextElems consume useAtomValue of that per-block atom (replace the
renamingBlockId === blockId check with
util.useAtomValueSafe(isRenamingBlockAtom(blockId))). If you create the derived
atom inside a factory, memoize it (or use an atomFamily helper) to avoid
recreating it each render.

In `@frontend/app/view/preview/preview-model.tsx`:
- Around line 173-176: Extract the long anonymous menu-data shape into a named
exported type (e.g. FollowTermMenuData) and replace the inline type on
followTermMenuDataAtom with that alias so the shape is defined once; update any
consumers (like preview.tsx where menuData is destructured) to import and use
FollowTermMenuData instead of repeating the inline type. Ensure the new type is
exported from the module that defines followTermMenuDataAtom and keep the exact
field names/structures (pos, terms array with blockId/title, currentFollowId,
bidir) to maintain compatibility.
- Around line 520-544: The fallback "Terminal N" index is being taken from the
filtered/map iteration index in showFollowTermMenu, which reflects the
post-filtered order rather than the tab order; to fix, build a termBlockIds
array from the original tabData.blockids in the same order (filtering out
this.blockId and non-term blocks using WOS.getObjectValue and block.meta.view
=== "term"), then when constructing terms use that ordered termBlockIds to
derive the fallback index (e.g., Terminal ${termBlockIds.indexOf(bid) + 1})
instead of the map's i; update the logic that sets followTermMenuDataAtom
accordingly so titles use the tab-order-based index while keeping frame:title
when present.

In `@frontend/app/view/preview/preview.tsx`:
- Around line 143-151: linkTerm currently only updates "preview:followtermid"
and leaves "preview:followterm:bidir" intact, causing a new link to inherit
bidir state; update the linkTerm implementation (the same area interacting with
model.env.services.object.UpdateObjectMeta / WOS.makeORef and model.blockId) to
also clear or explicitly set "preview:followterm:bidir" (e.g., null/false) when
writing the new followterm id so its behavior matches unlink which clears both
keys; ensure any client-side atoms like model.followTermMenuDataAtom are updated
consistently after the UpdateObjectMeta call.
- Around line 7-28: Move the top-level helper functions out of the middle of the
import section: relocate shellEscapePath and sendCdToTerminal so they appear
after all import statements (or extract them into a new helper module and import
them), ensuring you preserve their signatures and update any imports/exports if
you extract them; this restores a single contiguous import block and keeps the
functions (shellEscapePath, sendCdToTerminal) in their original file or a small
helper module referenced by preview.tsx.
🪄 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: CHILL

Plan: Pro

Run ID: 6113d99a-90b4-49d7-b8cd-b91bf62604df

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4fe8c and 0a2e3a9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • CLAUDE.md
  • frontend/app/block/blockframe-header.tsx
  • frontend/app/block/blockrenamestate.ts
  • frontend/app/view/preview/preview-model.tsx
  • frontend/app/view/preview/preview.tsx
  • frontend/types/gotypes.d.ts
  • launch_wave_dev.command
  • package.json
  • pkg/waveobj/metaconsts.go
  • pkg/waveobj/wtypemeta.go

Comment thread CLAUDE.md Outdated
Comment thread frontend/app/block/blockframe-header.tsx Outdated
Comment thread frontend/app/block/blockframe-header.tsx
Comment thread frontend/app/block/blockframe-header.tsx
Comment thread frontend/app/view/preview/preview.tsx Outdated
Comment thread frontend/app/view/preview/preview.tsx
Comment thread frontend/app/view/preview/preview.tsx Outdated
Comment thread launch_wave_dev.command Outdated
Block rename improvements:
- Restrict rename menu to terminal blocks only (match badge visibility)
- Disable rename for preview/ephemeral blocks
- Add race condition protection for Escape/blur handling
- Auto-select text on focus for better UX
- Await RPC calls and log errors instead of fire-and-forget

Preview follow terminal enhancements:
- Add keyboard accessibility (Escape, Enter, Space navigation)
- Add ARIA roles and labels for screen readers
- Implement focus management (capture and restore)
- Fix effect dependencies to prevent stale closures
- Improve bidir suppression with timeout cleanup
- Make sendCdToTerminal shell-aware (POSIX, PowerShell, cmd.exe)
- Clear bidir state when linking new terminal
- Fix terminal numbering to use tab order instead of filtered index
- Move helper functions after imports for better organization

Dev build improvements:
- Update CLAUDE.md with explicit config/data env variables
- Make launch_wave_dev.command portable with dynamic paths
- Document why explicit overrides needed for clean installs

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
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

🧹 Nitpick comments (2)
frontend/app/block/blockframe-header.tsx (1)

158-168: cursor-pointer on a non-clickable badge is misleading.

The badge sets cursor-pointer but has no onClick handler; the tooltip instructs the user to right-click. This mismatch suggests left-click should do something. Either drop cursor-pointer (use default/contextmenu cursor), or wire a onDoubleClick={() => startBlockRename(blockId)} for a natural rename-entry UX that matches the cursor affordance.

♻️ Proposed tweak
-            <div
-                key="frame-title"
-                className="block-frame-text shrink-0 opacity-70 cursor-pointer"
-                title="Right-click header to rename"
-            >
+            <div
+                key="frame-title"
+                className="block-frame-text shrink-0 opacity-70 cursor-pointer"
+                title="Double-click to rename (or right-click header)"
+                onDoubleClick={(e) => {
+                    e.stopPropagation();
+                    startBlockRename(blockId);
+                }}
+            >
                 {frameTitle}
             </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/block/blockframe-header.tsx` around lines 158 - 168, The badge
element rendered when useTermHeader && frameTitle is true shows a misleading
cursor via className "cursor-pointer" but has no left-click behavior; either
remove "cursor-pointer" from headerTextElems' div className or add a
left-click/double-click handler to match the affordance—e.g., wire
onDoubleClick={() => startBlockRename(blockId)} on the same div (referencing
startBlockRename and blockId) so the visual cue matches actual behavior; update
the JSX in blockframe-header.tsx accordingly.
frontend/app/view/preview/preview-model.tsx (1)

524-538: Use .map index instead of indexOf for terminal numbering.

termBlockIds.indexOf(bid) inside .map(bid => …) over the same array is O(n²) and redundant — the map callback already receives the index. Block IDs are unique so the result is identical. Also worth collapsing the two iterations (filter + map) into a single pass if you want, but the indexOf fix alone is the main win.

♻️ Proposed refactor
-        const termBlockIds = blockIds
-            .filter((bid) => {
-                if (bid === this.blockId) return false;
-                const block = WOS.getObjectValue<Block>(WOS.makeORef("block", bid), globalStore.get);
-                return block?.meta?.view === "term";
-            });
-
-        const terms = termBlockIds.map((bid) => {
-            const block = WOS.getObjectValue<Block>(WOS.makeORef("block", bid), globalStore.get);
-            const termIndex = termBlockIds.indexOf(bid) + 1;
-            return {
-                blockId: bid,
-                title: (block?.meta?.["frame:title"] as string) || `Terminal ${termIndex}`,
-            };
-        });
+        const terms: { blockId: string; title: string }[] = [];
+        for (const bid of blockIds) {
+            if (bid === this.blockId) continue;
+            const block = WOS.getObjectValue<Block>(WOS.makeORef("block", bid), globalStore.get);
+            if (block?.meta?.view !== "term") continue;
+            terms.push({
+                blockId: bid,
+                title: (block?.meta?.["frame:title"] as string) || `Terminal ${terms.length + 1}`,
+            });
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/preview/preview-model.tsx` around lines 524 - 538, The
current code uses termBlockIds.indexOf(bid) inside the map callback which makes
numbering O(n²); change the map to use its index parameter (e.g.,
termBlockIds.map((bid, i) => { const termIndex = i + 1; ... })) to compute
Terminal N, and optionally collapse the filter+map into a single reduce/map over
blockIds while still skipping this.blockId and checking block?.meta?.view ===
"term" (use WOS.getObjectValue(WOS.makeORef("block", bid), globalStore.get) as
before) so numbering is computed from the iteration index instead of indexOf.
🤖 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/block/blockframe-header.tsx`:
- Around line 111-119: The RPC failure is currently only logged but
stopBlockRename() is always called, closing the input and hiding the failed
save; change the flow so stopBlockRename() is only invoked after a successful
waveEnv.rpc.SetMetaCommand(TabRpcClient, ...) and on catch keep the input open
and surface an error notification to the user (use the app's existing
notification/toast utility or add a showToast/errorToast call) so they can
retry; ensure you reference the same parameters (oref via WOS.makeORef("block",
blockId) and meta "frame:title": val) and do not swallow the caught error — pass
its message into the toast.

In `@frontend/app/view/preview/preview-model.tsx`:
- Line 517: followTermMenuDataAtom is initialized with atom(null) which infers
PrimitiveAtom<null> and fails assignment to the declared PrimitiveAtom<{ pos;
terms; currentFollowId; bidir } | null>; change the initialization to use the
same explicit cast pattern as siblings by casting atom(null) to PrimitiveAtom<{
pos: any; terms: any; currentFollowId: any; bidir: any } | null> (i.e., replace
the current atom(null) expression for followTermMenuDataAtom with an explicit
type cast matching the declared atom type).

---

Nitpick comments:
In `@frontend/app/block/blockframe-header.tsx`:
- Around line 158-168: The badge element rendered when useTermHeader &&
frameTitle is true shows a misleading cursor via className "cursor-pointer" but
has no left-click behavior; either remove "cursor-pointer" from headerTextElems'
div className or add a left-click/double-click handler to match the
affordance—e.g., wire onDoubleClick={() => startBlockRename(blockId)} on the
same div (referencing startBlockRename and blockId) so the visual cue matches
actual behavior; update the JSX in blockframe-header.tsx accordingly.

In `@frontend/app/view/preview/preview-model.tsx`:
- Around line 524-538: The current code uses termBlockIds.indexOf(bid) inside
the map callback which makes numbering O(n²); change the map to use its index
parameter (e.g., termBlockIds.map((bid, i) => { const termIndex = i + 1; ... }))
to compute Terminal N, and optionally collapse the filter+map into a single
reduce/map over blockIds while still skipping this.blockId and checking
block?.meta?.view === "term" (use WOS.getObjectValue(WOS.makeORef("block", bid),
globalStore.get) as before) so numbering is computed from the iteration index
instead of indexOf.
🪄 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: CHILL

Plan: Pro

Run ID: 85e68495-48c3-45f1-8a71-6f19eb3e074d

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2e3a9 and a28a45e.

📒 Files selected for processing (5)
  • CLAUDE.md
  • frontend/app/block/blockframe-header.tsx
  • frontend/app/view/preview/preview-model.tsx
  • frontend/app/view/preview/preview.tsx
  • launch_wave_dev.command
✅ Files skipped from review due to trivial changes (2)
  • launch_wave_dev.command
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/app/view/preview/preview.tsx

Comment on lines +111 to +119
try {
await waveEnv.rpc.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", blockId),
meta: { "frame:title": val },
});
} catch (error) {
console.error("Failed to save block rename:", error);
}
stopBlockRename();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent RPC failure leaves user thinking rename succeeded.

On SetMetaCommand failure, the error is logged but stopBlockRename() still runs, so the input closes and the pre-existing frame:title stays — the user has no indication their edit was lost. Consider surfacing a toast/notification, or keeping the input open on failure so the user can retry.

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

In `@frontend/app/block/blockframe-header.tsx` around lines 111 - 119, The RPC
failure is currently only logged but stopBlockRename() is always called, closing
the input and hiding the failed save; change the flow so stopBlockRename() is
only invoked after a successful waveEnv.rpc.SetMetaCommand(TabRpcClient, ...)
and on catch keep the input open and surface an error notification to the user
(use the app's existing notification/toast utility or add a showToast/errorToast
call) so they can retry; ensure you reference the same parameters (oref via
WOS.makeORef("block", blockId) and meta "frame:title": val) and do not swallow
the caught error — pass its message into the toast.

this.followTermBidirAtom = atom<boolean>((get) => {
return (get(this.blockAtom)?.meta?.["preview:followterm:bidir"] as boolean) ?? false;
});
this.followTermMenuDataAtom = atom(null);
Copy link
Copy Markdown
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

🌐 Web query:

In TypeScript, is PrimitiveAtomfrom jotai'satom(null)assignable to a field typedPrimitiveAtom<SomeObject | null> under strict mode?

💡 Result:

No, PrimitiveAtom<null> from atom(null) is not assignable to PrimitiveAtom<SomeObject | null> under strict mode in TypeScript.

Citations:


🏁 Script executed:

cd frontend/app/view/preview && sed -n '515,520p' preview-model.tsx

Repository: wavetermdev/waveterm

Length of output: 281


🏁 Script executed:

cd frontend/app/view/preview && sed -n '189,193p; 197,202p; 441,445p; 467,472p' preview-model.tsx

Repository: wavetermdev/waveterm

Length of output: 1038


🏁 Script executed:

cd frontend/app/view/preview && sed -n '524,540p' preview-model.tsx

Repository: wavetermdev/waveterm

Length of output: 819


🏁 Script executed:

cd frontend/app/view/preview && sed -n '1,50p' preview-model.tsx | grep -A 5 "followTermMenuDataAtom"

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

cd frontend/app/view/preview && grep -n "followTermMenuDataAtom" preview-model.tsx | head -5

Repository: wavetermdev/waveterm

Length of output: 358


🏁 Script executed:

cd frontend/app/view/preview && sed -n '470,520p' preview-model.tsx | grep -B 50 "followTermMenuDataAtom" | head -60

Repository: wavetermdev/waveterm

Length of output: 2150


Add explicit type cast for followTermMenuDataAtom, consistent with sibling nullable atoms.

atom(null) infers as PrimitiveAtom<null>, which is not assignable to the declared PrimitiveAtom<{ pos; terms; currentFollowId; bidir } | null> (Jotai's atom type parameter is invariant). The rest of this class uses the explicit cast pattern (e.g. lines 199, 201, 203, 469); this one was missed and will trip strict TS compilation.

🔧 Proposed fix
-        this.followTermMenuDataAtom = atom(null);
+        this.followTermMenuDataAtom = atom(null) as PrimitiveAtom<{
+            pos: { x: number; y: number };
+            terms: { blockId: string; title: string }[];
+            currentFollowId: string | null;
+            bidir: boolean;
+        } | null>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/preview/preview-model.tsx` at line 517,
followTermMenuDataAtom is initialized with atom(null) which infers
PrimitiveAtom<null> and fails assignment to the declared PrimitiveAtom<{ pos;
terms; currentFollowId; bidir } | null>; change the initialization to use the
same explicit cast pattern as siblings by casting atom(null) to PrimitiveAtom<{
pos: any; terms: any; currentFollowId: any; bidir: any } | null> (i.e., replace
the current atom(null) expression for followTermMenuDataAtom with an explicit
type cast matching the declared atom type).

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