Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.
日本語の概要は準備中です。原文の説明を表示しています。
Guide for implementing a new view type in Wave Terminal. Use when creating a new view component, implementing the ViewModel interface, registering a new view type in BlockRegistry, or adding a new content type to display within blocks.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
This guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.
Wave Terminal uses a Model-View architecture where:
The separation between model and component ensures:
Every view must implement the ViewModel interface defined in frontend/types/custom.d.ts:
interface ViewModel {
// Required: The type identifier for this view (e.g., "term", "web", "preview")
viewType: string;
// Required: The React component that renders this view
viewComponent: ViewComponent<ViewModel>;
// Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)
viewIcon?: jotai.Atom<string | IconButtonDecl>;
// Optional: Display name shown in block header (e.g., "Terminal", "Web", "Preview")
viewName?: jotai.Atom<string>;
// Optional: Additional header elements (text, buttons, inputs) shown after the name
viewText?: jotai.Atom<string | HeaderElem[]>;
// Optional: Icon button shown before the view name in header
preIconButton?: jotai.Atom<IconButtonDecl>;
// Optional: Icon buttons shown at the end of the header (before settings/close)
endIconButtons?: jotai.Atom<IconButtonDecl[]>;
// Optional: Custom background styling for the block
blockBg?: jotai.Atom<MetaType>;
// Optional: If true, completely hides the block header
noHeader?: jotai.Atom<boolean>;
// Optional: If true, shows connection picker in header for remote connections
manageConnection?: jotai.Atom<boolean>;
// Optional: If true, filters out 'nowsh' connections from connection picker
filterOutNowsh?: jotai.Atom<boolean>;
// Optional: If true, removes default padding from content area
noPadding?: jotai.Atom<boolean>;
// Optional: Atoms for managing in-block search functionality
searchAtoms?: SearchAtoms;
// Optional: Returns whether this is a basic terminal (for multi-input feature)
isBasicTerm?: (getFn: jotai.Getter) => boolean;
// Optional: Returns context menu items for the settings dropdown
getSettingsMenuItems?: () => ContextMenuItem[];
// Optional: Focuses the view when called, returns true if successful
giveFocus?: () => boolean;
// Optional: Handles keyboard events, returns true if handled
keyDownHandler?: (e: WaveKeyboardEvent) => boolean;
// Optional: Cleanup when block is closed
dispose?: () => void;
}
Atoms: All UI-related properties must be Jotai atoms. This enables:
globalStore.get()/globalStore.set()ViewComponent: The React component receives these props:
type ViewComponentProps<T extends ViewModel> = {
blockId: string; // Unique ID for this block
blockRef: React.RefObject<HTMLDivElement>; // Ref to block container
contentRef: React.RefObject<HTMLDivElement>; // Ref to content area
model: T; // Your ViewModel instance
};
Create a new file for your view model (e.g., frontend/app/view/myview/myview-model.ts):
import { BlockNodeModel } from "@/app/block/blocktypes";
import { globalStore } from "@/app/store/jotaiStore";
import { WOS, useBlockAtom } from "@/store/global";
import * as jotai from "jotai";
import { MyView } from "./myview";
export class MyViewModel implements ViewModel {
viewType: string;
blockId: string;
nodeModel: BlockNodeModel;
blockAtom: jotai.Atom<Block>;
// Define your atoms (simple field initializers)
viewIcon = jotai.atom<string>("circle");
viewName = jotai.atom<string>("My View");
noPadding = jotai.atom<boolean>(true);
// Derived atom (created in constructor)
viewText!: jotai.Atom<HeaderElem[]>;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "myview";
this.blockId = blockId;
this.nodeModel = nodeModel;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
// Create derived atoms that depend on block data or other atoms
this.viewText = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const rtn: HeaderElem[] = [];
// Add header buttons/text based on state
rtn.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Refresh",
click: () => this.refresh(),
});
return rtn;
});
}
get viewComponent(): ViewComponent {
return MyView;
}
refresh() {
// Update state using globalStore
// Never use React hooks in model methods
console.log("refreshing...");
}
giveFocus(): boolean {
// Focus your view component
return true;
}
dispose() {
// Cleanup resources (unsubscribe from events, etc.)
}
}
Create your React component (e.g., frontend/app/view/myview/myview.tsx):
import { ViewComponentProps } from "@/app/block/blocktypes";
import { MyViewModel } from "./myview-model";
import { useAtomValue } from "jotai";
import "./myview.scss";
export const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({
blockId,
model,
contentRef
}) => {
// Use atoms from the model (these are React hooks - call at top level!)
const blockData = useAtomValue(model.blockAtom);
return (
<div className="myview-container" ref={contentRef}>
<div>Block ID: {blockId}</div>
<div>View: {model.viewType}</div>
{/* Your view content here */}
</div>
);
};
Add your view to the BlockRegistry in frontend/app/block/block.tsx:
const BlockRegistry: Map<string, ViewModelClass> = new Map();
BlockRegistry.set("term", TermViewModel);
BlockRegistry.set("preview", PreviewModel);
BlockRegistry.set("web", WebViewModel);
// ... existing registrations ...
BlockRegistry.set("myview", MyViewModel); // Add your view here
The registry key (e.g., "myview") becomes the view type used in block metadata.
Users can create blocks with your view type:
wsh view myviewmeta.view field set to "myview"term-model.ts)The terminal view demonstrates:
manageConnection atomKey features:
this.manageConnection = jotai.atom((get) => {
const termMode = get(this.termMode);
if (termMode == "vdom") return false;
return true; // Show connection picker for regular terminal mode
});
this.endIconButtons = jotai.atom((get) => {
const shellProcStatus = get(this.shellProcStatus);
const buttons: IconButtonDecl[] = [];
if (shellProcStatus == "running") {
buttons.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Restart Shell",
click: this.forceRestartController.bind(this),
});
}
return buttons;
});
webview.tsx)The web view shows:
noPadding for full-bleed contentKey features:
this.viewText = jotai.atom((get) => {
const url = get(this.url);
const rtn: HeaderElem[] = [];
// Navigation buttons
rtn.push({
elemtype: "iconbutton",
icon: "chevron-left",
click: this.handleBack.bind(this),
disabled: this.shouldDisableBackButton(),
});
// URL input with nested controls
rtn.push({
elemtype: "div",
className: "block-frame-div-url",
children: [
{
elemtype: "input",
value: url,
onChange: this.handleUrlChange.bind(this),
onKeyDown: this.handleKeyDown.bind(this),
},
{
elemtype: "iconbutton",
icon: "rotate-right",
click: this.handleRefresh.bind(this),
},
],
});
return rtn;
});
HeaderElem)The viewText atom can return an array of these element types:
// Icon button
{
elemtype: "iconbutton",
icon: "refresh",
title: "Tooltip text",
click: () => { /* handler */ },
disabled?: boolean,
iconColor?: string,
iconSpin?: boolean,
noAction?: boolean, // Shows icon but no click action
}
// Text element
{
elemtype: "text",
text: "Display text",
className?: string,
noGrow?: boolean,
ref?: React.RefObject<HTMLElement>,
onClick?: (e: React.MouseEvent) => void,
}
// Text button
{
elemtype: "textbutton",
text: "Button text",
className?: string,
title: "Tooltip",
onClick: (e: React.MouseEvent) => void,
}
// Input field
{
elemtype: "input",
value: string,
className?: string,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,
ref?: React.RefObject<HTMLInputElement>,
}
// Container with children
{
elemtype: "div",
className?: string,
children: HeaderElem[],
onMouseOver?: (e: React.MouseEvent) => void,
onMouseOut?: (e: React.MouseEvent) => void,
}
// Menu button (dropdown)
{
elemtype: "menubutton",
// ... MenuButtonProps ...
}
Follow these rules for Jotai atoms in models:
Simple atoms as field initializers:
viewIcon = jotai.atom<string>("circle");
noPadding = jotai.atom<boolean>(true);
Derived atoms in constructor (need dependency on other atoms):
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewText = jotai.atom((get) => {
const blockData = get(this.blockAtom);
return [/* computed based on blockData */];
});
}
Models never use React hooks - Use globalStore.get()/set():
refresh() {
const currentData = globalStore.get(this.blockAtom);
globalStore.set(this.dataAtom, newData);
}
Components use hooks for atoms:
const data = useAtomValue(model.dataAtom);
const [value, setValue] = useAtom(model.valueAtom);
useBlockAtom() helper for block-scoped atoms that persistglobalStore for imperative access outside React componentswaveEventSubscribe().scss file for your view stylesnoPadding: atom(true) for full-bleed contentblockBg atom to customize block backgroundImplement giveFocus() to focus your view when:
true if successfully focused, false otherwiseImplement keyDownHandler(e: WaveKeyboardEvent) for:
true if event was handled (prevents propagation)keyutil.checkKeyPressed(waveEvent, "Cmd:K") for shortcut checksImplement dispose() to:
For views that need remote connections:
this.manageConnection = jotai.atom(true); // Show connection picker
this.filterOutNowsh = jotai.atom(true); // Hide nowsh connections
Access connection status:
const connStatus = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const connName = blockData?.meta?.connection;
return get(getConnStatusAtom(connName));
});
import { getBlockMetaKeyAtom } from "@/store/global";
// In constructor:
this.someFlag = getBlockMetaKeyAtom(blockId, "myview:flag");
// In component:
const flag = useAtomValue(model.someFlag);
Wave has a hierarchical config system (global → connection → block):
import { getOverrideConfigAtom } from "@/store/global";
this.settingAtom = jotai.atom((get) => {
// Checks block meta, then connection config, then global settings
return get(getOverrideConfigAtom(this.blockId, "myview:setting")) ?? defaultValue;
});
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { WOS } from "@/store/global";
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "myview:key": value },
});
frontend/app/block/blockframe-header.tsx - Block header renderingfrontend/app/view/term/term-model.ts - Complex view examplefrontend/app/view/webview/webview.tsx - Navigation UI examplefrontend/types/custom.d.ts - Type definitionsまだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.
日本語の概要は準備中です。原文の説明を表示しています。
Guide for adding new RPC calls to Wave Terminal. Use when implementing new RPC commands, adding server-client communication methods, or extending the RPC interface with new functionality.
日本語の概要は準備中です。原文の説明を表示しています。
Guide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.
日本語の概要は準備中です。原文の説明を表示しています。
Guide for creating and displaying context menus in Wave Terminal. Use when implementing right-click menus, adding context menu items, creating submenus, or handling menu interactions with checkboxes and separators.
日本語の概要は準備中です。原文の説明を表示しています。
Guide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.
日本語の概要は準備中です。原文の説明を表示しています。
Search and order products online. Compares prices across Polish marketplaces (Allegro, Ceneo, Google Shopping), presents options in a comparison table, and assists with checkout. Handles the full flow: priority selection, research, comparison, cart, and checkout handoff. Triggers on: "order", "buy", "find product", "compare prices", "cheapest", "zamow", "kup", "znajdz produkt", "porownaj ceny", "najtaniej".
日本語の概要は準備中です。原文の説明を表示しています。