@forgeax/interface 0.9.1 → 0.9.3

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.
@@ -61,14 +61,12 @@ export async function bootstrapAppHost(overrides = {}) {
61
61
  ...m,
62
62
  async setup(ctx) {
63
63
  control.beginSetup(m);
64
- const menuCleanups = [];
64
+ const cleanups = [];
65
+ let pageCleanup;
65
66
  try {
66
- const cleanups = [];
67
- let pageCleanup;
68
67
  for (const menu of m.contributes?.menus ?? []) {
69
- menuCleanups.push(host.menus.register(menu));
68
+ cleanups.push(host.menus.register(menu));
70
69
  }
71
- cleanups.push(...menuCleanups);
72
70
  if (m.contributes?.panels) {
73
71
  cleanups.push(control.contributePanels(m.id, m.contributes.panels));
74
72
  }
@@ -100,7 +98,10 @@ export async function bootstrapAppHost(overrides = {}) {
100
98
  };
101
99
  }
102
100
  catch (error) {
103
- for (const cleanup of menuCleanups.slice().reverse())
101
+ // Preserve the same page-close decision boundary on failed setup.
102
+ // A dirty page must not lose its surrounding contributions underneath it.
103
+ await pageCleanup?.();
104
+ for (const cleanup of cleanups.slice().reverse())
104
105
  await cleanup();
105
106
  throw error;
106
107
  }
@@ -1,6 +1,4 @@
1
- import type { Cleanup } from '../extension-foundation/types';
2
- import type { PanelActionContribution, PanelActionsApi } from './types';
3
- export interface PanelActionRegistry extends PanelActionsApi {
4
- contribute(owner: string, actions: readonly PanelActionContribution[]): Cleanup;
5
- }
6
- export declare function createPanelActionRegistry(): PanelActionRegistry;
1
+ import { type PanelActionRegistry as Registry } from '@forgeax/app-shell/application';
2
+ import type { PanelActionContribution } from './types';
3
+ export type PanelActionRegistry = Registry<PanelActionContribution>;
4
+ export declare const createPanelActionRegistry: () => Registry<PanelActionContribution>;
@@ -1,62 +1,2 @@
1
- export function createPanelActionRegistry() {
2
- const entries = [];
3
- const listeners = new Set();
4
- let cache = null;
5
- let version = 0;
6
- const emit = () => {
7
- cache = null;
8
- version++;
9
- for (const listener of [...listeners])
10
- listener();
11
- };
12
- // Batch-deferred notification: data mutations (push/splice) happen
13
- // immediately so reads (list/all) always return fresh data, but listener
14
- // notifications are deferred to a microtask. This avoids triggering
15
- // forceStoreRerender (useSyncExternalStore) during React 19's commit
16
- // phase, which otherwise causes "Maximum update depth exceeded".
17
- let emitScheduled = false;
18
- const scheduleEmit = () => {
19
- cache = null;
20
- if (emitScheduled)
21
- return;
22
- emitScheduled = true;
23
- queueMicrotask(() => {
24
- emitScheduled = false;
25
- emit();
26
- });
27
- };
28
- const all = () => {
29
- if (cache)
30
- return cache;
31
- cache = entries.flatMap((entry) => entry.actions).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
32
- return cache;
33
- };
34
- return {
35
- contribute(owner, actions) {
36
- const entry = { owner, actions };
37
- entries.push(entry);
38
- scheduleEmit();
39
- let removed = false;
40
- return () => {
41
- if (removed)
42
- return;
43
- removed = true;
44
- const index = entries.indexOf(entry);
45
- if (index >= 0)
46
- entries.splice(index, 1);
47
- scheduleEmit();
48
- };
49
- },
50
- list(panelId) {
51
- return all().filter((action) => action.panelId === panelId);
52
- },
53
- all,
54
- onChange(listener) {
55
- listeners.add(listener);
56
- return () => { listeners.delete(listener); };
57
- },
58
- version() {
59
- return version;
60
- },
61
- };
62
- }
1
+ import { createPanelActionRegistry as createRegistry, } from '@forgeax/app-shell/application';
2
+ export const createPanelActionRegistry = (createRegistry);
@@ -1,6 +1,4 @@
1
- import type { Cleanup } from '../extension-foundation/types';
2
- import type { PanelControlContribution, PanelControlsApi } from './types';
3
- export interface PanelControlRegistry extends PanelControlsApi {
4
- contribute(owner: string, controls: readonly PanelControlContribution[]): Cleanup;
5
- }
6
- export declare function createPanelControlRegistry(): PanelControlRegistry;
1
+ import { type PanelControlRegistry as Registry } from '@forgeax/app-shell/application';
2
+ import type { PanelControlContribution } from './types';
3
+ export type PanelControlRegistry = Registry<PanelControlContribution>;
4
+ export declare const createPanelControlRegistry: () => Registry<PanelControlContribution>;
@@ -1,54 +1,2 @@
1
- export function createPanelControlRegistry() {
2
- const entries = [];
3
- const listeners = new Set();
4
- let version = 0;
5
- const emit = () => {
6
- version++;
7
- for (const listener of [...listeners])
8
- listener();
9
- };
10
- // Batch-deferred notification: data mutations (push/splice) happen
11
- // immediately so reads (list/get) always return fresh data, but listener
12
- // notifications are deferred to a microtask. This avoids triggering
13
- // forceStoreRerender (useSyncExternalStore) during React 19's commit
14
- // phase, which otherwise causes "Maximum update depth exceeded".
15
- let emitScheduled = false;
16
- const scheduleEmit = () => {
17
- if (emitScheduled)
18
- return;
19
- emitScheduled = true;
20
- queueMicrotask(() => {
21
- emitScheduled = false;
22
- emit();
23
- });
24
- };
25
- const list = () => entries.flatMap((entry) => entry.controls);
26
- return {
27
- contribute(owner, controls) {
28
- const entry = { owner, controls };
29
- entries.push(entry);
30
- scheduleEmit();
31
- let removed = false;
32
- return () => {
33
- if (removed)
34
- return;
35
- removed = true;
36
- const index = entries.indexOf(entry);
37
- if (index >= 0)
38
- entries.splice(index, 1);
39
- scheduleEmit();
40
- };
41
- },
42
- get(id) {
43
- return list().find((control) => control.id === id);
44
- },
45
- list,
46
- onChange(listener) {
47
- listeners.add(listener);
48
- return () => { listeners.delete(listener); };
49
- },
50
- version() {
51
- return version;
52
- },
53
- };
54
- }
1
+ import { createPanelControlRegistry as createRegistry, } from '@forgeax/app-shell/application';
2
+ export const createPanelControlRegistry = (createRegistry);
@@ -61,14 +61,12 @@ export async function bootstrapAppHost(overrides = {}) {
61
61
  ...m,
62
62
  async setup(ctx) {
63
63
  control.beginSetup(m);
64
- const menuCleanups = [];
64
+ const cleanups = [];
65
+ let pageCleanup;
65
66
  try {
66
- const cleanups = [];
67
- let pageCleanup;
68
67
  for (const menu of m.contributes?.menus ?? []) {
69
- menuCleanups.push(host.menus.register(menu));
68
+ cleanups.push(host.menus.register(menu));
70
69
  }
71
- cleanups.push(...menuCleanups);
72
70
  if (m.contributes?.panels) {
73
71
  cleanups.push(control.contributePanels(m.id, m.contributes.panels));
74
72
  }
@@ -100,7 +98,10 @@ export async function bootstrapAppHost(overrides = {}) {
100
98
  };
101
99
  }
102
100
  catch (error) {
103
- for (const cleanup of menuCleanups.slice().reverse())
101
+ // Preserve the same page-close decision boundary on failed setup.
102
+ // A dirty page must not lose its surrounding contributions underneath it.
103
+ await pageCleanup?.();
104
+ for (const cleanup of cleanups.slice().reverse())
104
105
  await cleanup();
105
106
  throw error;
106
107
  }
@@ -1,62 +1,2 @@
1
- export function createPanelActionRegistry() {
2
- const entries = [];
3
- const listeners = new Set();
4
- let cache = null;
5
- let version = 0;
6
- const emit = () => {
7
- cache = null;
8
- version++;
9
- for (const listener of [...listeners])
10
- listener();
11
- };
12
- // Batch-deferred notification: data mutations (push/splice) happen
13
- // immediately so reads (list/all) always return fresh data, but listener
14
- // notifications are deferred to a microtask. This avoids triggering
15
- // forceStoreRerender (useSyncExternalStore) during React 19's commit
16
- // phase, which otherwise causes "Maximum update depth exceeded".
17
- let emitScheduled = false;
18
- const scheduleEmit = () => {
19
- cache = null;
20
- if (emitScheduled)
21
- return;
22
- emitScheduled = true;
23
- queueMicrotask(() => {
24
- emitScheduled = false;
25
- emit();
26
- });
27
- };
28
- const all = () => {
29
- if (cache)
30
- return cache;
31
- cache = entries.flatMap((entry) => entry.actions).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
32
- return cache;
33
- };
34
- return {
35
- contribute(owner, actions) {
36
- const entry = { owner, actions };
37
- entries.push(entry);
38
- scheduleEmit();
39
- let removed = false;
40
- return () => {
41
- if (removed)
42
- return;
43
- removed = true;
44
- const index = entries.indexOf(entry);
45
- if (index >= 0)
46
- entries.splice(index, 1);
47
- scheduleEmit();
48
- };
49
- },
50
- list(panelId) {
51
- return all().filter((action) => action.panelId === panelId);
52
- },
53
- all,
54
- onChange(listener) {
55
- listeners.add(listener);
56
- return () => { listeners.delete(listener); };
57
- },
58
- version() {
59
- return version;
60
- },
61
- };
62
- }
1
+ import { createPanelActionRegistry as createRegistry, } from '@forgeax/app-shell/application';
2
+ export const createPanelActionRegistry = (createRegistry);
@@ -1,54 +1,2 @@
1
- export function createPanelControlRegistry() {
2
- const entries = [];
3
- const listeners = new Set();
4
- let version = 0;
5
- const emit = () => {
6
- version++;
7
- for (const listener of [...listeners])
8
- listener();
9
- };
10
- // Batch-deferred notification: data mutations (push/splice) happen
11
- // immediately so reads (list/get) always return fresh data, but listener
12
- // notifications are deferred to a microtask. This avoids triggering
13
- // forceStoreRerender (useSyncExternalStore) during React 19's commit
14
- // phase, which otherwise causes "Maximum update depth exceeded".
15
- let emitScheduled = false;
16
- const scheduleEmit = () => {
17
- if (emitScheduled)
18
- return;
19
- emitScheduled = true;
20
- queueMicrotask(() => {
21
- emitScheduled = false;
22
- emit();
23
- });
24
- };
25
- const list = () => entries.flatMap((entry) => entry.controls);
26
- return {
27
- contribute(owner, controls) {
28
- const entry = { owner, controls };
29
- entries.push(entry);
30
- scheduleEmit();
31
- let removed = false;
32
- return () => {
33
- if (removed)
34
- return;
35
- removed = true;
36
- const index = entries.indexOf(entry);
37
- if (index >= 0)
38
- entries.splice(index, 1);
39
- scheduleEmit();
40
- };
41
- },
42
- get(id) {
43
- return list().find((control) => control.id === id);
44
- },
45
- list,
46
- onChange(listener) {
47
- listeners.add(listener);
48
- return () => { listeners.delete(listener); };
49
- },
50
- version() {
51
- return version;
52
- },
53
- };
54
- }
1
+ import { createPanelControlRegistry as createRegistry, } from '@forgeax/app-shell/application';
2
+ export const createPanelControlRegistry = (createRegistry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/interface",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "type": "module",
5
5
  "description": "ForgeaX product interface shell and application composition boundary.",
6
6
  "license": "Apache-2.0",
@@ -92,7 +92,7 @@
92
92
  "lint:dep": "depcruise -c .dependency-cruiser.cjs src"
93
93
  },
94
94
  "dependencies": {
95
- "@forgeax/app-shell": "0.88.0",
95
+ "@forgeax/app-shell": "0.89.0",
96
96
  "@forgeax/extension-host": "0.3.2",
97
97
  "@forgeax/extension-platform": "0.5.0",
98
98
  "@forgeax/toolkit": "0.1.2",