cctally 1.63.0 → 1.65.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.
@@ -1118,18 +1118,21 @@ def _do_update_check() -> None:
1118
1118
  c._save_update_state(state)
1119
1119
 
1120
1120
 
1121
- def _spawn_background_update_check() -> None:
1122
- """Fire-and-forget the hidden `_update-check` worker.
1123
-
1124
- Detached `subprocess.Popen` with `start_new_session=True` so a
1125
- parent exit (the user closes the shell) doesn't propagate SIGHUP
1126
- to the child. stdin/stdout/stderr are all `/dev/null` so the child
1127
- can't accidentally pollute the parent's terminal. Exceptions are
1128
- swallowed: a failed spawn must not break the parent command.
1121
+ def _spawn_detached(command: str) -> None:
1122
+ """Fire-and-forget a hidden self-subcommand as a detached worker.
1123
+
1124
+ Detached `subprocess.Popen` with `start_new_session=True` so a parent
1125
+ exit (the user closes the shell) doesn't propagate SIGHUP to the child;
1126
+ stdin/stdout/stderr all `/dev/null` so the child can't pollute the
1127
+ parent's terminal; exceptions swallowed so a failed spawn can't break
1128
+ the parent command. Shared launch boilerplate for both dedicated
1129
+ background workers below — `_update-check` and `_telemetry-beat` stay
1130
+ deliberately separate spawns (spec review finding #1) and share only
1131
+ this Popen shape.
1129
1132
  """
1130
1133
  try:
1131
1134
  subprocess.Popen(
1132
- [sys.executable, os.path.realpath(sys.argv[0]), "_update-check"],
1135
+ [sys.executable, os.path.realpath(sys.argv[0]), command],
1133
1136
  stdin=subprocess.DEVNULL,
1134
1137
  stdout=subprocess.DEVNULL,
1135
1138
  stderr=subprocess.DEVNULL,
@@ -1141,6 +1144,11 @@ def _spawn_background_update_check() -> None:
1141
1144
  pass
1142
1145
 
1143
1146
 
1147
+ def _spawn_background_update_check() -> None:
1148
+ """Fire-and-forget the hidden ``_update-check`` worker (spec §3.6)."""
1149
+ _spawn_detached("_update-check")
1150
+
1151
+
1144
1152
  def cmd_update_check_internal(args) -> int:
1145
1153
  """Hidden ``_update-check`` subcommand handler (spec §3.6).
1146
1154
 
@@ -1167,6 +1175,37 @@ def cmd_update_check_internal(args) -> int:
1167
1175
  return 0
1168
1176
 
1169
1177
 
1178
+ def _spawn_background_telemetry_beat() -> None:
1179
+ """Fire-and-forget the hidden ``_telemetry-beat`` worker.
1180
+
1181
+ A DEDICATED detached worker, deliberately separate from
1182
+ ``_update-check`` (spec review finding #1): the anonymous install-count
1183
+ beat must never share a process, throttle marker, or failure mode with
1184
+ the update check. Only the launch boilerplate is shared, via
1185
+ ``_spawn_detached``."""
1186
+ _spawn_detached("_telemetry-beat")
1187
+
1188
+
1189
+ def cmd_telemetry_beat_internal(args) -> int:
1190
+ """Hidden ``_telemetry-beat`` subcommand handler.
1191
+
1192
+ The detached anonymous-install-count worker. It does the beat and
1193
+ NOTHING else — critically it must NOT read or write any update-check
1194
+ state (spec review finding #1: a dedicated worker fully decoupled
1195
+ from ``_update-check``). Always returns 0 so the parent's
1196
+ spawn-and-forget contract holds. The try/except wraps both the
1197
+ ``load_config`` read and ``do_telemetry_beat`` (which only guarantees
1198
+ the network POST is swallowed, not that pure helper defects can't
1199
+ raise), and its return value is unused."""
1200
+ c = _cctally()
1201
+ try:
1202
+ config = c.load_config()
1203
+ c.do_telemetry_beat(config)
1204
+ except Exception:
1205
+ pass
1206
+ return 0
1207
+
1208
+
1170
1209
  # === User-facing `cctally update` (spec §4) ===
1171
1210
  # `cmd_update` routes by mode flag. Mode flags are mutually exclusive
1172
1211
  # (argparse enforces it; the dispatcher's redundant check is defense in
@@ -1976,6 +2015,11 @@ class _DashboardUpdateCheckThread(threading.Thread):
1976
2015
  envelope_precompute=(
1977
2016
  c._cctally_tui._tui_precompute_envelope_config(load_config())
1978
2017
  ),
2018
+ # #278 §1.4.1: a version-banner refresh republishes complete data;
2019
+ # force the hydration latch clear so a republish that happens to
2020
+ # carry a prior hydrating seed/partial doesn't freeze the client's
2021
+ # loading skeletons.
2022
+ hydrating=False,
1979
2023
  )
1980
2024
  self._ref.set(fresh)
1981
2025
  self._hub.publish(fresh)