@forgeax/interface 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  forgeax 前端工程(Studio 三栏 UI)。
4
4
 
5
+ ## Application shortcut ownership (0.6.0)
6
+
7
+ > [!IMPORTANT]
8
+ > `configureInterfaceKeyboardRouter`, `KeyboardRouterDeps` and the private
9
+ > `editor-commands` extension have been removed. Products must install their
10
+ > Editor-owned keyboard extension through application bootstrap overrides.
11
+ > Interface consumes exact `@forgeax/app-shell@0.87.1`; no module-global Editor
12
+ > callback setter or second keyboard listener remains.
13
+
14
+ | Owner | Responsibility |
15
+ | :-- | :-- |
16
+ | App Shell | Per-host `shortcuts` registration, live snapshots and stale-callback fencing |
17
+ | Interface | One capture listener, contextual first refusal, shell bindings, typing/preview/surface guards and focused `text.selectAll` |
18
+ | Editor | Editor shortcuts, Play Escape, viewport callbacks and the twelve `editor.*` commands |
19
+
20
+ `useGlobalShortcuts(host.keybindings, host.shortcuts)` requires both host APIs.
21
+ Shortcut settings read `buildShortcuts(host.shortcuts)` so contributions added
22
+ or removed after boot are not hidden behind an earlier snapshot. A matched
23
+ shortcut returning `false` still terminates routing while yielding browser
24
+ default behavior. Shell bindings retain priority 10, Play-only Escape uses 20,
25
+ ordinary contributions use 0, and the palette fallback uses -20.
26
+
27
+ The normal/detached product shells, startup and global CSS remain Interface
28
+ entrypoints; this release does not complete their migration.
29
+
5
30
  ## Public action registry migration (0.5.0)
6
31
 
7
32
  > [!IMPORTANT]
@@ -25,7 +25,7 @@ import { isTauri } from './lib/platform/runtime';
25
25
  import './App.css';
26
26
  export { ApplicationDetachedShell } from './ApplicationDetachedShell';
27
27
  function KeyboardRouter({ runtime }) {
28
- useGlobalShortcuts(runtime.host.keybindings);
28
+ useGlobalShortcuts(runtime.host.keybindings, runtime.host.shortcuts);
29
29
  return null;
30
30
  }
31
31
  export function isApplicationOnboardingEnabled(onboarding) {
@@ -21,7 +21,6 @@ import { foundationStorageExtension } from './core/extensions/foundation-storage
21
21
  import { builtinCommandsExtension } from './core/extensions/builtin-commands';
22
22
  import { hostCommandsExtension } from './core/extensions/host-commands';
23
23
  import { builtinMenusExtension } from './core/extensions/builtin-menus';
24
- import { editorCommandsExtension } from './core/extensions/editor-commands';
25
24
  import { panelsViewportExtension } from './core/extensions/panels-viewport';
26
25
  import { panelsChatExtension } from './core/extensions/panels-chat';
27
26
  import { chromeStatusBarExtension } from './core/extensions/chrome-statusbar';
@@ -108,7 +107,6 @@ export async function bootstrapAppHost(overrides = {}) {
108
107
  builtinCommandsExtension,
109
108
  hostCommandsExtension,
110
109
  builtinMenusExtension,
111
- editorCommandsExtension,
112
110
  panelsViewportExtension,
113
111
  panelsChatExtension,
114
112
  chromeStatusBarExtension,
@@ -1,7 +1,6 @@
1
1
  import type { AppHost as ApplicationHost } from '@forgeax/app-shell/application';
2
2
  import { type AppHostBootstrapOverrides, type AppHostBootstrapResult } from './appHostBootstrap';
3
3
  import { type Locale } from './i18n';
4
- import { type KeyboardRouterDeps } from './lib/global-shortcuts';
5
4
  export type InterfaceApplicationRuntime = AppHostBootstrapResult & {
6
5
  readonly host: AppHostBootstrapResult['host'] & ApplicationHost;
7
6
  };
@@ -16,12 +15,6 @@ export interface InterfaceApplicationStartupOptions {
16
15
  * are observed. Product errors still propagate.
17
16
  */
18
17
  export declare function installApplicationOverlayRedirect(overlayId: string, onRedirect: () => void): () => void;
19
- /**
20
- * Configures product-owned editor callbacks consumed by Interface's single
21
- * keyboard router. Product assemblies use this public application boundary;
22
- * the router implementation remains private to Interface.
23
- */
24
- export declare function configureInterfaceKeyboardRouter(deps: KeyboardRouterDeps | null): void;
25
18
  /**
26
19
  * Starts the shared Interface application runtime without selecting a product
27
20
  * entry or product extensions. IDE and compatibility callers supply their own
@@ -29,5 +22,4 @@ export declare function configureInterfaceKeyboardRouter(deps: KeyboardRouterDep
29
22
  */
30
23
  export declare function startInterfaceApplication(overrides?: AppHostBootstrapOverrides, options?: InterfaceApplicationStartupOptions): Promise<InterfaceApplicationRuntime>;
31
24
  export type { AppHostBootstrapOverrides } from './appHostBootstrap';
32
- export type { KeyboardRouterDeps } from './lib/global-shortcuts';
33
25
  export { configureStudioDomainClients, type StudioDomainClients, } from './store-parts/domain-clients';
@@ -2,7 +2,6 @@ import { bootstrapAppHost, } from './appHostBootstrap';
2
2
  import { bootStageAppMounted } from './boot/driver';
3
3
  import { changeLanguage, initI18n } from './i18n';
4
4
  import { useShellStore } from './store';
5
- import { registerKeyboardRouterDeps, } from './lib/global-shortcuts';
6
5
  /**
7
6
  * Lets a product replace one shell overlay with its own destination. Interface
8
7
  * owns observation, clearing and synchronous reentry; the product owns routing.
@@ -24,14 +23,6 @@ export function installApplicationOverlayRedirect(overlayId, onRedirect) {
24
23
  }
25
24
  });
26
25
  }
27
- /**
28
- * Configures product-owned editor callbacks consumed by Interface's single
29
- * keyboard router. Product assemblies use this public application boundary;
30
- * the router implementation remains private to Interface.
31
- */
32
- export function configureInterfaceKeyboardRouter(deps) {
33
- registerKeyboardRouterDeps(deps);
34
- }
35
26
  /**
36
27
  * Starts the shared Interface application runtime without selecting a product
37
28
  * entry or product extensions. IDE and compatibility callers supply their own
@@ -13,8 +13,9 @@ import { derivePanelRenderers } from './derive-panel-renderers';
13
13
  import { DEFAULT_PANEL_RENDERERS } from '../../components/DockShell/panelRenderers';
14
14
  import { installPageNavigation } from '../page-navigation';
15
15
  import { createContextualKeybindings } from '../contextual-keybindings';
16
+ import { createApplicationShortcutRegistry } from '@forgeax/app-shell/application';
16
17
  const BUILT_IN_CAPS = [
17
- 'commands', 'keybindings', 'bus', 'storage', 'panels', 'panelActions', 'panelControls', 'contextKeys', 'pages',
18
+ 'commands', 'keybindings', 'shortcuts', 'bus', 'storage', 'panels', 'panelActions', 'panelControls', 'contextKeys', 'pages',
18
19
  'activities', 'resourceEditors',
19
20
  ];
20
21
  export function createAppHost(deps = {}) {
@@ -23,6 +24,7 @@ export function createAppHost(deps = {}) {
23
24
  for (const c of BUILT_IN_CAPS)
24
25
  caps.add(c);
25
26
  const commands = createCommandsRegistry();
27
+ const shortcuts = createApplicationShortcutRegistry();
26
28
  const keybindings = createContextualKeybindings(commands, {
27
29
  onCommandError(error, commandId) {
28
30
  log.error(`[app-shell] keybinding command "${commandId}" failed`, error);
@@ -62,7 +64,7 @@ export function createAppHost(deps = {}) {
62
64
  const extensionFields = {};
63
65
  let activeSetup = null;
64
66
  const base = {
65
- commands, keybindings, bus, storage, contextKeys,
67
+ commands, keybindings, shortcuts, bus, storage, contextKeys,
66
68
  get panels() { return panelsSnapshot(); },
67
69
  panelActions,
68
70
  panelControls,
@@ -181,6 +183,7 @@ export function createAppHost(deps = {}) {
181
183
  removePageNavigation();
182
184
  await pageSession.dispose();
183
185
  keybindings.dispose();
186
+ shortcuts.dispose();
184
187
  bus.destroy();
185
188
  },
186
189
  };
@@ -1,3 +1,4 @@
1
+ import type { ApplicationShortcutRegistry } from '@forgeax/app-shell/application';
1
2
  import type { ExtensionManifest, Cleanup } from '../extension-foundation';
2
3
  import type { EventBus } from '../extension-foundation/bus';
3
4
  import type { CommandsRegistry, CommandDescriptor } from '../extension-foundation/commands';
@@ -103,7 +104,7 @@ export interface AppBusEventMap extends Record<string, unknown> {
103
104
  provider: string;
104
105
  };
105
106
  }
106
- export type HostCapability = 'commands' | 'keybindings' | 'bus' | 'storage' | 'panels' | 'panelActions' | 'panelControls' | 'contextKeys' | 'pages' | 'activities' | 'resourceEditors' | 'session' | 'observability' | 'editor' | (string & {});
107
+ export type HostCapability = 'commands' | 'keybindings' | 'shortcuts' | 'bus' | 'storage' | 'panels' | 'panelActions' | 'panelControls' | 'contextKeys' | 'pages' | 'activities' | 'resourceEditors' | 'session' | 'observability' | 'editor' | (string & {});
107
108
  export interface AppLogger {
108
109
  debug(message: string, ...rest: unknown[]): void;
109
110
  info(message: string, ...rest: unknown[]): void;
@@ -113,6 +114,7 @@ export interface AppLogger {
113
114
  export interface AppHostBase {
114
115
  readonly commands: CommandsRegistry;
115
116
  readonly keybindings: ContextualKeybindingsApi;
117
+ readonly shortcuts: ApplicationShortcutRegistry;
116
118
  readonly bus: EventBus<AppBusEventMap>;
117
119
  readonly storage: StorageApi;
118
120
  readonly contextKeys: ContextKeysApi;
@@ -38,6 +38,7 @@ export const builtinCommandsExtension = {
38
38
  registerTextEditCommand('text.cut', '剪切输入框选区', 'cut');
39
39
  registerTextEditCommand('text.copy', '复制输入框选区', 'copy');
40
40
  registerTextEditCommand('text.paste', '粘贴到输入框', 'paste');
41
+ registerTextEditCommand('text.selectAll', 'Select all focused text', 'selectAll');
41
42
  cleanups.push(registerCommand({
42
43
  id: 'app.panel.open',
43
44
  title: 'Open (or focus) a dock panel by id',
@@ -1288,22 +1288,12 @@
1288
1288
  "toggleDashboard": "Toggle Dashboard overview panel",
1289
1289
  "toggleSettings": "Open / close Settings overlay",
1290
1290
  "openChangelog": "Settings → About (changelog)",
1291
- "hideSelected": "Hide selected entities (UE parity · editor-only, never affects the game)",
1292
- "hideUnselected": "Hide unselected entities (isolate selection)",
1293
- "showAllHidden": "Show all hidden entities",
1294
1291
  "closeOverlay": "Close current overlay · browser fullscreen → game fullscreen → Settings → Dashboard in order",
1295
1292
  "modePreview": "Switch to Preview mode",
1296
1293
  "modePage": "Switch to Page mode",
1297
1294
  "switchPageN": "Switch to extension {{n}} (Blender parity — indexed into extension list)",
1298
1295
  "openExtensions": "Settings → Extensions (was the Bus mode tab)",
1299
1296
  "focusComposer": "Focus the right ChatPanel composer",
1300
- "duplicateSelection": "Duplicate selection",
1301
- "toggleViewportGameView": "Toggle viewport Game View",
1302
- "undo": "Undo",
1303
- "redo": "Redo",
1304
- "save": "Save",
1305
- "viewportNavigation": "Viewport navigation and gizmo mode",
1306
- "shieldGameInput": "Shield game input while editor-owned",
1307
1297
  "toggleCommandPalette": "Toggle command palette"
1308
1298
  },
1309
1299
  "analytics": {
@@ -1288,22 +1288,12 @@
1288
1288
  "toggleDashboard": "切换 Dashboard 总览面板",
1289
1289
  "toggleSettings": "打开 / 关闭 Settings 浮层",
1290
1290
  "openChangelog": "Settings → 关于(更新日志)",
1291
- "hideSelected": "隐藏选中实体(UE 对标 · 编辑器临时隐藏,不影响游戏)",
1292
- "hideUnselected": "隐藏未选中实体(孤立选择)",
1293
- "showAllHidden": "显示所有隐藏实体",
1294
1291
  "closeOverlay": "关闭当前浮层 · 浏览器全屏 → 游戏全屏 → Settings → Dashboard 依次撤回",
1295
1292
  "modePreview": "切到 Preview 模式",
1296
1293
  "modePage": "切到 Page 模式",
1297
1294
  "switchPageN": "切到第 {{n}} 个 extension(Blender 风格 · 按列表索引)",
1298
1295
  "openExtensions": "Settings → Extensions(原 Bus mode tab)",
1299
1296
  "focusComposer": "聚焦右侧 ChatPanel 输入框",
1300
- "duplicateSelection": "复制选中项",
1301
- "toggleViewportGameView": "切换视口 Game View",
1302
- "undo": "撤销",
1303
- "redo": "重做",
1304
- "save": "保存",
1305
- "viewportNavigation": "视口导航与 Gizmo 模式",
1306
- "shieldGameInput": "编辑器接管时屏蔽游戏输入",
1307
1297
  "toggleCommandPalette": "切换命令面板"
1308
1298
  },
1309
1299
  "analytics": {
@@ -10,7 +10,7 @@
10
10
  * Ctrl+/ focus chat composer
11
11
  * Ctrl+Shift+H open Settings → Changelog (was Ctrl+H before 2026-08-04 —
12
12
  * UE-parity editor hide claimed Ctrl+H for "show all hidden")
13
- * Esc stop viewport Play; otherwise close current overlay
13
+ * Esc close current overlay (higher-priority owners may claim it)
14
14
  * (Settings → Dashboard → Fullscreen)
15
15
  *
16
16
  * IME 安全:
@@ -21,75 +21,15 @@
21
21
  *
22
22
  * 注册:在 App 顶层调用 `useGlobalShortcuts()` 一次。
23
23
  */
24
+ import type { ApplicationShortcut, ApplicationShortcutRegistry } from '@forgeax/app-shell/application';
24
25
  import type { ContextualKeybindingsApi } from '../core/contextual-keybindings';
25
- export interface ShortcutDef {
26
- /** 显示给用户的字符串,e.g. "Ctrl+Shift+F"。Mac 上 UI 会自动替换 Ctrl → ⌘/⌃。 */
27
- combo: string;
28
- /** 触发条件:keydown event → boolean。 */
29
- match: (e: KeyboardEvent) => boolean;
30
- /** 一行描述,显示在 Settings 表里。 */
31
- label: string;
32
- /** 分组(Layout / Mode / Overlay / Focus / general / edit)。 */
33
- group: 'layout' | 'mode' | 'overlay' | 'focus' | 'general' | 'edit';
34
- /** 触发动作。返回 true 表示 preventDefault。 */
35
- run: (e?: KeyboardEvent) => boolean | void;
36
- /** Esc 这种允许在 input 里触发(其他必须 target 不是 editable 才触发)。 */
37
- allowInInput?: boolean;
38
- }
26
+ export type ShortcutDef = ApplicationShortcut;
39
27
  export declare function isTypingTarget(e: KeyboardEvent): boolean;
40
28
  /** Preview canvases own their edit-domain keyboard input locally. */
41
29
  export declare function isKeyboardOwnedSurface(e: KeyboardEvent): boolean;
42
30
  export declare function shouldSkipGlobalShortcut(e: KeyboardEvent, shortcut: Pick<ShortcutDef, 'group'>): boolean;
43
31
  export declare function isComposing(e: KeyboardEvent): boolean;
44
32
  export declare function isEditorSurfaceActive(): boolean;
45
- export interface RouterSelectedAsset {
46
- guid: string;
47
- kind: string;
48
- name: string;
49
- packPath: string;
50
- payload: Record<string, unknown>;
51
- }
52
- export interface KeyboardRouterDeps {
53
- /** Dispatch an editor op through the one gateway door. */
54
- dispatch: (op: {
55
- kind: string;
56
- [k: string]: unknown;
57
- }, origin?: string) => void;
58
- /** Current entity-selection handles (for Ctrl+D / H routing). */
59
- getEntitySelection: () => number[];
60
- /** Current asset-selection list (for Ctrl+D routing). */
61
- getAssetSelection: () => RouterSelectedAsset[];
62
- /** Derive of "who was selected last" — retained for not-yet-migrated Ctrl+D. */
63
- getLastSelectionDomain: () => 'entity' | 'asset' | 'folder' | null;
64
- /** True under ▶ Play. */
65
- isPlayMode: () => boolean;
66
- /** Current viewport display axis (for G / Shift+G Game View toggle, AC-Cb4). */
67
- getDisplay: () => 'scene' | 'game';
68
- /** Current input owner. */
69
- getInputTarget: () => 'editor' | 'game';
70
- /** Legacy command-registry support; no longer bound to global Delete. */
71
- deleteEntities: (ids: number[]) => void;
72
- /** Entity: duplicate the given handles. */
73
- duplicateEntities: (ids: number[]) => void;
74
- /** Entity: hide the given handles (H — UE parity; one transaction, one undo). */
75
- hideEntities: (ids: number[]) => void;
76
- /** Entity: show every hidden entity (Ctrl+H — UE parity). */
77
- showAllHidden: () => void;
78
- /** Entity: hide everything not selected (Shift+H — isolate selection). */
79
- hideUnselected: () => void;
80
- /** Legacy command-registry support; no longer bound to global Mod+A. */
81
- selectAllEntities: () => void;
82
- /** Asset: duplicate the given asset (guid, packPath). */
83
- duplicateAsset: (guid: string, packPath: string) => void;
84
- /** Editor history actions, injected so the interface stays editor-agnostic. */
85
- undo: () => void;
86
- redo: () => void;
87
- save: () => void;
88
- /** Recreate the active Play/runtime surface after a terminal viewport failure. */
89
- restartPreview?: () => void;
90
- /** Viewport W/E/R/F handling, including fly-mode/input ownership policy. */
91
- handleViewportKeyDown: (event: KeyboardEvent) => void;
92
- }
93
33
  /**
94
34
  * Register a short-lived keydown owner for an interaction that is not a
95
35
  * discoverable editor command (for example, Escape while a dock drag is
@@ -99,16 +39,7 @@ export interface KeyboardRouterDeps {
99
39
  export type GlobalKeydownHandler = (event: KeyboardEvent) => boolean;
100
40
  export declare function registerGlobalKeydownHandler(handler: GlobalKeydownHandler): () => void;
101
41
  export declare function dispatchGlobalKeydownHandlers(event: KeyboardEvent): boolean;
102
- /** Inject the editor-side callbacks the router needs. Called once at host boot
103
- * (forgeax-editor standalone/main.tsx) BEFORE the App mounts (useGlobalShortcuts
104
- * reads this at effect time, which is after mount, so registration first is safe). */
105
- export declare function registerKeyboardRouterDeps(deps: KeyboardRouterDeps | null): void;
106
- /** Read the currently injected router deps. Returns null until the host has
107
- * called registerKeyboardRouterDeps() at boot. Command-bus wrappers
108
- * (editor-commands extension) resolve deps lazily via this getter so they
109
- * stay editor-agnostic and boot-order-safe. */
110
- export declare function getKeyboardRouterDeps(): KeyboardRouterDeps | null;
111
- export declare function buildShortcuts(): ShortcutDef[];
42
+ export declare function buildShortcuts(contributions?: ApplicationShortcutRegistry): ShortcutDef[];
112
43
  /**
113
44
  * Mount once at App root. Returns nothing — purely an effect.
114
45
  *
@@ -117,5 +48,5 @@ export declare function buildShortcuts(): ShortcutDef[];
117
48
  * Ctrl+Shift+1/2/3 (which Chrome maps to tab switching only WITHOUT Shift,
118
49
  * so we're safe, but we preventDefault anyway).
119
50
  */
120
- export declare function useGlobalShortcuts(keybindings?: ContextualKeybindingsApi): void;
51
+ export declare function useGlobalShortcuts(keybindings: ContextualKeybindingsApi, contributions: ApplicationShortcutRegistry): void;
121
52
  export declare function prettyCombo(combo: string, platform?: 'mac' | 'windows'): string;