@nextclaw/ui 0.13.9 → 0.13.10-beta.1

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/assets/{api-CIJxdHmy.js → api-DhI2lb1f.js} +10 -5
  3. package/dist/assets/app-presenter-provider-BFbrQp8q.js +3 -0
  4. package/dist/assets/arrow-left-CI0Ocu9p.js +1 -0
  5. package/dist/assets/{channels-list-page-C_Xe0CNX.js → channels-list-page-B6CvmU9T.js} +1 -1
  6. package/dist/assets/{chat-page-CXRtoQR2.js → chat-page-Ch4ShlKB.js} +1 -1
  7. package/dist/assets/{config-split-page-DBAcw9mJ.js → config-split-page-bO0K6uQv.js} +1 -1
  8. package/dist/assets/{confirm-dialog-DwPTxq8C.js → confirm-dialog-DXRWJ5uB.js} +1 -1
  9. package/dist/assets/{desktop-update-config-BqABa_1C.js → desktop-update-config-DPk5V1Nl.js} +1 -1
  10. package/dist/assets/{dist-BfcibKyv.js → dist-DQoV-62W.js} +1 -1
  11. package/dist/assets/{dist-WjhKB-pc.js → dist-T6rf86Nf.js} +1 -1
  12. package/dist/assets/{doc-browser-8EEDwGCJ.js → doc-browser-CTFB6ACN.js} +1 -1
  13. package/dist/assets/{doc-browser-context-DLJOy6Am.js → doc-browser-context-CP2tI-Br.js} +1 -1
  14. package/dist/assets/doc-browser-yi02ASOL.js +1 -0
  15. package/dist/assets/{ellipsis-H-4aNwgt.js → ellipsis-B7CbNPNr.js} +1 -1
  16. package/dist/assets/{es2015-B5k1IpKX.js → es2015-BSPOGpH4.js} +1 -1
  17. package/dist/assets/{external-link-BN1wd-eI.js → external-link-CefT35zP.js} +1 -1
  18. package/dist/assets/index-CevK3jug.css +1 -0
  19. package/dist/assets/{index-WJt0X9wC.js → index-DZoXsgdz.js} +43 -43
  20. package/dist/assets/{key-round-D-7IaAQS.js → key-round-BpM3GZn4.js} +1 -1
  21. package/dist/assets/{loader-circle-CB38Q7Ws.js → loader-circle-_edjOFsw.js} +1 -1
  22. package/dist/assets/mcp-marketplace-page-9maf_9fL.js +1 -0
  23. package/dist/assets/{mcp-marketplace-page-ju1KH3TG.js → mcp-marketplace-page-WAgpy0hp.js} +1 -1
  24. package/dist/assets/{model-config-CgS6dp65.js → model-config-CixioI5U.js} +1 -1
  25. package/dist/assets/{notice-card-DW9BLKpG.js → notice-card-BLklZuEa.js} +1 -1
  26. package/dist/assets/play-DD0bg50i.js +1 -0
  27. package/dist/assets/plus-zZ2GC_AJ.js +1 -0
  28. package/dist/assets/{popover-ofN845gZ.js → popover-D_pJfOlM.js} +1 -1
  29. package/dist/assets/{provider-scoped-model-input-DaDqi8Zy.js → provider-scoped-model-input-BbWqk2yz.js} +1 -1
  30. package/dist/assets/{providers-list-CZNQXY4n.js → providers-list-C_JOSjYd.js} +1 -1
  31. package/dist/assets/{react-Bq34NL59.js → react-Bl8iJUcF.js} +1 -1
  32. package/dist/assets/{refresh-cw-DJttSXS8.js → refresh-cw-CqfS1EhK.js} +1 -1
  33. package/dist/assets/remote-qtmzkte2.js +1 -0
  34. package/dist/assets/rotate-cw-C7EvaSlb.js +1 -0
  35. package/dist/assets/{runtime-config-page-gUjM6Z_A.js → runtime-config-page-ByvKO6Yn.js} +1 -1
  36. package/dist/assets/{save-DtAFbS7x.js → save-CQERCODg.js} +1 -1
  37. package/dist/assets/search-C0b2yzmt.js +1 -0
  38. package/dist/assets/{search-config-C04PrmHj.js → search-config-Cl2_NgFY.js} +1 -1
  39. package/dist/assets/{secrets-config-DvxHdz9h.js → secrets-config-BkR72J4g.js} +1 -1
  40. package/dist/assets/{status-dot-D4jbKDLB.js → status-dot-DY0xUUvK.js} +1 -1
  41. package/dist/assets/{tabs-Ca2vw7YQ.js → tabs-C77i1w90.js} +1 -1
  42. package/dist/assets/{tabs-custom-DzGDjYxf.js → tabs-custom-CFpR54-m.js} +1 -1
  43. package/dist/assets/{tag-chip-CW-_pHjH.js → tag-chip-Kib_ljle.js} +1 -1
  44. package/dist/assets/{tooltip-Cq2mERPk.js → tooltip-fs7VQG7d.js} +1 -1
  45. package/dist/assets/{trash-2-rumaEu9l.js → trash-2-Bb8mTTLl.js} +1 -1
  46. package/dist/assets/x-BV4iiea8.js +1 -0
  47. package/dist/index.html +60 -33
  48. package/dist/sw.js +6 -31
  49. package/index.html +35 -8
  50. package/package.json +8 -8
  51. package/public/sw.js +6 -31
  52. package/src/app/index.tsx +0 -2
  53. package/src/features/panel-apps/components/panel-app-list-item.test.tsx +4 -0
  54. package/src/features/panel-apps/components/panel-apps-list.tsx +27 -1
  55. package/src/features/panel-apps/hooks/use-panel-apps.ts +10 -0
  56. package/src/features/panel-apps/managers/panel-app-bridge.manager.test.ts +18 -40
  57. package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +24 -36
  58. package/src/features/panel-apps/utils/panel-app-view.utils.test.ts +4 -0
  59. package/src/features/pwa/components/pwa-install-entry.test.tsx +5 -46
  60. package/src/features/pwa/components/pwa-install-entry.tsx +1 -35
  61. package/src/features/pwa/index.ts +1 -1
  62. package/src/features/pwa/managers/pwa-install.manager.ts +0 -2
  63. package/src/features/pwa/managers/pwa-runtime.manager.ts +2 -120
  64. package/src/features/pwa/managers/pwa-service-worker-cache.manager.test.ts +7 -5
  65. package/src/features/pwa/stores/pwa.store.ts +1 -3
  66. package/src/features/pwa/types/pwa.types.ts +0 -2
  67. package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.test.ts +4 -0
  68. package/src/features/service-apps/components/service-apps-panel.tsx +3 -3
  69. package/src/shared/lib/api/index.ts +1 -1
  70. package/src/shared/lib/api/{ncp-session-query-cache.test.ts → ncp-session-query-cache.utils.test.ts} +23 -0
  71. package/src/shared/lib/api/{ncp-session-query-cache.ts → ncp-session-query-cache.utils.ts} +9 -11
  72. package/src/shared/lib/api/types.ts +4 -0
  73. package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +5 -0
  74. package/dist/assets/app-presenter-provider-CKZqZfv5.js +0 -3
  75. package/dist/assets/arrow-left-DHcD-tbC.js +0 -1
  76. package/dist/assets/doc-browser-DlT90zQD.js +0 -1
  77. package/dist/assets/index-BCq7kbC8.css +0 -1
  78. package/dist/assets/mcp-marketplace-page-CmZwt64F.js +0 -1
  79. package/dist/assets/play-DGpH8jeI.js +0 -1
  80. package/dist/assets/plus-CkcQjwuH.js +0 -1
  81. package/dist/assets/remote-CYmsM9DD.js +0 -1
  82. package/dist/assets/rotate-cw-D_xslHna.js +0 -1
  83. package/dist/assets/search-CNMRwAFx.js +0 -1
  84. package/dist/assets/x-DGqjdMUt.js +0 -1
@@ -1,11 +1,6 @@
1
- import { usePwaStore } from '@/features/pwa/stores/pwa.store';
2
-
3
- const PWA_DEV_RESET_SESSION_KEY = 'nextclaw-pwa-dev-sw-reset';
4
-
5
1
  export class PwaRuntimeManager {
6
2
  private started = false;
7
3
  private registration: ServiceWorkerRegistration | null = null;
8
- private reloadWhenControllerChanges = false;
9
4
 
10
5
  start = async () => {
11
6
  if (this.started || typeof window === 'undefined') {
@@ -25,55 +20,9 @@ export class PwaRuntimeManager {
25
20
  try {
26
21
  const registration = await navigator.serviceWorker.register('/sw.js');
27
22
  this.registration = registration;
28
- this.bindRegistration(registration);
29
- navigator.serviceWorker.addEventListener('controllerchange', this.handleControllerChange);
30
- await this.syncUpdateAvailability();
31
23
  } catch {
32
- usePwaStore.setState({
33
- registrationFailed: true
34
- });
35
- }
36
- };
37
-
38
- syncUpdateAvailability = async () => {
39
- if (
40
- typeof window === 'undefined' ||
41
- !('serviceWorker' in navigator) ||
42
- typeof navigator.serviceWorker.getRegistration !== 'function'
43
- ) {
44
- usePwaStore.setState({
45
- updateAvailable: false
46
- });
47
24
  return;
48
25
  }
49
-
50
- const registration = this.registration ?? (await navigator.serviceWorker.getRegistration('/sw.js')) ?? null;
51
- usePwaStore.setState({
52
- updateAvailable: this.shouldSurfaceUpdate(registration)
53
- });
54
- };
55
-
56
- applyUpdate = async () => {
57
- const registration = this.registration ?? (await navigator.serviceWorker.getRegistration('/sw.js')) ?? null;
58
- if (!registration) {
59
- window.location.reload();
60
- return;
61
- }
62
-
63
- if (!registration.waiting) {
64
- await registration.update();
65
- }
66
-
67
- const waitingWorker = registration.waiting;
68
- if (!waitingWorker) {
69
- window.location.reload();
70
- return;
71
- }
72
-
73
- this.reloadWhenControllerChanges = true;
74
- waitingWorker.postMessage({
75
- type: 'SKIP_WAIT'
76
- });
77
26
  };
78
27
 
79
28
  stop = () => {
@@ -81,34 +30,8 @@ export class PwaRuntimeManager {
81
30
  return;
82
31
  }
83
32
 
84
- navigator.serviceWorker.removeEventListener('controllerchange', this.handleControllerChange);
85
33
  this.started = false;
86
34
  this.registration = null;
87
- this.reloadWhenControllerChanges = false;
88
- };
89
-
90
- private bindRegistration = (registration: ServiceWorkerRegistration) => {
91
- registration.addEventListener('updatefound', () => {
92
- const installingWorker = registration.installing;
93
- if (!installingWorker) {
94
- return;
95
- }
96
-
97
- installingWorker.addEventListener('statechange', () => {
98
- if (installingWorker.state === 'installed') {
99
- void this.syncUpdateAvailability();
100
- }
101
- });
102
- });
103
- };
104
-
105
- private handleControllerChange = () => {
106
- if (!this.reloadWhenControllerChanges) {
107
- return;
108
- }
109
-
110
- this.reloadWhenControllerChanges = false;
111
- window.location.reload();
112
35
  };
113
36
 
114
37
  private isEligibleInstallContext = (): boolean => {
@@ -123,13 +46,6 @@ export class PwaRuntimeManager {
123
46
  return import.meta.env.DEV && !import.meta.env.VITEST;
124
47
  };
125
48
 
126
- private shouldSurfaceUpdate = (registration: ServiceWorkerRegistration | null): boolean => {
127
- if (!registration?.waiting || !navigator.serviceWorker.controller) {
128
- return false;
129
- }
130
- return usePwaStore.getState().installability === 'installed';
131
- };
132
-
133
49
  private cleanupDevelopmentRegistrations = async () => {
134
50
  if (typeof navigator.serviceWorker.getRegistrations !== 'function') {
135
51
  return;
@@ -142,21 +58,10 @@ export class PwaRuntimeManager {
142
58
  .some((scriptUrl) => scriptUrl.endsWith('/sw.js'))
143
59
  );
144
60
 
145
- if (nextclawRegistrations.length === 0) {
146
- this.clearDevResetMarker();
147
- return;
148
- }
149
-
150
61
  await Promise.all(nextclawRegistrations.map(async (registration) => await registration.unregister()));
151
- await this.clearNextClawCaches();
152
-
153
- if (navigator.serviceWorker.controller && !this.hasDevResetMarker()) {
154
- this.setDevResetMarker();
155
- window.location.reload();
156
- return;
62
+ if (nextclawRegistrations.length > 0) {
63
+ await this.clearNextClawCaches();
157
64
  }
158
-
159
- this.clearDevResetMarker();
160
65
  };
161
66
 
162
67
  private clearNextClawCaches = async () => {
@@ -168,29 +73,6 @@ export class PwaRuntimeManager {
168
73
  await Promise.all(keys.filter((key) => key.startsWith('nextclaw-ui-')).map(async (key) => await caches.delete(key)));
169
74
  };
170
75
 
171
- private hasDevResetMarker = (): boolean => {
172
- try {
173
- return window.sessionStorage.getItem(PWA_DEV_RESET_SESSION_KEY) === '1';
174
- } catch {
175
- return false;
176
- }
177
- };
178
-
179
- private setDevResetMarker = () => {
180
- try {
181
- window.sessionStorage.setItem(PWA_DEV_RESET_SESSION_KEY, '1');
182
- } catch {
183
- // ignore sessionStorage availability failures in dev cleanup
184
- }
185
- };
186
-
187
- private clearDevResetMarker = () => {
188
- try {
189
- window.sessionStorage.removeItem(PWA_DEV_RESET_SESSION_KEY);
190
- } catch {
191
- // ignore sessionStorage availability failures in dev cleanup
192
- }
193
- };
194
76
  }
195
77
 
196
78
  export const pwaRuntimeManager = new PwaRuntimeManager();
@@ -6,11 +6,13 @@ describe('NextClaw service worker cache policy', () => {
6
6
  });
7
7
 
8
8
  it('does not fall back to stale navigation responses', () => {
9
- const navigationHandler = serviceWorkerSource.slice(
10
- serviceWorkerSource.indexOf('async function handleNavigation'),
11
- serviceWorkerSource.indexOf('async function handleStaticAsset')
12
- );
9
+ expect(serviceWorkerSource).not.toContain("request.mode === 'navigate'");
10
+ expect(serviceWorkerSource).not.toContain('handleNavigation');
11
+ expect(serviceWorkerSource).toContain('SHELL_ASSETS.includes(url.pathname)');
12
+ });
13
13
 
14
- expect(navigationHandler).not.toContain('caches.match(request)');
14
+ it('does not force service worker activation or page control', () => {
15
+ expect(serviceWorkerSource).not.toContain('skipWaiting');
16
+ expect(serviceWorkerSource).not.toContain('clients.claim');
15
17
  });
16
18
  });
@@ -8,9 +8,7 @@ export function createInitialPwaState(): PwaInstallStateSnapshot {
8
8
  installability: 'unsupported',
9
9
  installMethod: 'none',
10
10
  blockedReason: 'missing-browser-support',
11
- dismissedInstallPrompt: isPwaInstallBannerDismissed(),
12
- updateAvailable: false,
13
- registrationFailed: false
11
+ dismissedInstallPrompt: isPwaInstallBannerDismissed()
14
12
  };
15
13
  }
16
14
 
@@ -17,6 +17,4 @@ export interface PwaInstallStateSnapshot {
17
17
  installMethod: PwaInstallMethod;
18
18
  blockedReason: PwaInstallBlockedReason;
19
19
  dismissedInstallPrompt: boolean;
20
- updateAvailable: boolean;
21
- registrationFailed: boolean;
22
20
  }
@@ -4,11 +4,15 @@ import { createPanelAppRightPanelResourceTarget } from '@/features/right-panel-r
4
4
 
5
5
  function createPanelAppEntry(overrides: Partial<PanelAppEntryView> = {}): PanelAppEntryView {
6
6
  return {
7
+ appId: 'demo',
8
+ clientDeclared: false,
9
+ clientGranted: false,
7
10
  contentPath: '/api/panel-apps/demo/content',
8
11
  createdAt: '2026-06-02T00:00:00.000Z',
9
12
  favorite: false,
10
13
  fileName: 'demo.html',
11
14
  id: 'demo',
15
+ kind: 'single-file',
12
16
  openCount: 0,
13
17
  sizeBytes: 1024,
14
18
  title: 'Demo App',
@@ -77,9 +77,9 @@ export function ServiceAppsPanel({
77
77
  );
78
78
  }
79
79
 
80
- const apps = serviceApps.data?.entries ?? [];
81
- const actions = serviceActions.data?.actions ?? [];
82
- const grants = serviceActionGrants.data?.grants ?? [];
80
+ const apps: ServiceAppRecordView[] = serviceApps.data?.entries ?? [];
81
+ const actions: ServiceActionView[] = serviceActions.data?.actions ?? [];
82
+ const grants: ServiceActionGrantView[] = serviceActionGrants.data?.grants ?? [];
83
83
  const discover = (appId: string) => {
84
84
  void discoverServiceAppActions.mutateAsync(appId).then((result) => {
85
85
  setDiscoveredActionsByApp((current) => ({
@@ -11,7 +11,7 @@ export * from './utils/mcp-marketplace.utils';
11
11
  export * from './utils/ncp-attachments.utils';
12
12
  export * from './utils/ncp-session.utils';
13
13
  export * from './ncp-session.types';
14
- export * from './ncp-session-query-cache';
14
+ export * from './ncp-session-query-cache.utils';
15
15
  export * from './raw-client.utils';
16
16
  export * from './utils/remote.utils';
17
17
  export * from './remote.types';
@@ -131,6 +131,29 @@ describe('ncp-session-query-cache', () => {
131
131
  });
132
132
  });
133
133
 
134
+ it('keeps the realtime running overlay when a newer persisted idle summary arrives', () => {
135
+ const current = updateNcpSessionRunStatusList(createSessionsList(), {
136
+ sessionKey: 'session-1',
137
+ status: 'running'
138
+ });
139
+
140
+ const updated = upsertNcpSessionSummaryList(current, {
141
+ sessionId: 'session-1',
142
+ messageCount: 2,
143
+ createdAt: '2026-03-29T08:00:00.000Z',
144
+ updatedAt: '2026-03-29T10:01:00.000Z',
145
+ lastMessageAt: '2026-03-29T10:01:00.000Z',
146
+ status: 'idle',
147
+ metadata: { last_activity_preview: { state: 'running' } }
148
+ });
149
+
150
+ expect(updated?.sessions[0]).toMatchObject({
151
+ sessionId: 'session-1',
152
+ messageCount: 2,
153
+ status: 'running'
154
+ });
155
+ });
156
+
134
157
  it('applies realtime upsert/delete events to every ncp-sessions query cache entry', () => {
135
158
  const queryClient = new QueryClient();
136
159
  queryClient.setQueryData(['ncp-sessions', 200], createSessionsList());
@@ -14,18 +14,11 @@ function shouldReplaceSessionSummary(
14
14
  next: NcpSessionSummaryView
15
15
  ): boolean {
16
16
  const timeOrder = next.updatedAt.localeCompare(current.updatedAt);
17
- if (timeOrder > 0) {
18
- return true;
19
- }
20
- if (timeOrder < 0) {
21
- return false;
22
- }
23
-
24
- if (current.status === next.status) {
25
- return true;
17
+ if (timeOrder !== 0) {
18
+ return timeOrder > 0;
26
19
  }
27
20
 
28
- return next.status === 'idle';
21
+ return current.status === next.status || next.status === 'idle';
29
22
  }
30
23
 
31
24
  export function upsertNcpSessionSummaryList(
@@ -40,7 +33,12 @@ export function upsertNcpSessionSummaryList(
40
33
  const nextSessions =
41
34
  existingIndex >= 0
42
35
  ? current.sessions.map((session, index) =>
43
- index === existingIndex && shouldReplaceSessionSummary(session, summary) ? summary : session
36
+ index === existingIndex && shouldReplaceSessionSummary(session, summary)
37
+ ? {
38
+ ...summary,
39
+ status: session.status === 'running' && summary.status === 'idle' ? 'running' : summary.status
40
+ }
41
+ : session
44
42
  )
45
43
  : [...current.sessions, summary];
46
44
  const sortedSessions = sortSessionSummaries(nextSessions);
@@ -363,7 +363,9 @@ export type ServerPathReadView = { requestedPath: string; resolvedPath: string;
363
363
 
364
364
  export type PanelAppEntryView = {
365
365
  id: string;
366
+ appId: string;
366
367
  fileName: string;
368
+ kind: "single-file" | "folder";
367
369
  title: string;
368
370
  description?: string;
369
371
  icon?: string;
@@ -372,6 +374,8 @@ export type PanelAppEntryView = {
372
374
  updatedAt: string;
373
375
  sizeBytes: number;
374
376
  favorite: boolean;
377
+ clientDeclared: boolean;
378
+ clientGranted: boolean;
375
379
  lastOpenedAt?: string;
376
380
  openCount: number;
377
381
  };
@@ -39,6 +39,11 @@ export const DOC_BROWSER_LABELS = {
39
39
  zh: '将删除这个面板应用文件,删除后不可恢复。',
40
40
  en: 'This will delete the panel app file and cannot be undone.',
41
41
  },
42
+ panelAppsClientGrantTitle: { zh: 'NextClaw Client SDK', en: 'NextClaw Client SDK' },
43
+ panelAppsClientGrantDescription: {
44
+ zh: '允许这个面板应用使用完整 NextClaw Client SDK。',
45
+ en: 'Allow this panel app to use the full NextClaw Client SDK.',
46
+ },
42
47
  panelAppsLastOpened: { zh: '上次打开', en: 'Last opened' },
43
48
  panelAppsUpdated: { zh: '更新于', en: 'Updated' },
44
49
  serviceAppsTitle: { zh: '服务应用', en: 'Service Apps' },