stim 1.12.0 → 1.13.0

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 (71) hide show
  1. package/README.md +3 -1
  2. package/dist/{activity-CCZJs__P.mjs → activity-DfVKZTEB.mjs} +42 -25
  3. package/dist/{android-BQiKJYK0.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-B7X-8H7h.mjs → android-CD8pr8Q2.mjs} +62 -47
  5. package/dist/{android-cas-DzLPXiEn.mjs → android-cas-B9Kv9aIy.mjs} +9 -7
  6. package/dist/{android-75Sk2rIR.mjs → android-qx_31wJo.mjs} +395 -116
  7. package/dist/{app-install-CsZi0SAT.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/{budget-DbzW5VTD.mjs → budget-E5o91NR1.mjs} +154 -37
  9. package/dist/{build-plan-CqntbR7b.mjs → build-plan-BTa93o_r.mjs} +21 -88
  10. package/dist/{build-progress-h60GPV27.mjs → build-progress-l7d-X-ZP.mjs} +75 -7
  11. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  12. package/dist/chrome-B5hWgQJS.mjs +61 -0
  13. package/dist/cli.mjs +16 -15
  14. package/dist/collector-run.mjs +14 -8
  15. package/dist/{command-output-BWCj5Vh-.mjs → command-output-BIL7rlX7.mjs} +1 -1
  16. package/dist/created-devices-Cd558h31.mjs +37 -0
  17. package/dist/{deps-CO-_7UOm.mjs → deps-2Wo81np9.mjs} +1 -1
  18. package/dist/{device-9r9xRYDa.mjs → device-Cf7vl-so.mjs} +6 -6
  19. package/dist/{device-lease-B9LAXIUH.mjs → device-lease-CL9dZf73.mjs} +52 -3
  20. package/dist/{device-pool-BoezJd-c.mjs → device-pool-MuoiyRcu.mjs} +3 -3
  21. package/dist/{device-remote-C0_o-tJH.mjs → device-remote-DT9ngZiD.mjs} +10 -10
  22. package/dist/{doctor-LfDlbR-N.mjs → doctor-D2iysNNH.mjs} +47 -18
  23. package/dist/{error-diagnostics-CCxjn31V.mjs → error-diagnostics-BoiOhck8.mjs} +6 -6
  24. package/dist/{gc-C4nQFcTX.mjs → gc-eA-PRBX8.mjs} +853 -126
  25. package/dist/{guide-BPBlh9ML.mjs → guide-BfZ0OmiP.mjs} +736 -110
  26. package/dist/{guide-status-i9ogHt4v.mjs → guide-status-BaMYu55M.mjs} +1 -1
  27. package/dist/{idle-S3tFZ53_.mjs → idle-DxLi5OkN.mjs} +42 -9
  28. package/dist/{in-use-D9XYEgFD.mjs → in-use-wYEaKQM9.mjs} +9 -5
  29. package/dist/{ios-CDzWL1mj.mjs → ios-CcfCeZ19.mjs} +98 -54
  30. package/dist/{ios-CZWOHTTc.mjs → ios-CmEzZiOj.mjs} +10 -5
  31. package/dist/{ios-device-D9cLxVr5.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  32. package/dist/{launch-verify-B2oR_kFN.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  33. package/dist/{logs-SzuYudOj.mjs → logs-DcSRbC2t.mjs} +11 -10
  34. package/dist/{metro-Dzo30hvJ.mjs → metro-BPeY1YMh.mjs} +4 -4
  35. package/dist/{named-ports-Dn1o61D9.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  36. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  37. package/dist/{ownership-Dqnrjrio.mjs → ownership-Bz2kETAS.mjs} +1 -1
  38. package/dist/{ownership-B8d4kXBx.mjs → ownership-PUP4Dwai.mjs} +134 -209
  39. package/dist/page-BTu7I60K.mjs +29 -0
  40. package/dist/{ports-Dp_QTwTC.mjs → ports-C5UutqpJ.mjs} +4 -4
  41. package/dist/{project-B8rbutVC.mjs → project-DGeclhvO.mjs} +53 -17
  42. package/dist/{reload-nocmx8KB.mjs → reload-C-cV5yy_.mjs} +79 -20
  43. package/dist/{remote-cache-BRsRZru5.mjs → remote-cache--aehr4Pr.mjs} +3 -3
  44. package/dist/{server-bare-Gn3ssSdK.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  45. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  46. package/dist/{server-expo-CeT1nIIk.mjs → server-expo-DUCg52-K.mjs} +2 -2
  47. package/dist/{settings-bE-Gu3Ti.mjs → settings-ssVBToDR.mjs} +10 -10
  48. package/dist/{settings-BlyDn2Oz.mjs → settings-xB0Frkg6.mjs} +11 -2
  49. package/dist/settings.schema.json +53 -1
  50. package/dist/{simslim-COtEEP7x.mjs → simslim-Bbg9UWrt.mjs} +12 -6
  51. package/dist/{slot-launch-DIACGEfH.mjs → slot-launch-BIWEW-wI.mjs} +3 -3
  52. package/dist/{start-BboE1HIS.mjs → start-BZhItkiL.mjs} +11 -10
  53. package/dist/{start-BFstVBlx.mjs → start-Rw2GD3nV.mjs} +1 -1
  54. package/dist/{state-DcDgzfu3.mjs → state-DwAU2_-D.mjs} +1 -1
  55. package/dist/state-l1pYwOre.mjs +148 -0
  56. package/dist/{stats-DiCtmluY.mjs → stats-CaSwmsSZ.mjs} +2 -2
  57. package/dist/{status-DI9Tjo7T.mjs → status-TJlL0HWo.mjs} +624 -103
  58. package/dist/{status-BUASYyy_.mjs → status-sszP4XKU.mjs} +59 -2
  59. package/dist/{stim-desktop-D0xhpZt8.mjs → stim-desktop-CXt8-Z3J.mjs} +13 -2
  60. package/dist/{stop-Bn6v-GvS.mjs → stop-BZ2qMiBP.mjs} +37 -12
  61. package/dist/{stop-BI7QY76M.mjs → stop-DskDKNTU.mjs} +2 -2
  62. package/dist/supervisor-run.mjs +10 -10
  63. package/dist/web-run.d.mts +22 -0
  64. package/dist/web-run.mjs +481 -0
  65. package/dist/web-sWXd5iek.mjs +394 -0
  66. package/dist/{worktree-B5sTRskL.mjs → worktree-3cBa-bQt.mjs} +1 -1
  67. package/dist/{worktree-ChDL5fO1.mjs → worktree-D4Do_XiQ.mjs} +226 -19
  68. package/dist/{xcode-BzplpQzc.mjs → xcode-LM6A8cNZ.mjs} +5 -5
  69. package/package.json +5 -5
  70. package/dist/server-expo-eWi9Coof.mjs +0 -2
  71. package/dist/workspace-process-lock-BvvJTfo_.mjs +0 -57
@@ -1,7 +1,7 @@
1
- import { i as findProjectRoot } from "./project-B8rbutVC.mjs";
2
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-BlyDn2Oz.mjs";
3
- import { t as RECENT_LAUNCH_MS } from "./status-BUASYyy_.mjs";
4
- import { t as guideStatus } from "./guide-status-i9ogHt4v.mjs";
1
+ import { a as findProjectRoot } from "./project-DGeclhvO.mjs";
2
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-xB0Frkg6.mjs";
3
+ import { t as RECENT_LAUNCH_MS } from "./status-sszP4XKU.mjs";
4
+ import { t as guideStatus } from "./guide-status-BaMYu55M.mjs";
5
5
  import { SETTINGS_SCHEMA_URL } from "@stim-cli/core/state";
6
6
  import chalk from "chalk";
7
7
  //#endregion
@@ -81,7 +81,14 @@ other apps or other workspaces' devices.
81
81
  stim worktree warm
82
82
 
83
83
  stim start
84
- stim ios # or: stim android
84
+ stim ios # or: stim android, or: stim web
85
+
86
+ For the web target, stim web opens the page in a Stim-owned headless Chrome
87
+ and captures its console and errors in stim logs. It never starts a web
88
+ server: start the dev server first, then run stim web. For Expo web the dev
89
+ server is stim start, and the app needs the react-dom, react-native-web and
90
+ @expo/metro-runtime dependencies. Any other server runs on stim ports get web
91
+ and needs web.url. Read stim guide web first.
85
92
 
86
93
  For a project using EAS development builds, read stim guide lifecycle eas to
87
94
  select a profile from eas.json for the requested target, then run
@@ -223,7 +230,8 @@ Ask the user before these actions:
223
230
  - worktree remove --force, because it also discards uncommitted and untracked
224
231
  files.
225
232
  - gc --delete, because it deletes orphaned resources, removes clean linked
226
- worktrees whose branch is merged, and clears the build outputs of every
233
+ worktrees whose branch or pull request is merged (or whose pull request
234
+ was closed), and clears the build outputs of every
227
235
  workspace not in use. Run stim gc --json first and show
228
236
  the user the entries its sections list (guide facts gc). gc --delete
229
237
  --cache all empties the shared build caches and those outputs instead, and
@@ -272,7 +280,8 @@ Read the matching guide before acting in these situations:
272
280
  | Refusal with a CODE | stim guide errors <CODE> |
273
281
  | Running under a sandbox | stim guide errors sandbox |
274
282
  | Release configuration or ...Release variant | stim guide lifecycle release |
275
- | Web or API server ports | stim guide ports |
283
+ | Web app in an owned Chrome (stim web) | stim guide web |
284
+ | Web or API server ports | stim guide ports |
276
285
  | Remote device, custom Metro, or tunnel | stim guide metro |
277
286
  | Cache miss, bypass, or fingerprint exclusions | stim guide lifecycle builds |
278
287
  | Capacity limits | stim guide lifecycle concurrency |
@@ -316,6 +325,7 @@ FULL TOPIC LIST
316
325
  stim guide lifecycle release # Release configurations and ...Release variants
317
326
  stim guide facts # the --json payloads
318
327
  stim guide facts devmenu # the Expo dev menu or Tools button over the app
328
+ stim guide web # stim web: owned Chrome, page logs, launched, teardown
319
329
  stim guide ports # named ports for web and API servers
320
330
  stim guide metro # supervisor, custom Metro, tunnels, and remote devices
321
331
  stim guide logs # filters, record shape, and capture limits
@@ -324,7 +334,7 @@ FULL TOPIC LIST
324
334
  stim guide settings # configuration files and supported keys`
325
335
  },
326
336
  facts: {
327
- summary: "The --json payloads: `start`, `ios`, `android`, `ios|android --plan`, `reload`, `stop`, `status`, `doctor`, `device lock`/`unlock`, `gc`, and the error contract",
337
+ summary: "The --json payloads: `start`, `ios`, `android`, `web`, `ios|android --plan`, `reload`, `stop`, `status`, `doctor`, `device lock`/`unlock`, `gc`, and the error contract",
328
338
  preamble: () => `SLOTS
329
339
  Named ios/android runs add slot to their JSON facts. Default-run fields remain
330
340
  compatible. status adds a slots array per environment with each named slot's
@@ -333,7 +343,7 @@ Named collector, lease-holder, and launch keys use platform:slot internally.
333
343
 
334
344
  FACTS CONTRACT
335
345
 
336
- \`start\`, \`ios\`, \`android\`, \`reload\`, \`stop\`, \`status\`, \`stats\`, \`doctor\`,
346
+ \`start\`, \`ios\`, \`android\`, \`web\`, \`reload\`, \`stop\`, \`status\`, \`stats\`, \`doctor\`,
337
347
  \`gc\`, and \`device lock\`/\`device unlock\` each print exactly ONE line of JSON on
338
348
  stdout for \`--json\`. Every other line goes to stderr, so it is always safe
339
349
  to pipe. \`logs --json\` is the one exception: it is NDJSON, one record per
@@ -372,8 +382,11 @@ status runs \`git status --porcelain=v2 --branch\` in every worktree in
372
382
  parallel, and caches the merge verdict by HEAD and default-branch commit
373
383
  under $STIM_HOME/git-merge. One call starts no new merge check 250 ms after
374
384
  its first; later calls judge the rest, and until then a verdict for the same
375
- HEAD at an older default-branch commit stands in. \`status --watch\` reuses a worktree's git read
376
- for 5 s. Plain status prints "git: 2 changed, 1 untracked, ahead 3" under
385
+ HEAD at an older default-branch commit stands in; a check that timed out
386
+ is retried after 5 minutes. \`status --watch\` reuses a worktree's git read
387
+ until its index, HEAD, reflog or the branch, upstream or default-branch refs
388
+ change, and for at most 60 s, so a file edit, creation or deletion that is
389
+ not staged can take up to a minute to show. Plain status prints "git: 2 changed, 1 untracked, ahead 3" under
377
390
  each environment, and the same after each worktree with no environment.
378
391
 
379
392
  status's remoteDevices lists each environment's recorded EAS Simulator
@@ -396,7 +409,7 @@ Plain status prints one "remote <platform>: EAS session <id> billable" line
396
409
  per session, with the preview URL.`,
397
410
  sections: {
398
411
  payloads: {
399
- summary: "every field of the start, ios, android and reload payloads, the error contract, the device rules",
412
+ summary: "every field of the start, ios, android, web and reload payloads, the error contract, the device rules",
400
413
  body: () => ` stim start --json
401
414
 
402
415
  port the Metro port RESERVED for this workspace
@@ -437,13 +450,18 @@ per session, with the preview URL.`,
437
450
  hit 6564e2.. (post-prebuild key)\`), so a cold tree -- a
438
451
  fresh worktree or clone of a CNG app -- installs an entry
439
452
  another workspace already built instead of compiling
440
- beside it. Android also fingerprints after Gradle because
441
- Gradle plugins can rewrite native inputs while they build;
442
- its artifact is stored only under that post-build hash. A
443
- stable second fingerprint prints no shift line. If the iOS
444
- fingerprint after prebuild or pod install, or the Android
445
- fingerprint after Gradle, cannot be computed, the build is
446
- installed but not cached, and fingerprint and cacheKey are null
453
+ beside it. Both platforms fingerprint again after the
454
+ compile because Gradle plugins can rewrite native inputs
455
+ while they build; a change under node_modules/ or the
456
+ native directory stores the artifact only under that
457
+ post-build hash. A stable fingerprint prints no shift line.
458
+ If any other input changed while the build ran (the app
459
+ config or a config plugin since the first lookup, other
460
+ than the bundle id or package prebuild adds, or any
461
+ other source during the compile), or a fingerprint after
462
+ prebuild, pod install or the compile cannot be computed,
463
+ the build is installed but not cached, and fingerprint and
464
+ cacheKey are null
447
465
  configuration the Xcode configuration that was built ("Release" from
448
466
  --configuration or the ios.configuration setting); null for
449
467
  the default Debug
@@ -524,6 +542,12 @@ per session, with the preview URL.`,
524
542
  finishing
525
543
  "unverified" nothing was observed at all: usually a
526
544
  dev-client server picker awaiting a tap
545
+ Neither "bundling" nor "unverified" is reported for an app
546
+ whose process is gone when the bundle timeout closes: the
547
+ run fails as FATAL, "the app process exited", with the
548
+ device log's errors. An iOS crash report can land a minute
549
+ after the crash, so \`logs --errors\` may show the native
550
+ stack only later
527
551
  See \`guide facts devmenu\` for the dev menu and its button.
528
552
  metroPort the port the app was wired to; NULL on a non-Debug
529
553
  configuration, whose JS is embedded and which is launched
@@ -621,18 +645,20 @@ per session, with the preview URL.`,
621
645
  reason is "not running" or "stopped (idle)" when the
622
646
  supervisor had stopped it after metro.idleStopMinutes
623
647
 
624
- stim reload [ios|android] --json
648
+ stim reload [ios|android|web] --json
625
649
 
626
650
  Exit 0 and this payload confirm that the reload request was sent. They do
627
651
  not prove that new JavaScript loaded or that the screen recovered. The
628
652
  command does not observe completion. Verify the expected UI on deviceId
629
653
  and inspect stim logs --errors before claiming recovery.
630
654
 
631
- platform "ios" | "android"
632
- deviceId the exact owned simulator UDID or emulator serial targeted
633
- deviceName the owned simulator or AVD name
634
- appId the live bundle id or Android package
635
- metroPort the workspace's verified Metro port
655
+ platform "ios" | "android" | "web"
656
+ deviceId the exact owned simulator UDID or emulator serial targeted;
657
+ for web, the owned Chrome's DevTools endpoint
658
+ deviceName the owned simulator or AVD name; for web, the Chrome version
659
+ appId the live bundle id or Android package; for web, the page URL
660
+ metroPort the workspace's verified Metro port; for web, the reserved
661
+ Metro port or null
636
662
  strategy how the reload was addressed.
637
663
  "metro-websocket" -- Metro named its clients and Stim
638
664
  addressed every peer matching this platform. A workspace
@@ -642,11 +668,35 @@ per session, with the preview URL.`,
642
668
  the reload went to all of them and Stim cannot confirm appId
643
669
  was among them. Verify the UI on deviceId; if it did not
644
670
  change, reload from the app's own error screen or dev menu
671
+ "cdp" -- web: Page.reload on the owned Chrome page, sent
672
+ over a DevTools connection verified to reach that Chrome
645
673
  targets how many peers the reload was addressed to, or null when
646
674
  broadcast. Greater than 1 means several devices are running
647
675
  this app on that Metro and the request addressed all of
648
676
  them, not only deviceId. Completion is not observed
649
677
 
678
+ stim web --json
679
+
680
+ platform "web"
681
+ browser "chrome"
682
+ version the Chrome product, such as "Chrome/153.0.8010.49", or null
683
+ running true: the owned Chrome answered and holds the page
684
+ pid the owned Chrome's browser process; null when not running
685
+ supervisorPid the Stim process holding its DevTools session, or null
686
+ url the page opened, web.url with its ports filled in
687
+ headless false only with --headed
688
+ viewport "desktop" | "phone" (web.viewport)
689
+ profile the Stim-owned Chrome user data directory
690
+ cdpEndpoint http://127.0.0.1:<port>, the reserved DevTools endpoint, or
691
+ null when not running
692
+ reused true when the running Chrome navigated again instead of
693
+ starting: same --headed, viewport and certificate options
694
+ launched true | "bundling" | "unverified" (see \`guide web\`)
695
+ metroPort the Metro port the page loads from, or null for a web.url
696
+ that does not use {port:metro}
697
+ logs { dir }: web.ndjson holds the page records
698
+ durationMs wall time of the run
699
+
650
700
  stim doctor --json
651
701
 
652
702
  project the resolved app root
@@ -670,7 +720,7 @@ per session, with the preview URL.`,
670
720
  costs-time finding with a PATH or installation remedy
671
721
 
672
722
  ON FAILURE
673
- \`start\`, \`ios\` and \`android\` all print the error contract instead,
723
+ \`start\`, \`ios\`, \`android\` and \`web\` all print the error contract instead,
674
724
  still one line on stdout, and exit 1:
675
725
 
676
726
  { "code": "STIM_METRO_TIMEOUT", "message": "...", "remedy": "..." }
@@ -695,6 +745,12 @@ ON FAILURE
695
745
 
696
746
  { "root": "...", "ok": false, "code": "STIM_STOP_BLOCKED", "message": "...", "remedy": "..." }
697
747
 
748
+ device.web is present when the workspace had an owned Chrome:
749
+ { status, label: "Chrome", kind?, reason?, remedy? }; kind is null when no
750
+ kind applies. status is "shut-down"
751
+ when Chrome closed (its profile is kept), else "skipped" (its identity could
752
+ not be verified) or "failed", and ok is then false.
753
+
698
754
  Branch on \`code\`, never on the message text. \`guide errors\` enumerates
699
755
  every code.
700
756
 
@@ -811,13 +867,13 @@ RULES
811
867
  full commands.`
812
868
  },
813
869
  gc: {
814
- summary: "the gc report payload: mode, sections, reasons, failures, and the gc refusals",
870
+ summary: "the gc report payload: mode, sections, reasons, failures, results, inventory, and the gc refusals",
815
871
  body: () => ` stim gc [--delete] [--older-than <days>] [--cache <name|all|workspaces>]
816
872
  [--worktrees] [--idle <duration>] --json
817
873
 
818
874
  The report the text prints, as one payload. Show the user its sections
819
875
  before you run \`gc --delete\`. Under --delete it is the report that run
820
- acted on: each entry's outcome is a stderr line, \`failures\` counts the
876
+ acted on: \`results\` lists each entry's outcome, \`failures\` counts the
821
877
  entries it could not delete, and a nonzero count exits 1. Run
822
878
  \`stim gc --json\` again to see what is left.
823
879
 
@@ -834,6 +890,46 @@ RULES
834
890
  actionable true when --delete with the same flags reclaims something
835
891
  failures null on a dry run without --idle; otherwise the entries it
836
892
  could not delete or shut down
893
+ results what --delete or --idle did, one { kind, status, label,
894
+ id, bytes, detail } per entry it acted on; empty on a dry
895
+ run. status is "done", "kept" (left alone, detail says
896
+ why) or "failed" (detail says why and what to retry).
897
+ kind: device, parkedDevice, idleDevice, deviceRecord,
898
+ workspaceOutputs, workspaceDirectory, project, buildLock,
899
+ buildSlot, deviceLease, easSession, worktree, cache.
900
+ label is a device, path or cache name; id is the UDID,
901
+ AVD name or path behind it, or null
902
+ inventory null except on a dry run without --cache or --idle. Report
903
+ only: gc never acts on it, even under --delete.
904
+ { devices, runtimes, systemImages, notices }
905
+ devices { kind, id, name, model, runtime, state, lastUsedAt, bytes,
906
+ directory, owner, project, slot } every available iOS
907
+ simulator and registered AVD. runtime is the simctl
908
+ runtime identifier or the AVD's system image package.
909
+ lastUsedAt is simctl's last use, or when the emulator
910
+ last wrote the AVD's hardware-qemu.ini. bytes is the
911
+ simulator's data size from simctl; null for AVDs.
912
+ owner is workspace (project and slot say which),
913
+ parked, orphaned (this Stim home created it and no
914
+ workspace holds it), otherStimHome (a stim-* device this
915
+ home has no record of creating, even when a workspace
916
+ names it) or user. Off macOS no simulator is listed
917
+ runtimes { identifier, runtimeIdentifier, version, build, bytes,
918
+ lastUsedAt, deviceCount, command } iOS simulator
919
+ runtimes from \`xcrun simctl runtime list -j\`, then
920
+ any other \`simctl list runtimes\` shows, with bytes
921
+ and command null. deviceCount is the simulators on
922
+ it; command is the \`xcrun simctl runtime delete\`
923
+ line for a deletable runtime, else null. Stim never
924
+ runs it
925
+ systemImages { package, directory, avdCount, command } installed
926
+ Android system images; avdCount is the AVDs whose
927
+ image.sysdir.1 names it; command is the
928
+ \`sdkmanager --uninstall\` line. Stim never runs it
929
+ notices why a listing is missing or partial, such as simctl
930
+ timing out or an AVD or system image folder Stim
931
+ cannot read. A macOS privacy denial (EPERM) names the
932
+ Privacy & Security setting to grant
837
933
  sections one array per report section, in the text order. Every key
838
934
  is present, empty when there is nothing to report:
839
935
  deadProjects { path }
@@ -841,12 +937,18 @@ RULES
841
937
  orphanedPorts { project, label, port }
842
938
  orphanedWorkspaces { dir, projectRoot, bytes } --delete removes the
843
939
  whole workspace directory
844
- linkedWorktrees { path, idleDays, mergedInto, willRemove,
845
- reason, detail, eligibleAt } mergedInto is
846
- the default branch HEAD is merged into
847
- ("origin/main"), or null; detail says why it
848
- is removed ("merged into origin/main", "idle
849
- 9d") or kept. eligibleAt is the ISO time a
940
+ linkedWorktrees { path, idleDays, mergedInto, pullRequest,
941
+ pullRequestUnknown, willRemove, reason,
942
+ detail, eligibleAt } mergedInto is the
943
+ default branch HEAD is merged into
944
+ ("origin/main"), or null. pullRequest is the
945
+ branch's pull request { number, state:
946
+ "open" | "merged" | "closed", url,
947
+ containsHead }, or null; pullRequestUnknown
948
+ says why gh could not answer, else null.
949
+ detail says why it is removed ("merged into
950
+ origin/main", "PR #12 closed", "idle 9d") or
951
+ kept. eligibleAt is the ISO time a
850
952
  recent-activity worktree becomes removable,
851
953
  else null. Without --worktrees, the source
852
954
  checkout and roots outside git are left out
@@ -856,13 +958,21 @@ RULES
856
958
  parkedEmulators { name, systemImage, deviceProfile, parkedAt, bytes, listed }
857
959
  likewise
858
960
  orphanedDevices { kind, id, name, bytes, directory }
859
- unverifiedDevices { kind, id, name, command } stim-* devices Stim
860
- has no record of creating; never deleted, run
861
- command yourself
961
+ unverifiedDevices { kind, id, name, command } stim-* devices this
962
+ Stim home has no record of creating; never
963
+ deleted, run command yourself (rm -rf <dir>
964
+ for AVD data with no registration)
862
965
  staleDevices { kind, id, name, project, slot, idleDays,
863
966
  bytes } only with --older-than
864
967
  staleDeviceRecords { kind, id, project, slot } --delete clears
865
968
  the record only
969
+ staleLedgerEntries { kind: "ios" | "android" | "web", id }
970
+ simulators this Stim home created that a
971
+ complete simctl listing no longer shows, AVD
972
+ names with no registration or data in any AVD
973
+ root, and browser profile paths whose
974
+ directory is gone; --delete forgets the
975
+ ledger entry only
866
976
  idleDevices { kind, id, name, project, slot, lastActivityAt,
867
977
  idleForMs, buildInProgress } booted owned
868
978
  devices whose status activity is "idle";
@@ -880,6 +990,12 @@ RULES
880
990
  deviceSweepNotices { message }
881
991
  easSessionSweepNotices { message }
882
992
  skipped { path, detail } not classified as dead
993
+ workspaceLogs { dir, projectRoot, bytes, trimBytes, willTrim,
994
+ reason, detail } logs/ of each workspace;
995
+ trimBytes is what --delete would drop from
996
+ Metro, client and device logs over twice the
997
+ 8 MiB cap; willTrim marks the ones it would
998
+ trim
883
999
  workspaceBuildOutputs { dir, projectRoot, bytes, idleDays, willClear,
884
1000
  reason, detail } derived-data, gradle-build,
885
1001
  android-cas and cache-provider of each
@@ -898,6 +1014,7 @@ RULES
898
1014
  A linked worktree's detail is never null.
899
1015
  workspaceBuildOutputs unresolved | in-use | last-use-unknown |
900
1016
  recently-used
1017
+ workspaceLogs unresolved | in-use | collector
901
1018
  linkedWorktrees not-a-worktree | bare-repository |
902
1019
  source-checkout-unknown | source-checkout |
903
1020
  locked | in-use | status-unreadable | dirty |
@@ -915,7 +1032,7 @@ RULES
915
1032
  - --cache together with --worktrees or --idle`
916
1033
  },
917
1034
  status: {
918
- summary: "the status payload's issues and their codes, build and device activity fields: a running build, its estimate, each platform's last build, and who drives each device",
1035
+ summary: "the status payload's issues and their codes, build and device activity fields: a running build, its estimate, each platform's last build, who drives each device, whether the app runs on it, and what uses CPU and memory now",
919
1036
  body: () => ` stim status --json
920
1037
 
921
1038
  Each environment carries issues, the things in that workspace that need
@@ -945,6 +1062,12 @@ RULES
945
1062
  supervisor-unverified a supervisor record whose process status
946
1063
  cannot prove gone or ours; stop refuses to
947
1064
  signal it
1065
+ browser-unverified the owned Chrome's supervisor or Chrome
1066
+ process cannot be proven gone or ours;
1067
+ stop and stim web refuse to signal it
1068
+ browser-orphaned the owned Chrome runs but its supervisor
1069
+ exited, so page logs are not captured;
1070
+ stim stop closes it
948
1071
  severity "error" when stop or start refuses until it is resolved, else
949
1072
  "warning"
950
1073
  remedy a command to run from workspace, such as "stim android --slot
@@ -956,6 +1079,19 @@ RULES
956
1079
  \`stop --slot <name>\` forgets that slot's launch, so a device stopped on
957
1080
  purpose is not reported while the shared dev server keeps running.
958
1081
 
1082
+ An environment where \`stim web\` ran carries web, its Stim-owned Chrome:
1083
+
1084
+ web { browser, version, running, pid, supervisorPid, url, headless,
1085
+ viewport, profile, cdpEndpoint }
1086
+
1087
+ running the browser supervisor and Chrome are both verified live;
1088
+ pid, supervisorPid and cdpEndpoint are null when false
1089
+ cdpEndpoint http://127.0.0.1:<port>, the reserved loopback DevTools
1090
+ endpoint of that Chrome. Attach Playwright MCP
1091
+ (--cdp-endpoint) or agent-browser (--cdp <port>) to it; it
1092
+ reaches only the Stim profile, never your own browser
1093
+ profile the Stim-owned user data directory under STIM_HOME
1094
+
959
1095
  Each booted simulator and detected emulator in environments (and in
960
1096
  slots) carries activity; a shut-down or physical device has none:
961
1097
 
@@ -972,7 +1108,8 @@ RULES
972
1108
  the claim does not record them
973
1109
  lastActivityAt the newest of this device's app log records, this
974
1110
  platform's Metro bundle requests and the workspace's last
975
- Stim run; absent when none is recorded
1111
+ Stim run, rounded down to the minute; absent when none is
1112
+ recorded
976
1113
  basis the evidence behind state, strongest first:
977
1114
  agent-device-claim, agent-device-lease agent-device state,
978
1115
  read only; live only when every recorded process is alive
@@ -988,6 +1125,29 @@ RULES
988
1125
  \`gc --idle <duration>\` shuts down owned devices idle that long
989
1126
  (\`guide cleanup gc\`).
990
1127
 
1128
+ An owned booted simulator or detected emulator also carries app, whether
1129
+ the workspace's app process is alive on it now:
1130
+
1131
+ app { id, state }
1132
+
1133
+ id the bundle identifier or package checked: the one launched on this
1134
+ device, else the project's
1135
+ state "running" a process of that app runs on the device
1136
+ "stopped" no such process: it crashed, was killed or never launched
1137
+ "unknown" the process listing or the app's Info.plist could not be
1138
+ read; never "stopped"
1139
+
1140
+ app is absent when the device is not owned or no app id is known: the
1141
+ process was not checked.
1142
+
1143
+ This is current process state, read from one host ps (simulator apps are
1144
+ host processes) and the same adb shell ps as activity, so a status --watch
1145
+ refresh notices an exit within its 30-second fallback. It is not launch
1146
+ evidence: launched in an ios or android result keeps its own meaning. For
1147
+ "stopped", run \`stim ios\` or \`stim android\` to launch it again. Plain
1148
+ status appends "<id> running", "<id> not running" or "<id> process
1149
+ unknown" to the device line.
1150
+
991
1151
  An environment's metro carries idleStop { reason: "idle", at, idleMinutes }
992
1152
  when its supervisor stopped the dev server for idleness and nothing serves
993
1153
  the port since; plain \`status\` prints "stopped (idle)" (\`guide metro\`).
@@ -1027,7 +1187,7 @@ RULES
1027
1187
 
1028
1188
  lastBuilds { ios?, android? }, each { platform, status, cacheHit,
1029
1189
  cacheSkipped, durationMs, fingerprint, startedAt, finishedAt,
1030
- errorCode?, missReason? }
1190
+ errorCode?, missReason?, diagnostics? }
1031
1191
 
1032
1192
  status "ok" or "failed"
1033
1193
  cacheHit "local" or "remote" for an app from that cache tier; false
@@ -1045,12 +1205,79 @@ RULES
1045
1205
  baseline is { fingerprint, from: "workspace" | "project" }, the
1046
1206
  cached build compared with; rekeyedBy lists "prebuild" or
1047
1207
  "pod install" when those steps moved the key.
1208
+ diagnostics only on a failed run whose compiler reported errors: up to
1209
+ 5 { file, line, column, message }, null where the compiler
1210
+ gave no position.
1048
1211
 
1049
1212
  Plain status prints "last build: ios local cache in 12s, android compiled
1050
1213
  in 7m02s". To predict the next run instead, see \`guide facts plan\`.
1051
1214
 
1215
+ An environment with a recorded run also carries builds, each platform's
1216
+ last 10 runs, newest first. Its newest entry that is not "interrupted" is
1217
+ the run lastBuilds reports, once a run has recorded builds.
1218
+
1219
+ builds { ios?, android? }, each a list of lastBuilds entries
1220
+ with { result, slot, configuration, cacheKey, phases }
1221
+ result "succeeded", "failed", "cancelled" (Stim stopped the run
1222
+ after an interrupt or \`stim stop\`) or "interrupted": its
1223
+ process ended without recording a result, such as a kill or a
1224
+ second interrupt, so the next run recorded it with
1225
+ null durationMs and finishedAt, no cache facts, and 0 for the
1226
+ phase it stopped in
1227
+ slot the device slot, "default" without --slot
1228
+ configuration the iOS configuration or Android variant the run built,
1229
+ "Debug" or "debug" by default; null when the run ended
1230
+ before resolving it
1231
+ cacheKey the cache key the run looked up or stored under
1232
+ phases milliseconds spent in each phase the run entered, among
1233
+ prepare, cache-lookup, wait, prebuild, pods, compile,
1234
+ install and launch
1235
+
1236
+ Only runs that record a last build are listed: a run that stops before
1237
+ looking up a build, such as a bad flag, is not. Estimates (expectedMs) come from run statistics, not builds.
1238
+
1052
1239
  There is no completion fraction: a compile's log volume depends on what
1053
- is already built, so it does not measure progress.`
1240
+ is already built, so it does not measure progress.
1241
+
1242
+ An environment's memoryMb is an estimate, not a measurement: a fixed amount
1243
+ per booted simulator, detected emulator, running Metro and running Chrome.
1244
+ capacity.committedMb sums it, and the memory budget uses the same estimate.
1245
+ What is using CPU and memory now is the top-level machine section:
1246
+
1247
+ machine null, or { owners: [{ kind, name, workspace, slot?, id, owned,
1248
+ cpuPercent, residentMb, processes }] }
1249
+
1250
+ kind simulator a booted simulator's launchd_sim tree
1251
+ emulator an emulator's launcher and qemu tree, by its -avd
1252
+ metro a workspace's supervisor and Metro trees
1253
+ build a running ios or android run's process tree,
1254
+ xcodebuild, Gradle and compilers included
1255
+ browser the Chrome of \`stim web\` and its supervisor
1256
+ server stim-server
1257
+ shared machine-wide processes no workspace owns:
1258
+ CoreSimulator services, adb server, Gradle and
1259
+ Kotlin daemons, Watchman, the emulator's netsimd
1260
+ workspace the workspace that records the device or runs the process;
1261
+ null for a device no workspace records and for server and
1262
+ shared
1263
+ id the simulator's UDID, the AVD name, Metro's port or the
1264
+ build's platform; null otherwise
1265
+ owned true for what Stim started and stops: a workspace's owned
1266
+ device (\`stim stop --slot <slot>\`), its Metro (\`stim
1267
+ stop\`), build or Chrome. Never act on an owner with false.
1268
+ cpuPercent ps %CPU summed over the owner's processes; 100 is one core
1269
+ residentMb summed resident set size. Pages shared between processes
1270
+ count once per process, so a simulator reports well above
1271
+ its physical footprint.
1272
+
1273
+ Each process counts in exactly one owner, the one whose root process is
1274
+ its nearest ancestor, so the supervisor a build started counts as Metro,
1275
+ not as the build, and a Gradle or Kotlin daemon counts in the build that
1276
+ started it until that build exits, then as shared. Processes with no owner
1277
+ are left out. machine comes from one host ps, the one status reads for
1278
+ device activity, and is null when no simulator is booted, no workspace is
1279
+ live and no build runs: status then reads no process table. \`status --watch --json\` rereads it every 15 seconds while
1280
+ machine is not null, with no other subprocess.`
1054
1281
  },
1055
1282
  plan: {
1056
1283
  summary: "the ios and android --plan payload: fingerprint, cacheHit, prebuild, missReason, expectedMs and basis",
@@ -1355,7 +1582,16 @@ their owning workspaces.
1355
1582
 
1356
1583
  The machine registry, under STIM_HOME, serializes allocation and cleanup.
1357
1584
  The workspace is the nearest package.json directory, resolved through
1358
- symlinks. Use the same package directory for every command in a monorepo.
1585
+ symlinks. In a monorepo, a package that depends on neither react-native nor
1586
+ expo, such as a Vite web app, uses the one Stim app registered in the same
1587
+ git worktree instead, and says so on stderr. ports, web, settings, logs,
1588
+ reload, stop and status follow this rule (status stars the app instead of
1589
+ printing the note); other commands use the nearest package.json. Register the app first: run stim ports get <label>, stim start,
1590
+ stim ios or stim android from the app directory. Every ports command then
1591
+ acts on the app's ports, so ports stop without a label there also stops the
1592
+ app's other labels. A package that already holds ports keeps them; release
1593
+ them to move to the app. With no registered app, or more than one, the
1594
+ nearest package.json stays the workspace; run ports from the app directory.
1359
1595
  The allocation reserves a number, not a listening socket. Another process
1360
1596
  can bind it before your server does. Use strict-port behavior when supported
1361
1597
  and verify the server bound the number supplied. Stim does not start,
@@ -1397,7 +1633,10 @@ discovered, not enumerated.
1397
1633
  metro.ndjson, client.ndjson and device.ndjson are size-capped. At about 8 MiB a file
1398
1634
  becomes <name>.1, replacing the previous .1, and a new file starts. Queries read
1399
1635
  both generations, so the oldest records drop off first. History and --since
1400
- reach back only as far as those records.
1636
+ reach back only as far as those records. A file written before the cap
1637
+ existed can be larger; \`gc\` reports each workspace's log size and \`gc
1638
+ --delete\` trims a file over 16 MiB to its newest 8 MiB in a workspace not in use
1639
+ (\`guide cleanup disk\`).
1401
1640
 
1402
1641
  EXIT 0 MEANS THE QUERY SUCCEEDED, whether or not records matched. A clean
1403
1642
  \`stim logs --errors\` check requires exit code 0 AND no matching errors in
@@ -1427,18 +1666,23 @@ FLAGS
1427
1666
  --tail <n> only the last n MATCHING records (applied after filtering,
1428
1667
  so --level error --tail 5 is the last five ERRORS)
1429
1668
  --errors errors and fatals since the last marker, from metro, client
1430
- and build, plus confirmed native app-crash reports.
1669
+ and build, plus confirmed native app-crash reports and the
1670
+ owned Chrome's device errors (platform web: failed
1671
+ requests, browser errors).
1431
1672
  Capped at 20 printed records.
1432
1673
  --follow keep streaming until interrupted (Ctrl+C is exit 0)
1433
1674
  --json the raw records, one per line, so stdout is valid NDJSON.
1434
1675
  ZERO matches is ZERO bytes on stdout (an empty NDJSON
1435
1676
  stream), exit 0 -- parse stdout line by line, never as one
1436
1677
  JSON document. The "No matching log records" note is human
1437
- mode only, on stderr.
1678
+ mode only, on stderr. With --errors, an Expo error record
1679
+ also carries context (see THE RECORD).
1438
1680
 
1439
1681
  --ERRORS, PRECISELY
1440
1682
  Level error or fatal, from metro, client and build, plus device records with
1441
- event native_crash (app/device/time-correlated OS reports or fatal app console output), timestamped after the
1683
+ event native_crash (app/device/time-correlated OS reports or fatal app console output)
1684
+ and device records with platform web (the owned page's failed requests and
1685
+ browser errors, from stim web), timestamped after the
1442
1686
  marker that closes their window. Three rules, and a field test
1443
1687
  caught all three wrong at once -- it returned 3,004 iOS syslog lines on a
1444
1688
  healthy app while hiding a real startup crash.
@@ -1447,7 +1691,10 @@ FLAGS
1447
1691
  \`simctl log stream\` is predicated on the app's PROCESS, and inside that
1448
1692
  process Apple's frameworks log thousands of Error-typed lines (nw_socket,
1449
1693
  SecTrust, WebKit, CoreUI) that have nothing to do with your app. The proven
1450
- ones are demoted to info by the collector; the scope rule covers the rest.
1694
+ ones are demoted to info by the collector, and iOS main-thread
1695
+ "Synchronous URL loading" performance diagnostics (Expo icons served by
1696
+ Metro raise them) to warn, so a launch does not count them as app errors;
1697
+ the scope rule covers the rest.
1451
1698
  The metro stream carries exactly one demotion of its own, and it is Stim's
1452
1699
  doing: the dev-client deep link \`ios\`/\`android\` open to wire the app to
1453
1700
  your port arrives inside the app as a link, and React Navigation logs at
@@ -1473,8 +1720,15 @@ FLAGS
1473
1720
  errors are reported -- and nothing else. A failed attempt's own summary
1474
1721
  and details land at or after its marker, so they stay reported.
1475
1722
  a LAUNCH marker (src build, written before \`ios\` / \`android\` attempts
1476
- launch) resets EVERYTHING. It precedes the tool call so an immediate
1477
- native crash is not hidden by a marker written after the process died.
1723
+ launch) resets EVERYTHING except the web page's records. It precedes the
1724
+ tool call so an immediate native crash is not hidden by a marker written
1725
+ after the process died.
1726
+ a PAGE-LOAD marker (the web_navigation record \`stim web\` writes for each
1727
+ load of the page's top-level document) resets only the page's records,
1728
+ those with platform web. It is written when the load starts, so a
1729
+ failure logged in the same millisecond belongs to that load, and so does
1730
+ anything the old page logs before it is replaced. Until the first page
1731
+ load is logged, the page's records follow the launch windows.
1478
1732
  A finished bundle is not evidence that the app which loaded it is fine.
1479
1733
  In the field case the app threw at 16:03:54 and Metro wrote its marker at
1480
1734
  16:03:55, one second later, because the bundler finishes accounting for a
@@ -1495,11 +1749,18 @@ FLAGS
1495
1749
  not stack depth. For untouched captured records use:
1496
1750
  stim logs --source all --json
1497
1751
  Neither form can restore text the runtime truncated before capture.
1498
- In non-follow human output, an Expo error includes its immediately
1499
- following code frame and Call Stack lines. Bare React Native symbolication is
1500
- shown as separate context because Metro does not provide an error correlation
1501
- identifier. Context does not change the error count or the raw error records
1502
- returned by --json. --json is never capped, and neither is an explicit --tail.
1752
+ With --errors, an Expo error includes the code frame and stack lines Expo
1753
+ printed after it, in human and --json output, with or without --follow.
1754
+ Stim's own Metro records that land between those lines, such as bundle
1755
+ responses, do not cut the stack short. Human output joins the lines to the
1756
+ message. --json leaves msg as captured and adds them to the error record as
1757
+ a context array of strings; each record is still one line. --follow holds an
1758
+ Expo error until a poll, about every 500 ms, adds no more of its lines, so
1759
+ it can print up to about a second after Expo does. Non-follow human output
1760
+ also shows bare React Native symbolication as separate context, because
1761
+ Metro does not provide an error correlation identifier. Context never
1762
+ changes the error count or adds records to --json. --json is never capped,
1763
+ and neither is an explicit --tail.
1503
1764
 
1504
1765
  In --follow mode the marker window is dropped -- every error arriving from
1505
1766
  then on is by definition after the last marker seen.
@@ -1521,6 +1782,10 @@ THE RECORD
1521
1782
  A collector_clock warning then says timestamps retain device time.
1522
1783
  raw true when the level was inferred from a line of text rather than
1523
1784
  reported by the producer (every expo-child record)
1785
+ context --errors --json only: the code frame and stack lines Expo
1786
+ printed after this error, as an array of strings. Absent when
1787
+ there are none. Stim adds it at query time; it is not in the
1788
+ log files.
1524
1789
 
1525
1790
  WHAT WRITES WHAT
1526
1791
  metro.ndjson the bundler, in both supervisor modes
@@ -1536,7 +1801,8 @@ WHAT WRITES WHAT
1536
1801
  is where a native crash that never reached JS shows up
1537
1802
  -- and, on iOS, where every Apple framework running in
1538
1803
  the app's process also logs. The proven noise sources
1539
- are recorded at info rather than error; the rest is why
1804
+ are recorded at info rather than error (Synchronous
1805
+ URL loading diagnostics at warn); the rest is why
1540
1806
  --errors leaves this source out unless asked. A VERIFIED
1541
1807
  LAUNCH counts these records and prints one line:
1542
1808
 
@@ -1798,11 +2064,17 @@ Branch on the code, never on the message.`,
1798
2064
  the source checkout, then warm again.`
1799
2065
  },
1800
2066
  STIM_BUILD_FAILED: {
1801
- summary: "xcodebuild or gradle failed; the two Android APK refusals; a damaged compilation-cache object",
2067
+ summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals; a damaged compilation-cache object",
1802
2068
  body: () => `STIM_BUILD_FAILED
1803
2069
  xcodebuild or gradle failed. The EXTRACTED diagnostics are printed (capped),
1804
2070
  not the transcript. Read the log path on the next line for the rest.
1805
- Two Android refusals share this code without gradle itself failing:
2071
+ Three Android refusals share this code without gradle itself failing:
2072
+ - NO ANDROID SDK, before gradle starts: nothing exists at the SDK path
2073
+ Stim resolves (ANDROID_HOME, else ANDROID_SDK_ROOT, else the default
2074
+ location), and there is no android/local.properties. Set ANDROID_HOME to the SDK or write sdk.dir
2075
+ into android/local.properties; \`stim doctor --platform android\` reports
2076
+ the same condition. When the SDK is found and neither variable is set,
2077
+ Stim passes its path to gradle as ANDROID_HOME.
1806
2078
  - MORE THAN ONE debug APK under android/app/build/outputs/apk and nothing
1807
2079
  configured to pick one (a project with product flavors, several flavors
1808
2080
  already built). Stim will not guess which flavor to install: the
@@ -2305,6 +2577,12 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
2305
2577
  \`gc --delete\`) and run \`stim ios\` again to create the requested model.
2306
2578
  That loses the old sim's app state.
2307
2579
 
2580
+ "this project's sim runs iOS X, but --runtime asked for Y"
2581
+ The same refusal for an explicit \`--runtime\` that names another installed
2582
+ iOS version than the project's sim runs. Reap the sim the same way, or pass
2583
+ \`--slot <name>\` to create one on the requested runtime beside it. The
2584
+ ios.runtime setting alone never refuses; it applies at creation.
2585
+
2308
2586
  "this project's emulator uses device profile X, but Y was requested"
2309
2587
  The Android counterpart, from \`--device-profile\` or android.deviceProfile.
2310
2588
  Reap the AVD the same way, or pass \`--slot <name>\` to create the requested
@@ -2485,10 +2763,11 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
2485
2763
  metro.publicUrl for an existing endpoint.`
2486
2764
  },
2487
2765
  STIM_RELOAD_AMBIGUOUS: {
2488
- summary: "both owned apps are live; name the platform",
2489
- separator: "--- RELOAD CODES (`stim reload [ios|android]`) ---",
2766
+ summary: "more than one owned app or the owned Chrome is live; name the platform",
2767
+ separator: "--- RELOAD CODES (`stim reload [ios|android|web]`) ---",
2490
2768
  body: () => `STIM_RELOAD_AMBIGUOUS
2491
- Both owned apps are live. Name ios or android; Stim never guesses.`
2769
+ More than one of the owned iOS app, Android app and Chrome page is live.
2770
+ Name ios, android or web; Stim never guesses.`
2492
2771
  },
2493
2772
  STIM_RELOAD_RELEASE: {
2494
2773
  summary: "the live app has embedded JS; run a Debug build first",
@@ -2501,7 +2780,10 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
2501
2780
  aliases: ["STIM_RELOAD_UNOWNED", "STIM_RELOAD_PROBE_FAILED"],
2502
2781
  body: () => `STIM_RELOAD_STOPPED / STIM_RELOAD_UNOWNED / STIM_RELOAD_PROBE_FAILED
2503
2782
  The recorded app is gone, its exact device is not live and owned by this
2504
- workspace, or simctl/adb could not prove the process exists. No launch or
2783
+ workspace, or simctl/adb could not prove the process exists. For web,
2784
+ STOPPED means no owned Chrome is running (run stim web) and PROBE_FAILED
2785
+ that its identity could not be verified. A bare reload picks the Chrome page
2786
+ only when no native launch is recorded. No launch or
2505
2787
  device lifecycle action is taken; follow the printed platform-command or
2506
2788
  process-probe remedy.`
2507
2789
  },
@@ -2533,6 +2815,51 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
2533
2815
  MORE THAN ONE MATCHING PEER IS NOT A FAILURE. A workspace Metro serves one
2534
2816
  app, so several matching peers are that app on several devices. Stim reloads
2535
2817
  every one of them and reports the count in the facts as targets.`
2818
+ },
2819
+ STIM_WEB_NO_CHROME: {
2820
+ summary: "stim web found no installed Chrome or Chromium",
2821
+ separator: "--- WEB CODES (`stim web`) ---",
2822
+ body: () => `STIM_WEB_NO_CHROME
2823
+ stim web drives the installed Google Chrome (or Chromium) with a profile
2824
+ Stim creates. It looks in /Applications and ~/Applications on macOS, in
2825
+ Program Files on Windows, and for google-chrome or chromium on PATH. Stim
2826
+ never installs a browser. Install Chrome, then run stim doctor.`
2827
+ },
2828
+ STIM_WEB_NO_URL: {
2829
+ summary: "not an Expo app and web.url is unset, so Stim does not know which page to open",
2830
+ body: () => `STIM_WEB_NO_URL
2831
+ Only Expo serves web from Metro, so for any other app Stim needs web.url.
2832
+ stim web never starts a web server, for any framework. Start yours on a
2833
+ named port and point web.url at it:
2834
+ pnpm exec vite --port "$(stim ports get web)" --strictPort
2835
+ stim settings set web.url 'http://localhost:{port:web}/' --scope workspace
2836
+ See stim guide web.`
2837
+ },
2838
+ STIM_WEB_DEPS_MISSING: {
2839
+ summary: "an Expo app without react-native-web cannot render on the web",
2840
+ body: () => `STIM_WEB_DEPS_MISSING
2841
+ The Expo app does not resolve react-native-web, so Metro cannot build a web
2842
+ bundle. Install the web dependencies, start Metro, then run stim web again:
2843
+ npx expo install react-dom react-native-web @expo/metro-runtime
2844
+ stim start`
2845
+ },
2846
+ STIM_WEB_BROWSER_HELD: {
2847
+ summary: "the previous owned Chrome could not be stopped or verified; it was left running",
2848
+ body: () => `STIM_WEB_BROWSER_HELD
2849
+ stim web replaces the workspace's owned Chrome when its options change, and
2850
+ the previous one could not be stopped: its supervisor or Chrome did not
2851
+ exit, or their process identities could not be verified. Stim never signals
2852
+ a process it cannot verify. Check stim status for browser-unverified, then
2853
+ follow stim guide errors teardown.`
2854
+ },
2855
+ STIM_WEB_LAUNCH_FAILED: {
2856
+ summary: "the owned Chrome did not start or did not open DevTools on its reserved port",
2857
+ body: () => `STIM_WEB_LAUNCH_FAILED
2858
+ The browser supervisor exited before Chrome answered on its reserved
2859
+ DevTools port. The remedy names the supervisor log; web.ndjson carries the
2860
+ failure as web_browser_failed, and browser.log holds Chrome's own output.
2861
+ A port another process bound first also lands here: run stim web again to
2862
+ reserve a fresh one.`
2536
2863
  },
2537
2864
  STIM_WORKTREE_REMOVAL_IN_PROGRESS: {
2538
2865
  summary: "a managed remote start found worktree remove holding the lock; wait, then rerun",
@@ -2727,8 +3054,10 @@ captured" (in metro.ndjson, bare RN)
2727
3054
  "Timed out waiting for the lock at <path>."
2728
3055
  Short directory locks serialize writes to config, workspace state, device
2729
3056
  leases, ownership records, metadata, and cache manifests. The path identifies
2730
- the lock. These locks wait up to 12s and never expire based on age. Wait for
2731
- the command holding it; if none is running, remove the named directory.
3057
+ the lock. These locks wait up to 12s and never expire based on age. When
3058
+ another Stim command holds the lock, the message says so: wait for it and
3059
+ retry. A failed marker write, such as ENOSPC on a full disk, removes the
3060
+ directory it just created before Stim reports the write error.
2732
3061
  Short locks use the same process-identity claims as long operations, stored
2733
3062
  beside the visible directory at <path>.claims. An opaque marker in the visible
2734
3063
  directory also excludes older Stim versions. Only the exclusive claim holder
@@ -2738,9 +3067,12 @@ captured" (in metro.ndjson, bare RN)
2738
3067
  STIM_CLAIM_REFUSED and names the claim to inspect; an unavailable native
2739
3068
  identity reports STIM_CLAIM_UNAVAILABLE without running the protected work.
2740
3069
  Older lock directories have no process identity. A visible directory left
2741
- empty before marker publication or during final removal also cannot prove it is free.
2742
- These paths still time out; verify that no holder is running before removing
2743
- only the named directory. Standalone Metro and Expo cache packages use the
3070
+ empty before marker publication (a process killed between creating it and
3071
+ writing its marker) or during final removal also cannot prove it is free.
3072
+ These paths still time out, and the message says no current Stim holds the
3073
+ lock and ends with \`rm -rf '<path>'\`. An older Stim version may still be
3074
+ using it: if none is running, remove the named directory with that command.
3075
+ Standalone Metro and Expo cache packages use the
2744
3076
  same core protocol and do not require the Stim CLI.`
2745
3077
  },
2746
3078
  teardown: {
@@ -2777,7 +3109,23 @@ the supervisor could not be verified" (stop)
2777
3109
  "teardown failed: <reason>"
2778
3110
  Stim could not release the owned device and keeps its record for a retry.
2779
3111
  \`worktree remove\` exits 1 without removing the worktree while the device is
2780
- still tracked. Fix the reported cause and re-run.`
3112
+ still tracked. Fix the reported cause and re-run.
3113
+
3114
+ "teardown failed: Owned AVD <name> did not finish shutting down after <n>s:
3115
+ emulator process <pid> is still running"
3116
+ An owned emulator counts as stopped only when no process launched for its
3117
+ AVD (\`qemu-system-*\` or \`emulator\` with \`-avd <name>\`) is left in the
3118
+ process table. Stim asks it to quit with \`adb emu kill\` and waits 60s.
3119
+ Then it sends SIGTERM and, 5s later, SIGKILL. When adb cannot reach the
3120
+ emulator, Stim sends SIGTERM at once and waits 60s before SIGKILL. It signals
3121
+ only a pid whose command line names the AVD and whose process identity,
3122
+ recorded before shutdown, still matches. "(Stim could not verify the
3123
+ identity of <pid>, so it sent no signal)" means that identity could not be
3124
+ read or no longer matches. Check \`ps -p <pid> -o command=\`, stop the
3125
+ process yourself, then re-run the command or \`gc --delete\`. Windows has
3126
+ no process-table check and Stim signals nothing there: it waits for the pid
3127
+ in the AVD's process lock after \`adb emu kill\`, and refuses at once when
3128
+ adb cannot reach the emulator.`
2781
3129
  },
2782
3130
  remove: {
2783
3131
  summary: "worktree remove refused a dirty tree: what it restores itself and what --force discards",
@@ -3186,7 +3534,7 @@ ANDROID EMULATOR RESTARTS
3186
3534
  display until it next boots.
3187
3535
 
3188
3536
  DESTRUCTIVE COMMANDS -- ask the user first
3189
- gc --delete deletes orphaned devices Stim created, tens of GB
3537
+ gc --delete deletes orphaned devices this Stim home created, tens of GB
3190
3538
  gc --delete --cache all empties the shared build caches every project uses
3191
3539
  gc --delete --cache <name>
3192
3540
  empties only the caches that carry <name>
@@ -3239,8 +3587,10 @@ WAITING FOR A CHANGE
3239
3587
  status payload per line: one at once, then one each time the payload
3240
3588
  changes, never two identical ones in a row. It reacts to Stim state files,
3241
3589
  the EAS session ledger, adb device arrivals and departures, and simulator
3242
- state, and recomputes every 30 seconds as a fallback. Ctrl+C, SIGTERM or
3243
- closing its stdout ends it with exit 0. Without --json it reprints the human view on change.`,
3590
+ state, and recomputes every 30 seconds as a fallback. A log append updates
3591
+ only the log error count and device activity, no sooner than 15 seconds
3592
+ after the previous refresh. Ctrl+C, SIGTERM or closing its stdout ends it
3593
+ with exit 0. Without --json it reprints the human view on change.`,
3244
3594
  sections: {
3245
3595
  eas: {
3246
3596
  summary: "download a matching EAS development build; explicit profile, costs, cache and miss remedies",
@@ -3968,7 +4318,10 @@ THE BUILD CACHE HAS THREE LEVELS
3968
4318
  records the reason as lastBuilds.<platform>.missReason in status --json
3969
4319
  (see \`guide facts status\`), and writes the changed sources (capped at 20
3970
4320
  names) to the build log as a fingerprint_diff record. With no baseline the
3971
- line says there was nothing to compare with.
4321
+ line says there was nothing to compare with. status --json also lists each
4322
+ platform's last 10 runs in builds.<platform>, with their result, cache
4323
+ outcome, miss reason, phase timings and diagnostics, so a hit that turned
4324
+ into a miss shows which run changed what.
3972
4325
 
3973
4326
  THE KEY CAN MOVE MID-RUN, and the run says so in two facts rather than two
3974
4327
  explanations. \`expo prebuild\` and \`pod install\` rewrite fingerprinted
@@ -4005,11 +4358,28 @@ THE BUILD CACHE HAS THREE LEVELS
4005
4358
  \`expo prebuild --clean\` yourself or delete the directory so the next build
4006
4359
  regenerates it.
4007
4360
 
4008
- If the iOS fingerprint after prebuild or pod install is unavailable, Stim
4009
- installs the build but skips local storage and remote uploads. fingerprint
4010
- and cacheKey are null in the result and lastBuild; the old key is not reused.
4011
- Android does the same if its post-Gradle fingerprint cannot be computed.
4012
- These null fields mean unavailable cache information, not an install failure.
4361
+ Both platforms fingerprint once more after the compile. A change the build
4362
+ itself makes to a fingerprinted file under node_modules/ or the native
4363
+ directory moves the key the same way, printed as \`(after Gradle)\` or
4364
+ \`(after xcodebuild)\`. Any other input that changed while the build ran
4365
+ -- the app config or a config plugin at any point after the first lookup,
4366
+ or any other source during the compile -- means the artifact may not match
4367
+ the key, so Stim installs what it built and stores nothing. The one config
4368
+ change exempted is the ios.bundleIdentifier or android.package that
4369
+ \`expo prebuild\` adds to a static app.json that lacks one:
4370
+
4371
+ fingerprint expoConfig changed while the build ran, so the artifact may not match its key; the build will be installed but not cached
4372
+
4373
+ The prebuild record never takes a hash that includes the edit, so the next
4374
+ run regenerates a CNG directory from the edited config and compiles it
4375
+ instead of reusing the stale one. An edit under node_modules/ or the native
4376
+ directory during the compile cannot be told apart from the build's own
4377
+ writes and moves the key. Stim also skips storing when the fingerprint
4378
+ after prebuild, pod install or the compile cannot be computed. In every
4379
+ skipped case nothing goes to the local cache, the cache provider or a
4380
+ remote upload, fingerprint and cacheKey are null in the result and
4381
+ lastBuild, and the old key is not reused. These null fields mean
4382
+ unavailable cache information, not an install failure.
4013
4383
 
4014
4384
  WHAT MAKES THE CACHE ACTUALLY HIT: .FINGERPRINTIGNORE
4015
4385
  Every entry is keyed on what the tree hashes, so two workspaces share an
@@ -4430,14 +4800,14 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
4430
4800
  (\`iOS 26.5\`), exactly; no prefix or suffix matches.
4431
4801
 
4432
4802
  These flags describe a device that does not exist yet. When this workspace
4433
- ALREADY owns a simulator and \`--device-type\` names a different model,
4434
- Stim refuses rather than silently booting the wrong one: reap the current
4435
- sim with \`stim worktree remove\` (or \`stim gc --delete\`), then run
4436
- \`stim ios\` again to create the requested one. \`--device-profile\` does
4437
- the same for an AVD of another profile. To keep both devices, give the new
4438
- one its own \`--slot\`. \`--runtime\` and the ios.runtime and
4439
- android.systemImage settings apply at creation only, so an existing device
4440
- keeps the version it was made with. An explicit \`--system-image\` that
4803
+ ALREADY owns a simulator and \`--device-type\` names a different model, or
4804
+ \`--runtime\` a different iOS version, Stim refuses rather than silently
4805
+ booting the wrong one: reap the current sim with \`stim worktree remove\`
4806
+ (or \`stim gc --delete\`), then run \`stim ios\` again to create the
4807
+ requested one. \`--device-profile\` does the same for an AVD of another
4808
+ profile. To keep both devices, give the new one its own \`--slot\`. The
4809
+ ios.runtime and android.systemImage settings apply at creation only, so an
4810
+ existing device keeps the version it was made with. An explicit \`--system-image\` that
4441
4811
  names another image than this workspace's AVD refuses the same way, unless
4442
4812
  that AVD never finished a boot (for example one made from an image its
4443
4813
  profile cannot boot): Stim then deletes it through owned-device teardown and
@@ -4817,6 +5187,8 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
4817
5187
  and takes at most 10 seconds. A timeout is not proof of an app crash or OOM.
4818
5188
  During boot, Stim reports elapsed time, the simulator name, last boot output,
4819
5189
  current pressure and the highest observed pressure roughly every 15 seconds.
5190
+ Warning or critical pressure adds recovery advice to the first report at
5191
+ that level; later reports repeat it only after the level changes.
4820
5192
  Failure diagnostics retain the highest pressure and unavailable sample count;
4821
5193
  they do not infer the cause of a timeout. Monitoring stops when boot ends.
4822
5194
  Monitoring and timeout handling are best-effort: synchronous CLI work can
@@ -4855,12 +5227,13 @@ WHAT RECLAIMS AN OWNED DEVICE
4855
5227
  stim worktree remove parks eligible owned simulators and emulators
4856
5228
  (\`guide lifecycle pool\`); deletes them when
4857
5229
  parking is disabled or their setup cannot be verified
4858
- stim gc --delete sweeps devices Stim created that no project
5230
+ stim gc --delete sweeps devices this Stim home created that no project
4859
5231
  references (\`guide cleanup gc\`), clears
4860
5232
  verified parked simulators and emulators, and
4861
5233
  runs \`stim worktree remove\` on every clean,
4862
- Stim-managed linked worktree whose branch is merged
4863
- and that shows no activity within
5234
+ Stim-managed linked worktree whose branch is merged,
5235
+ or whose pull request was merged or closed, and
5236
+ that shows no activity within
4864
5237
  gc.worktreeGraceMinutes
4865
5238
  stim gc --delete --older-than <days>
4866
5239
  also reaps the device of a workspace no Stim
@@ -4877,8 +5250,9 @@ WHAT RECLAIMS AN OWNED DEVICE
4877
5250
 
4878
5251
  \`worktree remove\` and \`gc --delete\` are the only two commands that delete;
4879
5252
  \`gc --delete\` deletes worktrees only through \`worktree remove\`. \`gc
4880
- --delete\` also clears workspace build outputs (\`guide cleanup disk\`) and
4881
- orphaned workspace directories, never a checkout. \`stim stop\` shuts a device
5253
+ --delete\` also clears workspace build outputs, trims oversized workspace logs
5254
+ (\`guide cleanup disk\`) and removes orphaned workspace directories, never a
5255
+ checkout. \`stim stop\` shuts a device
4882
5256
  DOWN and leaves it assigned, which is what makes returning to a branch cost a
4883
5257
  boot rather than a create, a provision and a reinstall.
4884
5258
 
@@ -4936,7 +5310,8 @@ SWEEPING FINISHED WORKTREES
4936
5310
  Every \`gc\` without --cache looks at every registered project root and
4937
5311
  every workspace.json root, grouped by git worktree, and reports each linked
4938
5312
  worktree with the reason it is removed or kept. A worktree is finished when
4939
- its branch is merged into the default branch. \`--worktrees\` also removes
5313
+ its branch is merged into the default branch, or its pull request was
5314
+ merged or closed (PULL REQUESTS below). \`--worktrees\` also removes
4940
5315
  one that is idle: no recorded use for --older-than days, 7 without it; a
4941
5316
  worktree whose last use is unknown is kept. Both need the same clean state;
4942
5317
  a worktree is kept when it is the source checkout, bare, locked, in use,
@@ -4958,7 +5333,8 @@ SWEEPING FINISHED WORKTREES
4958
5333
  gc.worktreeGraceMinutes (120 by default, \`guide settings\`) have passed
4959
5334
  since the latest of: a write to its git index, HEAD or HEAD reflog, a write
4960
5335
  to its Stim workspace state or log files, and, for a merged branch, the
4961
- committer date of the commit that brought it into the default branch. That
5336
+ committer date of the commit that brought it into the default branch, or
5337
+ the time its pull request was merged or closed. That
4962
5338
  window is when an agent that just merged runs \`stim stop\` and \`stim
4963
5339
  worktree remove\` itself. The reason is recent-activity, and the report
4964
5340
  and the JSON eligibleAt field say when it becomes removable. When that
@@ -4977,16 +5353,33 @@ SWEEPING FINISHED WORKTREES
4977
5353
  commit on the default branch (a rebase merge), or its whole diff since the
4978
5354
  merge base has the same verbatim patch id as a commit there, compared on
4979
5355
  the files the branch changes (a squash merge).
4980
- Merge state comes from git alone, not from a hosting service. A squash
4981
- merge whose content changed during the merge (a conflict resolution, a
4982
- suggested edit, even whitespace) does not match and is kept, and so is a
4983
- fast-forwarded branch. When origin/HEAD is not set, the fetch fails, or git
5356
+ From git alone, a squash merge whose content changed during the merge (a
5357
+ conflict resolution, a suggested edit, even whitespace) does not match, and
5358
+ neither does a fast-forwarded branch or a stacked pull request merged after
5359
+ the one below it; the pull request check covers those. When origin/HEAD is not set, the fetch fails, or git
4984
5360
  cannot answer, the state is unknown and never counts as merged (reason
4985
5361
  merge-unknown, with the remedy); with --worktrees an idle worktree is still
4986
5362
  removed. A squash- or rebase-merged branch whose upstream is gone (deleted
4987
5363
  after the merge and pruned locally) has commits only it reaches; they do
4988
5364
  not block removal, because their change is on the default branch, and the
4989
5365
  branch is kept.
5366
+
5367
+ PULL REQUESTS: gc runs one \`gh api graphql\` query per repository that
5368
+ asks for the 20 newest pull requests of each linked worktree's branch, as
5369
+ \`gh pr list --head <branch> --state all\` would, and for each worktree
5370
+ takes the pull request whose head is HEAD, else one whose head contains
5371
+ HEAD. A pull request whose head HEAD is past ("PR #12 merged, and HEAD has
5372
+ commits it does not"), one unrelated to HEAD (an older use of the branch
5373
+ name), and one from a fork do not count. A merged or closed one makes the worktree finished
5374
+ under the same clean state. Merged, its commits are kept on GitHub, so local
5375
+ commits whose remote branch was deleted do not block removal. Closed, they
5376
+ do: a closed pull request whose remote branch is gone keeps the worktree as
5377
+ unpushed. An open pull request never makes a worktree finished. When gh is
5378
+ not installed, not signed in, or fails, the JSON pullRequestUnknown field
5379
+ and the report say why, and gc judges from git alone. A detached HEAD is
5380
+ not looked up. \`stim worktree remove\` makes the same check when
5381
+ local-only commits alone would refuse the removal, and accepts a merged
5382
+ pull request whose head is or contains HEAD.
4990
5383
  stim gc # report merged worktrees
4991
5384
  stim gc --delete # remove them
4992
5385
  stim gc --delete --worktrees --older-than 3 # also the clean idle ones
@@ -5055,15 +5448,16 @@ it. A record is what makes the device findable again, so it outlives a failed
5055
5448
  teardown rather than turning it into an orphan.
5056
5449
 
5057
5450
  WHICH stim-* DEVICES gc DELETES
5058
- A \`stim-\` prefix alone is not proof that Stim made a device. gc deletes an
5059
- unreferenced device only when Stim created it: the device is listed in
5451
+ A \`stim-\` name is not proof that this Stim home made a device: every
5452
+ STIM_HOME names its devices the same way. gc deletes an unreferenced device
5453
+ only when this home created it: the device is listed in
5060
5454
  $STIM_HOME/created-devices.json, where Stim records every simulator and AVD
5061
- it creates, or it predates that ledger and matches Stim's own format
5062
- exactly -- an iOS name \`stim-<label> (<model> <runtime>)\`, or an AVD
5063
- named \`stim-<label>\` whose config.ini holds the byte-valued
5064
- disk.dataPartition.size Stim writes. gc lists any other stim-* device under
5455
+ it creates, or it predates that ledger and this home's config records it in
5456
+ the device pool or in a project as owned. gc lists any other stim-* device,
5457
+ including those another STIM_HOME created, under
5065
5458
  "Unrecognized stim-* devices" with the command that deletes it
5066
- (\`xcrun simctl delete <udid>\` or \`avdmanager delete avd -n <name>\`), and
5459
+ (\`xcrun simctl delete <udid>\`, \`avdmanager delete avd -n <name>\`, or
5460
+ \`rm -rf <dir>\` for AVD data with no registration), and
5067
5461
  never runs it. Boot, shutdown and teardown re-check ownership by the same
5068
5462
  rule, so a device that stops matching is left alone.
5069
5463
 
@@ -5132,6 +5526,26 @@ THE MIRROR IMAGE: A STALE DEVICE RECORD
5132
5526
  at simctl or avdmanager, and the project keeps its entry, its label and its
5133
5527
  Metro port. The next \`ios\` / \`android\` creates a fresh owned device.
5134
5528
 
5529
+ The ledger of devices Stim created (created-devices.json) keeps a
5530
+ simulator's UDID after the simulator is deleted outside Stim. \`gc\`
5531
+ reports such UDIDs under "Stale device ledger entries" when a complete
5532
+ simctl listing, unavailable simulators included, does not show them, and
5533
+ \`gc --delete\` forgets them under the ledger lock. If the listing fails,
5534
+ nothing is reported or forgotten, and like the device sweep it is skipped
5535
+ without a config or under a scoped STIM_HOME. UDIDs are never reused, so a
5536
+ forgotten entry cannot belong to a later simulator.
5537
+
5538
+ Android ledger entries are AVD names, and names are reused: another
5539
+ STIM_HOME can later create an AVD with the same name. \`gc\` reports an
5540
+ AVD name as stale only when \`emulator -list-avds\` answered, the first AVD
5541
+ root exists (a missing one may be an unmounted volume), no AVD root holds
5542
+ <name>.ini or <name>.avd, and no unfinished setup reserves the name.
5543
+ \`gc --delete\` takes that AVD's claim, then re-checks and forgets the name
5544
+ under the config and ledger locks, so a setup in progress or an AVD
5545
+ recreated since the report keeps its entry. A name reused before \`gc\`
5546
+ runs still counts as this home's; run \`gc\` after deleting a Stim AVD by
5547
+ hand.
5548
+
5135
5549
  THE ONE CASE GC WILL NOT REAP
5136
5550
  If the config is gone entirely (deleted ~/.stim, or a throwaway
5137
5551
  STIM_HOME), gc cannot tell your stale devices from another config's LIVE
@@ -5198,7 +5612,7 @@ THE ONE CASE GC WILL NOT REAP
5198
5612
  Wall-clock timestamps and command names are not ownership proof.`
5199
5613
  },
5200
5614
  disk: {
5201
- summary: "disk usage, workspace build outputs, AVD and build-log sizes, the data partition, trimming the shared caches",
5615
+ summary: "disk usage, workspace build outputs and logs, AVD and build-log sizes, the data partition, trimming the shared caches",
5202
5616
  body: () => `DISK
5203
5617
  Logs, state, pidfiles and Xcode DerivedData are under the global workspace
5204
5618
  directory, and \`worktree remove\` reclaims them. \`gc --delete\` clears the
@@ -5214,12 +5628,16 @@ THE ONE CASE GC WILL NOT REAP
5214
5628
  disabled. The first boot and a boot after the emulator, system image, or AVD
5215
5629
  settings change are cold, while later supported boots load the one automatic
5216
5630
  snapshot saved on exit. \`stop\` waits for the emulator process and, when
5217
- enabled, the snapshot save to finish.
5631
+ enabled, the snapshot save to finish. An emulator that ignores \`adb emu
5632
+ kill\` or is unreachable over adb is signalled once its identity is verified;
5633
+ one that still runs fails the teardown and keeps its record (see \`stim guide
5634
+ errors teardown\`).
5218
5635
  New owned AVDs default to an 8 GiB data partition, though project settings can
5219
5636
  change it. When enabled, Quick Boot keeps one automatic snapshot until the AVD is
5220
5637
  parked or deleted.
5221
5638
  \`gc\` prints the on-disk size beside an orphaned or stale owned Android AVD
5222
- when its content directory can be read.
5639
+ when its content directory can be read, and beside an orphaned or stale
5640
+ owned simulator the data size simctl reports for it.
5223
5641
 
5224
5642
  So are the logs, and one of them is not small: build-ios.ndjson /
5225
5643
  build-android.ndjson hold the whole xcodebuild or gradle transcript at debug
@@ -5268,6 +5686,19 @@ WORKSPACE BUILD OUTPUTS
5268
5686
  React Native 0.86 Swift does not use it (explicit modules are off), so that
5269
5687
  build recompiles Swift.
5270
5688
 
5689
+ WORKSPACE LOGS
5690
+ \`gc\` reports the size of each workspace's logs/ (the workspaceLogs
5691
+ section of \`gc --json\`). metro.ndjson, client.ndjson and device.ndjson and
5692
+ their .1 generations rotate at 8 MiB (\`guide logs\`), but a file written by
5693
+ a Stim version before the cap can be hundreds of MB. \`gc --delete\` trims
5694
+ each of those six files that is over 16 MiB to its newest 8 MiB, cut at a
5695
+ record boundary; a file just past 8 MiB is normal rotation and stays.
5696
+ \`--older-than\` does not limit it. It skips a workspace that is in use (a
5697
+ dev server, a native run, a build or a held tunnel) or that has a device
5698
+ log collector recorded; \`stim stop\`
5699
+ stops the collector. Build transcripts keep the whole run and are never
5700
+ trimmed, and nothing else under logs/ is touched.
5701
+
5271
5702
  SHARED BUILD CACHES
5272
5703
  The caches that make a second workspace fast are alive by design and never
5273
5704
  included in a plain \`gc --delete\`. Every \`gc\` run reports them anyway,
@@ -5316,7 +5747,9 @@ shape, before anything is written. Writes to the machine file take its lock
5316
5747
  and replace it atomically; a committed write keeps the file's other keys and
5317
5748
  indentation. Run \`stim settings\` from the app directory: workspace and
5318
5749
  committed resolve from the nearest package.json, repo from its Git
5319
- repository. worktree.exclude and worktree.defaultBranch are read only from
5750
+ repository. From a monorepo web package that resolves to its worktree's app
5751
+ (see stim guide ports), workspace and committed are that app's entry and
5752
+ .stim.json. worktree.exclude and worktree.defaultBranch are read only from
5320
5753
  the repo layer and the repository root's .stim.json.
5321
5754
 
5322
5755
  android.keystorePassword is sensitive: its value prints as ******** in every
@@ -5566,6 +5999,21 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
5566
5999
  driven. Read
5567
6000
  when \`start\` spawns the supervisor; see
5568
6001
  \`guide metro\`.
6002
+ web.url the page \`stim web\` opens in the owned Chrome, an
6003
+ http:// or https:// URL. {port:<label>} becomes the
6004
+ workspace's named port (allocated like \`stim ports
6005
+ get <label>\`), and {port:metro} its Metro port.
6006
+ Unset, Expo apps open http://localhost:<metroPort>/
6007
+ and other apps refuse until it is set. Example:
6008
+ http://localhost:{port:web}/apps/groups/.
6009
+ See \`guide web\`.
6010
+ web.ignoreCertificateErrors
6011
+ true starts the owned Chrome with
6012
+ --ignore-certificate-errors, for dev servers with
6013
+ self-signed certificates (default false). It applies
6014
+ only to Stim's own profile, never to your browser.
6015
+ web.viewport desktop (default, 1280x800) or phone (390x844 at 3x
6016
+ with touch events) for the owned Chrome page.
5569
6017
  worktree.exclude ignored-path skip list for worktree warm. Settings
5570
6018
  come from the source checkout's repository-root
5571
6019
  .stim.json. A nonempty .worktreeexclude in the source
@@ -5608,7 +6056,7 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
5608
6056
  them from the environment or the machine layers.
5609
6057
 
5610
6058
  Each setting takes its documented type: string, array of strings, number,
5611
- boolean, or object. ios.remote, android.remote, metro.tunnel,
6059
+ boolean, or object. ios.remote, android.remote, metro.tunnel, web.viewport,
5612
6060
  optimizations.android.compilerCache and optimizations.android.pch take only
5613
6061
  their listed choices. A value of the wrong
5614
6062
  type or outside those choices is refused by name on every command that resolves
@@ -5683,6 +6131,10 @@ To show the booted simulator in Stim Desktop and open no simulator window:
5683
6131
 
5684
6132
  Stim Desktop selects the workspace that owns the simulator and focuses that
5685
6133
  device. It only displays the simulator; it never boots or shuts it down.
6134
+ When Stim Desktop is not running, Stim starts it without the command's
6135
+ \`STIM_HOME\`, so it reads the same Stim home as when you open it yourself.
6136
+ It shows only devices from that home: under another \`STIM_HOME\`, pick
6137
+ \`--simulator-app xcode\` to see the simulator.
5686
6138
 
5687
6139
  Override the preference for one local launch with
5688
6140
  \`stim ios --simulator-app siniulator\`, \`stim ios --simulator-app stim-desktop\`,
@@ -5711,10 +6163,13 @@ Stim then starts the emulator with \`-no-window -gpu host\` and opens
5711
6163
  frames and sends input over the emulator's gRPC endpoint. The setting applies
5712
6164
  only when Stim boots the emulator: one that is already running keeps its
5713
6165
  current display until it next boots, and physical devices are unaffected.
5714
- An invalid value refuses before boot. On Linux and Windows the setting has
5715
- no effect.
6166
+ Like the iOS viewer, it starts Stim Desktop without the command's
6167
+ \`STIM_HOME\`. An invalid value refuses before boot. On Linux and Windows the
6168
+ setting has no effect.
5716
6169
 
5717
6170
  Stim finds Stim Desktop by its bundle id, dev.stim.desktop, in Launch Services.
6171
+ A command Stim Desktop runs skips that lookup: Desktop sets \`STIM_DESKTOP_APP\`
6172
+ to its app path for every command it starts.
5718
6173
  \`stim settings\` shows that default as \`(default: Stim Desktop installed)\`,
5719
6174
  \`settings get\` prints it on stderr, and \`--json\` adds
5720
6175
  \`"defaultReason": "Stim Desktop installed"\`. A set value always wins; set
@@ -5754,7 +6209,8 @@ THE GC WORKTREE GRACE PERIOD IS MACHINE-LEVEL
5754
6209
  \`gc.worktreeGraceMinutes\` is how long \`gc --delete\` waits before it removes
5755
6210
  a merged or idle linked worktree. The clock starts at the worktree's latest
5756
6211
  activity: a write to its git index, HEAD or HEAD reflog, its Stim workspace
5757
- state or logs, or the merge of its branch into the default branch.
6212
+ state or logs, the merge of its branch into the default branch, or when its
6213
+ pull request was merged or closed.
5758
6214
 
5759
6215
  {
5760
6216
  "gc": { "worktreeGraceMinutes": 120 }
@@ -5992,6 +6448,176 @@ platform instead of one entry. Pass prune: 'atomic' for a cache whose index
5992
6448
  references its own data (an LLVM CAS): it is then left alone by --older-than
5993
6449
  and emptied whole only by 'gc --delete --cache all'.
5994
6450
  Registration is idempotent and keyed on the directory.`
6451
+ },
6452
+ web: {
6453
+ summary: "stim web: an owned headless Chrome per workspace, its logs, launched, and teardown",
6454
+ body: () => `WEB: AN OWNED CHROME PER WORKSPACE
6455
+
6456
+ If Stim is not installed globally, replace stim with npx stim.
6457
+
6458
+ stim web opens this workspace's page in a Chrome that Stim owns, captures the
6459
+ page's console, uncaught errors and failed requests in stim logs, and reports
6460
+ whether the page loaded. It uses the installed Google Chrome (or Chromium)
6461
+ with a profile Stim creates under STIM_HOME. Stim never installs a browser;
6462
+ stim doctor reports a missing Chrome.
6463
+
6464
+ stim web never starts a web server, for any framework. It opens, reuses or
6465
+ reloads the owned Chrome at the page URL. Every web project follows the same
6466
+ two steps: start your dev server, then run stim web.
6467
+
6468
+ stim web # headless, the default
6469
+ stim web --headed # show the Chrome window
6470
+ stim web --json # one payload on stdout; progress on stderr
6471
+ stim logs --errors # page errors land here with platform "web"
6472
+ stim reload web # Page.reload on the owned page
6473
+ stim stop # closes Chrome with the rest; the profile stays
6474
+
6475
+ WHICH PAGE AND WHICH DEV SERVER
6476
+
6477
+ Expo web runs on the workspace's Metro. With web.url unset, an Expo app
6478
+ opens http://localhost:<metroPort>/. Its dev server is stim start:
6479
+
6480
+ npx expo install react-dom react-native-web @expo/metro-runtime # once
6481
+ stim start
6482
+ stim web
6483
+
6484
+ Any other web server (Vite, Next, webpack) starts with its own command.
6485
+ Reserve its port with stim ports get, start it with strict-port behavior, and
6486
+ point web.url at it. {port:<label>} in web.url becomes that named port,
6487
+ {port:metro} the Metro port:
6488
+
6489
+ pnpm exec vite --port "$(stim ports get web)" --strictPort
6490
+ stim settings set web.url 'http://localhost:{port:web}/' --scope workspace
6491
+ stim web
6492
+
6493
+ When nothing serves the page, stim web still opens Chrome, reports launched
6494
+ "unverified", and prints the remedy for this workspace: stim start for an
6495
+ Expo web app, the stim ports get web recipe for any other server. A Metro
6496
+ port held by another process is also "unverified", and the page it serves
6497
+ still reaches stim logs; stim start then reserves a free port for this
6498
+ workspace. When this workspace's supervisor is still recorded, run stim stop
6499
+ first.
6500
+
6501
+ In a monorepo whose web app is its own package (apps/web beside apps/mobile),
6502
+ stim ports, web, settings, logs, reload, stop and status run from the web
6503
+ package resolve to the one Stim app registered in the same Git worktree; each
6504
+ names the app on stderr, and status stars it. The port, the browser, web.url
6505
+ and the logs all belong to that app's workspace, so every command can run from
6506
+ the web package. Register the app first, from its directory. See stim guide
6507
+ ports for the exact rule.
6508
+
6509
+ HTTPS dev servers with a self-signed certificate, such as Vite with
6510
+ @vitejs/plugin-basic-ssl, fail with net::ERR_CERT_AUTHORITY_INVALID until
6511
+ web.ignoreCertificateErrors is true, which accepts the certificate in the
6512
+ owned profile only:
6513
+ stim settings set web.url 'https://localhost:{port:web}/' --scope workspace
6514
+ stim settings set web.ignoreCertificateErrors true --scope workspace
6515
+ An https:// URL on a plain HTTP server fails with ERR_SSL_PROTOCOL_ERROR, and
6516
+ an http:// URL on an HTTPS server with ERR_EMPTY_RESPONSE; the remedy line
6517
+ prints the settings command that switches the scheme. web.viewport phone
6518
+ gives the page a 390x844 touch screen at 3x instead of the 1280x800 desktop
6519
+ window. See stim guide settings.
6520
+
6521
+ MONOREPO RECIPE: A VITE PACKAGE WITH A BASE PATH AND HTTPS
6522
+
6523
+ apps/web runs Vite through its dev script, serves the app under /apps/groups/,
6524
+ and uses @vitejs/plugin-basic-ssl; apps/mobile is the Stim app. The flow is
6525
+ the same as for any web project: start the dev server, then run stim web.
6526
+
6527
+ cd apps/mobile && stim ports get web # registers the app; no Metro
6528
+ cd ../web
6529
+ stim settings set web.url 'https://localhost:{port:web}/apps/groups/' --scope repo
6530
+ stim settings set web.ignoreCertificateErrors true --scope repo
6531
+ pnpm dev --port "$(stim ports get web)" --strictPort # keep it running
6532
+ stim web
6533
+ stim logs --errors
6534
+ stim reload web
6535
+ stim stop # closes Chrome; stim ports stop web stops Vite
6536
+ stim worktree remove # in a linked worktree: Chrome, Vite and profile
6537
+
6538
+ The repo layer is shared by every worktree of the repository, so a new
6539
+ worktree skips the two settings lines; the certificate and scheme remedies
6540
+ print --scope workspace, which overrides it for one worktree only. Run ports
6541
+ get from the app directory before any ports or web command in the web
6542
+ package: a reservation made there first keeps the web package as its own
6543
+ workspace until you release it and stop. Pass --port and --strictPort
6544
+ through the dev script: Vite otherwise binds its config default and moves to
6545
+ the next free port when that one is taken. Put the base path in web.url: a
6546
+ path outside it can reach the dev server's proxy instead of the app. API
6547
+ calls the dev server proxies to a backend that is not running fail as device
6548
+ errors in stim logs --errors; the page still loads.
6549
+
6550
+ LAUNCHED
6551
+
6552
+ The web payload's launched keeps its native meaning, from evidence in the
6553
+ owned page rather than a shared port:
6554
+ true the page's document answered and its load event fired; on
6555
+ Metro, the page also fetched a web bundle
6556
+ "bundling" Metro was still building the web bundle when the check ended
6557
+ "unverified" the document failed (for example ERR_CONNECTION_REFUSED:
6558
+ the dev server is not running, or ERR_CERT_AUTHORITY_INVALID: a
6559
+ self-signed certificate), loaded without a Metro bundle, or
6560
+ did not finish: 20 seconds with no answer, 60 once a server
6561
+ other than Metro answered. The remedy line names the fix
6562
+ false is never produced. A page that loads and then throws is still launched
6563
+ true; read stim logs --errors.
6564
+
6565
+ A second stim web with the same options reuses the running Chrome and
6566
+ navigates it again. Changing --headed, web.viewport or the certificate
6567
+ setting restarts it.
6568
+
6569
+ LOGS
6570
+
6571
+ Records go to web.ndjson in the workspace log directory, all with
6572
+ platform "web":
6573
+ src client console calls at their level (console.error is error) and
6574
+ uncaught errors and rejections, with stack frames
6575
+ src device failed requests, which stim logs --errors includes: a failed
6576
+ page document is error whatever its
6577
+ status; another request's network failure or HTTP 5xx is
6578
+ error, a 4xx warn, a canceled request debug; browser messages such
6579
+ as CSP violations; the browser's own lifecycle
6580
+ Expo also prints web console calls on Metro ("Web LOG"), so they can appear
6581
+ twice: once from the page (client), once from Metro (metro, level info).
6582
+ Each load of the page's top-level document is a page-load marker: stim web,
6583
+ stim reload, and a reload or navigation the page makes itself. logs --errors
6584
+ and the status error count report the page's records (platform web) from the
6585
+ latest load only, the way Chrome DevTools clears its console when the page
6586
+ navigates. A page load does not hide the native app's errors, and an ios or
6587
+ android launch does not hide the page's.
6588
+
6589
+ AGENTS AND OTHER TOOLS ON THE SAME BROWSER
6590
+
6591
+ stim status --json shows environments[].web.cdpEndpoint, a reserved loopback
6592
+ DevTools endpoint on 127.0.0.1. Point browser tools at it instead of letting
6593
+ them start their own browser:
6594
+ Playwright MCP --cdp-endpoint http://127.0.0.1:<port>
6595
+ agent-browser --cdp <port>
6596
+ The port reaches only the Stim profile: Chrome refuses remote debugging on
6597
+ your default profile, and Stim never attaches to a browser it did not start.
6598
+ The port is managed like metro: stim ports lists it as web-cdp (managed),
6599
+ and ports get, stop and release refuse it.
6600
+
6601
+ OWNERSHIP AND CLEANUP
6602
+
6603
+ A browser supervisor process holds the DevTools session and an ownership
6604
+ claim for its lifetime; Chrome is the claim's child. Stim signals the
6605
+ supervisor or Chrome only after verifying the process identity it recorded.
6606
+ stim stop closes Chrome and keeps the profile, so cookies and
6607
+ storage survive the next stim web
6608
+ stim worktree remove closes Chrome and deletes the profile
6609
+ stim gc --delete does the same for a workspace whose path is gone,
6610
+ and forgets ledger entries of deleted profiles
6611
+ The profile is deleted only when the created-devices ledger lists it. After
6612
+ Chrome is gone, Stim removes the SingletonLock files a killed Chrome leaves,
6613
+ which would otherwise block the next launch. status reports
6614
+ browser-unverified when an identity cannot be proven; follow stim guide
6615
+ errors teardown.
6616
+
6617
+ LIMITS
6618
+
6619
+ Chrome and Chromium only. No Stim Desktop or phone viewer yet. The owned page
6620
+ is one tab: a page the app opens in a new window is not captured.`
5995
6621
  }
5996
6622
  };
5997
6623
  //#endregion