waveenv
$
npx mdskill add wavetermdev/waveterm/waveenvDefine precise environment subsets for testing and documentation.
- Creates named environment types that document exact component dependencies.
- Integrates with Wave Terminal to generate narrowings for mock servers.
- Selects only fields and methods the component tree actually accesses.
- Delivers type contracts that guide callers and simplify test implementations.
SKILL.md
.github/skills/waveenvView on GitHub ↗
---
name: waveenv
description: Guide for creating WaveEnv narrowings in Wave Terminal. Use when writing a named subset type of WaveEnv for a component tree, documenting environmental dependencies, or enabling mock environments for preview/test server usage.
---
# WaveEnv Narrowing Skill
## Purpose
A WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that:
1. Documents exactly which parts of the environment a component tree actually uses.
2. Forms a type contract so callers and tests know what to provide.
3. Enables mocking in the preview/test server — you only need to implement what's listed.
## When To Create One
Create a narrowing whenever you are writing a component (or group of components) that you want to test in the preview server, or when you want to make the environmental dependencies of a component tree explicit.
## Core Principle: Only Include What You Use
**Only list the fields, methods, atoms, and keys that the component tree actually accesses.** If you don't call `wos`, don't include `wos`. If you only call one RPC command, only list that one command. The narrowing is a precise dependency declaration — not a copy of `WaveEnv`.
## File Location
- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. `frontend/app/block/blockenv.ts`.
- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in `frontend/app/workspace/widgets.tsx`.
## Imports Required
```ts
import {
MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom
ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom
SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom
WaveEnv,
WaveEnvSubset,
} from "@/app/waveenv/waveenv";
```
## The Shape
```ts
export type MyEnv = WaveEnvSubset<{
// --- Simple WaveEnv properties ---
// Copy the type verbatim from WaveEnv with WaveEnv["key"] syntax.
isDev: WaveEnv["isDev"];
createBlock: WaveEnv["createBlock"];
showContextMenu: WaveEnv["showContextMenu"];
platform: WaveEnv["platform"];
// --- electron: list only the methods you call ---
electron: {
openExternal: WaveEnv["electron"]["openExternal"];
};
// --- rpc: list only the commands you call ---
rpc: {
ActivityCommand: WaveEnv["rpc"]["ActivityCommand"];
ConnEnsureCommand: WaveEnv["rpc"]["ConnEnsureCommand"];
};
// --- atoms: list only the atoms you read ---
atoms: {
modalOpen: WaveEnv["atoms"]["modalOpen"];
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
};
// --- wos: always take the whole thing, no sub-typing needed ---
wos: WaveEnv["wos"];
// --- services: list only the services you call; no method-level narrowing ---
services: {
block: WaveEnv["services"]["block"];
workspace: WaveEnv["services"]["workspace"];
};
// --- key-parameterized atom factories: enumerate the keys you use ---
getSettingsKeyAtom: SettingsKeyAtomFnType<"app:focusfollowscursor" | "window:magnifiedblockopacity">;
getBlockMetaKeyAtom: MetaKeyAtomFnType<"view" | "frame:title" | "connection">;
getTabMetaKeyAtom: MetaKeyAtomFnType<"tabid" | "name">;
getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">;
// --- other atom helpers: copy verbatim ---
getConnStatusAtom: WaveEnv["getConnStatusAtom"];
getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"];
getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"];
}>;
```
### Automatically Included Fields
Every `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:
- `isMock: boolean`
- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`
- `mockModels?: Map<any, any>`
### Rules for Each Section
| Section | Pattern | Notes |
| -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `electron` | `electron: { method: WaveEnv["electron"]["method"]; }` | List every method called; omit the rest. |
| `rpc` | `rpc: { Cmd: WaveEnv["rpc"]["Cmd"]; }` | List every RPC command called; omit the rest. |
| `atoms` | `atoms: { atom: WaveEnv["atoms"]["atom"]; }` | List every atom read; omit the rest. |
| `wos` | `wos: WaveEnv["wos"]` | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |
| `services` | `services: { svc: WaveEnv["services"]["svc"]; }` | List each service used; take the whole service object (no method-level narrowing). |
| `getSettingsKeyAtom` | `SettingsKeyAtomFnType<"key1" \| "key2">` | Union all settings keys accessed. |
| `getBlockMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. |
| `getTabMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all tab meta keys accessed. |
| `getConnConfigKeyAtom` | `ConnConfigKeyAtomFnType<"key1">` | Union all conn config keys accessed. |
| All other `WaveEnv` fields | `WaveEnv["fieldName"]` | Copy type verbatim. |
## Using the Narrowed Type in Components
```ts
import { useWaveEnv } from "@/app/waveenv/waveenv";
import { MyEnv } from "./myenv";
const MyComponent = memo(() => {
const env = useWaveEnv<MyEnv>();
// TypeScript now enforces you only access what's in MyEnv.
const val = useAtomValue(env.getSettingsKeyAtom("app:focusfollowscursor"));
...
});
```
The generic parameter on `useWaveEnv<MyEnv>()` casts the context to your narrowed type. The real production `WaveEnv` satisfies every narrowing; mock envs only need to implement the listed subset.
## Real Examples
- `BlockEnv` in `frontend/app/block/blockenv.ts` — complex narrowing with all section types, in a separate file.
- `WidgetsEnv` in `frontend/app/workspace/widgets.tsx` — smaller narrowing defined inline in the component file.
More from wavetermdev/waveterm
- add-configGuide 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.
- add-rpcGuide 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.
- add-wshcmdGuide for adding new wsh commands to Wave Terminal. Use when implementing new CLI commands, adding command-line functionality, or extending the wsh command interface.
- context-menuGuide 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.
- create-viewGuide 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.
- electron-apiGuide for adding new Electron APIs to Wave Terminal. Use when implementing new frontend-to-electron communications via preload/IPC.
- wps-eventsGuide for working with Wave Terminal's WPS (Wave PubSub) event system. Use when implementing new event types, publishing events, subscribing to events, or adding asynchronous communication between components.