From afaebe531e8df4a51dcafd7f341dc0f158fe435c Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 10:54:41 -0800 Subject: [PATCH 01/10] link color handling --- frontend/app/element/markdown.tsx | 4 ++-- frontend/app/element/streamdown.tsx | 2 +- frontend/app/onboarding/onboarding.tsx | 20 ++++++++++++++++---- tsunami/frontend/src/element/markdown.tsx | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/app/element/markdown.tsx b/frontend/app/element/markdown.tsx index 97ee3f9d68..5ecc252876 100644 --- a/frontend/app/element/markdown.tsx +++ b/frontend/app/element/markdown.tsx @@ -54,7 +54,7 @@ const Link = ({ } }; return ( - + {props.children} ); @@ -391,7 +391,7 @@ const Markdown = ({ return ( setFocusedHeading(item.href)} > diff --git a/frontend/app/element/streamdown.tsx b/frontend/app/element/streamdown.tsx index 6ec12914a4..6eddf976ae 100644 --- a/frontend/app/element/streamdown.tsx +++ b/frontend/app/element/streamdown.tsx @@ -291,7 +291,7 @@ export const WaveStreamdown = ({ }, summary: () => null, // Don't render summary separately a: (props: React.AnchorHTMLAttributes) => ( - + ), strong: (props: React.HTMLAttributes) => ( diff --git a/frontend/app/onboarding/onboarding.tsx b/frontend/app/onboarding/onboarding.tsx index b2accee961..bf1920955f 100644 --- a/frontend/app/onboarding/onboarding.tsx +++ b/frontend/app/onboarding/onboarding.tsx @@ -74,7 +74,8 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => { @@ -87,7 +88,8 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => { Github (wavetermdev/waveterm) @@ -96,7 +98,12 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => {
- +
@@ -106,7 +113,12 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => { Get help, submit feature requests, report bugs, or just chat with fellow terminal enthusiasts.
- + Join the Wave Discord Channel
diff --git a/tsunami/frontend/src/element/markdown.tsx b/tsunami/frontend/src/element/markdown.tsx index 632964f86f..71df1ebdbe 100644 --- a/tsunami/frontend/src/element/markdown.tsx +++ b/tsunami/frontend/src/element/markdown.tsx @@ -18,7 +18,7 @@ const markdownComponents: Partial = { h6: ({ children }) =>
{children}
, p: ({ children }) =>

{children}

, a: ({ href, children }) => ( - + {children} ), From ac19b332e50fdd40148b700716f0f270ba6b8df9 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:03:54 -0800 Subject: [PATCH 02/10] remove plain-link class convert to tailwind --- frontend/app/app.scss | 8 -------- frontend/app/onboarding/onboarding.tsx | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/app/app.scss b/frontend/app/app.scss index 9ce3bc9f5d..3dccb41a1a 100644 --- a/frontend/app/app.scss +++ b/frontend/app/app.scss @@ -33,14 +33,6 @@ body { background-color: transparent; } -a.plain-link { - color: var(--secondary-text-color); - - &:hover { - text-decoration: underline; - } -} - *::-webkit-scrollbar { width: 4px; height: 4px; diff --git a/frontend/app/onboarding/onboarding.tsx b/frontend/app/onboarding/onboarding.tsx index bf1920955f..fc4c566ae4 100644 --- a/frontend/app/onboarding/onboarding.tsx +++ b/frontend/app/onboarding/onboarding.tsx @@ -133,7 +133,7 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => { Anonymous usage data helps us improve features you use.
Date: Tue, 24 Feb 2026 11:10:43 -0800 Subject: [PATCH 03/10] remove unused style.scss --- public/style.scss | 61 ----------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 public/style.scss diff --git a/public/style.scss b/public/style.scss deleted file mode 100644 index 2628f4fdb3..0000000000 --- a/public/style.scss +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -@import "./reset.scss"; -@import "./theme.scss"; - -body { - display: flex; - flex-direction: row; - width: 100vw; - height: 100vh; - background-color: var(--main-bg-color); - color: var(--main-text-color); - font: var(--base-font); - overflow: hidden; -} - -*::-webkit-scrollbar { - width: 4px; - height: 4px; -} - -*::-webkit-scrollbar-track { - background-color: var(--scrollbar-background-color) !important; -} - -*::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-thumb-color) !important; - border-radius: 4px; - margin: 0 1px 0 1px; -} - -*::-webkit-scrollbar-thumb:hover { - background-color: var(--scrollbar-thumb-hover-color) !important; -} - -.flex-spacer { - flex-grow: 1; -} - -.text-fixed { - font: var(--fixed-font); -} - -#main, -.mainapp { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; -} - -.titlebar { - height: 35px; - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; -} - -.error-boundary { - color: var(--error-color); -} From 40845e9ea27e1a324c90a9db67e649dd1184bdd0 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:11:01 -0800 Subject: [PATCH 04/10] move main styling to tailwind --- frontend/app/app.scss | 7 ------- frontend/preview/index.html | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/frontend/app/app.scss b/frontend/app/app.scss index 3dccb41a1a..41429a86c2 100644 --- a/frontend/app/app.scss +++ b/frontend/app/app.scss @@ -22,13 +22,6 @@ body { transform: translateZ(0); } -#main { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; -} - .is-transparent { background-color: transparent; } diff --git a/frontend/preview/index.html b/frontend/preview/index.html index 62f228247d..cf9e957e37 100644 --- a/frontend/preview/index.html +++ b/frontend/preview/index.html @@ -13,7 +13,7 @@ -
+
From 08880e10d983aae20a4a78b85e7eab93c3f1e896 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:11:13 -0800 Subject: [PATCH 05/10] 2nd index.html for main styling --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 2b0213e57b..ddda20d5f2 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,6 @@ -
+
From ea0ec57c92ca60a66daf02fe0e0ecbca788a79f1 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:50:51 -0800 Subject: [PATCH 06/10] move window type into its own file. does not need to be an atom (it is not mutable) --- frontend/app/aipanel/aipanel.tsx | 4 ++-- frontend/app/aipanel/waveai-model.tsx | 7 +++---- frontend/app/store/global-atoms.ts | 9 +++------ frontend/app/store/keymodel.ts | 5 +++-- frontend/app/store/windowtype.ts | 23 +++++++++++++++++++++++ frontend/types/custom.d.ts | 3 +-- 6 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 frontend/app/store/windowtype.ts diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 83e7648f02..46780455c2 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -6,6 +6,7 @@ import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils"; import { ErrorBoundary } from "@/app/element/errorboundary"; import { atoms, getSettingsKeyAtom } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; +import { isBuilderWindow } from "@/app/store/windowtype"; import { maybeUseTabModel } from "@/app/store/tab-model"; import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { isMacOS, isWindows } from "@/util/platformutil"; @@ -269,14 +270,13 @@ const AIPanelComponentInner = memo(() => { api: model.getUseChatEndpointUrl(), prepareSendMessagesRequest: (opts) => { const msg = model.getAndClearMessage(); - const windowType = globalStore.get(atoms.waveWindowType); const body: any = { msg, chatid: globalStore.get(model.chatId), widgetaccess: globalStore.get(model.widgetAccessAtom), aimode: globalStore.get(model.currentAIMode), }; - if (windowType === "builder") { + if (isBuilderWindow()) { body.builderid = globalStore.get(atoms.builderId); body.builderappid = globalStore.get(atoms.builderAppId); } else { diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index a13bbf4f1c..9af1d88508 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -10,6 +10,7 @@ import { import { FocusManager } from "@/app/store/focusManager"; import { atoms, createBlock, getOrefMetaKeyAtom, getSettingsKeyAtom } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; +import { isBuilderWindow } from "@/app/store/windowtype"; import * as WOS from "@/app/store/wos"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; @@ -150,17 +151,15 @@ export class WaveAIModel { static getInstance(): WaveAIModel { if (!WaveAIModel.instance) { - const windowType = globalStore.get(atoms.waveWindowType); let orefContext: ORef; - const inBuilder = windowType === "builder"; - if (inBuilder) { + if (isBuilderWindow()) { const builderId = globalStore.get(atoms.builderId); orefContext = WOS.makeORef("builder", builderId); } else { const tabId = globalStore.get(atoms.staticTabId); orefContext = WOS.makeORef("tab", tabId); } - WaveAIModel.instance = new WaveAIModel(orefContext, inBuilder); + WaveAIModel.instance = new WaveAIModel(orefContext, isBuilderWindow()); (window as any).WaveAIModel = WaveAIModel.instance; } return WaveAIModel.instance; diff --git a/frontend/app/store/global-atoms.ts b/frontend/app/store/global-atoms.ts index 91f14ce3a1..e719483c05 100644 --- a/frontend/app/store/global-atoms.ts +++ b/frontend/app/store/global-atoms.ts @@ -1,8 +1,9 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { atom, Atom, PrimitiveAtom } from "jotai"; import { globalStore } from "./jotaiStore"; +import { setWaveWindowType } from "./windowtype"; import * as WOS from "./wos"; let atoms!: GlobalAtomsType; @@ -15,10 +16,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { const windowIdAtom = atom(initOpts.windowId) as PrimitiveAtom; const builderIdAtom = atom(initOpts.builderId) as PrimitiveAtom; const builderAppIdAtom = atom(null) as PrimitiveAtom; - const waveWindowTypeAtom = atom((get) => { - const builderId = get(builderIdAtom); - return builderId != null ? "builder" : "tab"; - }) as Atom<"tab" | "builder">; + setWaveWindowType(initOpts.builderId != null ? "builder" : "tab"); const uiContextAtom = atom((get) => { const uiContext: UIContext = { windowid: initOpts.windowId, @@ -128,7 +126,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { // initialized in wave.ts (will not be null inside of application) builderId: builderIdAtom, builderAppId: builderAppIdAtom, - waveWindowType: waveWindowTypeAtom, uiContext: uiContextAtom, workspace: workspaceAtom, fullConfigAtom, diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index ab409b4467..94ed8bbdcc 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -29,6 +29,7 @@ import { CHORD_TIMEOUT } from "@/util/sharedconst"; import { fireAndForget } from "@/util/util"; import * as jotai from "jotai"; import { modalsModel } from "./modalmodel"; +import { isBuilderWindow, isTabWindow } from "./windowtype"; type KeyHandler = (event: WaveKeyboardEvent) => boolean; @@ -322,7 +323,7 @@ function globalRefocusWithTimeout(timeoutVal: number) { } function globalRefocus() { - if (globalStore.get(atoms.waveWindowType) == "builder") { + if (isBuilderWindow()) { return; } @@ -449,7 +450,7 @@ function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean { return true; } } - if (globalStore.get(atoms.waveWindowType) == "tab") { + if (isTabWindow()) { const layoutModel = getLayoutModelForStaticTab(); const focusedNode = globalStore.get(layoutModel.focusedNode); const blockId = focusedNode?.data?.blockId; diff --git a/frontend/app/store/windowtype.ts b/frontend/app/store/windowtype.ts new file mode 100644 index 0000000000..c43f44f4fa --- /dev/null +++ b/frontend/app/store/windowtype.ts @@ -0,0 +1,23 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// waveWindowType is set once at startup and never changes. +let waveWindowType: "tab" | "builder" = "tab"; + +function getWaveWindowType(): "tab" | "builder" { + return waveWindowType; +} + +function isBuilderWindow(): boolean { + return waveWindowType === "builder"; +} + +function isTabWindow(): boolean { + return waveWindowType === "tab"; +} + +function setWaveWindowType(windowType: "tab" | "builder") { + waveWindowType = windowType; +} + +export { getWaveWindowType, isBuilderWindow, isTabWindow, setWaveWindowType }; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 7fccbd57db..0febeea6a1 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -7,9 +7,8 @@ import type * as rxjs from "rxjs"; declare global { type GlobalAtomsType = { - builderId: jotai.PrimitiveAtom; // readonly (for builder mode) + builderId: jotai.Atom; // readonly (for builder mode) builderAppId: jotai.PrimitiveAtom; // app being edited in builder mode - waveWindowType: jotai.Atom<"tab" | "builder">; // derived from builderId uiContext: jotai.Atom; // driven from windowId, tabId workspace: jotai.Atom; // driven from WOS fullConfigAtom: jotai.PrimitiveAtom; // driven from WOS, settings -- updated via WebSocket From 84fda8168e701619ad0e431b5647540b6838fb5a Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:51:18 -0800 Subject: [PATCH 07/10] move the rules file under .kilocode ... the .roo rules were not getting picked up --- .kilocode/rules/rules.md | 198 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 .kilocode/rules/rules.md diff --git a/.kilocode/rules/rules.md b/.kilocode/rules/rules.md new file mode 100644 index 0000000000..6518187a1c --- /dev/null +++ b/.kilocode/rules/rules.md @@ -0,0 +1,198 @@ +Wave Terminal is a modern terminal which provides graphical blocks, dynamic layout, workspaces, and SSH connection management. It is cross platform and built on electron. + +### Project Structure + +It has a TypeScript/React frontend and a Go backend. They talk together over `wshrpc` a custom RPC protocol that is implemented over websocket (and domain sockets). + +### Coding Guidelines + +- **Go Conventions**: + - Don't use custom enum types in Go. Instead, use string constants (e.g., `const StatusRunning = "running"` rather than creating a custom type like `type Status string`). + - Use string constants for status values, packet types, and other string-based enumerations. + - in Go code, prefer using Printf() vs Println() + - use "Make" as opposed to "New" for struct initialization func names + - in general const decls go at the top fo the file (before types and functions) + - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors. +- **Synchronization**: + - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible + - Avoid inline lock/unlock pairs - instead create helper functions that use the defer pattern + - When accessing shared data structures (maps, slices, etc.), ensure proper locking + - Example: Instead of `gc.lock.Lock(); gc.map[key]++; gc.lock.Unlock()`, create a helper function like `getNextValue(key string) int { gc.lock.Lock(); defer gc.lock.Unlock(); gc.map[key]++; return gc.map[key] }` +- **TypeScript Imports**: + - Use `@/...` for imports from different parts of the project (configured in `tsconfig.json` as `"@/*": ["frontend/*"]`). + - Prefer relative imports (`"./name"`) only within the same directory. + - Use named exports exclusively; avoid default exports. It's acceptable to export functions directly (e.g., React Components). + - Our indent is 4 spaces +- **JSON Field Naming**: All fields must be lowercase, without underscores. +- **TypeScript Conventions** + - **Type Handling**: + - In TypeScript we have strict null checks off, so no need to add "| null" to all the types. + - In TypeScript for Jotai atoms, if we want to write, we need to type the atom as a PrimitiveAtom + - Jotai has a bug with strict null checks off where if you create a null atom, e.g. atom(null) it does not "type" correctly. That's no issue, just cast it to the proper PrimitiveAtom type (no "| null") and it will work fine. + - Generally never use "=== undefined" or "!== undefined". This is bad style. Just use a "== null" or "!= null" unless it is a very specific case where we need to distinguish undefined from null. + - **Coding Style**: + - Use all lowercase filenames (except where case is actually important like Taskfile.yml) + - Import the "cn" function from "@/util/util" to do classname / clsx class merge (it uses twMerge underneath) + - For element variants use class-variance-authority + - Do NOT create private fields in classes (they are impossible to inspect) + - Use PascalCase for global consts at the top of files + - **Component Practices**: + - Make sure to add cursor-pointer to buttons/links and clickable items + - NEVER use cursor-help (it looks terrible) + - useAtom() and useAtomValue() are react HOOKS, so they must be called at the component level not inline in JSX + - If you use React.memo(), make sure to add a displayName for the component + - Other + - never use atob() or btoa() (not UTF-8 safe). use functions in frontend/util/util.ts for base64 decoding and encoding +- In general, when writing functions, we prefer _early returns_ rather than putting the majority of a function inside of an if block. + +### Styling + +- We use **Tailwind v4** to style. Custom stuff is defined in frontend/tailwindsetup.css +- _never_ use cursor-help, or cursor-not-allowed (it looks terrible) +- We have custom CSS setup as well, so it is a hybrid system. For new code we prefer tailwind, and are working to migrate code to all use tailwind. +- For accent buttons, use "bg-accent/80 text-primary rounded hover:bg-accent transition-colors cursor-pointer" (if you do "bg-accent hover:bg-accent/80" it looks weird as on hover the button gets darker instead of lighter) + +### RPC System + +To define a new RPC call, add the new definition to `pkg/wshrpc/wshrpctypes.go` including any input/output data that is required. After modifying wshrpctypes.go run `task generate` to generate the client APIs. + +For normal "server" RPCs (where a frontend client is calling the main server) you should implement the RPC call in `pkg/wshrpc/wshserver.go`. + +### Electron API + +From within the FE to get the electron API (e.g. the preload functions): + +``` +import { getApi } from "@/store/global"; + +getApi().getIsDev() +``` + +The full API is defined in custom.d.ts as type ElectronApi. + +### Code Generation + +- **TypeScript Types**: TypeScript types are automatically generated from Go types. After modifying Go types in `pkg/wshrpc/wshrpctypes.go`, run `task generate` to update the TypeScript type definitions in `frontend/types/gotypes.d.ts`. +- **Manual Edits**: Do not manually edit generated files like `frontend/types/gotypes.d.ts` or `frontend/app/store/wshclientapi.ts`. Instead, modify the source Go types and run `task generate`. + +### Frontend Architecture + +- The application uses Jotai for state management. +- When working with Jotai atoms that need to be updated, define them as `PrimitiveAtom` rather than just `atom`. + +### Notes + +- **CRITICAL: Completion format MUST be: "Done: [one-line description]"** +- **Keep your Task Completed summaries VERY short** +- **No lengthy pre-completion summaries** - Do not provide detailed explanations of implementation before using attempt_completion +- **No recaps of changes** - Skip explaining what was done before completion +- **Go directly to completion** - After making changes, proceed directly to attempt_completion without summarizing +- The project is currently an un-released POC / MVP. Do not worry about backward compatibility when making changes +- With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to "protect" it with unnecessary checks or workarounds. +- **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested. +- **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code. +- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow. +- It is now 2026, so if you write new files, or update files use 2026 for the copyright year + +### Strict Comment Rules + +- **NEVER add comments that merely describe what code is doing**: + - ❌ `mutex.Lock() // Lock the mutex` + - ❌ `counter++ // Increment the counter` + - ❌ `buffer.Write(data) // Write data to buffer` + - ❌ `// Header component for app run list` (above AppRunListHeader) + - ❌ `// Updated function to include onClick parameter` + - ❌ `// Changed padding calculation` + - ❌ `// Removed unnecessary div` + - ❌ `// Using the model's width value here` +- **Only use comments for**: + - Explaining WHY a particular approach was chosen + - Documenting non-obvious edge cases or side effects + - Warning about potential pitfalls in usage + - Explaining complex algorithms that can't be simplified +- **When in doubt, leave it out**. No comment is better than a redundant comment. +- **Never add comments explaining code changes** - The code should speak for itself, and version control tracks changes. The one exception to this rule is if it is a very unobvious implementation. Something that someone would typically implement in a different (wrong) way. Then the comment helps us remember WHY we changed it to a less obvious implementation. +- **Never remove existing comments** unless specifically directed by the user. Comments that are already defined in existing code have been vetted by the user. + +### Jotai Model Pattern (our rules) + +- **Atoms live on the model.** +- **Simple atoms:** define as **field initializers**. +- **Atoms that depend on values/other atoms:** create in the **constructor**. +- Models **never use React hooks**; they use `globalStore.get/set`. +- It’s fine to call model methods from **event handlers** or **`useEffect`**. + +```ts +// model/MyModel.ts +import { atom, type PrimitiveAtom } from "jotai"; +import { globalStore } from "@/app/store/jotaiStore"; + +export class MyModel { + // simple atoms (field init) + statusAtom = atom<"idle" | "running" | "error">("idle"); + outputAtom = atom(""); + + // ctor-built atoms (need types) + lengthAtom!: PrimitiveAtom; // read-only derived via atom(get=>...) + thresholdedAtom!: PrimitiveAtom; + + constructor(initialThreshold = 20) { + this.lengthAtom = atom((get) => get(this.outputAtom).length); + this.thresholdedAtom = atom((get) => get(this.lengthAtom) > initialThreshold); + } + + async doWork() { + globalStore.set(this.statusAtom, "running"); + try { + for await (const chunk of this.stream()) { + globalStore.set(this.outputAtom, (prev) => prev + chunk); + } + globalStore.set(this.statusAtom, "idle"); + } catch { + globalStore.set(this.statusAtom, "error"); + } + } + + private async *stream() { + /* ... */ + } +} +``` + +```tsx +// component usage (events & effects OK) +import { useAtomValue } from "jotai"; + +function Panel({ model }: { model: MyModel }) { + const status = useAtomValue(model.statusAtom); + const isBig = useAtomValue(model.thresholdedAtom); + + const onClick = () => model.doWork(); + // useEffect(() => { model.doWork() }, [model]) + + return ( +
+ {status} • {String(isBig)} +
+ ); +} +``` + +**Remember:** atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`. + +### Tool Use + +Do NOT use write_to_file unless it is a new file or very short. Always prefer to use replace_in_file. Often your diffs fail when a file may be out of date in your cache vs the actual on-disk format. You should RE-READ the file and try to create diffs again if your diffs fail rather than fall back to write_to_file. If you feel like your ONLY option is to use write_to_file please ask first. + +Also when adding content to the end of files prefer to use the new append_file tool rather than trying to create a diff (as your diffs are often not specific enough and end up inserting code in the middle of existing functions). + +### Directory Awareness + +- **ALWAYS verify the current working directory before executing commands** +- Either run "pwd" first to verify the directory, or do a "cd" to the correct absolute directory before running commands +- When running tests, do not "cd" to the pkg directory and then run the test. This screws up the cwd and you never recover. run the test from the project root instead. + +### Testing / Compiling Go Code + +No need to run a `go build` or a `go run` to just check if the Go code compiles. VSCode's errors/problems cover this well. +If there are no Go errors in VSCode you can assume the code compiles fine. From 7830f407e846ae5b0a62e8edf9231da2af9aaf64 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 24 Feb 2026 11:56:30 -0800 Subject: [PATCH 08/10] add preview window type --- frontend/app/store/windowtype.ts | 12 ++++++++---- frontend/preview/preview.tsx | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/app/store/windowtype.ts b/frontend/app/store/windowtype.ts index c43f44f4fa..363a790de8 100644 --- a/frontend/app/store/windowtype.ts +++ b/frontend/app/store/windowtype.ts @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // waveWindowType is set once at startup and never changes. -let waveWindowType: "tab" | "builder" = "tab"; +let waveWindowType: "tab" | "builder" | "preview" = "tab"; -function getWaveWindowType(): "tab" | "builder" { +function getWaveWindowType(): "tab" | "builder" | "preview" { return waveWindowType; } @@ -16,8 +16,12 @@ function isTabWindow(): boolean { return waveWindowType === "tab"; } -function setWaveWindowType(windowType: "tab" | "builder") { +function isPreviewWindow(): boolean { + return waveWindowType === "preview"; +} + +function setWaveWindowType(windowType: "tab" | "builder" | "preview") { waveWindowType = windowType; } -export { getWaveWindowType, isBuilderWindow, isTabWindow, setWaveWindowType }; +export { getWaveWindowType, isBuilderWindow, isPreviewWindow, isTabWindow, setWaveWindowType }; diff --git a/frontend/preview/preview.tsx b/frontend/preview/preview.tsx index d9a5a27cb7..55ed1cf47a 100644 --- a/frontend/preview/preview.tsx +++ b/frontend/preview/preview.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import Logo from "@/app/asset/logo.svg"; +import { setWaveWindowType } from "@/app/store/windowtype"; import React, { lazy, Suspense } from "react"; import { createRoot } from "react-dom/client"; @@ -113,5 +114,10 @@ function PreviewApp() { return ; } -const root = createRoot(document.getElementById("main")!); -root.render(); +function initPreview() { + setWaveWindowType("preview"); + const root = createRoot(document.getElementById("main")!); + root.render(); +} + +initPreview(); From 797b322842620859dfc44b96487fe54bfb14ed08 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 25 Feb 2026 15:58:06 -0800 Subject: [PATCH 09/10] clean up overview document, duplicate to kilocode --- .kilocode/rules/overview.md | 154 +++++++++++++++++++++++++++++ .kilocode/rules/rules.md | 3 +- .roo/rules/overview.md | 191 ++++++++---------------------------- .roo/rules/rules.md | 4 +- 4 files changed, 201 insertions(+), 151 deletions(-) create mode 100644 .kilocode/rules/overview.md diff --git a/.kilocode/rules/overview.md b/.kilocode/rules/overview.md new file mode 100644 index 0000000000..944a4021dd --- /dev/null +++ b/.kilocode/rules/overview.md @@ -0,0 +1,154 @@ +# Wave Terminal - High Level Architecture Overview + +## Project Description + +Wave Terminal is an open-source AI-native terminal built for seamless workflows. It's an Electron application that serves as a command line terminal host (it hosts CLI applications rather than running inside a CLI). The application combines a React frontend with a Go backend server to provide a modern terminal experience with advanced features. + +## Top-Level Directory Structure + +``` +waveterm/ +├── emain/ # Electron main process code +├── frontend/ # React application (renderer process) +├── cmd/ # Go command-line applications +├── pkg/ # Go packages/modules +├── db/ # Database migrations +├── docs/ # Documentation (Docusaurus) +├── build/ # Build configuration and assets +├── assets/ # Application assets (icons, images) +├── public/ # Static public assets +├── tests/ # Test files +├── .github/ # GitHub workflows and configuration +└── Configuration files (package.json, tsconfig.json, etc.) +``` + +## Architecture Components + +### 1. Electron Main Process (`emain/`) + +The Electron main process handles the native desktop application layer: + +**Key Files:** + +- [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management +- [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class) +- [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class) +- [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration +- [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration +- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication +- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system +- [`updater.ts`](emain/updater.ts) - Auto-update functionality +- [`preload.ts`](emain/preload.ts) - Preload script for renderer security +- [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script + +### 2. Frontend React Application (`frontend/`) + +The React application runs in the Electron renderer process: + +**Structure:** + +``` +frontend/ +├── app/ # Main application code +│ ├── app.tsx # Root App component +│ ├── aipanel/ # AI panel UI +│ ├── block/ # Block-based UI components +│ ├── element/ # Reusable UI elements +│ ├── hook/ # Custom React hooks +│ ├── modals/ # Modal components +│ ├── store/ # State management (Jotai) +│ ├── tab/ # Tab components +│ ├── view/ # Different view types +│ │ ├── codeeditor/ # Code editor (Monaco) +│ │ ├── preview/ # File preview +│ │ ├── sysinfo/ # System info view +│ │ ├── term/ # Terminal view +│ │ ├── tsunami/ # Tsunami builder view +│ │ ├── vdom/ # Virtual DOM view +│ │ ├── waveai/ # AI chat integration +│ │ ├── waveconfig/ # Config editor view +│ │ └── webview/ # Web view +│ └── workspace/ # Workspace management +├── builder/ # Builder app entry +├── layout/ # Layout system +├── preview/ # Standalone preview renderer +├── types/ # TypeScript type definitions +└── util/ # Utility functions +``` + +**Key Technologies:** + +- Electron (desktop application shell) +- React 19 with TypeScript +- Jotai for state management +- Monaco Editor for code editing +- XTerm.js for terminal emulation +- Tailwind CSS v4 for styling +- SCSS for additional styling (deprecated, new components should use Tailwind) +- Vite / electron-vite for bundling +- Task (Taskfile.yml) for build and code generation commands + +### 3. Go Backend Server (`cmd/server/`) + +The Go backend server handles all heavy lifting operations: + +**Entry Point:** [`main-server.go`](cmd/server/main-server.go) + +### 4. Go Packages (`pkg/`) + +The Go codebase is organized into modular packages: + +**Key Packages:** + +- `wstore/` - Database and storage layer +- `wconfig/` - Configuration management +- `wcore/` - Core business logic +- `wshrpc/` - RPC communication system +- `wshutil/` - WSH (Wave Shell) utilities +- `blockcontroller/` - Block execution management +- `remote/` - Remote connection handling +- `filestore/` - File storage system +- `web/` - Web server and WebSocket handling +- `telemetry/` - Usage analytics and telemetry +- `waveobj/` - Core data objects +- `service/` - Service layer +- `wps/` - Wave PubSub event system +- `waveai/` - AI functionality +- `shellexec/` - Shell execution +- `util/` - Common utilities + +### 5. Command Line Tools (`cmd/`) + +Key Go command-line utilities: + +- `wsh/` - Wave Shell command-line tool +- `server/` - Main backend server +- `generatego/` - Code generation +- `generateschema/` - Schema generation +- `generatets/` - TypeScript generation + +## Communication Architecture + +The core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL). + +### WSH RPC System (`pkg/wshrpc/`) + +The WSH RPC system is the backbone of Wave Terminal's communication architecture: + +**Key Components:** + +- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands) +- [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation +- [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling +- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls +- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client + +**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `"waveapp"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection. + +## Development Notes + +- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands +- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go` +- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages +- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/` +- **Documentation** - Docusaurus site in `docs/` diff --git a/.kilocode/rules/rules.md b/.kilocode/rules/rules.md index 6518187a1c..1ec12ca843 100644 --- a/.kilocode/rules/rules.md +++ b/.kilocode/rules/rules.md @@ -11,7 +11,7 @@ It has a TypeScript/React frontend and a Go backend. They talk together over `ws - Use string constants for status values, packet types, and other string-based enumerations. - in Go code, prefer using Printf() vs Println() - use "Make" as opposed to "New" for struct initialization func names - - in general const decls go at the top fo the file (before types and functions) + - in general const decls go at the top of the file (before types and functions) - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors. - **Synchronization**: - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible @@ -93,6 +93,7 @@ The full API is defined in custom.d.ts as type ElectronApi. - **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code. - for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow. - It is now 2026, so if you write new files, or update files use 2026 for the copyright year +- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable) ### Strict Comment Rules diff --git a/.roo/rules/overview.md b/.roo/rules/overview.md index 84289a1623..944a4021dd 100644 --- a/.roo/rules/overview.md +++ b/.roo/rules/overview.md @@ -29,57 +29,64 @@ waveterm/ The Electron main process handles the native desktop application layer: **Key Files:** + - [`emain.ts`](emain/emain.ts) - Main entry point, application lifecycle management - [`emain-window.ts`](emain/emain-window.ts) - Window management (`WaveBrowserWindow` class) - [`emain-tabview.ts`](emain/emain-tabview.ts) - Tab view management (`WaveTabView` class) - [`emain-wavesrv.ts`](emain/emain-wavesrv.ts) - Go backend server integration - [`emain-wsh.ts`](emain/emain-wsh.ts) - WSH (Wave Shell) client integration -- [`menu.ts`](emain/menu.ts) - Application menu system +- [`emain-ipc.ts`](emain/emain-ipc.ts) - IPC handlers for frontend ↔ main process communication +- [`emain-menu.ts`](emain/emain-menu.ts) - Application menu system - [`updater.ts`](emain/updater.ts) - Auto-update functionality - [`preload.ts`](emain/preload.ts) - Preload script for renderer security - [`preload-webview.ts`](emain/preload-webview.ts) - Webview preload script -**Responsibilities:** -- Window and tab management -- Native OS integration -- Auto-updater -- Menu system -- Security (preload scripts) -- Communication with Go backend - ### 2. Frontend React Application (`frontend/`) The React application runs in the Electron renderer process: **Structure:** + ``` frontend/ ├── app/ # Main application code │ ├── app.tsx # Root App component +│ ├── aipanel/ # AI panel UI │ ├── block/ # Block-based UI components │ ├── element/ # Reusable UI elements │ ├── hook/ # Custom React hooks │ ├── modals/ # Modal components │ ├── store/ # State management (Jotai) +│ ├── tab/ # Tab components │ ├── view/ # Different view types -│ │ ├── chat/ # Chat interface │ │ ├── codeeditor/ # Code editor (Monaco) +│ │ ├── preview/ # File preview +│ │ ├── sysinfo/ # System info view │ │ ├── term/ # Terminal view -│ │ ├── webview/ # Web view -│ │ └── waveai/ # AI integration +│ │ ├── tsunami/ # Tsunami builder view +│ │ ├── vdom/ # Virtual DOM view +│ │ ├── waveai/ # AI chat integration +│ │ ├── waveconfig/ # Config editor view +│ │ └── webview/ # Web view │ └── workspace/ # Workspace management +├── builder/ # Builder app entry ├── layout/ # Layout system +├── preview/ # Standalone preview renderer ├── types/ # TypeScript type definitions └── util/ # Utility functions ``` **Key Technologies:** -- React 18 with TypeScript + +- Electron (desktop application shell) +- React 19 with TypeScript - Jotai for state management - Monaco Editor for code editing - XTerm.js for terminal emulation -- Tailwind CSS for styling -- SCSS for additional styling +- Tailwind CSS v4 for styling +- SCSS for additional styling (deprecated, new components should use Tailwind) +- Vite / electron-vite for bundling +- Task (Taskfile.yml) for build and code generation commands ### 3. Go Backend Server (`cmd/server/`) @@ -87,21 +94,12 @@ The Go backend server handles all heavy lifting operations: **Entry Point:** [`main-server.go`](cmd/server/main-server.go) -**Key Responsibilities:** -- Database operations -- SSH connections -- File system operations -- Networking -- Telemetry -- Configuration management -- WebSocket communication -- RPC services - ### 4. Go Packages (`pkg/`) The Go codebase is organized into modular packages: -**Core Packages:** +**Key Packages:** + - `wstore/` - Database and storage layer - `wconfig/` - Configuration management - `wcore/` - Core business logic @@ -114,146 +112,43 @@ The Go codebase is organized into modular packages: - `telemetry/` - Usage analytics and telemetry - `waveobj/` - Core data objects - `service/` - Service layer -- `util/` - Common utilities - -**Additional Packages:** -- `authkey/` - Authentication -- `eventbus/` - Event system -- `wcloud/` - Cloud integration +- `wps/` - Wave PubSub event system - `waveai/` - AI functionality -- `panichandler/` - Error handling - `shellexec/` - Shell execution +- `util/` - Common utilities ### 5. Command Line Tools (`cmd/`) -Additional Go command-line utilities: +Key Go command-line utilities: + - `wsh/` - Wave Shell command-line tool - `server/` - Main backend server - `generatego/` - Code generation - `generateschema/` - Schema generation - `generatets/` - TypeScript generation -- `packfiles/` - File packaging utility - -### 6. Database Layer (`db/`) - -Database migrations for two main stores: -- `migrations-wstore/` - Main application database -- `migrations-filestore/` - File storage database ## Communication Architecture -The core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication. - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Electron │ │ React │ │ Go Backend │ -│ Main Process │◄──►│ Frontend │◄──►│ Server │ -│ (emain/) │ │ (frontend/) │ │ (cmd/server/) │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ - │ │ │ - │ WSH RPC │ WSH RPC │ - ▼ ▼ ▼ -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Native OS │ │ Web APIs │ │ System APIs │ -│ Integration │ │ DOM/Canvas │ │ SSH/Network │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ - │ - │ WSH RPC - ▼ - ┌─────────────────────┐ - │ Remote Systems │ - │ (SSH, WSL, etc.) │ - └─────────────────────┘ -``` +The core communication system is built around the **WSH RPC (Wave Shell RPC)** system, which provides a unified interface for all inter-process communication: frontend ↔ Go backend, Electron main process ↔ backend, and backend ↔ remote systems (SSH, WSL). ### WSH RPC System (`pkg/wshrpc/`) The WSH RPC system is the backbone of Wave Terminal's communication architecture: **Key Components:** -- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions + +- [`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go) - Core RPC interface and type definitions (source of truth for all RPC commands) - [`wshserver/`](pkg/wshrpc/wshserver/) - Server-side RPC implementation - [`wshremote/`](pkg/wshrpc/wshremote/) - Remote connection handling -- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Client-side RPC implementation - -**Transport Flexibility:** -- **WebSockets** - Primary transport for frontend ↔ backend communication -- **Unix Domain Sockets** - Local process communication -- **Terminal/Stdio** - Communication over terminal connections -- **SSH Tunneling** - Remote system communication - -**RPC Interface:** -The [`WshRpcInterface`](pkg/wshrpc/wshrpctypes.go:147) defines 100+ commands including: -- File operations (`FileRead`, `FileWrite`, `FileCopy`, etc.) -- Block management (`CreateBlock`, `DeleteBlock`, `ControllerInput`) -- Remote operations (`RemoteStreamFile`, `RemoteFileInfo`, etc.) -- Connection management (`ConnStatus`, `ConnConnect`, `ConnDisconnect`) -- AI integration (`AiSendMessage`, `StreamWaveAi`) -- Event system (`EventPublish`, `EventSub`, `EventRecv`) - -**Communication Flow:** -1. **Frontend → Backend**: React components call WSH RPC methods via WebSocket -2. **Electron → Backend**: Main process uses WSH RPC for system integration -3. **Backend → Remote**: Go server uses WSH RPC over SSH/terminal for remote operations -4. **WSH Binary**: Command-line tool communicates with backend via same RPC system - -This unified RPC system allows the `wsh` binary to work both locally and remotely, providing the same interface whether running on the local machine or on a remote server via SSH. - -### Type-Safe Code Generation - -Wave Terminal uses an innovative code generation system to maintain type safety between Go and TypeScript: - -**Generation Process:** -1. **Go Definitions** - All RPC types and interfaces are defined in Go ([`wshrpctypes.go`](pkg/wshrpc/wshrpctypes.go)) -2. **TypeScript Generation** - [`cmd/generatets/main-generatets.go`](cmd/generatets/main-generatets.go) automatically generates TypeScript bindings -3. **Build Integration** - The `generate` task in [`Taskfile.yml`](Taskfile.yml:252) runs code generation as part of the build process - -**Generated Files:** -- [`frontend/types/gotypes.d.ts`](frontend/types/gotypes.d.ts) - TypeScript type definitions from Go structs -- [`frontend/app/store/services.ts`](frontend/app/store/services.ts) - Service layer bindings -- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - RPC client API methods - -**Benefits:** -- **Type Safety** - Compile-time type checking between frontend and backend -- **Single Source of Truth** - Go types are the authoritative definition -- **Automatic Sync** - Changes to Go types automatically propagate to TypeScript -- **IDE Support** - Full IntelliSense and autocomplete for RPC calls - -This approach ensures that the frontend and backend stay in sync, preventing runtime errors from type mismatches and providing excellent developer experience with full type safety across the entire stack. - -## Build System - -**Configuration Files:** -- [`package.json`](package.json) - Node.js dependencies and scripts -- [`electron.vite.config.ts`](electron.vite.config.ts) - Vite build configuration -- [`tsconfig.json`](tsconfig.json) - TypeScript configuration -- [`electron-builder.config.cjs`](electron-builder.config.cjs) - Electron packaging - -**Build Targets:** -- **Main Process** - TypeScript → JavaScript (Node.js) -- **Preload Scripts** - TypeScript → CommonJS -- **Renderer** - React/TypeScript → ES6 bundle -- **Go Backend** - Go → Native binary - -## Key Features - -1. **Terminal Emulation** - XTerm.js-based terminal with modern features -2. **Block-based UI** - Modular block system for different content types -3. **AI Integration** - Built-in AI assistance and chat -4. **Code Editor** - Monaco Editor integration -5. **Remote Connections** - SSH and WSL support -6. **File Management** - Integrated file browser and operations -7. **Workspace Management** - Multi-workspace support -8. **Auto-updates** - Electron-based update system -9. **Cross-platform** - macOS, Linux, Windows support - -## Development Workflow - -1. **Frontend Development** - React components with hot reload -2. **Backend Development** - Go server with live reload -3. **Electron Integration** - Main process development -4. **Database Migrations** - SQL migration system -5. **Testing** - Vitest for frontend, Go testing for backend -6. **Documentation** - Docusaurus-based docs site - -This architecture provides a robust foundation for a modern terminal application, combining the best of web technologies (React, TypeScript) with native performance (Go, Electron) and system integration capabilities. \ No newline at end of file +- [`wshclient.go`](pkg/wshrpc/wshclient.go) - Go client for making RPC calls +- [`frontend/app/store/wshclientapi.ts`](frontend/app/store/wshclientapi.ts) - Generated TypeScript RPC client + +**Routing:** Callers address RPC calls using _routes_ (e.g. a block ID, connection name, or `"waveapp"`) rather than caring about the underlying transport. The RPC layer resolves the route to the correct transport (WebSocket, Unix socket, SSH tunnel, stdio) automatically. This means the same RPC interface works whether the target is local or a remote SSH connection. + +## Development Notes + +- **Build commands** - Use `task` (Taskfile.yml) for all build, generate, and packaging commands +- **Code generation** - Run `task generate` after modifying Go types in `pkg/wshrpc/wshrpctypes.go`, `pkg/wconfig/settingsconfig.go`, or `pkg/waveobj/wtypemeta.go` +- **Testing** - Vitest for frontend unit tests; standard `go test` for Go packages +- **Database migrations** - SQL migration files in `db/migrations-wstore/` and `db/migrations-filestore/` +- **Documentation** - Docusaurus site in `docs/` diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md index 466c9e06d3..1ec12ca843 100644 --- a/.roo/rules/rules.md +++ b/.roo/rules/rules.md @@ -11,7 +11,7 @@ It has a TypeScript/React frontend and a Go backend. They talk together over `ws - Use string constants for status values, packet types, and other string-based enumerations. - in Go code, prefer using Printf() vs Println() - use "Make" as opposed to "New" for struct initialization func names - - in general const decls go at the top fo the file (before types and functions) + - in general const decls go at the top of the file (before types and functions) - NEVER run `go build` (especially in weird sub-package directories). we can tell if everything compiles by seeing there are no problems/errors. - **Synchronization**: - Always prefer to use the `lock.Lock(); defer lock.Unlock()` pattern for synchronization if possible @@ -93,7 +93,7 @@ The full API is defined in custom.d.ts as type ElectronApi. - **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code. - for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow. - It is now 2026, so if you write new files, or update files use 2026 for the copyright year -- React.MutableRefObject is deprecated, just use React.RefObject now (current is now always mutable) +- React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable) ### Strict Comment Rules From 2c089d6d6b12bfbff631df5405fbfc8c63bb2574 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 25 Feb 2026 16:05:27 -0800 Subject: [PATCH 10/10] update model section of rules as well --- .kilocode/rules/rules.md | 58 ++++++++++++++++++++++------------------ .roo/rules/rules.md | 58 ++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/.kilocode/rules/rules.md b/.kilocode/rules/rules.md index 1ec12ca843..7efa154ea7 100644 --- a/.kilocode/rules/rules.md +++ b/.kilocode/rules/rules.md @@ -62,10 +62,10 @@ For normal "server" RPCs (where a frontend client is calling the main server) yo From within the FE to get the electron API (e.g. the preload functions): -``` +```ts import { getApi } from "@/store/global"; -getApi().getIsDev() +getApi().getIsDev(); ``` The full API is defined in custom.d.ts as type ElectronApi. @@ -91,7 +91,7 @@ The full API is defined in custom.d.ts as type ElectronApi. - With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to "protect" it with unnecessary checks or workarounds. - **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested. - **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code. -- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow. +- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow. - It is now 2026, so if you write new files, or update files use 2026 for the copyright year - React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable) @@ -121,41 +121,46 @@ The full API is defined in custom.d.ts as type ElectronApi. - **Simple atoms:** define as **field initializers**. - **Atoms that depend on values/other atoms:** create in the **constructor**. - Models **never use React hooks**; they use `globalStore.get/set`. -- It’s fine to call model methods from **event handlers** or **`useEffect`**. +- It's fine to call model methods from **event handlers** or **`useEffect`**. +- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method. +- The constructor is `private`; callers always use `getInstance()`. ```ts // model/MyModel.ts -import { atom, type PrimitiveAtom } from "jotai"; +import * as jotai from "jotai"; import { globalStore } from "@/app/store/jotaiStore"; export class MyModel { + private static instance: MyModel | null = null; + // simple atoms (field init) - statusAtom = atom<"idle" | "running" | "error">("idle"); - outputAtom = atom(""); + statusAtom = jotai.atom<"idle" | "running" | "error">("idle"); + outputAtom = jotai.atom(""); // ctor-built atoms (need types) - lengthAtom!: PrimitiveAtom; // read-only derived via atom(get=>...) - thresholdedAtom!: PrimitiveAtom; + lengthAtom!: jotai.Atom; + thresholdedAtom!: jotai.Atom; - constructor(initialThreshold = 20) { - this.lengthAtom = atom((get) => get(this.outputAtom).length); - this.thresholdedAtom = atom((get) => get(this.lengthAtom) > initialThreshold); + private constructor(initialThreshold = 20) { + this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length); + this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold); } - async doWork() { - globalStore.set(this.statusAtom, "running"); - try { - for await (const chunk of this.stream()) { - globalStore.set(this.outputAtom, (prev) => prev + chunk); - } - globalStore.set(this.statusAtom, "idle"); - } catch { - globalStore.set(this.statusAtom, "error"); + static getInstance(): MyModel { + if (!MyModel.instance) { + MyModel.instance = new MyModel(); } + return MyModel.instance; } - private async *stream() { - /* ... */ + static resetInstance(): void { + MyModel.instance = null; + } + + async doWork() { + globalStore.set(this.statusAtom, "running"); + // ... do work ... + globalStore.set(this.statusAtom, "idle"); } } ``` @@ -164,12 +169,12 @@ export class MyModel { // component usage (events & effects OK) import { useAtomValue } from "jotai"; -function Panel({ model }: { model: MyModel }) { +function Panel() { + const model = MyModel.getInstance(); const status = useAtomValue(model.statusAtom); const isBig = useAtomValue(model.thresholdedAtom); const onClick = () => model.doWork(); - // useEffect(() => { model.doWork() }, [model]) return (
@@ -179,7 +184,8 @@ function Panel({ model }: { model: MyModel }) { } ``` -**Remember:** atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`. +**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`. +**Note** Older models may not use the singleton pattern ### Tool Use diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md index 1ec12ca843..7efa154ea7 100644 --- a/.roo/rules/rules.md +++ b/.roo/rules/rules.md @@ -62,10 +62,10 @@ For normal "server" RPCs (where a frontend client is calling the main server) yo From within the FE to get the electron API (e.g. the preload functions): -``` +```ts import { getApi } from "@/store/global"; -getApi().getIsDev() +getApi().getIsDev(); ``` The full API is defined in custom.d.ts as type ElectronApi. @@ -91,7 +91,7 @@ The full API is defined in custom.d.ts as type ElectronApi. - With React hooks, always complete all hook calls at the top level before any conditional returns (including jotai hook calls useAtom and useAtomValue); when a user explicitly tells you a function handles null inputs, trust them and stop trying to "protect" it with unnecessary checks or workarounds. - **Match response length to question complexity** - For simple, direct questions in Ask mode (especially those that can be answered in 1-2 sentences), provide equally brief answers. Save detailed explanations for complex topics or when explicitly requested. - **CRITICAL** - useAtomValue and useAtom are React HOOKS. They cannot be used inline in JSX code, they must appear at the top of a component in the hooks area of the react code. -- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow. +- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern over `if (cond) { functionality }` because it produces less indentation and is easier to follow. - It is now 2026, so if you write new files, or update files use 2026 for the copyright year - React.MutableRefObject is deprecated, just use React.RefObject now (in React 19 RefObject is always mutable) @@ -121,41 +121,46 @@ The full API is defined in custom.d.ts as type ElectronApi. - **Simple atoms:** define as **field initializers**. - **Atoms that depend on values/other atoms:** create in the **constructor**. - Models **never use React hooks**; they use `globalStore.get/set`. -- It’s fine to call model methods from **event handlers** or **`useEffect`**. +- It's fine to call model methods from **event handlers** or **`useEffect`**. +- Models use the **singleton pattern** with a `private static instance` field, a `private constructor`, and a `static getInstance()` method. +- The constructor is `private`; callers always use `getInstance()`. ```ts // model/MyModel.ts -import { atom, type PrimitiveAtom } from "jotai"; +import * as jotai from "jotai"; import { globalStore } from "@/app/store/jotaiStore"; export class MyModel { + private static instance: MyModel | null = null; + // simple atoms (field init) - statusAtom = atom<"idle" | "running" | "error">("idle"); - outputAtom = atom(""); + statusAtom = jotai.atom<"idle" | "running" | "error">("idle"); + outputAtom = jotai.atom(""); // ctor-built atoms (need types) - lengthAtom!: PrimitiveAtom; // read-only derived via atom(get=>...) - thresholdedAtom!: PrimitiveAtom; + lengthAtom!: jotai.Atom; + thresholdedAtom!: jotai.Atom; - constructor(initialThreshold = 20) { - this.lengthAtom = atom((get) => get(this.outputAtom).length); - this.thresholdedAtom = atom((get) => get(this.lengthAtom) > initialThreshold); + private constructor(initialThreshold = 20) { + this.lengthAtom = jotai.atom((get) => get(this.outputAtom).length); + this.thresholdedAtom = jotai.atom((get) => get(this.lengthAtom) > initialThreshold); } - async doWork() { - globalStore.set(this.statusAtom, "running"); - try { - for await (const chunk of this.stream()) { - globalStore.set(this.outputAtom, (prev) => prev + chunk); - } - globalStore.set(this.statusAtom, "idle"); - } catch { - globalStore.set(this.statusAtom, "error"); + static getInstance(): MyModel { + if (!MyModel.instance) { + MyModel.instance = new MyModel(); } + return MyModel.instance; } - private async *stream() { - /* ... */ + static resetInstance(): void { + MyModel.instance = null; + } + + async doWork() { + globalStore.set(this.statusAtom, "running"); + // ... do work ... + globalStore.set(this.statusAtom, "idle"); } } ``` @@ -164,12 +169,12 @@ export class MyModel { // component usage (events & effects OK) import { useAtomValue } from "jotai"; -function Panel({ model }: { model: MyModel }) { +function Panel() { + const model = MyModel.getInstance(); const status = useAtomValue(model.statusAtom); const isBig = useAtomValue(model.thresholdedAtom); const onClick = () => model.doWork(); - // useEffect(() => { model.doWork() }, [model]) return (
@@ -179,7 +184,8 @@ function Panel({ model }: { model: MyModel }) { } ``` -**Remember:** atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`. +**Remember:** singleton pattern with `getInstance()`, `private constructor`, atoms on the model, simple-as-fields, ctor for dependent/derived, updates via `globalStore.set/get`. +**Note** Older models may not use the singleton pattern ### Tool Use