`WindowService.MoveBlockToNewWindow` appears to be unreferenced, and its
supporting path had become isolated. This change removes that dead RPC
surface and the backend/eventbus helpers that existed only for that
flow.
- **Window service cleanup (backend RPC)**
- Removed `MoveBlockToNewWindow_Meta` and `MoveBlockToNewWindow` from
`pkg/service/windowservice/windowservice.go`.
- Dropped now-unused imports tied to that method (`log`, `eventbus`).
- **Store cleanup**
- Removed `MoveBlockToTab` from `pkg/wstore/wstore.go`.
- Removed now-unused `utilfn` import from the same file.
- **Eventbus cleanup**
- Removed unused event constant `WSEvent_ElectronNewWindow`.
- Removed `getWindowWatchesForWindowId` and `BusyWaitForWindowId`, which
were only used by the deleted move-to-new-window path.
- Removed now-unused `time` import.
- **Generated frontend service surface**
- Regenerated service bindings and removed
`WindowServiceType.MoveBlockToNewWindow(...)` from
`frontend/app/store/services.ts`.
Example of removed RPC surface:
```ts
// removed from frontend/app/store/services.ts
MoveBlockToNewWindow(currentTabId: string, blockId: string): Promise<void> {
return WOS.callBackendService("window", "MoveBlockToNewWindow", Array.from(arguments))
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/wavetermdev/waveterm/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
AppsFloatingWindow currently lists local apps but does not provide an
in-context way to open the app builder picker. This change adds a bottom
ghost-style action strip (`+ Build/Edit Apps`) that launches the builder
via Electron preload with `openBuilder(null)`.
- **What changed**
- Added a new bottom strip action inside `AppsFloatingWindow`:
- Label: `+ Build/Edit Apps`
- Visual style: ghost-like footer strip with top border, hover state,
full-width click target
- Wired the strip to call the preload API and close the floating window:
- `getApi().openBuilder(null)` (`null` app id opens the app picker)
- Kept the change scoped to `frontend/app/workspace/widgets.tsx` with no
behavior changes to app-grid item launching.
- **Implementation detail**
- Imported `getApi` from `@/store/global`
- Added a memoized handler for builder launch:
```tsx
const handleOpenBuilder = useCallback(() => {
getApi().openBuilder(null);
onClose();
}, [onClose]);
```
- **UI preview**
-
<screenshot>https://github.com/user-attachments/assets/1448588f-ff1d-41b5-af72-2849135ca1f3</screenshot>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/wavetermdev/waveterm/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
Dragging in the vertical tab bar had two UX artifacts: the drop marker
reserved layout space (leaving a visible accent gap above selected
tabs), and hover styling could remain on the old row after drop until
the mouse moved. This updates drag visuals to be overlay-based and
forces hover recalculation at drag end.
- **Drop marker moved out of flow (no selected-tab accent gap)**
- Replaced per-row in-flow divider placeholders with a single absolutely
positioned drop line in the scroll container.
- Drop line now aligns to actual tab boundaries (`offsetTop` /
`offsetHeight`) so it covers the divider location directly.
- **Drop target rendering simplified**
- Container is `relative`; marker is conditionally rendered only while
reordering.
- `dropLineTop` is tracked during drag events and used to position the
marker without affecting layout.
- **Stale hover state cleared after drop**
- Added a minimal drag-lifecycle reset mechanism (`hoverResetVersion`)
and used it in `VTab` keys.
- On drag end/drop, rows remount once, clearing browser-retained
`:hover` on the old index immediately.
```tsx
<VTab key={`${tab.id}:${hoverResetVersion}`} ... />
{dragTabId != null && dropIndex != null && dropLineTop != null && (
<div
className="pointer-events-none absolute left-0 right-0 border-t-2 border-accent/80"
style={{ top: dropLineTop, transform: "translateY(-1px)" }}
/>
)}
```
- **<screenshot>**
-
https://github.com/user-attachments/assets/8c25ef6f-c600-484e-a4fa-6ac83657b484
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
This PR extends WSH RPC command signatures to support `ctx + 2+ typed
args` while preserving existing `ctx` and `ctx + 1 arg` behavior. It
also adds a concrete `TestMultiArgCommand` end-to-end so the generated
Go/TS client surfaces can be inspected and exercised from CLI.
- **RPC wire + dispatch model**
- Added `wshrpc.MultiArg` (`args []any`) as the over-the-wire envelope
for 2+ arg commands.
- Extended RPC metadata to track all command arg types
(`CommandDataTypes`) and exposed a helper for normalized access.
- Updated server adapter unmarshalling to:
- decode `MultiArg` for 2+ arg commands,
- validate arg count,
- re-unmarshal each arg into its declared type before invoking typed
handlers.
- Kept single-arg commands on the existing non-`MultiArg` path.
- **Code generation (Go + TS)**
- Go codegen now emits multi-parameter wrappers for 2+ arg methods and
packs payload as `wshrpc.MultiArg`.
- TS codegen now emits multi-parameter API methods and packs payload as
`{ args: [...] }`.
- 0/1-arg generation remains unchanged to avoid wire/API churn.
- **Concrete command added for validation**
- Added to `WshRpcInterface`:
- `TestMultiArgCommand(ctx context.Context, arg1 string, arg2 int, arg3
bool) (string, error)`
- Implemented in `wshserver` with deterministic formatted return output
including source + all args.
- Updated `wsh test` command to call `TestMultiArgCommand` and print the
returned string.
- **Focused coverage**
- Added/updated targeted tests around RPC metadata and Go/TS multi-arg
codegen behavior, including command declaration for `testmultiarg`.
Example generated call shape:
```go
func TestMultiArgCommand(w *wshutil.WshRpc, arg1 string, arg2 int, arg3 bool, opts *wshrpc.RpcOpts) (string, error) {
return sendRpcRequestCallHelper[string](
w,
"testmultiarg",
wshrpc.MultiArg{Args: []any{arg1, arg2, arg3}},
opts,
)
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
The onboarding preview was missing the first two pages in the
new-install flow (`InitPage` and `NoTelemetryStarPage`). This update
adds those views to the preview and aligns state access with the current
store pattern so they render correctly in preview mode.
- **Preview coverage**
- Added `InitPage` and `NoTelemetryStarPage` to
`frontend/preview/previews/onboarding.preview.tsx` so the full early
onboarding path is visible in the preview server.
- **Settings access modernization**
- Replaced full `settingsAtom` usage in `InitPage` with targeted
`useSettingsKeyAtom("telemetry:enabled")`.
- Removes broad settings dependency and uses the existing preview-safe
settings path.
- **Preview bootstrap state init**
- Added `ClientModel.getInstance().initialize(null)` in
`frontend/preview/preview.tsx` to ensure `clientAtom` is initialized in
preview runtime without backend client data.
```tsx
// onboarding.tsx
const telemetrySetting = useSettingsKeyAtom("telemetry:enabled");
const clientData = useAtomValue(ClientModel.getInstance().clientAtom);
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(!!telemetrySetting);
// preview.tsx
setWaveWindowType("preview");
ClientModel.getInstance().initialize(null);
```
- **<screenshot>**
- 
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
`FilesPage` was previously previewed through a special path because
`EditBashrcCommand` (Monaco via `CodeEditor`) threw in preview mode.
With preview/global handling now fixed, preview can run the real Files
onboarding flow end-to-end without command overrides.
- **Files preview now uses the real FilesPage path**
- `OnboardingPreview` renders `FilesPage` directly.
- Removed preview-only command injection/override behavior for Files
onboarding.
- **Reverted FilesPage customization**
- Dropped optional command renderer plumbing added for preview.
- Restored FilesPage to its original internal command rotation:
`EditBashrcCommand -> ViewShortcutsCommand -> ViewLogoCommand`.
- **Result**
- No Files-specific preview fork remains.
- Preview and production use the same FilesPage command lifecycle.
```tsx
const commands = [
(onComplete: () => void) => <EditBashrcCommand onComplete={onComplete} />,
(onComplete: () => void) => <ViewShortcutsCommand isMac={isMac} onComplete={onComplete} />,
(onComplete: () => void) => <ViewLogoCommand onComplete={onComplete} />,
];
```
<screenshot>

</screenshot>
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/wavetermdev/waveterm/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
The new color validator is used exclusively in the Electron/Chromium
frontend, so fallback parsing via temporary DOM elements is unnecessary.
This update tightens the implementation to rely on the browser-native
CSS capability check only.
- **Scope**
- Keep `validateCssColor(color: string): string` behavior unchanged
(returns normalized type for valid colors, throws on invalid).
- Remove non-Chromium fallback logic from validation path.
- **Implementation**
- **`frontend/util/color-validator.ts`**
- `isValidCssColor` now exclusively uses:
- `CSS.supports("color", color)`
- Removed fallback using `document.createElement(...).style.color`
assignment/parsing.
- **Behavioral contract (unchanged)**
- Valid values still return specific type strings (`hex`, `hex8`, `rgb`,
`rgba`, `hsl`, `keyword`, etc.).
- Invalid values still throw `Error("Invalid CSS color: ...")`.
```ts
function isValidCssColor(color: string): boolean {
if (typeof CSS == "undefined" || typeof CSS.supports != "function") {
return false;
}
return CSS.supports("color", color);
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/wavetermdev/waveterm/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
There may be more cases here that I don't know about, but this fixes a
good chunk of them. This catches the CC "repaint" transaction and forces
a scrollToBottom. That should handle context repaints and resize
repaints.
Also adds a new (hidden) terminal escape sequence debugger, and (in dev
mode) adds a last 50 writes cache that can be used to look at and debug
output.
This follow-up tightens the new multi-file remote stat RPC behavior to
match expected usage: duplicate input paths are skipped, per-path stat
failures no longer fail the entire call, and cancellation is respected
during iteration.
- **RPC response model**
- Added `staterror` to `FileInfo`:
- `StatError string \`json:"staterror,omitempty"\``
- Generated TS type now exposes `staterror?: string` on `FileInfo`.
- **`RemoteFileMultiInfoCommand` behavior updates**
- **Dedup:** if an input path key is already present in the result map,
the loop continues.
- **Non-fatal stat failures:** on `fileInfoInternal` error, the command
now emits a `FileInfo` entry for that input with:
- resolved `path`, `dir`, `name`
- `staterror` populated with `err.Error()`
- continues processing remaining paths.
- **Cancellation:** checks `ctx.Err()` at the top of each iteration and
returns immediately if canceled.
- **PR scope cleanup**
- Removed the previously added test file from this PR per request.
```go
if _, found := rtn[path]; found {
continue
}
if ctx.Err() != nil {
return nil, ctx.Err()
}
fileInfo, err := impl.fileInfoInternal(cleanedPath, false)
if err != nil {
rtn[path] = wshrpc.FileInfo{
Path: wavebase.ReplaceHomeDir(cleanedPath),
Dir: computeDirPart(cleanedPath),
Name: filepath.Base(cleanedPath),
StatError: err.Error(),
}
continue
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Context-menu opens previously produced renderer callbacks only on
selection (0 or 1 events). This change makes the lifecycle
deterministic: every menu open now emits exactly one completion signal,
with `null` on cancel and item id on selection.
- **Main process: single terminal callback per popup**
- Updated `emain/emain-menu.ts` to use `menu.popup({ callback })`.
- Tracks whether a menu item click occurred during the popup.
- Emits `contextmenu-click: null` only when the menu closes without
selection.
- Suppresses duplicate close events when a click already fired.
- **Preload + API typing: nullable context-menu callback payload**
- Updated preload bridge and `frontend/types/custom.d.ts` so:
- `onContextMenuClick` now accepts `(id: string | null) => void`.
- Keeps existing channel semantics while allowing explicit cancel
signal.
- **Renderer context menu model: close/select/cancel hooks**
- Extended `showContextMenu` with optional `opts`:
- `onSelect?: (item) => void`
- `onCancel?: () => void`
- `onClose?: (item: MenuItem | null) => void`
- Execution order on selection:
1. original item `click`
2. `onSelect`
3. `onClose`
- Execution order on cancel:
1. `onCancel`
2. `onClose(null)`
- **Targeted behavior tests**
- Expanded `frontend/app/store/contextmenu.test.ts` to verify:
- singleton wiring still initializes once,
- selection path ordering (`click -> onSelect -> onClose`),
- cancel path ordering (`onCancel -> onClose(null)`).
```ts
ContextMenuModel.getInstance().showContextMenu(menu, e, {
onSelect: (item) => { /* after item.click */ },
onCancel: () => { /* close without selection */ },
onClose: (itemOrNull) => { /* always called */ },
});
```
<screenshot>
Not applicable — this is a behavioral/API flow change in native context
menu lifecycle rather than a visual UI update.
</screenshot>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
`FlashError` was legacy UI/state that was never wired into real runtime
flows (only self-referenced and window-exported for ad-hoc testing).
This PR removes that dead path end-to-end to reduce global surface area
and stale styling/types.
- **Scope: UI removal (`frontend/app/app.tsx`)**
- Deleted the `FlashError` component and its render site (`<FlashError
/>`).
- Removed now-unused imports tied to that component (`removeFlashError`,
`Fragment`, `useState`).
- **Scope: global store cleanup (`frontend/app/store/global.ts`,
`frontend/app/store/global-atoms.ts`)**
- Removed `pushFlashError` / `removeFlashError`.
- Removed `flashErrors` atom from global atom initialization and
registry.
- Kept notification path intact (`pushNotification`,
`removeNotificationById`, etc.).
- **Scope: external/debug surface cleanup (`frontend/wave.ts`)**
- Removed `pushFlashError` from imports and from `window` export wiring.
- **Scope: type + style cleanup (`frontend/types/custom.d.ts`,
`frontend/app/app.scss`, `frontend/app/theme.scss`)**
- Removed `GlobalAtomsType.flashErrors`.
- Removed `FlashErrorType`.
- Removed `.flash-error-container` style block and related z-index token
`--zindex-flash-error-container`.
```ts
// before
(window as any).pushFlashError = pushFlashError;
// after
(window as any).pushNotification = pushNotification;
```
- **`<screenshot>`**
- User-provided screenshot URL (if suitable for PR timeline context):
https://github.com/user-attachments/assets/aacc2e61-a65c-4dbf-bfcc-f9f99a490f20
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
This refactor keeps `global-atoms.ts` as the owner of atom
state/initialization while correcting ownership boundaries for UI/event
wiring. Specifically, the About menu handler was moved out of atom
initialization and back into `global.ts` startup orchestration.
- **Context / intent**
- `global-atoms.ts` should own atom construction and registries only.
- `global.ts` should orchestrate app-level startup side effects and
re-export compatibility APIs.
- **What changed**
- **`global-atoms.ts`**
- Removed `onMenuItemAbout` registration from `initGlobalAtoms`.
- Removed `modalsModel` import (no longer needed; improves layering).
- Continued to own atom state (`atoms`), atom init (`initGlobalAtoms`),
and atom registries/caches.
- **`global.ts`**
- Added `onMenuItemAbout` registration to `initGlobal` immediately after
`initGlobalAtoms(initOpts)`.
- Keeps UI-level event wiring with global startup orchestration.
- **Boundary after this change**
- `global-atoms.ts`: atom ownership + atom graph init.
- `global.ts`: runtime orchestration + menu/event side effects +
compatibility re-exports.
```ts
// global.ts
function initGlobal(initOpts: GlobalInitOptions) {
globalEnvironment = initOpts.environment;
globalPrimaryTabStartup = initOpts.primaryTabStartup ?? false;
setPlatform(initOpts.platform);
initGlobalAtoms(initOpts);
try {
getApi().onMenuItemAbout(() => {
modalsModel.pushModal("AboutModal");
});
} catch (e) {
console.log("failed to initialize onMenuItemAbout handler", e);
}
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
`contextmenu.ts` eagerly instantiated its model at import time, which
triggered constructor side effects (`getApi()`) too early. This change
aligns ContextMenuModel with the `client-model.ts` singleton pattern so
initialization is explicit and deferred.
- **Singleton pattern migration (`frontend/app/store/contextmenu.ts`)**
- Replaced eager module-level instance creation with a class-based
singleton:
- `private constructor()`
- `static getInstance()`
- Removed `ContextMenuModelType` export and now export
`ContextMenuModel` class only.
- Constructor side effects (`onContextMenuClick` wiring) now run only on
first `getInstance()` call.
- **Usage updates (all context menu entry points)**
- Updated all direct static-style calls:
- `ContextMenuModel.showContextMenu(...)`
- ⟶ `ContextMenuModel.getInstance().showContextMenu(...)`
- Applied across app/builder/preview/term/tab/widget context-menu
handlers to keep behavior consistent while deferring initialization.
- **Focused regression test**
- Added `frontend/app/store/contextmenu.test.ts` to verify:
- importing `contextmenu` does not initialize the API binding;
- first `getInstance()` initializes once;
- repeated `getInstance()` returns the same singleton instance.
```ts
// before
ContextMenuModel.showContextMenu(menu, e);
// after
ContextMenuModel.getInstance().showContextMenu(menu, e);
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
adds a new "preview server" for UI testing. hooking up to cloudflare
pages deployments. for now, just a test "about modal" component. will
add more later.
This PR adds a new app setting to control whether block focus follows
cursor movement:
- `app:focusfollowscursor: "off" | "on" | "term"`
- Default is `"off"` (no behavior change unless enabled)
## What changed
- Added hover-focus behavior on block pointer enter.
- Added guardrails so hover focus does not trigger when:
- touch input is used
- pointer buttons are pressed (drag/select scenarios)
- modal is open
- pointer events are disabled
- block is resizing
- Added config key plumbing across settings types and schema:
- `pkg/wconfig/settingsconfig.go`
- `pkg/wconfig/metaconsts.go`
- `schema/settings.json`
- `pkg/wconfig/defaultconfig/settings.json` ->
`"app:focusfollowscursor": "off"`
- Added docs for the new key and default example:
- `docs/docs/config.mdx`
## Behavior
- `"off"`: do not focus on cursor movement
- `"on"`: focus follows cursor for all block types
- `"term"`: focus follows cursor only for terminal blocks
## Summary
This PR adds vim-style movement aliases for block navigation and updates
one conflicting
shortcut.
## Changes
- Added global keybindings:
- `Ctrl+Shift+h` -> move focus left
- `Ctrl+Shift+j` -> move focus down
- `Ctrl+Shift+k` -> move focus up
- `Ctrl+Shift+l` -> move focus right
- Kept existing arrow-based movement:
- `Ctrl+Shift+ArrowUp/Down/Left/Right`
- **Changed launcher shortcut**:
- from `Ctrl+Shift+k`
- to `Ctrl+Shift+x`
## Why `k` changed to `x`
`Ctrl+Shift+k` is now used for vim-style upward movement (`k` = up), so
launcher replace
needed a new binding.
`Ctrl+Shift+x` was chosen to avoid collisions with existing Wave global
keybindings.
## Config / behavior notes
- `app:disablectrlshiftarrows` now disables both block-navigation sets:
- Arrow keys
- `h/j/k/l` aliases
- It does **not** disable `Ctrl+Shift+x` (launcher replace).
## Docs updated
- `docs/docs/keybindings.mdx`
- Added `Ctrl+Shift+h/j/k/l` navigation display
- Updated launcher replace shortcut to `Ctrl+Shift+x`
- `docs/docs/config.mdx`
- Clarified `app:disablectrlshiftarrows` scope to include `Arrow` +
`h/j/k/l`
This change adds an optional `workspaces` field to widget config entries
so widgets can be scoped to specific workspace UUIDs. Widgets with no
`workspaces` field (or an empty array) continue to render globally,
preserving current `widgets.json` behavior.
- **Config model updates**
- Added `workspaces []string` to `wconfig.WidgetConfigType` with
`json:"workspaces,omitempty"`.
- Updated frontend generated type `WidgetConfigType` with `workspaces?:
string[]`.
- **Sidebar widget filtering**
- `frontend/app/workspace/widgets.tsx` now applies workspace-aware
filtering when building the sidebar widget list:
- include if `workspaces` is missing or empty
- include if current `workspace.oid` is present in `workspaces`
- otherwise exclude
- Existing `defwidget@ai` filtering logic remains intact.
- **Isolated filtering logic + coverage**
- Added `frontend/app/workspace/widgetfilter.ts` with:
- `shouldIncludeWidgetForWorkspace(widget, workspaceId)`
- Added focused tests in `frontend/app/workspace/widgetfilter.test.ts`
for:
- missing/empty `workspaces`
- matching/non-matching workspace IDs
- missing active workspace ID
```ts
function shouldIncludeWidgetForWorkspace(widget: WidgetConfigType, workspaceId?: string): boolean {
const workspaces = widget.workspaces;
return !Array.isArray(workspaces) || workspaces.length === 0 || (workspaceId != null && workspaces.includes(workspaceId));
}
```
- **Screenshot**
-
<screenshot>https://github.com/user-attachments/assets/b1727003-793b-4eff-8fc1-00eac9c50b83</screenshot>
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
Co-authored-by: sawka <mike@commandline.dev>
This refactor moves the counters helpers out of
`frontend/app/store/global.ts` into a dedicated `counters.ts` module,
and updates existing call sites to import from the new location. The
goal is to keep `global.ts` focused on global store/state concerns while
preserving current counters behavior.
- **Module extraction**
- Added `frontend/app/store/counters.ts` containing:
- `counterInc(name, incAmt = 1)`
- `countersClear()`
- `countersPrint()`
- Moved logic unchanged from `global.ts`.
- **Global store cleanup**
- Removed counters state/functions from `frontend/app/store/global.ts`.
- Removed counters exports from `global.ts`’s export surface.
- **Call site updates**
- Updated imports to use `@/store/counters` in:
- `frontend/app/block/block.tsx` (`counterInc`)
- `frontend/wave.ts` (`countersClear`, `countersPrint`)
```ts
// before
import { counterInc } from "@/store/global";
// after
import { counterInc } from "@/store/counters";
```
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
This pull request adds a new command-line feature for exporting terminal
scrollback, improves the accuracy of scrollback extraction (especially
for wrapped lines), and introduces a "Save Session As..." menu option in
the frontend to make exporting session logs more user-friendly. The
changes touch both the backend CLI and the frontend, ensuring users can
easily capture and save terminal output for processing or archiving.
- Add tab:confirmclose boolean config option to SettingsType (Go),
schema/settings.json, and gotypes.d.ts
- Update close-tab IPC handler to use ipcMain.handle (async) and accept
confirmClose param
- Show a native confirmation dialog via dialog.showMessageBoxSync when
confirmClose is true
- Update preload.ts to use ipcRenderer.invoke for close-tab, returning
Promise<boolean>
- Update closeTab type signature in custom.d.ts to return
Promise<boolean>
- Update tabbar.tsx and keymodel.ts to await closeTab result and only
delete layout model on confirmed close
- Document tab:confirmclose in docs/docs/config.mdx
---------
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>