@rubytech/create-maxy 1.0.785 → 1.0.787

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/dist/index.js CHANGED
@@ -1358,6 +1358,19 @@ function buildPlatform() {
1358
1358
  // server/package.json but NOT shipped as pre-built node_modules — npm pack silently
1359
1359
  // strips files from nested node_modules (e.g. rxjs/package.json), breaking require().
1360
1360
  // Install fresh on device to guarantee a complete dependency tree.
1361
+ //
1362
+ // On upgrade, wipe `node_modules` first so npm extracts a clean tree. Without
1363
+ // this, an interrupted previous install (network blip, operator cancellation,
1364
+ // power loss) can leave nested package.json files half-truncated — the most
1365
+ // common manifestation is `Error: Invalid package config .../rxjs/package.json`
1366
+ // at server startup, which loops the brand service indefinitely. The wipe
1367
+ // adds ~30 s to upgrades but eliminates a class of unrecoverable customer
1368
+ // states; reliability wins over speed for a one-shot install path.
1369
+ const serverNodeModules = join(INSTALL_DIR, "server", "node_modules");
1370
+ if (existsSync(serverNodeModules)) {
1371
+ console.log(" Wiping previous server/node_modules for a clean reinstall...");
1372
+ rmSync(serverNodeModules, { recursive: true, force: true });
1373
+ }
1361
1374
  console.log(` Installing server dependencies (${join(INSTALL_DIR, "server")})...`);
1362
1375
  shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: join(INSTALL_DIR, "server") }, 3, 15);
1363
1376
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.785",
3
+ "version": "1.0.787",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -292,12 +292,10 @@ function portalHTML() {
292
292
  <div class="success-icon">&#10003;</div>
293
293
  <h1>Connected!</h1>
294
294
  <p class="hint">${escapedBrandName} is now online. Redirecting in <span id="redirect-countdown">15</span>s…</p>
295
- <div class="address-box">
296
- <a id="device-link" href="#"></a>
297
- </div>
298
- <p class="hint" id="manual-hint" style="display:none">If your phone doesn't redirect automatically, open this in your normal browser:</p>
299
- <div class="address-box" id="manual-box" style="display:none">
300
- <span id="device-link-text" style="user-select:all"></span>
295
+ <a id="open-btn" href="#" class="btn" style="text-decoration:none;display:flex;align-items:center;justify-content:center">Open ${escapedBrandName} →</a>
296
+ <p class="hint" style="margin-top:16px">Or copy this URL into your browser:</p>
297
+ <div class="address-box" style="user-select:all" id="manual-box">
298
+ <span id="device-link-text"></span>
301
299
  </div>
302
300
  <p class="hint">This access point will close shortly.<br>Your phone will reconnect to your WiFi automatically.</p>
303
301
  </div>
@@ -421,21 +419,11 @@ function portalHTML() {
421
419
  var ipAddr = data.ip ? "http://" + data.ip + devicePort : null;
422
420
  // Auto-navigate target: prefer the IP because mDNS .local resolution
423
421
  // is flaky on Android (notably Brave) and the redirect dead-ends.
424
- // The displayed link uses the same URL so what the user sees is what
425
- // they get if they tap.
426
422
  var redirectAddr = ipAddr || hostnameAddr;
427
- var link = document.getElementById("device-link");
428
- link.href = redirectAddr;
429
- link.textContent = redirectAddr;
430
- // Always surface a manual fallback line — captive webviews on some
431
- // phone OSes silently swallow JS navigation, and the user needs to
432
- // be able to copy-paste the URL into a real browser.
423
+ var openBtn = document.getElementById("open-btn");
424
+ if (openBtn) openBtn.href = redirectAddr;
433
425
  var manualText = document.getElementById("device-link-text");
434
- if (manualText) {
435
- manualText.textContent = redirectAddr;
436
- document.getElementById("manual-hint").style.display = "block";
437
- document.getElementById("manual-box").style.display = "block";
438
- }
426
+ if (manualText) manualText.textContent = redirectAddr;
439
427
  showScreen("success-screen");
440
428
  // 15s countdown: the Pi keeps the AP up for ~10s after writing the
441
429
  // success result so this poll can land, then tears it down; the
@@ -667,8 +655,22 @@ function handleRequest(req, res) {
667
655
  // Use async open+write to avoid blocking the event loop (a FIFO
668
656
  // write blocks until a reader opens the other end).
669
657
  connecting = true;
670
- res.writeHead(202, { "Content-Type": "application/json" });
671
- res.end('{"status":"connecting"}');
658
+ // Return success immediately with the post-connect URL info.
659
+ // Why not poll for verification? The AP and station modes are
660
+ // mutually exclusive on a single radio: as soon as the bash
661
+ // script tears down hostapd to run `nmcli connect`, the phone
662
+ // loses its connection to 192.168.4.1 and any subsequent
663
+ // `/result` poll dies. The success page must be in the phone's
664
+ // hands BEFORE the AP goes away. We send the hostname (.local
665
+ // resolution works on iOS/macOS) and the operator can paste
666
+ // the URL into a browser if .local fails on their device.
667
+ res.writeHead(200, { "Content-Type": "application/json" });
668
+ res.end(JSON.stringify({
669
+ success: true,
670
+ hostname: HOSTNAME,
671
+ // ip is intentionally absent: we don't yet know the new
672
+ // home-WiFi IP and the AP IP (192.168.4.1) is about to die.
673
+ }));
672
674
 
673
675
  const fifoData = `${ssid}\n${password}`;
674
676
  fs.open(CONNECT_FIFO, "w", (err, fd) => {