Skip to content

Commit c2477b2

Browse files
committed
Merge remote-tracking branch 'origin/main' into sawka/css
2 parents 7830f40 + ba7aea7 commit c2477b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+927
-1062
lines changed

.github/workflows/copilot-setup-steps.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ on:
77
pull_request:
88
paths: [.github/workflows/copilot-setup-steps.yml]
99

10-
env:
11-
GO_VERSION: "1.25.6"
12-
NODE_VERSION: 22
10+
# Note: global env vars are NOT used here — they are not reliable in all
11+
# GitHub Actions contexts (e.g. Copilot setup steps). Values are inlined
12+
# directly into each step that needs them.
1313

1414
jobs:
1515
copilot-setup-steps:
@@ -23,12 +23,12 @@ jobs:
2323
# Go + Node versions match your helper
2424
- uses: actions/setup-go@v6
2525
with:
26-
go-version: ${{ env.GO_VERSION }}
26+
go-version: "1.25.6"
2727
cache-dependency-path: go.sum
2828

2929
- uses: actions/setup-node@v6
3030
with:
31-
node-version: ${{ env.NODE_VERSION }}
31+
node-version: 22
3232
cache: npm
3333
cache-dependency-path: package-lock.json
3434

.kilocode/skills/context-menu/SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { ContextMenuModel } from "@/app/store/contextmenu";
4040
To display the context menu, call:
4141

4242
```ts
43-
ContextMenuModel.showContextMenu(menu, event);
43+
ContextMenuModel.getInstance().showContextMenu(menu, event);
4444
```
4545

4646
- **menu**: An array of `ContextMenuItem`.
@@ -75,7 +75,7 @@ const menu: ContextMenuItem[] = [
7575
},
7676
];
7777

78-
ContextMenuModel.showContextMenu(menu, e);
78+
ContextMenuModel.getInstance().showContextMenu(menu, e);
7979
```
8080

8181
---
@@ -111,7 +111,7 @@ const menu: ContextMenuItem[] = [
111111
},
112112
];
113113

114-
ContextMenuModel.showContextMenu(menu, e);
114+
ContextMenuModel.getInstance().showContextMenu(menu, e);
115115
```
116116

117117
---
@@ -143,7 +143,7 @@ Open a configuration file (e.g., `widgets.json`) in preview mode:
143143
- **Actions**: Use `click` for actions; use `submenu` for nested options.
144144
- **Separators**: Use `type: "separator"` to group items.
145145
- **Toggles**: Use `type: "checkbox"` or `"radio"` with the `checked` property.
146-
- **Displaying**: Use `ContextMenuModel.showContextMenu(menu, event)` to render the menu.
146+
- **Displaying**: Use `ContextMenuModel.getInstance().showContextMenu(menu, event)` to render the menu.
147147

148148
## Common Use Cases
149149

.roo/rules/rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ The full API is defined in custom.d.ts as type ElectronApi.
9393
- **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.
9494
- for simple functions, we prefer `if (!cond) { return }; functionality;` pattern overn `if (cond) { functionality }` because it produces less indentation and is easier to follow.
9595
- It is now 2026, so if you write new files, or update files use 2026 for the copyright year
96+
- React.MutableRefObject is deprecated, just use React.RefObject now (current is now always mutable)
9697

9798
### Strict Comment Rules
9899

docs/docs/config.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ wsh editconfig
6969
| term:allowbracketedpaste | bool | allow bracketed paste mode in terminal (default false) |
7070
| term:shiftenternewline | bool | when enabled, Shift+Enter sends escape sequence + newline (\u001b\n) instead of carriage return, useful for claude code and similar AI coding tools (default false) |
7171
| term:macoptionismeta | bool | on macOS, treat the Option key as Meta key for terminal keybindings (default false) |
72+
| term:cursor <VersionBadge version="v0.14" /> | string | terminal cursor style. valid values are `block` (default), `underline`, and `bar` |
73+
| term:cursorblink <VersionBadge version="v0.14" /> | bool | when enabled, terminal cursor blinks (default false) |
7274
| term:bellsound <VersionBadge version="v0.14" /> | bool | when enabled, plays the system beep sound when the terminal bell (BEL character) is received (default false) |
7375
| term:bellindicator <VersionBadge version="v0.14" /> | bool | when enabled, shows a visual indicator in the tab when the terminal bell is received (default false) |
7476
| term:durable <VersionBadge version="v0.14" /> | bool | makes remote terminal sessions durable across network disconnects (defaults to false) |
@@ -145,6 +147,8 @@ For reference, this is the current default configuration (v0.14.0):
145147
"telemetry:enabled": true,
146148
"term:bellsound": false,
147149
"term:bellindicator": false,
150+
"term:cursor": "block",
151+
"term:cursorblink": false,
148152
"term:copyonselect": true,
149153
"term:durable": false,
150154
"waveai:showcloudmodes": true,

emain/emain-menu.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -408,27 +408,24 @@ function getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId: string): ele
408408

409409
function convertMenuDefArrToMenu(
410410
webContents: electron.WebContents,
411-
menuDefArr: ElectronContextMenuItem[]
411+
menuDefArr: ElectronContextMenuItem[],
412+
menuState: { hasClick: boolean }
412413
): electron.Menu {
413414
const menuItems: electron.MenuItem[] = [];
414415
for (const menuDef of menuDefArr) {
415416
const menuItemTemplate: electron.MenuItemConstructorOptions = {
416417
role: menuDef.role as any,
417418
label: menuDef.label,
418419
type: menuDef.type,
419-
click: (_, window) => {
420-
const wc = getWindowWebContents(window) ?? webContents;
421-
if (!wc) {
422-
console.error("invalid window for context menu click handler:", window);
423-
return;
424-
}
425-
wc.send("contextmenu-click", menuDef.id);
420+
click: () => {
421+
menuState.hasClick = true;
422+
webContents.send("contextmenu-click", menuDef.id);
426423
},
427424
checked: menuDef.checked,
428425
enabled: menuDef.enabled,
429426
};
430427
if (menuDef.submenu != null) {
431-
menuItemTemplate.submenu = convertMenuDefArrToMenu(webContents, menuDef.submenu);
428+
menuItemTemplate.submenu = convertMenuDefArrToMenu(webContents, menuDef.submenu, menuState);
432429
}
433430
const menuItem = new electron.MenuItem(menuItemTemplate);
434431
menuItems.push(menuItem);
@@ -439,19 +436,27 @@ function convertMenuDefArrToMenu(
439436
electron.ipcMain.on(
440437
"contextmenu-show",
441438
(event, workspaceOrBuilderId: string, menuDefArr: ElectronContextMenuItem[]) => {
439+
const webContents = getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId);
440+
if (!webContents) {
441+
console.error("invalid window for context menu:", workspaceOrBuilderId);
442+
event.returnValue = true;
443+
return;
444+
}
442445
if (menuDefArr.length === 0) {
446+
webContents.send("contextmenu-click", null);
443447
event.returnValue = true;
444448
return;
445449
}
446450
fireAndForget(async () => {
447-
const webContents = getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId);
448-
if (!webContents) {
449-
console.error("invalid window for context menu:", workspaceOrBuilderId);
450-
return;
451-
}
452-
453-
const menu = convertMenuDefArrToMenu(webContents, menuDefArr);
454-
menu.popup();
451+
const menuState = { hasClick: false };
452+
const menu = convertMenuDefArrToMenu(webContents, menuDefArr, menuState);
453+
menu.popup({
454+
callback: () => {
455+
if (!menuState.hasClick) {
456+
webContents.send("contextmenu-click", null);
457+
}
458+
},
459+
});
455460
});
456461
event.returnValue = true;
457462
}

emain/preload.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ contextBridge.exposeInMainWorld("api", {
2121
showWorkspaceAppMenu: (workspaceId) => ipcRenderer.send("workspace-appmenu-show", workspaceId),
2222
showBuilderAppMenu: (builderId) => ipcRenderer.send("builder-appmenu-show", builderId),
2323
showContextMenu: (workspaceId, menu) => ipcRenderer.send("contextmenu-show", workspaceId, menu),
24-
onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", (_event, id) => callback(id)),
24+
onContextMenuClick: (callback: (id: string | null) => void) =>
25+
ipcRenderer.on("contextmenu-click", (_event, id: string | null) => callback(id)),
2526
downloadFile: (filePath) => ipcRenderer.send("download", { filePath }),
2627
openExternal: (url) => {
2728
if (url && typeof url === "string") {

eslint.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ export default [
7373

7474
{
7575
rules: {
76-
"@typescript-eslint/no-unused-vars": "warn",
76+
"@typescript-eslint/no-unused-vars": [
77+
"warn",
78+
{
79+
argsIgnorePattern: "^_$",
80+
varsIgnorePattern: "^_$",
81+
},
82+
],
7783
"prefer-const": "warn",
7884
"no-empty": "warn",
7985
},

frontend/app/app.scss

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,41 +96,3 @@ a {
9696
transition-delay: none !important;
9797
}
9898
}
99-
100-
.flash-error-container {
101-
position: absolute;
102-
right: 10px;
103-
bottom: 10px;
104-
z-index: var(--zindex-flash-error-container);
105-
display: flex;
106-
flex-direction: column;
107-
gap: 10px;
108-
109-
.flash-error {
110-
background: var(--error-color);
111-
color: var(--main-text-color);
112-
border-radius: 4px;
113-
padding: 10px;
114-
display: flex;
115-
flex-direction: column;
116-
width: 280px;
117-
border: 1px solid transparent;
118-
max-height: 100px;
119-
cursor: pointer;
120-
121-
.flash-error-scroll {
122-
overflow-y: auto;
123-
display: flex;
124-
flex-direction: column;
125-
}
126-
127-
&.hovered {
128-
border: 1px solid var(--main-text-color);
129-
}
130-
131-
.flash-error-title {
132-
font-weight: bold;
133-
margin-bottom: 5px;
134-
}
135-
}
136-
}

frontend/app/app.tsx

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import {
1313
getSettingsPrefixAtom,
1414
getTabIndicatorAtom,
1515
globalStore,
16-
isDev,
17-
removeFlashError,
1816
} from "@/store/global";
1917
import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel";
2018
import { getElemAsStr } from "@/util/focusutil";
@@ -25,12 +23,11 @@ import clsx from "clsx";
2523
import debug from "debug";
2624
import { Provider, useAtomValue } from "jotai";
2725
import "overlayscrollbars/overlayscrollbars.css";
28-
import { Fragment, useEffect, useState } from "react";
26+
import { useEffect } from "react";
2927
import { DndProvider } from "react-dnd";
3028
import { HTML5Backend } from "react-dnd-html5-backend";
3129
import { AppBackground } from "./app-bg";
3230
import { CenteredDiv } from "./element/quickelems";
33-
import { NotificationBubbles } from "./notification/notificationbubbles";
3431

3532
import "./app.scss";
3633

@@ -237,78 +234,6 @@ const TabIndicatorAutoClearing = () => {
237234
return null;
238235
};
239236

240-
const FlashError = () => {
241-
const flashErrors = useAtomValue(atoms.flashErrors);
242-
const [hoveredId, setHoveredId] = useState<string>(null);
243-
const [ticker, setTicker] = useState<number>(0);
244-
245-
useEffect(() => {
246-
if (flashErrors.length == 0 || hoveredId != null) {
247-
return;
248-
}
249-
const now = Date.now();
250-
for (let ferr of flashErrors) {
251-
if (ferr.expiration == null || ferr.expiration < now) {
252-
removeFlashError(ferr.id);
253-
}
254-
}
255-
setTimeout(() => setTicker(ticker + 1), 1000);
256-
}, [flashErrors, ticker, hoveredId]);
257-
258-
if (flashErrors.length == 0) {
259-
return null;
260-
}
261-
262-
function copyError(id: string) {
263-
const ferr = flashErrors.find((f) => f.id === id);
264-
if (ferr == null) {
265-
return;
266-
}
267-
let text = "";
268-
if (ferr.title != null) {
269-
text += ferr.title;
270-
}
271-
if (ferr.message != null) {
272-
if (text.length > 0) {
273-
text += "\n";
274-
}
275-
text += ferr.message;
276-
}
277-
navigator.clipboard.writeText(text);
278-
}
279-
280-
function convertNewlinesToBreaks(text) {
281-
return text.split("\n").map((part, index) => (
282-
<Fragment key={index}>
283-
{part}
284-
<br />
285-
</Fragment>
286-
));
287-
}
288-
289-
return (
290-
<div className="flash-error-container">
291-
{flashErrors.map((err, idx) => (
292-
<div
293-
key={idx}
294-
className={clsx("flash-error", { hovered: hoveredId === err.id })}
295-
onClick={() => copyError(err.id)}
296-
onMouseEnter={() => setHoveredId(err.id)}
297-
onMouseLeave={() => setHoveredId(null)}
298-
title="Click to Copy Error Message"
299-
>
300-
<div className="flash-error-scroll">
301-
{err.title != null ? <div className="flash-error-title">{err.title}</div> : null}
302-
{err.message != null ? (
303-
<div className="flash-error-message">{convertNewlinesToBreaks(err.message)}</div>
304-
) : null}
305-
</div>
306-
</div>
307-
))}
308-
</div>
309-
);
310-
};
311-
312237
const AppInner = () => {
313238
const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom);
314239
const client = useAtomValue(ClientModel.getInstance().clientAtom);
@@ -340,8 +265,6 @@ const AppInner = () => {
340265
<DndProvider backend={HTML5Backend}>
341266
<Workspace />
342267
</DndProvider>
343-
<FlashError />
344-
{isDev() ? <NotificationBubbles></NotificationBubbles> : null}
345268
</div>
346269
);
347270
};

frontend/app/block/blockframe-header.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { ConnectionButton } from "@/app/block/connectionbutton";
1212
import { DurableSessionFlyover } from "@/app/block/durable-session-flyover";
1313
import { ContextMenuModel } from "@/app/store/contextmenu";
14-
import { getConnStatusAtom, recordTEvent, WOS } from "@/app/store/global";
14+
import { recordTEvent, refocusNode, WOS } from "@/app/store/global";
1515
import { globalStore } from "@/app/store/jotaiStore";
1616
import { uxCloseBlock } from "@/app/store/keymodel";
1717
import { RpcApi } from "@/app/store/wshclientapi";
@@ -141,7 +141,10 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI
141141
<OptMagnifyButton
142142
key="unmagnify"
143143
magnified={magnified}
144-
toggleMagnify={nodeModel.toggleMagnify}
144+
toggleMagnify={() => {
145+
nodeModel.toggleMagnify();
146+
setTimeout(() => refocusNode(blockId), 50);
147+
}}
145148
disabled={magnifyDisabled}
146149
/>
147150
);
@@ -218,7 +221,13 @@ const BlockFrame_Header = ({
218221
/>
219222
)}
220223
{useTermHeader && termConfigedDurable != null && (
221-
<DurableSessionFlyover key="durable-status" blockId={nodeModel.blockId} viewModel={viewModel} placement="bottom" divClassName="iconbutton disabled text-[13px] ml-[-4px]" />
224+
<DurableSessionFlyover
225+
key="durable-status"
226+
blockId={nodeModel.blockId}
227+
viewModel={viewModel}
228+
placement="bottom"
229+
divClassName="iconbutton disabled text-[13px] ml-[-4px]"
230+
/>
222231
)}
223232
<HeaderTextElems viewModel={viewModel} blockData={blockData} preview={preview} error={error} />
224233
<HeaderEndIcons viewModel={viewModel} nodeModel={nodeModel} blockId={nodeModel.blockId} />

0 commit comments

Comments
 (0)