@dimina-kit/devtools 0.4.0-dev.20260824071518 → 0.4.0-dev.20260828161241

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 (160) hide show
  1. package/dist/main/api.d.ts +1 -1
  2. package/dist/main/app/app.js +59 -2
  3. package/dist/main/index.bundle.js +986 -245
  4. package/dist/main/ipc/index.d.ts +2 -0
  5. package/dist/main/ipc/index.js +2 -0
  6. package/dist/main/ipc/popover.js +1 -1
  7. package/dist/main/ipc/project-create.d.ts +15 -0
  8. package/dist/main/ipc/project-create.js +26 -0
  9. package/dist/main/ipc/projects.d.ts +1 -1
  10. package/dist/main/ipc/projects.js +36 -1
  11. package/dist/main/ipc/settings.js +2 -1
  12. package/dist/main/ipc/simulator-module.d.ts +4 -3
  13. package/dist/main/ipc/simulator-module.js +4 -5
  14. package/dist/main/ipc/tooltip.js +1 -1
  15. package/dist/main/ipc/views.d.ts +15 -1
  16. package/dist/main/ipc/views.js +61 -4
  17. package/dist/main/runtime/miniapp-runtime.d.ts +87 -19
  18. package/dist/main/services/notifications/renderer-notifier.d.ts +30 -1
  19. package/dist/main/services/notifications/renderer-notifier.js +23 -1
  20. package/dist/main/services/projects/create-project-service.js +9 -2
  21. package/dist/main/services/projects/local-provider.js +6 -0
  22. package/dist/main/services/projects/project-repository.d.ts +29 -1
  23. package/dist/main/services/projects/project-repository.js +151 -17
  24. package/dist/main/services/projects/types.d.ts +13 -2
  25. package/dist/main/services/update/update-manager.d.ts +17 -3
  26. package/dist/main/services/update/update-manager.js +25 -6
  27. package/dist/main/services/views/host-dialog-port-channel.d.ts +18 -0
  28. package/dist/main/services/views/host-dialog-port-channel.js +15 -0
  29. package/dist/main/services/views/host-dialog-session-runtime.d.ts +10 -0
  30. package/dist/main/services/views/host-dialog-session-runtime.js +17 -0
  31. package/dist/main/services/views/host-dialog-view.d.ts +95 -0
  32. package/dist/main/services/views/host-dialog-view.js +147 -0
  33. package/dist/main/services/views/host-sidebar-port-channel.d.ts +18 -0
  34. package/dist/main/services/views/host-sidebar-port-channel.js +15 -0
  35. package/dist/main/services/views/host-sidebar-session-runtime.d.ts +10 -0
  36. package/dist/main/services/views/host-sidebar-session-runtime.js +17 -0
  37. package/dist/main/services/views/host-sidebar-view.d.ts +59 -0
  38. package/dist/main/services/views/host-sidebar-view.js +44 -0
  39. package/dist/main/services/views/host-slot-port-channel.d.ts +107 -0
  40. package/dist/main/services/views/host-slot-port-channel.js +261 -0
  41. package/dist/main/services/views/host-slot-session-runtime.d.ts +40 -0
  42. package/dist/main/services/views/host-slot-session-runtime.js +50 -0
  43. package/dist/main/services/views/host-slot-view.d.ts +109 -0
  44. package/dist/main/services/views/host-slot-view.js +147 -0
  45. package/dist/main/services/views/host-toolbar-port-channel.d.ts +6 -91
  46. package/dist/main/services/views/host-toolbar-port-channel.js +10 -243
  47. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +5 -30
  48. package/dist/main/services/views/host-toolbar-session-runtime.js +9 -47
  49. package/dist/main/services/views/host-toolbar-view.d.ts +34 -7
  50. package/dist/main/services/views/host-toolbar-view.js +30 -224
  51. package/dist/main/services/views/managed-web-contents-view.d.ts +48 -0
  52. package/dist/main/services/views/managed-web-contents-view.js +123 -0
  53. package/dist/main/services/views/overlay-panels-view.d.ts +23 -0
  54. package/dist/main/services/views/overlay-panels-view.js +93 -4
  55. package/dist/main/services/views/placement-reconciler.d.ts +14 -2
  56. package/dist/main/services/views/placement-reconciler.js +27 -0
  57. package/dist/main/services/views/placement-test-driver.d.ts +1 -0
  58. package/dist/main/services/views/placement-test-driver.js +3 -0
  59. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +3 -1
  60. package/dist/main/services/views/view-manager.d.ts +10 -78
  61. package/dist/main/services/views/view-manager.js +34 -3
  62. package/dist/main/services/workbench-context.d.ts +18 -4
  63. package/dist/main/services/workbench-context.js +1 -0
  64. package/dist/main/services/workspace/workspace-service.d.ts +2 -0
  65. package/dist/main/services/workspace/workspace-service.js +14 -0
  66. package/dist/main/utils/paths.d.ts +4 -0
  67. package/dist/main/utils/paths.js +4 -0
  68. package/dist/main/utils/sender-policy.js +10 -0
  69. package/dist/native-host/common/common.js +65 -62
  70. package/dist/native-host/render/render.js +2230 -2052
  71. package/dist/native-host/service/service.js +2 -2
  72. package/dist/preload/runtime/host-dialog-advertiser.d.ts +22 -0
  73. package/dist/preload/runtime/host-dialog-advertiser.js +37 -0
  74. package/dist/preload/runtime/host-dialog-port.d.ts +44 -0
  75. package/dist/preload/runtime/host-dialog-port.js +53 -0
  76. package/dist/preload/runtime/host-dialog-runtime.d.ts +28 -0
  77. package/dist/preload/runtime/host-dialog-runtime.js +36 -0
  78. package/dist/preload/runtime/host-sidebar-advertiser.d.ts +19 -0
  79. package/dist/preload/runtime/host-sidebar-advertiser.js +35 -0
  80. package/dist/preload/runtime/host-sidebar-port.d.ts +44 -0
  81. package/dist/preload/runtime/host-sidebar-port.js +53 -0
  82. package/dist/preload/runtime/host-sidebar-runtime.d.ts +28 -0
  83. package/dist/preload/runtime/host-sidebar-runtime.js +36 -0
  84. package/dist/preload/runtime/host-slot-port-bridge.d.ts +18 -0
  85. package/dist/preload/runtime/host-slot-port-bridge.js +112 -0
  86. package/dist/preload/runtime/host-toolbar-advertiser.d.ts +5 -13
  87. package/dist/preload/runtime/host-toolbar-advertiser.js +18 -38
  88. package/dist/preload/runtime/host-toolbar-port.d.ts +9 -0
  89. package/dist/preload/runtime/host-toolbar-port.js +11 -95
  90. package/dist/preload/runtime/wait-for-slot-root.d.ts +20 -0
  91. package/dist/preload/runtime/wait-for-slot-root.js +52 -0
  92. package/dist/preload/windows/host-dialog-runtime.cjs +353 -0
  93. package/dist/preload/windows/host-dialog-runtime.cjs.map +7 -0
  94. package/dist/preload/windows/host-dialog-runtime.d.ts +2 -0
  95. package/dist/preload/windows/host-dialog-runtime.js +13 -0
  96. package/dist/preload/windows/host-sidebar-runtime.cjs +354 -0
  97. package/dist/preload/windows/host-sidebar-runtime.cjs.map +7 -0
  98. package/dist/preload/windows/host-sidebar-runtime.d.ts +2 -0
  99. package/dist/preload/windows/host-sidebar-runtime.js +13 -0
  100. package/dist/preload/windows/host-toolbar-runtime.cjs +134 -51
  101. package/dist/preload/windows/host-toolbar-runtime.cjs.map +3 -3
  102. package/dist/preload/windows/simulator.cjs.map +1 -1
  103. package/dist/renderer/assets/button-D5Q2wkXq.js +2 -0
  104. package/dist/renderer/assets/constants-D7DfVqMp.js +2 -0
  105. package/dist/renderer/assets/constants-D8zsejFq.js +2 -0
  106. package/dist/renderer/assets/createLucideIcon-Bqi8hpdi.js +2 -0
  107. package/dist/renderer/assets/dialog-e1TecTOY.js +46 -0
  108. package/dist/renderer/assets/dist-DKWdE1TK.js +2 -0
  109. package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +2 -0
  110. package/dist/renderer/assets/index-CsPD68N_.js +5 -0
  111. package/dist/renderer/assets/input-Ct3alvkq.js +2 -0
  112. package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +2 -0
  113. package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +1 -0
  114. package/dist/renderer/assets/jsx-runtime-DGLHmmOq.js +9 -0
  115. package/dist/renderer/assets/popover-Dix5cm_b.js +2 -0
  116. package/dist/renderer/assets/project-api-k7Oq9iaq.js +2 -0
  117. package/dist/renderer/assets/projectCreateDialog-Bbjz75bP.js +2 -0
  118. package/dist/renderer/assets/select-acGb0AmJ.js +2 -0
  119. package/dist/renderer/assets/settings-CQrTMr43.js +2 -0
  120. package/dist/renderer/assets/settings-api-CrUQ9rYV.js +2 -0
  121. package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +2 -0
  122. package/dist/renderer/assets/tooltip-B_-_YVbw.js +2 -0
  123. package/dist/renderer/assets/types-CsEKtFyr.js +2 -0
  124. package/dist/renderer/assets/updateDialog-NsF8223A.js +2 -0
  125. package/dist/renderer/assets/utils-kfQov6Pa.js +2 -0
  126. package/dist/renderer/assets/view-api-CLn3rcQE.js +2 -0
  127. package/dist/renderer/assets/{workbenchSettings-oq_EyfKf.js → workbenchSettings-CV4LVA9b.js} +3 -3
  128. package/dist/renderer/entries/host-sidebar-default/index.html +21 -0
  129. package/dist/renderer/entries/main/index.html +17 -8
  130. package/dist/renderer/entries/popover/index.html +10 -7
  131. package/dist/renderer/entries/project-create-dialog/index.html +27 -0
  132. package/dist/renderer/entries/settings/index.html +10 -7
  133. package/dist/renderer/entries/tooltip/index.html +5 -4
  134. package/dist/renderer/entries/update-dialog/index.html +25 -0
  135. package/dist/renderer/entries/workbench-settings/index.html +11 -7
  136. package/dist/service-host/preload.cjs +10 -0
  137. package/dist/shared/constants.d.ts +22 -0
  138. package/dist/shared/constants.js +22 -0
  139. package/dist/shared/ipc-channels-overlays.d.ts +156 -0
  140. package/dist/shared/ipc-channels-overlays.js +186 -0
  141. package/dist/shared/ipc-channels.d.ts +4 -77
  142. package/dist/shared/ipc-channels.js +4 -98
  143. package/dist/shared/ipc-schemas.d.ts +44 -0
  144. package/dist/shared/ipc-schemas.js +55 -0
  145. package/dist/shared/types.d.ts +71 -0
  146. package/dist/shared/view-ids.d.ts +7 -0
  147. package/dist/shared/view-ids.js +10 -0
  148. package/package.json +8 -8
  149. package/dist/renderer/assets/constants-Bdz0LHdy.js +0 -2
  150. package/dist/renderer/assets/index-CtznrmHR.js +0 -49
  151. package/dist/renderer/assets/input-Dmm6nob5.js +0 -2
  152. package/dist/renderer/assets/ipc-transport-BOg0gR9W.js +0 -9
  153. package/dist/renderer/assets/ipc-transport-DgQhZaE9.css +0 -1
  154. package/dist/renderer/assets/popover-Cd1FEGOX.js +0 -2
  155. package/dist/renderer/assets/select-BaYox7If.js +0 -2
  156. package/dist/renderer/assets/settings-BXvwuFaz.js +0 -2
  157. package/dist/renderer/assets/settings-api-Bo9GbYqs.js +0 -2
  158. package/dist/renderer/assets/settings-tab-bar-Beia6lMr.js +0 -2
  159. package/dist/renderer/assets/tooltip-DfQqK3Sk.js +0 -2
  160. package/dist/renderer/assets/view-api-nn69je7A.js +0 -2
@@ -14,6 +14,12 @@ export function createLocalProjectsProvider() {
14
14
  listProjects: () => repo.listProjects(),
15
15
  addProject: (dirPath) => repo.addProject(dirPath),
16
16
  removeProject: (dirPath) => repo.removeProject(dirPath),
17
+ updateProject: (dirPath, patch) => {
18
+ const updated = repo.updateProject(dirPath, patch);
19
+ if (!updated)
20
+ throw new Error(`No such project: ${dirPath}`);
21
+ return updated;
22
+ },
17
23
  validateProjectDir: (dirPath) => repo.validateProjectDir(dirPath),
18
24
  updateLastOpened: (dirPath) => repo.updateLastOpened(dirPath),
19
25
  getCompileConfig: (dirPath) => repo.getCompileConfig(dirPath),
@@ -1,9 +1,28 @@
1
- import type { CompileConfig } from '../../../shared/types.js';
1
+ import type { CompileConfig, ProjectType } from '../../../shared/types.js';
2
+ export type { ProjectType };
2
3
  export interface Project {
3
4
  name: string;
4
5
  path: string;
5
6
  lastOpened?: string | null;
6
7
  compileConfig?: CompileConfig;
8
+ /** Absent on projects added before mini-game support — treat as 'miniprogram'. */
9
+ type?: ProjectType;
10
+ /**
11
+ * User-supplied icon for the project card. Absent means the card falls back
12
+ * to the first character of `name`.
13
+ */
14
+ iconUrl?: string;
15
+ }
16
+ /**
17
+ * The subset of a project record the user may edit after import. `path` is the
18
+ * record's identity (it keys every other per-project store — compile config,
19
+ * thumbnail, watcher) and is deliberately not patchable: pointing an existing
20
+ * record at another directory is an import, not an edit.
21
+ */
22
+ export interface ProjectPatch {
23
+ name?: string;
24
+ /** Empty string clears the icon and restores the name-initial fallback. */
25
+ iconUrl?: string;
7
26
  }
8
27
  export interface ProjectPages {
9
28
  pages: string[];
@@ -17,6 +36,15 @@ export declare function validateProjectDir(dirPath: string): string | null;
17
36
  export declare function hasProject(dirPath: string): boolean;
18
37
  export declare function addProject(dirPath: string): Project;
19
38
  export declare function removeProject(dirPath: string): void;
39
+ /**
40
+ * Apply a user edit to the record at `dirPath`. Returns the updated record, or
41
+ * `null` when no record matches — callers decide whether a missing project is
42
+ * an error (the IPC path) or a no-op.
43
+ *
44
+ * A name change is written through to the project's own config (see
45
+ * `writeProjectName`); the icon lives only in our list.
46
+ */
47
+ export declare function updateProject(dirPath: string, patch: ProjectPatch): Project | null;
20
48
  export declare function updateLastOpened(dirPath: string): void;
21
49
  export declare function getCompileConfig(dirPath: string): CompileConfig;
22
50
  export declare function getProjectPages(dirPath: string): ProjectPages;
@@ -4,6 +4,92 @@ import fs from 'fs';
4
4
  import { DEFAULT_SCENE } from '../../../shared/constants.js';
5
5
  import { createLogger } from '../../utils/logger.js';
6
6
  const log = createLogger('projects');
7
+ /**
8
+ * Merge project.config.json + project.private.config.json (private wins),
9
+ * mirroring `dimina/fe/packages/compiler/src/env.js`'s `storeInfo`.
10
+ */
11
+ function readProjectConfig(dirPath) {
12
+ let merged = {};
13
+ for (const fileName of ['project.config.json', 'project.private.config.json']) {
14
+ const configPath = path.join(dirPath, fileName);
15
+ if (!fs.existsSync(configPath))
16
+ continue;
17
+ try {
18
+ merged = { ...merged, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) };
19
+ }
20
+ catch (err) {
21
+ log.warn(`Failed to parse ${configPath}`, err);
22
+ }
23
+ }
24
+ return merged;
25
+ }
26
+ /**
27
+ * The project's display name as the project itself carries it.
28
+ *
29
+ * `projectname` lives in the merged config with the private file winning, and
30
+ * is URL-encoded when it leaves ASCII — WeChat DevTools 36.6.0 writes
31
+ * `"projectname": "%E6%BD%AE%E7%8E%A9%E6%97%8F"` and its own project list only
32
+ * mirrors that value. ASCII names encode to themselves, so plain values that
33
+ * were written by hand round-trip unchanged.
34
+ *
35
+ * Returns null when the config carries no usable name, leaving the caller to
36
+ * fall back to the directory name.
37
+ */
38
+ function readProjectName(dirPath) {
39
+ const raw = readProjectConfig(dirPath).projectname;
40
+ if (typeof raw !== 'string' || !raw.trim())
41
+ return null;
42
+ try {
43
+ return decodeURIComponent(raw);
44
+ }
45
+ catch {
46
+ // A literal '%' that isn't a valid escape — keep the author's text.
47
+ return raw;
48
+ }
49
+ }
50
+ /**
51
+ * Persist the display name into `project.private.config.json`, the per-
52
+ * developer half of the project config (WeChat DevTools writes the name there
53
+ * too, leaving the shared `project.config.json` alone).
54
+ *
55
+ * This is what makes the config — not our list file — the single owner of the
56
+ * name: `addProject` re-reads it, so re-importing a directory restores the
57
+ * user's rename instead of resetting it.
58
+ */
59
+ function writeProjectName(dirPath, name) {
60
+ const configPath = path.join(dirPath, 'project.private.config.json');
61
+ let config = {};
62
+ if (fs.existsSync(configPath)) {
63
+ try {
64
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
65
+ }
66
+ catch {
67
+ // Refuse rather than overwrite: this file also holds the user's IDE
68
+ // settings and compile conditions, which a blind rewrite would drop.
69
+ throw new Error(`无法重命名:${configPath} 不是合法 JSON`);
70
+ }
71
+ }
72
+ fs.writeFileSync(configPath, JSON.stringify({ ...config, projectname: encodeURIComponent(name) }, null, 2));
73
+ }
74
+ /**
75
+ * Mirrors `dimina/fe/packages/compiler/src/env.js`'s `detectRuntimeType` —
76
+ * an explicit `compileType` wins, else a mini-game is inferred from
77
+ * game.json + game.js/.ts with no app.json present.
78
+ */
79
+ function detectRuntimeType(dirPath) {
80
+ const compileType = readProjectConfig(dirPath).compileType;
81
+ if (compileType === 'game')
82
+ return 'minigame';
83
+ if (compileType === 'miniprogram')
84
+ return 'miniprogram';
85
+ const hasMiniProgramConfig = fs.existsSync(path.join(dirPath, 'app.json'));
86
+ const hasMiniGameConfig = fs.existsSync(path.join(dirPath, 'game.json'));
87
+ const hasMiniGameEntry = ['game.js', 'game.ts']
88
+ .some((fileName) => fs.existsSync(path.join(dirPath, fileName)));
89
+ if (!hasMiniProgramConfig && hasMiniGameConfig && hasMiniGameEntry)
90
+ return 'minigame';
91
+ return 'miniprogram';
92
+ }
7
93
  function getProjectsFile() {
8
94
  return path.join(app.getPath('userData'), 'dimina-projects.json');
9
95
  }
@@ -28,6 +114,12 @@ export function validateProjectDir(dirPath) {
28
114
  if (!fs.existsSync(dirPath)) {
29
115
  return `小程序目录不存在:${dirPath}`;
30
116
  }
117
+ if (detectRuntimeType(dirPath) === 'minigame') {
118
+ if (!fs.existsSync(path.join(dirPath, 'game.json'))) {
119
+ return '该目录缺少 game.json,请选择包含小游戏源码的目录';
120
+ }
121
+ return null;
122
+ }
31
123
  if (!fs.existsSync(path.join(dirPath, 'app.json'))) {
32
124
  const configPath = path.join(dirPath, 'project.config.json');
33
125
  if (fs.existsSync(configPath)) {
@@ -51,22 +143,14 @@ export function hasProject(dirPath) {
51
143
  }
52
144
  export function addProject(dirPath) {
53
145
  const projects = load();
54
- let name = path.basename(dirPath);
55
- try {
56
- const configPath = path.join(dirPath, 'project.config.json');
57
- if (fs.existsSync(configPath)) {
58
- const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
59
- if (cfg.projectname)
60
- name = cfg.projectname;
61
- }
62
- }
63
- catch (err) {
64
- log.warn('Failed to read project name from config', err);
65
- }
66
- const project = { name, path: dirPath, lastOpened: null };
146
+ // The config is the name's owner, so re-adding a directory picks up a rename
147
+ // the user made here rather than reverting it.
148
+ const name = readProjectName(dirPath) ?? path.basename(dirPath);
149
+ const type = detectRuntimeType(dirPath);
150
+ const project = { name, path: dirPath, lastOpened: null, type };
67
151
  const idx = projects.findIndex((p) => p.path === dirPath);
68
152
  if (idx >= 0) {
69
- projects[idx] = { ...projects[idx], name };
153
+ projects[idx] = { ...projects[idx], name, type };
70
154
  }
71
155
  else {
72
156
  projects.unshift(project);
@@ -77,6 +161,43 @@ export function addProject(dirPath) {
77
161
  export function removeProject(dirPath) {
78
162
  save(load().filter((p) => p.path !== dirPath));
79
163
  }
164
+ /**
165
+ * Apply a user edit to the record at `dirPath`. Returns the updated record, or
166
+ * `null` when no record matches — callers decide whether a missing project is
167
+ * an error (the IPC path) or a no-op.
168
+ *
169
+ * A name change is written through to the project's own config (see
170
+ * `writeProjectName`); the icon lives only in our list.
171
+ */
172
+ export function updateProject(dirPath, patch) {
173
+ const projects = load();
174
+ const idx = projects.findIndex((p) => p.path === dirPath);
175
+ if (idx < 0)
176
+ return null;
177
+ const next = { ...projects[idx] };
178
+ if (patch.name !== undefined) {
179
+ const name = patch.name.trim();
180
+ if (!name)
181
+ throw new Error('Project name cannot be empty');
182
+ next.name = name;
183
+ }
184
+ if (patch.iconUrl !== undefined) {
185
+ const iconUrl = patch.iconUrl.trim();
186
+ if (iconUrl)
187
+ next.iconUrl = iconUrl;
188
+ else
189
+ delete next.iconUrl;
190
+ }
191
+ // The config write comes first and is allowed to throw: a rename that only
192
+ // reached our list would look applied until the next re-import silently
193
+ // reverted it. The icon has no counterpart in the project config — it is
194
+ // ours alone — so it never touches the project directory.
195
+ if (next.name !== projects[idx].name)
196
+ writeProjectName(dirPath, next.name);
197
+ projects[idx] = next;
198
+ save(projects);
199
+ return next;
200
+ }
80
201
  export function updateLastOpened(dirPath) {
81
202
  const projects = load();
82
203
  const idx = projects.findIndex((p) => p.path === dirPath);
@@ -88,13 +209,26 @@ export function updateLastOpened(dirPath) {
88
209
  export function getCompileConfig(dirPath) {
89
210
  const projects = load();
90
211
  const project = projects.find((p) => p.path === dirPath);
91
- return (project?.compileConfig ?? {
92
- startPage: '',
212
+ if (project?.compileConfig)
213
+ return project.compileConfig;
214
+ // Default startPage to the project's real entry page (mirrors
215
+ // getProjectPages: 'game' for mini-games, app.json's entryPagePath for
216
+ // mini-programs) rather than a mini-program-only literal — the simulator
217
+ // URL builders' own 'pages/index/index' fallback only fires when this is
218
+ // still empty (unreadable/malformed manifest).
219
+ return {
220
+ startPage: getProjectPages(dirPath).entryPagePath,
93
221
  scene: DEFAULT_SCENE,
94
222
  queryParams: [],
95
- });
223
+ };
96
224
  }
97
225
  export function getProjectPages(dirPath) {
226
+ if (detectRuntimeType(dirPath) === 'minigame') {
227
+ // Mini-games have no page router — the compiler always emits a single
228
+ // synthetic 'game' entry (dimina/fe/packages/compiler/src/env.js
229
+ // storeAppConfig), mirrored here rather than reading a nonexistent app.json.
230
+ return { pages: ['game'], entryPagePath: 'game' };
231
+ }
98
232
  const appJsonPath = path.join(dirPath, 'app.json');
99
233
  try {
100
234
  const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
@@ -5,8 +5,8 @@
5
5
  * template catalog, and the "新建项目" dialog.
6
6
  */
7
7
  import type { CompileConfig } from '../../../shared/types.js';
8
- import type { Project } from './project-repository.js';
9
- export type { Project };
8
+ import type { Project, ProjectPatch } from './project-repository.js';
9
+ export type { Project, ProjectPatch };
10
10
  export type { ProjectTemplate, CreateProjectInput } from '../../../shared/types.js';
11
11
  /**
12
12
  * Default compile config returned by WorkspaceService when the injected
@@ -40,6 +40,17 @@ export interface ProjectsProvider {
40
40
  validateProjectDir?(dirPath: string): string | null | Promise<string | null>;
41
41
  addProject(dirPath: string): Project | Promise<Project>;
42
42
  removeProject(dirPath: string): void | Promise<void>;
43
+ /**
44
+ * Apply a user edit (name / icon) to an existing record. `dirPath` is the
45
+ * record's identity, so it is never part of the patch — see `ProjectPatch`.
46
+ *
47
+ * Default when omitted: throws, and the renderer shows that error in the
48
+ * edit dialog. A silent no-op would report success (dialog closes, list
49
+ * reloads) while discarding the user's input, since the caller has no way
50
+ * to tell "saved" apart from "provider doesn't support this". Implement
51
+ * this if your UI exposes the project-edit dialog.
52
+ */
53
+ updateProject?(dirPath: string, patch: ProjectPatch): Project | Promise<Project>;
43
54
  /**
44
55
  * Record that the user just opened a project (drives "recent" ordering).
45
56
  *
@@ -1,9 +1,13 @@
1
- import { BrowserWindow } from 'electron';
2
1
  import type { UpdateChecker, UpdateInfo } from '../../../shared/types.js';
3
2
  import { type SenderPolicy } from '../../utils/ipc-registry.js';
4
3
  export interface UpdateManagerOptions {
5
4
  checker: UpdateChecker;
6
- mainWindow: BrowserWindow;
5
+ /** Show the update-available overlay panel, fed the discovered update's info. */
6
+ showUpdatePanel: (info: UpdateInfo) => void;
7
+ /** Forward a download-progress tick into the (already-shown) update overlay. */
8
+ notifyDownloadProgress: (percent: number) => void;
9
+ /** Hide the update overlay panel — the renderer's close (any path) forwards here. */
10
+ hideUpdatePanel: () => void;
7
11
  /** Check interval in milliseconds. Default: 1 hour */
8
12
  checkInterval?: number;
9
13
  /** Delay before the first check after startup in ms. Default: 5000 */
@@ -20,7 +24,9 @@ export interface UpdateManagerOptions {
20
24
  }
21
25
  export declare class UpdateManager {
22
26
  private checker;
23
- private mainWindow;
27
+ private showUpdatePanel;
28
+ private notifyDownloadProgress;
29
+ private hideUpdatePanel;
24
30
  private getCurrentVersion;
25
31
  private latestUpdate;
26
32
  private downloadedPath;
@@ -37,6 +43,14 @@ export declare class UpdateManager {
37
43
  private timer;
38
44
  private initialTimer;
39
45
  private ipc;
46
+ /**
47
+ * Set by `dispose()`. Clearing the timers only stops FUTURE ticks — an
48
+ * in-flight `checkAndNotify()` invocation (already past its `await
49
+ * this.check()`) has no timer left to cancel it, and would otherwise call
50
+ * `showUpdatePanel` after dispose has torn down the overlay panel that
51
+ * owns (e.g. `views.disposeAll()` destroying its WebContentsView).
52
+ */
53
+ private disposed;
40
54
  constructor(opts: UpdateManagerOptions);
41
55
  private registerIpc;
42
56
  check(): Promise<{
@@ -1,9 +1,11 @@
1
1
  import { app, shell } from 'electron';
2
- import { UpdateChannel } from '../../../shared/ipc-channels.js';
2
+ import { UpdateChannel } from '../../../shared/ipc-channels-overlays.js';
3
3
  import { IpcRegistry } from '../../utils/ipc-registry.js';
4
4
  export class UpdateManager {
5
5
  checker;
6
- mainWindow;
6
+ showUpdatePanel;
7
+ notifyDownloadProgress;
8
+ hideUpdatePanel;
7
9
  getCurrentVersion;
8
10
  latestUpdate = null;
9
11
  downloadedPath = null;
@@ -20,9 +22,19 @@ export class UpdateManager {
20
22
  timer = null;
21
23
  initialTimer = null;
22
24
  ipc;
25
+ /**
26
+ * Set by `dispose()`. Clearing the timers only stops FUTURE ticks — an
27
+ * in-flight `checkAndNotify()` invocation (already past its `await
28
+ * this.check()`) has no timer left to cancel it, and would otherwise call
29
+ * `showUpdatePanel` after dispose has torn down the overlay panel that
30
+ * owns (e.g. `views.disposeAll()` destroying its WebContentsView).
31
+ */
32
+ disposed = false;
23
33
  constructor(opts) {
24
34
  this.checker = opts.checker;
25
- this.mainWindow = opts.mainWindow;
35
+ this.showUpdatePanel = opts.showUpdatePanel;
36
+ this.notifyDownloadProgress = opts.notifyDownloadProgress;
37
+ this.hideUpdatePanel = opts.hideUpdatePanel;
26
38
  this.getCurrentVersion = opts.getCurrentVersion ?? (() => app.getVersion());
27
39
  this.ipc = new IpcRegistry(opts.senderPolicy);
28
40
  this.registerIpc();
@@ -32,7 +44,8 @@ export class UpdateManager {
32
44
  this.ipc
33
45
  .handle(UpdateChannel.Check, async () => this.check())
34
46
  .handle(UpdateChannel.Download, async () => this.download())
35
- .handle(UpdateChannel.Install, async () => this.install());
47
+ .handle(UpdateChannel.Install, async () => this.install())
48
+ .on(UpdateChannel.Close, () => this.hideUpdatePanel());
36
49
  }
37
50
  async check() {
38
51
  try {
@@ -69,7 +82,7 @@ export class UpdateManager {
69
82
  }
70
83
  try {
71
84
  const filePath = await this.checker.downloadUpdate(this.latestUpdate, (percent) => {
72
- this.mainWindow.webContents.send(UpdateChannel.DownloadProgress, { percent });
85
+ this.notifyDownloadProgress(percent);
73
86
  });
74
87
  this.downloadedPath = filePath;
75
88
  return { success: true, filePath };
@@ -104,6 +117,7 @@ export class UpdateManager {
104
117
  * the timer clears + handler removals are scheduled synchronously.
105
118
  */
106
119
  dispose() {
120
+ this.disposed = true;
107
121
  if (this.timer) {
108
122
  clearInterval(this.timer);
109
123
  this.timer = null;
@@ -120,12 +134,17 @@ export class UpdateManager {
120
134
  }
121
135
  async checkAndNotify() {
122
136
  const result = await this.check();
137
+ // dispose() only cancels the timers that SCHEDULE future ticks — a tick
138
+ // already in flight (past this await) has nothing left to cancel it, and
139
+ // must not call into a torn-down panel/registry after the fact.
140
+ if (this.disposed)
141
+ return;
123
142
  if (!result.hasUpdate || !result.info)
124
143
  return;
125
144
  if (this.hasNotified)
126
145
  return;
127
146
  this.hasNotified = true;
128
- this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
147
+ this.showUpdatePanel(result.info);
129
148
  }
130
149
  }
131
150
  //# sourceMappingURL=update-manager.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Dialog-specific instantiation of `createHostSlotPortChannel` (see that
3
+ * module for the full per-load handshake / navigation-invalidation
4
+ * contract).
5
+ */
6
+ import type { WebContents } from 'electron';
7
+ import { type HostSlotMessageSubscription, type HostSlotPortChannel } from './host-slot-port-channel.js';
8
+ export type HostDialogMessageSubscription = HostSlotMessageSubscription;
9
+ export type HostDialogPortChannel = HostSlotPortChannel;
10
+ export declare function createHostDialogPortChannel(opts: {
11
+ /**
12
+ * Is `wc` still the manager's CURRENT live dialog webContents? Guards a
13
+ * stale wc's late `did-finish-load` from hijacking the channel after a
14
+ * rebuild swapped the view out underneath it.
15
+ */
16
+ isCurrent: (wc: WebContents) => boolean;
17
+ }): HostDialogPortChannel;
18
+ //# sourceMappingURL=host-dialog-port-channel.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dialog-specific instantiation of `createHostSlotPortChannel` (see that
3
+ * module for the full per-load handshake / navigation-invalidation
4
+ * contract).
5
+ */
6
+ import { ViewChannel } from '../../../shared/ipc-channels-overlays.js';
7
+ import { createHostSlotPortChannel, } from './host-slot-port-channel.js';
8
+ export function createHostDialogPortChannel(opts) {
9
+ return createHostSlotPortChannel({
10
+ isCurrent: opts.isCurrent,
11
+ channel: ViewChannel.HostDialogPort,
12
+ logPrefix: '[host-dialog]',
13
+ });
14
+ }
15
+ //# sourceMappingURL=host-dialog-port-channel.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dialog-specific instantiation of `createHostSlotSessionRuntime` (see that
3
+ * module for the full ref-counting contract). Kept as a thin, independently
4
+ * named wrapper — same rationale as `host-toolbar-session-runtime.ts` — so
5
+ * a dialog-focused test can `vi.resetModules()` this module's state without
6
+ * touching the toolbar/sidebar's independent ref-counts.
7
+ */
8
+ export declare function acquireHostDialogSessionRuntime(): void;
9
+ export declare function releaseHostDialogSessionRuntime(): void;
10
+ //# sourceMappingURL=host-dialog-session-runtime.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Dialog-specific instantiation of `createHostSlotSessionRuntime` (see that
3
+ * module for the full ref-counting contract). Kept as a thin, independently
4
+ * named wrapper — same rationale as `host-toolbar-session-runtime.ts` — so
5
+ * a dialog-focused test can `vi.resetModules()` this module's state without
6
+ * touching the toolbar/sidebar's independent ref-counts.
7
+ */
8
+ import { createHostSlotSessionRuntime } from './host-slot-session-runtime.js';
9
+ import { hostDialogRuntimePreloadPath } from '../../utils/paths.js';
10
+ const runtime = createHostSlotSessionRuntime(hostDialogRuntimePreloadPath);
11
+ export function acquireHostDialogSessionRuntime() {
12
+ runtime.acquire();
13
+ }
14
+ export function releaseHostDialogSessionRuntime() {
15
+ runtime.release();
16
+ }
17
+ //# sourceMappingURL=host-dialog-session-runtime.js.map
@@ -0,0 +1,95 @@
1
+ import type { WebContents } from 'electron';
2
+ import type { PlacementReconciler } from './placement-reconciler.js';
3
+ import type { ViewManagerContext } from './view-manager.js';
4
+ import type { HostDialogMessageSubscription } from './host-dialog-port-channel.js';
5
+ export type HostDialogAxis = 'block' | 'inline';
6
+ /**
7
+ * The control object the downstream host uses to own the dialog
8
+ * WebContentsView. Unlike `HostToolbarControl` / `HostSidebarControl`
9
+ * (persistent strips), the dialog is a by-demand overlay: no extent-mode
10
+ * setter (it always self-sizes from its own reverse advertiser, with a
11
+ * conservative default while unmeasured — see below) and an explicit
12
+ * `show()`/`hide()`/`isVisible()` surface instead of always being present
13
+ * once loaded.
14
+ */
15
+ export interface HostDialogControl {
16
+ /** Load a URL into the dialog view (lazy-creates the view). */
17
+ loadURL(url: string): Promise<void>;
18
+ /** Load a local file into the dialog view (lazy-creates the view). */
19
+ loadFile(path: string): Promise<void>;
20
+ /** The dialog view's live WebContents, or null if not yet created/destroyed. */
21
+ readonly webContents: WebContents | null;
22
+ /** Same contract as `HostToolbarControl.setPreloadPath`. */
23
+ setPreloadPath(path: string | null): void;
24
+ /**
25
+ * Register a host-side handler for messages the dialog PAGE sends via
26
+ * `window.diminaHostDialog.send(channel, payload)`. Same contract as
27
+ * `HostToolbarControl.onMessage`.
28
+ */
29
+ onMessage(channel: string, handler: (payload: unknown) => void): HostDialogMessageSubscription;
30
+ /** Same contract as `HostToolbarControl.onReady`. */
31
+ onReady(handler: () => void): HostDialogMessageSubscription;
32
+ /** Same contract as `HostToolbarControl.send`. */
33
+ send(channel: string, payload: unknown): boolean;
34
+ /**
35
+ * Show the dialog, centered in the main window using its self-advertised
36
+ * size on both axes (a conservative default for any axis not yet
37
+ * measured). Creates the view's placement if needed; does not (re)load
38
+ * content — the host must `loadURL`/`loadFile` first. Idempotent while
39
+ * already visible (re-centers with the latest measured extent instead of
40
+ * no-op, in case a resize happened while hidden).
41
+ */
42
+ show(): void;
43
+ /** Hide the dialog (kept alive, content preserved) without destroying the view. */
44
+ hide(): void;
45
+ /** Whether the dialog is currently shown. */
46
+ isVisible(): boolean;
47
+ }
48
+ /**
49
+ * The host-dialog slice of `ViewManager`'s public surface, split into its
50
+ * own file for the same file-length reason as sidebar's
51
+ * `HostSidebarViewManagerMembers` — a by-demand, dual-axis, main-centered
52
+ * overlay rather than a persistent strip.
53
+ */
54
+ export interface HostDialogViewManagerMembers {
55
+ /** Return the webContents ID of the host-dialog overlay if alive, else null. */
56
+ getHostDialogWebContentsId(): number | null;
57
+ /** Reverse size-advertiser sink for either axis; re-centers while visible. */
58
+ reportHostDialogMeasuredExtent(axis: HostDialogAxis, extent: number): void;
59
+ /** Host-facing control surface for the dialog WebContentsView. */
60
+ readonly hostDialog: HostDialogControl;
61
+ }
62
+ export interface HostDialogView {
63
+ readonly control: HostDialogControl;
64
+ /**
65
+ * Reverse size-advertiser sink for either axis. While visible, immediately
66
+ * re-centers with the newly measured extent; while hidden, just retains it
67
+ * for the next `show()`. Non-finite / non-positive reports are dropped —
68
+ * same fail-closed posture as the base slots' extent validation.
69
+ */
70
+ reportMeasuredExtent(axis: HostDialogAxis, extent: number): void;
71
+ getHostDialogWebContentsId(): number | null;
72
+ /**
73
+ * Re-center in the main window's CURRENT content rect, e.g. after a
74
+ * main-window resize. No-op while hidden — the next `show()` computes
75
+ * fresh bounds anyway, so there is nothing to reapply.
76
+ */
77
+ reposition(): void;
78
+ dispose(): void;
79
+ }
80
+ /**
81
+ * Host-dialog factory — its PLACEMENT is deliberately NOT built on
82
+ * `createHostSlotView`. That factory models a persistent, single-axis,
83
+ * renderer-anchored strip (`setBaseDesired`); the dialog is a by-demand,
84
+ * dual-axis, main-centered overlay (`setOverlayDesired` /
85
+ * `deleteOverlayDesired`), closer in shape to the settings/popover/tooltip
86
+ * overlays in `overlay-panels-view.ts` than to the toolbar/sidebar. It still
87
+ * doesn't reuse `createOverlayPanel` from that file: those panels load a
88
+ * FIXED renderer-dir entry and push typed data, whereas the dialog loads
89
+ * ARBITRARY host content and needs the toolbar/sidebar's `loadURL`/`loadFile`
90
+ * + gated port-channel surface. The underlying WebContentsView lazy-create /
91
+ * liveness / load / teardown lifecycle IS shared with the base slots though
92
+ * — see `managed-web-contents-view.ts`.
93
+ */
94
+ export declare function createHostDialogView(ctx: ViewManagerContext, reconciler: PlacementReconciler): HostDialogView;
95
+ //# sourceMappingURL=host-dialog-view.d.ts.map