Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/electron/src/renderer/actions/action-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const ACTION_LABEL_KEYS: Partial<Record<ActionId, string>> = {
'panel.focusPrev': 'shortcuts.action.focusPrevPanel',
'chat.stopProcessing': 'shortcuts.action.stopProcessing',
'chat.cyclePermissionMode': 'shortcuts.action.cyclePermissionMode',
'chat.openThinkingMenu': 'shortcuts.action.openThinkingMenu',
'chat.nextSearchMatch': 'shortcuts.action.nextSearchMatch',
'chat.prevSearchMatch': 'shortcuts.action.prevSearchMatch',
}
Expand Down
7 changes: 7 additions & 0 deletions apps/electron/src/renderer/actions/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ export const actions = {
category: 'Chat',
when: '!inputFocus && !menuOpen',
},
'chat.openThinkingMenu': {
id: 'chat.openThinkingMenu',
label: 'Open Thinking Menu',
description: 'Open the thinking (reasoning effort) menu in the chat composer',
defaultHotkey: 'mod+shift+e',
category: 'Chat',
},
'chat.nextSearchMatch': {
id: 'chat.nextSearchMatch',
label: 'Next Search Match',
Expand Down
7 changes: 7 additions & 0 deletions apps/electron/src/renderer/components/app-shell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ import { hasOpenOverlay } from '@/lib/overlay-detection'
import { getNextPermissionMode } from '@/lib/permission-mode-cycle'
import { clearSourceIconCaches } from '@/lib/icon-cache'
import { dispatchFocusInputEvent } from './input/focus-input-events'
import { dispatchOpenComposerMenuEvent } from './input/composer-menu-events'
import { resolveEffectiveConnectionSlug } from '@config/llm-connections'
import { getWorkspaceDisplayName } from '@/utils/workspace'

Expand Down Expand Up @@ -1820,6 +1821,12 @@ function AppShellContent({
useAction('nav.goBackAlt', goBack)
useAction('nav.goForwardAlt', goForward)

// Open the composer's thinking (reasoning effort) menu (CMD+SHIFT+E).
// Targets the focused panel's composer via the scoped composer-menu event.
useAction('chat.openThinkingMenu', () =>
dispatchOpenComposerMenuEvent({ menu: 'thinking' }),
)

// Search match navigation (CMD+G next, CMD+SHIFT+G prev)
useAction(
'chat.nextSearchMatch',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ import {
clearPendingFocusForSession,
consumePendingFocusForSession,
} from './focus-input-events';
import {
COMPOSER_MENU_EVENT,
type ComposerMenuEventDetail,
} from './composer-menu-events';
import {
getRecentWorkingDirs,
addRecentWorkingDir,
Expand Down Expand Up @@ -1212,6 +1216,33 @@ export function FreeFormInput({
window.removeEventListener('craft:focus-input', handleFocusInput);
}, [sessionId, isFocusedPanel, richInputRef]);

// Listen for craft:open-composer-menu events (open a toolbar menu from a
// global keyboard shortcut). Scoped like focus-input so the focused panel's
// composer responds.
React.useEffect(() => {
const handleOpenMenu = (e: Event) => {
const detail = (e as CustomEvent<ComposerMenuEventDetail>).detail;
if (
!shouldHandleScopedInputEvent({
sessionId,
isFocusedPanel,
targetSessionId: detail?.sessionId,
})
)
return;

if (detail?.menu === 'thinking') {
// The thinking picker only renders in the full (non-compact) toolbar.
if (compactMode || !onThinkingLevelChange) return;
setThinkingDropdownOpen(true);
}
};

window.addEventListener(COMPOSER_MENU_EVENT, handleOpenMenu);
return () =>
window.removeEventListener(COMPOSER_MENU_EVENT, handleOpenMenu);
}, [sessionId, isFocusedPanel, compactMode, onThinkingLevelChange]);

// Recover queued focus requests after session switch/mount races.
React.useEffect(() => {
if (!consumePendingFocusForSession(sessionId)) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Cross-component bridge for opening a composer toolbar menu from a global
* keyboard shortcut.
*
* The action registry lives at the app shell (it owns the global hotkey
* listener), while the composer's toolbar dropdowns (thinking / model) own
* their controlled `open` state inside `FreeFormInput`. This event lets the
* shell ask the focused composer to open one of its menus, mirroring the
* scoping model of `focus-input-events.ts` so multi-panel layouts target the
* right composer.
*/

/** Composer toolbar menus that can be opened via a shortcut. */
export type ComposerMenu = 'thinking'

export interface ComposerMenuEventDetail {
/** Restrict to a specific session's composer; omit to target the focused panel. */
sessionId?: string
menu: ComposerMenu
}

export const COMPOSER_MENU_EVENT = 'craft:open-composer-menu'

/**
* Ask a composer to open one of its toolbar menus. With no `sessionId`, the
* focused panel's composer handles it (see `shouldHandleScopedInputEvent`).
*/
export function dispatchOpenComposerMenuEvent(detail: ComposerMenuEventDetail): void {
window.dispatchEvent(
new CustomEvent<ComposerMenuEventDetail>(COMPOSER_MENU_EVENT, { detail }),
)
}
11 changes: 7 additions & 4 deletions docs/loop/feature-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ log, not the system of record.

| slug | title | source | feasibility | status | issue | pr | branch | updated | notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | pr-open | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-03 | Renderer-only pref (localStorage) applied app-wide via `<MotionConfig reducedMotion>` + `data-reduce-motion` on `<html>` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; i18n parity ✅. CDP assertion included; **could not run locally** (egress 403s Electron binary download). |
| composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude/ChatGPT/Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-03 | Opened by a prior run. Adds `isComposerExpanded` toggle in `FreeFormInput`; 2 new i18n keys. Awaiting review. |
| scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code / ChatGPT / Codex desktop | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-02 | Opened by a prior run. Floating jump button in `ChatDisplay` + `seed()` harness hook. Awaiting review. |
| thinking-level-picker | Thinking-level (reasoning effort) picker in the chat composer | Claude Code Desktop effort menu (⌘⇧E) + OpenWork's own model picker | frontend-only | merged | [#44](https://github.com/modelstudioai/openwork/issues/44) | [#45](https://github.com/modelstudioai/openwork/pull/45) | loop/thinking-level-picker | 2026-07-03 | **Merged** into `main` (2026-07-02). `thinkingLevel`/`onThinkingLevelChange` already plumbed to `FreeFormInput`; only the UI trigger was missing. Reuses `thinking.*` + `settings.ai.thinking` i18n keys (zero new keys). |
| thinking-menu-shortcut | Keyboard shortcut (⌘⇧E) to open the composer's thinking (effort) menu | Claude Code Desktop effort-menu shortcut (⌘⇧E); explicit follow-up flagged in #44 | frontend-only | pr-open | [#54](https://github.com/modelstudioai/openwork/issues/54) | [#55](https://github.com/modelstudioai/openwork/pull/55) | loop/thinking-menu-shortcut | 2026-07-04 | New `chat.openThinkingMenu` action (`mod+shift+e`) → scoped `craft:open-composer-menu` event → `FreeFormInput` opens its already-controlled `thinkingDropdownOpen`. Mirrors `craft:focus-input`/`shouldHandleScopedInputEvent` for multi-panel safety. Auto-appears in Command Palette + Shortcuts reference. 1 new i18n key across 7 locales. Avoids ⌘⇧I (reserved for DevTools) / ⌘⇧M (permission mode already has Shift+Tab). typecheck zero-delta (11 pre-existing); `bun test` zero-delta (56 identical failures, diffed vs main); i18n parity OK; renderer build ✅; assertion transpiles ✅. **CDP blocked locally** (same egress limit: `libsignal` GitHub dep + Electron binary 403). |
| command-palette-search-bug (#43) | `Cmd+F` / `app.search` doesn't activate session search from its shortcut | Bug report | frontend-only | proposed | [#43](https://github.com/modelstudioai/openwork/issues/43) | — | — | 2026-07-04 | Open bug (no PR yet). Needs a seeded-session repro to diff the button path vs registry-`execute` path. Not selected this round. |
| composer-prompt-history | Recall previously-sent prompts in the composer with Up/Down arrows | Claude Code CLI / Codex desktop composer history recall | frontend-only | pr-open | [#52](https://github.com/modelstudioai/openwork/issues/52) | [#53](https://github.com/modelstudioai/openwork/pull/53) | loop/composer-prompt-history | 2026-07-04 | Reconciled from GitHub. Awaiting review. |
| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion; `prefers-reduced-motion` | frontend-only | pr-open | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-04 | Reconciled from GitHub. Awaiting review. |
| composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude / ChatGPT / Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-04 | Reconciled from GitHub. Awaiting review. |
| scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code Desktop / ChatGPT / Codex desktop jump-to-latest | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-04 | Reconciled from GitHub. Added a reusable `seed(profileDirs)` harness hook. Awaiting review. |
| thinking-level-picker | Thinking-level (reasoning effort) picker in the chat composer | Claude Code Desktop effort menu (⌘⇧E) + OpenWork's own model picker | frontend-only | merged | [#44](https://github.com/modelstudioai/openwork/issues/44) | [#45](https://github.com/modelstudioai/openwork/pull/45) | loop/thinking-level-picker | 2026-07-04 | **Merged** into `main` (commit `26f609c`). `thinkingLevel`/`onThinkingLevelChange` already plumbed to `FreeFormInput`; only the UI trigger was missing. Reuses `thinking.*` + `settings.ai.thinking` i18n keys (zero new keys). |
| command-palette | Global command palette (⌘K/Ctrl+K) to search & run any action | Claude Code Desktop ⌘K / VS Code & Codex ⌘⇧P / Linear ⌘K | frontend-only | merged | [#41](https://github.com/modelstudioai/openwork/issues/41) | [#42](https://github.com/modelstudioai/openwork/pull/42) | loop/command-palette | 2026-07-02 | Merged into `main`. Reuses action registry `execute()` + cmdk primitives; zero new i18n keys. CDP e2e 2/2 pass. typecheck/test +0 vs main. |
| settings-search | Searchable/filterable settings navigation | Claude Code Desktop / VS Code / Codex desktop settings search | frontend-only | merged | [#39](https://github.com/modelstudioai/openwork/issues/39) | [#40](https://github.com/modelstudioai/openwork/pull/40) | loop/settings-search | 2026-07-01 | Merged into `main`. Filters `SettingsNavigator` by title+description; reuses `common.search`/`common.noResultsFound` (no new locale keys). Also hardened `e2e/app.ts` teardown (per-launch profile dir + setsid process-group kill) so multiple CDP assertions run under headless xvfb. |
93 changes: 93 additions & 0 deletions e2e/assertions/thinking-menu-shortcut.assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Feature assertion: the keyboard shortcut that opens the composer's thinking
* (reasoning effort) menu.
*
* Mirrors Claude Code Desktop's effort-menu shortcut (⌘⇧E). Drives the real
* built app over CDP:
* composer renders a thinking-level trigger with its menu CLOSED → dispatch
* the Cmd/Ctrl+Shift+E keydown at the window level → the thinking menu OPENS
* (lists all six levels).
*
* Asserting the menu is closed *before* the keypress and open *after* it proves
* the shortcut actually opens the menu — not merely that the menu can render.
*/

import type { Assertion } from '../runner';

const TRIGGER = '[data-testid="thinking-level-trigger"]';
const ITEM = '[data-testid="thinking-level-item"]';

/** The six thinking levels are the single source of truth for the count. */
const EXPECTED_LEVEL_COUNT = 6;

/** Menu items that are actually visible (Radix renders them in a portal). */
const VISIBLE_ITEMS_EXPR = `[...document.querySelectorAll(${JSON.stringify(
ITEM,
)})].filter((el) => el.offsetParent !== null)`;

/**
* Dispatch the effort-menu shortcut at the window level, where the action
* registry's capture-phase keydown listener lives. `mod` resolves to Cmd on
* macOS and Ctrl elsewhere; set both `metaKey` and `ctrlKey` so the same event
* matches on either platform (the registry only checks the platform's mod key).
*/
const PRESS_SHORTCUT_EXPR = `(() => {
window.dispatchEvent(new KeyboardEvent('keydown', {
key: 'e',
code: 'KeyE',
ctrlKey: true,
metaKey: true,
shiftKey: true,
bubbles: true,
cancelable: true,
}));
return true;
})()`;

const assertion: Assertion = {
name: 'Cmd/Ctrl+Shift+E opens the composer thinking-level menu',
async run(app) {
const { session } = app;

// App fully mounted.
await session.waitForFunction(
'!document.getElementById("_loader") && (document.getElementById("root")?.childElementCount ?? 0) > 0',
{ timeoutMs: 30000, message: 'React UI did not mount' },
);

// Reach the ready AppShell (not onboarding / workspace picker) — the same
// stable, non-localized anchor the other composer assertions wait on.
await session.waitForSelector('[aria-label="Craft menu"]', {
timeoutMs: 30000,
message: 'app did not reach the ready AppShell state',
});

// The composer renders a thinking-level trigger.
await session.waitForSelector(TRIGGER, {
timeoutMs: 20000,
message: 'thinking-level picker trigger did not render in the composer',
});

// Precondition: the menu is closed (no visible items).
if ((await session.evaluate<number>(`${VISIBLE_ITEMS_EXPR}.length`)) !== 0) {
throw new Error('thinking-level menu was already open before pressing the shortcut');
}

// Press the shortcut at the window level.
if (!(await session.evaluate<boolean>(PRESS_SHORTCUT_EXPR))) {
throw new Error('failed to dispatch the Cmd/Ctrl+Shift+E keydown');
}

// The menu opens and lists all six thinking levels — proving the shortcut
// opened it.
await session.waitForFunction(
`${VISIBLE_ITEMS_EXPR}.length === ${EXPECTED_LEVEL_COUNT}`,
{
timeoutMs: 8000,
message: `thinking-level menu did not open (expected ${EXPECTED_LEVEL_COUNT} levels) after pressing Cmd/Ctrl+Shift+E`,
},
);
},
};

export default assertion;
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "Neuer Chat im Panel",
"shortcuts.action.newWindow": "Neues Fenster",
"shortcuts.action.nextSearchMatch": "Nächster Treffer",
"shortcuts.action.openThinkingMenu": "Denk-Menü öffnen",
"shortcuts.action.prevSearchMatch": "Vorheriger Treffer",
"shortcuts.action.quit": "Beenden",
"shortcuts.action.search": "Suchen",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "New Chat in Panel",
"shortcuts.action.newWindow": "New Window",
"shortcuts.action.nextSearchMatch": "Next Search Match",
"shortcuts.action.openThinkingMenu": "Open Thinking Menu",
"shortcuts.action.prevSearchMatch": "Previous Search Match",
"shortcuts.action.quit": "Quit",
"shortcuts.action.search": "Search",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "Nuevo chat en panel",
"shortcuts.action.newWindow": "Nueva ventana",
"shortcuts.action.nextSearchMatch": "Siguiente coincidencia",
"shortcuts.action.openThinkingMenu": "Abrir menú de razonamiento",
"shortcuts.action.prevSearchMatch": "Coincidencia anterior",
"shortcuts.action.quit": "Salir",
"shortcuts.action.search": "Buscar",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "Új chat a panelben",
"shortcuts.action.newWindow": "Új ablak",
"shortcuts.action.nextSearchMatch": "Következő találat",
"shortcuts.action.openThinkingMenu": "Gondolkodás menü megnyitása",
"shortcuts.action.prevSearchMatch": "Előző találat",
"shortcuts.action.quit": "Kilépés",
"shortcuts.action.search": "Keresés",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "パネルで新規チャット",
"shortcuts.action.newWindow": "新規ウィンドウ",
"shortcuts.action.nextSearchMatch": "次の検索結果",
"shortcuts.action.openThinkingMenu": "思考メニューを開く",
"shortcuts.action.prevSearchMatch": "前の検索結果",
"shortcuts.action.quit": "終了",
"shortcuts.action.search": "検索",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "Nowy czat w panelu",
"shortcuts.action.newWindow": "Nowe okno",
"shortcuts.action.nextSearchMatch": "Następny wynik wyszukiwania",
"shortcuts.action.openThinkingMenu": "Otwórz menu myślenia",
"shortcuts.action.prevSearchMatch": "Poprzedni wynik wyszukiwania",
"shortcuts.action.quit": "Zakończ",
"shortcuts.action.search": "Szukaj",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/i18n/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,7 @@
"shortcuts.action.newChatInPanel": "在面板中新建聊天",
"shortcuts.action.newWindow": "新建窗口",
"shortcuts.action.nextSearchMatch": "下一个搜索匹配",
"shortcuts.action.openThinkingMenu": "打开思考菜单",
"shortcuts.action.prevSearchMatch": "上一个搜索匹配",
"shortcuts.action.quit": "退出",
"shortcuts.action.search": "搜索",
Expand Down