specrails-desktop 2.30.1 → 2.30.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.
Files changed (28) hide show
  1. package/client/dist/assets/{ActivityFeedPage-AQgpr9qG.js → ActivityFeedPage-CcBOllqm.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-I7a0usG7.js → AgentBrowserCapture-BOJkZUj_.js} +1 -1
  3. package/client/dist/assets/{AgentModeCodePane-BqOVg15n.js → AgentModeCodePane-BIh67rGi.js} +2 -2
  4. package/client/dist/assets/{AgentModeJobsPane-4SG6FF27.js → AgentModeJobsPane-C5tkU2Ed.js} +1 -1
  5. package/client/dist/assets/{AgentsPage-C3FgYlEi.js → AgentsPage-CNlE_Uh4.js} +1 -1
  6. package/client/dist/assets/{AnalyticsPage-BcMNByW5.js → AnalyticsPage-cYcJxWPn.js} +1 -1
  7. package/client/dist/assets/{CodePage-CgdDkXp-.js → CodePage-D4QYa4t1.js} +1 -1
  8. package/client/dist/assets/{DesktopAnalyticsPage-DPMcS88n.js → DesktopAnalyticsPage-DCmgYV86.js} +1 -1
  9. package/client/dist/assets/{DocsDialog-BC7574Zi.js → DocsDialog-BXnOgm7V.js} +1 -1
  10. package/client/dist/assets/{DocsPage-Dd9yilUg.js → DocsPage-nFkHh-Dc.js} +1 -1
  11. package/client/dist/assets/{InteractiveJobComposer-CqC_0VQL.js → InteractiveJobComposer-qoeZjwVY.js} +1 -1
  12. package/client/dist/assets/{JobDetailModal-BFNXOhk1.js → JobDetailModal-ChRBB9bc.js} +1 -1
  13. package/client/dist/assets/{JobDetailPage-DM-6zYpA.js → JobDetailPage-BuOnlndB.js} +1 -1
  14. package/client/dist/assets/{JobsPage-1dkjPk1k.js → JobsPage-C_UEIXWA.js} +1 -1
  15. package/client/dist/assets/{LoopBuilderPage-BPeqz6cG.js → LoopBuilderPage-BV_wge0g.js} +1 -1
  16. package/client/dist/assets/{LoopPreviewModal-EdkyYaGN.js → LoopPreviewModal-Qk_Jaxo8.js} +1 -1
  17. package/client/dist/assets/{LoopsPage-nZfgiaK8.js → LoopsPage-lLi6ax0p.js} +1 -1
  18. package/client/dist/assets/{PluginsPage-CwFfFBzW.js → PluginsPage-DZIFv9kw.js} +1 -1
  19. package/client/dist/assets/{ProjectSettingsDialog-DHsxLFMT.js → ProjectSettingsDialog-DEHcyt_I.js} +1 -1
  20. package/client/dist/assets/{TemplatePreviewModal-wcI3nGNr.js → TemplatePreviewModal-Bxbtnzob.js} +1 -1
  21. package/client/dist/assets/{TicketDetailModal-7EloKP1C.js → TicketDetailModal-CqQ5gx7O.js} +2 -2
  22. package/client/dist/assets/{dist-js-Bt2zW2YG.js → dist-js-CBhuoTO6.js} +1 -1
  23. package/client/dist/assets/{dist-js-zvt5ap3o.js → dist-js-eSxaoEw0.js} +1 -1
  24. package/client/dist/assets/{index-xkzK3E5i.js → index-CewRMo0W.js} +10 -10
  25. package/client/dist/index.html +2 -2
  26. package/package.json +1 -1
  27. package/server/dist/core-update-manager.js +27 -4
  28. package/server/dist/framework-manager.js +41 -10
@@ -204,7 +204,7 @@
204
204
  .specrails-splash__mark .pill { opacity: 1; }
205
205
  }
206
206
  </style>
207
- <script type="module" crossorigin src="/assets/index-xkzK3E5i.js"></script>
207
+ <script type="module" crossorigin src="/assets/index-CewRMo0W.js"></script>
208
208
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CNC7AqOf.js">
209
209
  <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-zu2_FqZY.js">
210
210
  <link rel="modulepreload" crossorigin href="/assets/react-dom-DiWzBDP9.js">
@@ -276,7 +276,7 @@
276
276
  <link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-qrcBaLW1.js">
277
277
  <link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-ByKli7Q_.js">
278
278
  <link rel="modulepreload" crossorigin href="/assets/spending-DMnCXy84.js">
279
- <link rel="modulepreload" crossorigin href="/assets/TicketDetailModal-7EloKP1C.js">
279
+ <link rel="modulepreload" crossorigin href="/assets/TicketDetailModal-CqQ5gx7O.js">
280
280
  <link rel="stylesheet" crossorigin href="/assets/index-Dh9i4CnN.css">
281
281
  </head>
282
282
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.30.1",
3
+ "version": "2.30.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -116,7 +116,13 @@ class CoreUpdateManager {
116
116
  }
117
117
  this.updating = true;
118
118
  this.emit('downloading', { version: requested });
119
- const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'core-update-'));
119
+ // realpath the tmp root: on macOS os.tmpdir() is a symlinked path
120
+ // (/var/folders → /private/var/folders). The staged core's cli.js has an
121
+ // auto-run guard comparing import.meta.url (realpathed by Node's ESM
122
+ // loader) against process.argv[1] (the literal spawn arg) — a symlinked
123
+ // staging path makes them differ, so main() never runs and every
124
+ // install-framework/swap-current child exits 0 as a silent no-op.
125
+ const tmp = fs_1.default.mkdtempSync(path_1.default.join(realTmpDir(), 'core-update-'));
120
126
  try {
121
127
  this.npmInstallFn(`${exports.CORE_PACKAGE}@${requested}`, tmp);
122
128
  const coreRoot = path_1.default.join(tmp, 'node_modules', exports.CORE_PACKAGE);
@@ -136,9 +142,16 @@ class CoreUpdateManager {
136
142
  if (mat.errors.length > 0) {
137
143
  throw new Error(`framework materialize failed: ${mat.errors.map((e) => `${e.provider}: ${e.message}`).join('; ')}`);
138
144
  }
139
- const swapped = fm.swapCurrent(installed);
140
- if (!swapped) {
141
- throw new Error('framework swap-current failed');
145
+ // A materialize that reports no errors but ALSO no materialized providers
146
+ // wrote nothing — swapping `current` at a nonexistent version dir would
147
+ // fail downstream with a misleading "swap-current failed". Surface the
148
+ // real condition instead (mirrors FrameworkManager.versionCheck's guard).
149
+ if (mat.providers.length === 0) {
150
+ throw new Error(`framework materialize completed without materializing any provider (requested: ${providers.join(', ') || 'none'})`);
151
+ }
152
+ const swapped = fm.swapCurrentDetailed(installed);
153
+ if (!swapped.ok) {
154
+ throw new Error(`framework swap-current failed${swapped.detail ? `: ${swapped.detail}` : ''}`);
142
155
  }
143
156
  this.latestVersion = installed;
144
157
  this.lastCheckedAt = Date.now();
@@ -180,6 +193,16 @@ function uniqueProviders(values) {
180
193
  const out = Array.from(new Set(values.filter((v) => typeof v === 'string' && v.length > 0)));
181
194
  return out.length > 0 ? out : ['claude'];
182
195
  }
196
+ /** os.tmpdir() with symlinks resolved (macOS /var/folders → /private/var/folders). */
197
+ function realTmpDir() {
198
+ const dir = os_1.default.tmpdir();
199
+ try {
200
+ return fs_1.default.realpathSync(dir);
201
+ }
202
+ catch {
203
+ return dir;
204
+ }
205
+ }
183
206
  /** GET the latest published version from the npm registry (no npm binary needed). */
184
207
  async function fetchLatestFromRegistry() {
185
208
  const controller = new AbortController();
@@ -26,6 +26,22 @@ const path_resolver_1 = require("./path-resolver");
26
26
  function nodeInterpreter() {
27
27
  return (0, path_resolver_1.resolveBundledNodeExe)() ?? process.execPath;
28
28
  }
29
+ /**
30
+ * Resolve symlinks in the CLI path we pass as the child's argv[1]. Core's
31
+ * cli.js auto-run guard compares import.meta.url (realpathed by Node's ESM
32
+ * loader) against pathToFileURL(process.argv[1]) — a symlinked argv[1] (e.g.
33
+ * a staging dir under macOS /var/folders) makes them differ, so main() never
34
+ * runs and the child exits 0 as a silent no-op. Realpathing here keeps the
35
+ * guard true regardless of where the core package lives.
36
+ */
37
+ function realpathOrSelf(p) {
38
+ try {
39
+ return fs_1.default.realpathSync(p);
40
+ }
41
+ catch {
42
+ return p;
43
+ }
44
+ }
29
45
  /** `~/.specrails/framework` — same home as the registry. */
30
46
  function frameworkRoot(home) {
31
47
  return path_1.default.join((0, artifact_registry_1.resolveHome)(home), '.specrails', 'framework');
@@ -121,9 +137,10 @@ class FrameworkManager {
121
137
  resolveCli() {
122
138
  if (this.coreRoot) {
123
139
  const cli = path_1.default.join(this.coreRoot, 'dist', 'installer', 'cli.js');
124
- return fs_1.default.existsSync(cli) ? cli : null;
140
+ return fs_1.default.existsSync(cli) ? realpathOrSelf(cli) : null;
125
141
  }
126
- return (0, bundled_core_1.getBundledCoreCli)();
142
+ const bundled = (0, bundled_core_1.getBundledCoreCli)();
143
+ return bundled ? realpathOrSelf(bundled) : null;
127
144
  }
128
145
  /** True when a usable core (override or bundled) is present (else methods no-op). */
129
146
  isAvailable() {
@@ -204,16 +221,30 @@ class FrameworkManager {
204
221
  * `swap-current` only moves the version pointer (provider-invariant).
205
222
  */
206
223
  swapCurrent(version, _provider = 'claude') {
224
+ return this.swapCurrentDetailed(version).ok;
225
+ }
226
+ /**
227
+ * Like `swapCurrent`, but on failure carries WHY it failed (`detail`) — the
228
+ * subprocess stderr/exit or the missing-CLI/verify-mismatch case — so callers
229
+ * (the core update channel) can surface an actionable error instead of the
230
+ * bare "swap-current failed".
231
+ */
232
+ swapCurrentDetailed(version) {
207
233
  const cli = this.resolveCli();
208
234
  if (!cli)
209
- return false;
235
+ return { ok: false, detail: 'no usable core CLI to run swap-current with' };
210
236
  if (readCurrentFrameworkVersion(this.home) === version)
211
- return true;
237
+ return { ok: true, detail: null };
212
238
  const fwDir = frameworkRoot(this.home);
213
239
  const res = (0, child_process_1.spawnSync)(nodeInterpreter(), [cli, 'swap-current', '--framework-dir', fwDir, '--version', version], { env: this.childEnv(), encoding: 'utf-8', timeout: 120_000 });
214
- if (res.error || (res.status ?? 1) !== 0)
215
- return false;
216
- return readCurrentFrameworkVersion(this.home) === version;
240
+ if (res.error || (res.status ?? 1) !== 0) {
241
+ const detail = (res.error?.message ?? `${res.stderr || res.stdout || ''}`.trim()) || `exit ${res.status ?? 'unknown'}`;
242
+ return { ok: false, detail };
243
+ }
244
+ if (readCurrentFrameworkVersion(this.home) !== version) {
245
+ return { ok: false, detail: `swap-current exited 0 but current still resolves to ${readCurrentFrameworkVersion(this.home) ?? 'nothing'}` };
246
+ }
247
+ return { ok: true, detail: null };
217
248
  }
218
249
  /**
219
250
  * Compare the bundled core version with `framework/current`. When they differ
@@ -266,8 +297,8 @@ class FrameworkManager {
266
297
  // No errors yet nothing materialized (empty/duplicate-only provider list).
267
298
  return { swapped: false, version: current };
268
299
  }
269
- const ok = this.swapCurrent(bundled);
270
- if (ok) {
300
+ const swap = this.swapCurrentDetailed(bundled);
301
+ if (swap.ok) {
271
302
  try {
272
303
  this.broadcast?.({ type: 'framework.updated', version: bundled });
273
304
  }
@@ -281,7 +312,7 @@ class FrameworkManager {
281
312
  this.broadcast?.({
282
313
  type: 'framework.update_failed',
283
314
  version: bundled,
284
- errors: [{ provider: providers[0] ?? 'claude', message: 'swap-current failed' }],
315
+ errors: [{ provider: providers[0] ?? 'claude', message: `swap-current failed${swap.detail ? `: ${swap.detail}` : ''}` }],
285
316
  });
286
317
  }
287
318
  catch {