|
| 1 | +--- |
| 2 | +name: waveenv |
| 3 | +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. |
| 4 | +--- |
| 5 | + |
| 6 | +# WaveEnv Narrowing Skill |
| 7 | + |
| 8 | +## Purpose |
| 9 | + |
| 10 | +A WaveEnv narrowing creates a _named subset type_ of `WaveEnv` that: |
| 11 | + |
| 12 | +1. Documents exactly which parts of the environment a component tree actually uses. |
| 13 | +2. Forms a type contract so callers and tests know what to provide. |
| 14 | +3. Enables mocking in the preview/test server — you only need to implement what's listed. |
| 15 | + |
| 16 | +## When To Create One |
| 17 | + |
| 18 | +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. |
| 19 | + |
| 20 | +## Core Principle: Only Include What You Use |
| 21 | + |
| 22 | +**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`. |
| 23 | + |
| 24 | +## File Location |
| 25 | + |
| 26 | +- **Separate file** (preferred for shared/complex envs): name it `<feature>env.ts` next to the component, e.g. [`frontend/app/block/blockenv.ts`](frontend/app/block/blockenv.ts). |
| 27 | +- **Inline** (acceptable for small, single-file components): export the type directly from the component file, e.g. `WidgetsEnv` in [`frontend/app/workspace/widgets.tsx`](frontend/app/workspace/widgets.tsx:23). |
| 28 | + |
| 29 | +## Imports Required |
| 30 | + |
| 31 | +```ts |
| 32 | +import { |
| 33 | + BlockMetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom |
| 34 | + ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom |
| 35 | + SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom |
| 36 | + WaveEnv, |
| 37 | + WaveEnvSubset, |
| 38 | +} from "@/app/waveenv/waveenv"; |
| 39 | +``` |
| 40 | + |
| 41 | +## The Shape |
| 42 | + |
| 43 | +```ts |
| 44 | +export type MyEnv = WaveEnvSubset<{ |
| 45 | + // --- Simple WaveEnv properties --- |
| 46 | + // Copy the type verbatim from WaveEnv with WaveEnv["key"] syntax. |
| 47 | + isDev: WaveEnv["isDev"]; |
| 48 | + createBlock: WaveEnv["createBlock"]; |
| 49 | + showContextMenu: WaveEnv["showContextMenu"]; |
| 50 | + platform: WaveEnv["platform"]; |
| 51 | + |
| 52 | + // --- electron: list only the methods you call --- |
| 53 | + electron: { |
| 54 | + openExternal: WaveEnv["electron"]["openExternal"]; |
| 55 | + }; |
| 56 | + |
| 57 | + // --- rpc: list only the commands you call --- |
| 58 | + rpc: { |
| 59 | + ActivityCommand: WaveEnv["rpc"]["ActivityCommand"]; |
| 60 | + ConnEnsureCommand: WaveEnv["rpc"]["ConnEnsureCommand"]; |
| 61 | + }; |
| 62 | + |
| 63 | + // --- atoms: list only the atoms you read --- |
| 64 | + atoms: { |
| 65 | + modalOpen: WaveEnv["atoms"]["modalOpen"]; |
| 66 | + fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"]; |
| 67 | + }; |
| 68 | + |
| 69 | + // --- wos: always take the whole thing, no sub-typing needed --- |
| 70 | + wos: WaveEnv["wos"]; |
| 71 | + |
| 72 | + // --- key-parameterized atom factories: enumerate the keys you use --- |
| 73 | + getSettingsKeyAtom: SettingsKeyAtomFnType< |
| 74 | + | "app:focusfollowscursor" |
| 75 | + | "window:magnifiedblockopacity" |
| 76 | + >; |
| 77 | + getBlockMetaKeyAtom: BlockMetaKeyAtomFnType< |
| 78 | + | "view" |
| 79 | + | "frame:title" |
| 80 | + | "connection" |
| 81 | + >; |
| 82 | + getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">; |
| 83 | + |
| 84 | + // --- other atom helpers: copy verbatim --- |
| 85 | + getConnStatusAtom: WaveEnv["getConnStatusAtom"]; |
| 86 | + getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"]; |
| 87 | +}>; |
| 88 | +``` |
| 89 | + |
| 90 | +### Rules for Each Section |
| 91 | + |
| 92 | +| Section | Pattern | Notes | |
| 93 | +|---|---|---| |
| 94 | +| `electron` | `electron: { method: WaveEnv["electron"]["method"]; }` | List every method called; omit the rest. | |
| 95 | +| `rpc` | `rpc: { Cmd: WaveEnv["rpc"]["Cmd"]; }` | List every RPC command called; omit the rest. | |
| 96 | +| `atoms` | `atoms: { atom: WaveEnv["atoms"]["atom"]; }` | List every atom read; omit the rest. | |
| 97 | +| `wos` | `wos: WaveEnv["wos"]` | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. | |
| 98 | +| `getSettingsKeyAtom` | `SettingsKeyAtomFnType<"key1" \| "key2">` | Union all settings keys accessed. | |
| 99 | +| `getBlockMetaKeyAtom` | `BlockMetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. | |
| 100 | +| `getConnConfigKeyAtom` | `ConnConfigKeyAtomFnType<"key1">` | Union all conn config keys accessed. | |
| 101 | +| All other `WaveEnv` fields | `WaveEnv["fieldName"]` | Copy type verbatim. | |
| 102 | + |
| 103 | +## Using the Narrowed Type in Components |
| 104 | + |
| 105 | +```ts |
| 106 | +import { useWaveEnv } from "@/app/waveenv/waveenv"; |
| 107 | +import { MyEnv } from "./myenv"; |
| 108 | + |
| 109 | +const MyComponent = memo(() => { |
| 110 | + const env = useWaveEnv<MyEnv>(); |
| 111 | + // TypeScript now enforces you only access what's in MyEnv. |
| 112 | + const val = useAtomValue(env.getSettingsKeyAtom("app:focusfollowscursor")); |
| 113 | + ... |
| 114 | +}); |
| 115 | +``` |
| 116 | + |
| 117 | +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. |
| 118 | + |
| 119 | +## Real Examples |
| 120 | + |
| 121 | +- [`BlockEnv`](frontend/app/block/blockenv.ts:12) — complex narrowing with all section types, in a separate file. |
| 122 | +- [`WidgetsEnv`](frontend/app/workspace/widgets.tsx:23) — smaller narrowing defined inline in the component file. |
0 commit comments