termwright 0.3.2__tar.gz → 0.4.0__tar.gz

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 (45) hide show
  1. {termwright-0.3.2 → termwright-0.4.0}/.gitignore +1 -1
  2. {termwright-0.3.2 → termwright-0.4.0}/PKG-INFO +4 -4
  3. {termwright-0.3.2 → termwright-0.4.0}/README.md +3 -3
  4. {termwright-0.3.2 → termwright-0.4.0}/pyproject.toml +1 -1
  5. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/__init__.py +1 -1
  6. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/client.py +5 -6
  7. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/messages.py +35 -7
  8. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/roles.py +1 -0
  9. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/tree.py +1 -1
  10. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/validate.py +2 -2
  11. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/__init__.py +1 -1
  12. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/textual_tree.py +1 -1
  13. {termwright-0.3.2 → termwright-0.4.0}/tests/conftest.py +1 -1
  14. {termwright-0.3.2 → termwright-0.4.0}/tests/test_client.py +8 -8
  15. {termwright-0.3.2 → termwright-0.4.0}/tests/test_messages.py +12 -4
  16. {termwright-0.3.2 → termwright-0.4.0}/tests/test_probe_golden.py +3 -3
  17. {termwright-0.3.2 → termwright-0.4.0}/tests/test_probe_session.py +4 -4
  18. {termwright-0.3.2 → termwright-0.4.0}/tests/test_probe_tree.py +1 -1
  19. {termwright-0.3.2 → termwright-0.4.0}/tests/test_validate.py +1 -1
  20. {termwright-0.3.2 → termwright-0.4.0}/uv.lock +1 -1
  21. {termwright-0.3.2 → termwright-0.4.0}/examples/permission_app.py +0 -0
  22. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/debug.py +0 -0
  23. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/errors.py +0 -0
  24. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/evidence.py +0 -0
  25. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/framing.py +0 -0
  26. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/limits.py +0 -0
  27. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/logging_bridge.py +0 -0
  28. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/logs.py +0 -0
  29. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/marker.py +0 -0
  30. {termwright-0.3.2 → termwright-0.4.0}/src/termwright/textual.py +0 -0
  31. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/__main__.py +0 -0
  32. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/bootstrap.py +0 -0
  33. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/defer.py +0 -0
  34. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/session.py +0 -0
  35. {termwright-0.3.2 → termwright-0.4.0}/src/termwright_probe/textual_probe.py +0 -0
  36. {termwright-0.3.2 → termwright-0.4.0}/tests/fixtures/vanilla_textual_app.py +0 -0
  37. {termwright-0.3.2 → termwright-0.4.0}/tests/test_debug.py +0 -0
  38. {termwright-0.3.2 → termwright-0.4.0}/tests/test_evidence.py +0 -0
  39. {termwright-0.3.2 → termwright-0.4.0}/tests/test_framing.py +0 -0
  40. {termwright-0.3.2 → termwright-0.4.0}/tests/test_logs.py +0 -0
  41. {termwright-0.3.2 → termwright-0.4.0}/tests/test_marker.py +0 -0
  42. {termwright-0.3.2 → termwright-0.4.0}/tests/test_observation_vectors.py +0 -0
  43. {termwright-0.3.2 → termwright-0.4.0}/tests/test_probe_bootstrap.py +0 -0
  44. {termwright-0.3.2 → termwright-0.4.0}/tests/test_textual_annotations.py +0 -0
  45. {termwright-0.3.2 → termwright-0.4.0}/tests/test_textual_probe_hook.py +0 -0
@@ -1,5 +1,5 @@
1
1
  # Rust
2
- rust/target/
2
+ target/
3
3
  # Python
4
4
  python/*.egg-info/
5
5
  python/build/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: termwright
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Semantic side-channel client, automatic Textual probe, and annotation SDK for Termwright
5
5
  Project-URL: Homepage, https://github.com/gorce-ai/termwright
6
6
  Project-URL: Source, https://github.com/gorce-ai/termwright/tree/main/clients/python
@@ -31,8 +31,8 @@ An instrumented app publishes its widget tree over a unix socket and commits
31
31
  each render with a signed OSC marker, so the driver can assert on _roles and
32
32
  names_ instead of screen-scraping cells.
33
33
 
34
- The protocol client speaks `termwright/2`. Every published semantic revision
35
- is a complete v2 snapshot with evidence-qualified geometry and pointer
34
+ The protocol client speaks `termwright/3`. Every published semantic revision
35
+ is a complete v3 snapshot with evidence-qualified geometry and pointer
36
36
  observations.
37
37
 
38
38
  **Dormant rule.** Without `TERMWRIGHT_ENDPOINT` and `TERMWRIGHT_TOKEN` the
@@ -235,7 +235,7 @@ or, on a session that came up:
235
235
  ```text
236
236
  tw:sem [p41207] 0.002s dial unix:/tmp/tw-8f21/s timeout=5000ms
237
237
  tw:sem [p41207] 0.003s hello sent adapter=textual/1.0.0 caps=tree,states,actions,render-revisions,intended-geometry,clipped-geometry,pointer-hit-grid
238
- tw:sem [3f9c1a04] 0.011s hello-ack session=3f9c1a04… marker=on subscribe=snapshots logs=off
238
+ tw:sem [3f9c1a04] 0.011s hello-ack session=3f9c1a04… marker=on subscribe=semantic logs=off
239
239
  tw:io [3f9c1a04] 0.048s r1 snapshot nodes=17
240
240
  ```
241
241
 
@@ -7,8 +7,8 @@ An instrumented app publishes its widget tree over a unix socket and commits
7
7
  each render with a signed OSC marker, so the driver can assert on _roles and
8
8
  names_ instead of screen-scraping cells.
9
9
 
10
- The protocol client speaks `termwright/2`. Every published semantic revision
11
- is a complete v2 snapshot with evidence-qualified geometry and pointer
10
+ The protocol client speaks `termwright/3`. Every published semantic revision
11
+ is a complete v3 snapshot with evidence-qualified geometry and pointer
12
12
  observations.
13
13
 
14
14
  **Dormant rule.** Without `TERMWRIGHT_ENDPOINT` and `TERMWRIGHT_TOKEN` the
@@ -211,7 +211,7 @@ or, on a session that came up:
211
211
  ```text
212
212
  tw:sem [p41207] 0.002s dial unix:/tmp/tw-8f21/s timeout=5000ms
213
213
  tw:sem [p41207] 0.003s hello sent adapter=textual/1.0.0 caps=tree,states,actions,render-revisions,intended-geometry,clipped-geometry,pointer-hit-grid
214
- tw:sem [3f9c1a04] 0.011s hello-ack session=3f9c1a04… marker=on subscribe=snapshots logs=off
214
+ tw:sem [3f9c1a04] 0.011s hello-ack session=3f9c1a04… marker=on subscribe=semantic logs=off
215
215
  tw:io [3f9c1a04] 0.048s r1 snapshot nodes=17
216
216
  ```
217
217
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "termwright"
7
- version = "0.3.2"
7
+ version = "0.4.0"
8
8
  description = "Semantic side-channel client, automatic Textual probe, and annotation SDK for Termwright"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -92,7 +92,7 @@ from .tree import (
92
92
  )
93
93
  from .validate import ValidationResult, validate_snapshot
94
94
 
95
- __version__ = "0.3.2"
95
+ __version__ = "0.4.0"
96
96
 
97
97
  __all__ = [
98
98
  "ABSOLUTE_LIMITS",
@@ -33,7 +33,7 @@ from .messages import (
33
33
  parse_driver_message,
34
34
  protocol_error,
35
35
  revision_commit,
36
- snapshot_message,
36
+ semantic_full_message,
37
37
  )
38
38
  from .tree import SemanticSnapshot
39
39
  from .validate import validate_snapshot
@@ -186,7 +186,7 @@ class SemanticClient:
186
186
  self._log_bucket: Optional[_TokenBucket] = None
187
187
  #: Records dropped locally for being over budget or over a limit.
188
188
  self.logs_dropped = 0
189
- self.subscribe = "snapshots"
189
+ self.subscribe = "semantic"
190
190
  self.closed = False
191
191
 
192
192
  # -- lifecycle ---------------------------------------------------------
@@ -390,10 +390,9 @@ class SemanticClient:
390
390
  marker escapes.
391
391
  """
392
392
  messages = []
393
- sent_snapshot = self.subscribe != "revisions"
394
- if self.subscribe != "revisions":
395
- self._log("io", f"r{wire['revision']} snapshot nodes={len(wire.get('nodes', ()))}")
396
- messages.append(snapshot_message(wire))
393
+ sent_snapshot = True
394
+ self._log("io", f"r{wire['revision']} semantic-full nodes={len(wire.get('nodes', ()))}")
395
+ messages.append(semantic_full_message(wire))
397
396
  messages.append(revision_commit(wire["revision"]))
398
397
  return (
399
398
  tuple(encode_frame(message, self._limits.maxFrameBytes) for message in messages),
@@ -18,11 +18,11 @@ from .logs import LogRecord, validate_log_record
18
18
  from .roles import CAPABILITY_SET, EVIDENCE_PROVIDER_CAPABILITY_SET
19
19
  from .validate import validate_snapshot
20
20
 
21
- PROTOCOL_ID = "termwright/2"
22
- PROTOCOL_VERSION = 2
21
+ PROTOCOL_ID = "termwright/3"
22
+ PROTOCOL_VERSION = 3
23
23
 
24
24
  ERROR_CODES = ("bad-token", "bad-version", "malformed", "limit-exceeded", "duplicate-semantic-key", "adapter-guarantee-violation", "internal")
25
- SUBSCRIBE_MODES = ("snapshots", "revisions")
25
+ SUBSCRIBE_MODES = ("semantic",)
26
26
 
27
27
  MAX_IDENTIFIER_LENGTH = 1024
28
28
  _MAX_SAFE_INTEGER = 2**53 - 1
@@ -73,15 +73,19 @@ def hello(
73
73
  "capabilities": list(capabilities),
74
74
  }
75
75
  if probe is not None:
76
+ if not isinstance(probe.get("instrumentation"), Mapping):
77
+ raise ProtocolViolation(
78
+ "marker-argument", "probe.instrumentation is required for every probe"
79
+ )
76
80
  message["probe"] = dict(probe)
77
81
  if providers:
78
82
  message["providers"] = [dict(provider) for provider in providers]
79
83
  return message
80
84
 
81
85
 
82
- def snapshot_message(snapshot: Mapping[str, Any]) -> Dict[str, Any]:
86
+ def semantic_full_message(snapshot: Mapping[str, Any]) -> Dict[str, Any]:
83
87
  """Wrap a wire-form snapshot in its envelope."""
84
- return {"type": "snapshot", "snapshot": dict(snapshot)}
88
+ return {"type": "semantic-full", "snapshot": dict(snapshot)}
85
89
 
86
90
 
87
91
  def revision_commit(revision: int) -> Dict[str, Any]:
@@ -260,6 +264,12 @@ def parse_adapter_message(value: Any, limits: ProtocolLimits = DEFAULT_LIMITS) -
260
264
  for item in capabilities:
261
265
  if item not in CAPABILITY_SET:
262
266
  return _malformed(f"capabilities: unknown capability {item!r}")
267
+ if "probe" in message:
268
+ probe = message["probe"]
269
+ if not isinstance(probe, dict):
270
+ return _malformed("probe: expected an object")
271
+ if not isinstance(probe.get("instrumentation"), dict):
272
+ return _malformed("probe.instrumentation: required")
263
273
  if "providers" in message:
264
274
  providers = message["providers"]
265
275
  if not isinstance(providers, list) or len(providers) > 64:
@@ -295,7 +305,7 @@ def parse_adapter_message(value: Any, limits: ProtocolLimits = DEFAULT_LIMITS) -
295
305
  issue = _exact_keys(message, ("type", "revision")) or _revision(message.get("revision"), "revision")
296
306
  return _malformed(issue) if issue else ParseResult(ok=True, message=message)
297
307
 
298
- if kind == "snapshot":
308
+ if kind == "semantic-full":
299
309
  issue = _exact_keys(message, ("type", "snapshot"))
300
310
  if issue:
301
311
  return _malformed(issue)
@@ -368,7 +378,7 @@ def parse_driver_message(value: Any, limits: ProtocolLimits = DEFAULT_LIMITS) ->
368
378
  if issue:
369
379
  return _malformed(issue)
370
380
  if message["subscribe"] not in SUBSCRIBE_MODES:
371
- return _malformed("subscribe: expected 'snapshots' or 'revisions'")
381
+ return _malformed("subscribe: expected 'semantic'")
372
382
  marker = message["marker"]
373
383
  if not isinstance(marker, dict):
374
384
  return _malformed("marker: expected an object")
@@ -383,6 +393,24 @@ def parse_driver_message(value: Any, limits: ProtocolLimits = DEFAULT_LIMITS) ->
383
393
  return _malformed(issue)
384
394
  return ParseResult(ok=True, message=message)
385
395
 
396
+ if kind == "semantic-resync-request":
397
+ issue = _required_keys(
398
+ message, ("type", "sessionId", "expectedBaseRevision", "reason")
399
+ )
400
+ if issue:
401
+ return _malformed(issue)
402
+ issue = _identifier(message["sessionId"], "sessionId")
403
+ if issue:
404
+ return _malformed(issue)
405
+ expected = message["expectedBaseRevision"]
406
+ if expected is not None:
407
+ issue = _revision(expected, "expectedBaseRevision")
408
+ if issue:
409
+ return _malformed(issue)
410
+ if message["reason"] not in ("base-mismatch", "missing-base", "driver-reset"):
411
+ return _malformed("reason: unknown semantic resync reason")
412
+ return ParseResult(ok=True, message=message)
413
+
386
414
  if kind == "error":
387
415
  issue = _check_error(message, strict=False)
388
416
  return _malformed(issue) if issue else ParseResult(ok=True, message=message)
@@ -50,6 +50,7 @@ ADAPTER_CAPABILITIES = (
50
50
  "render-revisions",
51
51
  "logs",
52
52
  "pointer-hit-grid",
53
+ "incremental-tree",
53
54
  )
54
55
 
55
56
  EVIDENCE_PROVIDER_CAPABILITIES = (
@@ -307,7 +307,7 @@ class SemanticSnapshot:
307
307
  rootIds: Sequence[str] = field(default_factory=list)
308
308
  nodes: Sequence[SemanticNode] = field(default_factory=list)
309
309
  cursor: Optional[CursorInfo] = None
310
- v: int = 2
310
+ v: int = 3
311
311
 
312
312
  def to_wire(self) -> Dict[str, Any]:
313
313
  wire: Dict[str, Any] = {
@@ -572,8 +572,8 @@ def _snapshot_schema(value: Any, limits: ProtocolLimits) -> None:
572
572
  version = snapshot.get("v")
573
573
  _strict(snapshot, _SNAPSHOT_KEYS, ())
574
574
 
575
- if version != 2:
576
- raise _Issue(("v",), "expected the literal 2")
575
+ if version != 3:
576
+ raise _Issue(("v",), "expected the literal 3")
577
577
  if "sessionId" not in snapshot:
578
578
  raise _Issue(("sessionId",), "expected a string")
579
579
  if _text(snapshot["sessionId"], ("sessionId",), limits) == "":
@@ -31,7 +31,7 @@ from .bootstrap import (
31
31
  )
32
32
  from .defer import when_imported
33
33
 
34
- __version__ = "0.3.2"
34
+ __version__ = "0.4.0"
35
35
 
36
36
  #: Set once :func:`install` has run, so a second `sitecustomize` on the path
37
37
  #: cannot attach the probe twice.
@@ -506,7 +506,7 @@ def build_snapshot(
506
506
  rows=rows,
507
507
  rootIds=root_ids,
508
508
  nodes=nodes,
509
- v=2,
509
+ v=3,
510
510
  coordinateSpace=WireObservation(status="known", value="viewport-cells", evidence=framework_evidence("textual-probe")),
511
511
  hitGrid=WireObservation(status="known", value={"regions": hit_regions}, evidence=framework_evidence("textual-compositor-hit-grid")),
512
512
  )
@@ -29,7 +29,7 @@ def load_vectors(name: str) -> Dict[str, Any]:
29
29
 
30
30
 
31
31
  def geometry(rect: Rect = Rect(0, 0, 1, 1)) -> NodeGeometryObservations:
32
- """Known viewport geometry for protocol-v2 unit fixtures."""
32
+ """Known viewport geometry for protocol-v3 unit fixtures."""
33
33
  return NodeGeometryObservations(
34
34
  displayed=Observation("known", True, evidence=framework_evidence("python-test")),
35
35
  intendedRect=Observation("known", rect, evidence=framework_evidence("python-test")),
@@ -70,7 +70,7 @@ class FakeDriver:
70
70
  self,
71
71
  path: str,
72
72
  logs: Optional[Dict[str, Any]] = None,
73
- subscribe: str = "snapshots",
73
+ subscribe: str = "semantic",
74
74
  ) -> None:
75
75
  self.path = path
76
76
  self.logs = logs
@@ -186,7 +186,7 @@ async def test_handshake_and_publish(endpoint):
186
186
  await driver.wait_for(2)
187
187
 
188
188
  snapshot_frame, commit_frame = driver.received[0], driver.received[1]
189
- assert snapshot_frame["type"] == "snapshot"
189
+ assert snapshot_frame["type"] == "semantic-full"
190
190
  assert snapshot_frame["snapshot"]["sessionId"] == SESSION
191
191
  assert snapshot_frame["snapshot"]["revision"] == 1
192
192
  assert commit_frame == {"type": "revision-commit", "revision": 1}
@@ -282,23 +282,23 @@ async def test_nowait_oversize_has_no_marker_and_recovers():
282
282
 
283
283
  decoder = FrameDecoder(limits.maxFrameBytes, limits.maxDepth)
284
284
  messages = [message for frame in writer.frames for message in decoder.push(frame)]
285
- assert [message["type"] for message in messages] == ["snapshot", "revision-commit"]
285
+ assert [message["type"] for message in messages] == ["semantic-full", "revision-commit"]
286
286
  assert messages[0]["snapshot"]["revision"] == 1
287
287
  assert messages[1]["revision"] == 1
288
288
  assert client.connected
289
289
 
290
290
 
291
- async def test_a_revisions_subscription_gets_commits_only(endpoint):
292
- driver = FakeDriver(endpoint, subscribe="revisions")
291
+ async def test_the_single_semantic_subscription_always_gets_a_full_tree(endpoint):
292
+ driver = FakeDriver(endpoint, subscribe="semantic")
293
293
  await driver.start()
294
294
  client = SemanticClient(endpoint, TOKEN, adapter_name="pytest", adapter_version="0.1.0")
295
295
  assert await client.start(timeout=2.0) is True
296
296
 
297
297
  await client.publish(sample_snapshot())
298
- await driver.wait_for(1)
298
+ await driver.wait_for(2)
299
299
 
300
300
  kinds = [frame["type"] for frame in driver.received]
301
- assert kinds == ["revision-commit"], kinds
301
+ assert kinds == ["semantic-full", "revision-commit"], kinds
302
302
 
303
303
  await client.close()
304
304
  await driver.close()
@@ -363,7 +363,7 @@ async def test_a_write_to_a_stalled_driver_is_bounded(endpoint):
363
363
  "protocol": PROTOCOL_ID,
364
364
  "sessionId": SESSION,
365
365
  "limits": DEFAULT_LIMITS.to_wire(),
366
- "subscribe": "snapshots",
366
+ "subscribe": "semantic",
367
367
  "marker": {"enabled": True},
368
368
  }
369
369
  writer.write(encode_frame(ack, DEFAULT_LIMITS.maxFrameBytes))
@@ -22,20 +22,28 @@ def ack(**changes):
22
22
  "protocol": PROTOCOL_ID,
23
23
  "sessionId": "s-1",
24
24
  "limits": DEFAULT_LIMITS.to_wire(),
25
- "subscribe": "snapshots",
25
+ "subscribe": "semantic",
26
26
  "marker": {"enabled": True},
27
27
  }
28
28
  message.update(changes)
29
29
  return message
30
30
 
31
31
 
32
- def test_protocol_v2_messages_are_accepted():
32
+ def test_protocol_v3_messages_are_accepted():
33
33
  tree = snapshot(nodes=[node(id="root", role="application", name="app")], root_ids=["root"])
34
34
  messages = [
35
35
  (parse_adapter_message, hello("token", "pytest", "0.1.0", ["tree"])),
36
- (parse_adapter_message, {"type": "snapshot", "snapshot": tree.to_wire()}),
36
+ (parse_adapter_message, {"type": "semantic-full", "snapshot": tree.to_wire()}),
37
37
  (parse_driver_message, ack()),
38
- (parse_driver_message, ack(subscribe="revisions")),
38
+ (
39
+ parse_driver_message,
40
+ {
41
+ "type": "semantic-resync-request",
42
+ "sessionId": "s-1",
43
+ "expectedBaseRevision": None,
44
+ "reason": "driver-reset",
45
+ },
46
+ ),
39
47
  ]
40
48
  for parser, message in messages:
41
49
  result = parser(message, DEFAULT_LIMITS)
@@ -148,11 +148,11 @@ async def test_instrumented_output_is_the_same_render_plus_markers(endpoint):
148
148
  asyncio.to_thread(run_on_pty, env, release)
149
149
  )
150
150
  while not any(
151
- message.get("type") == "snapshot" for message in driver.received
151
+ message.get("type") == "semantic-full" for message in driver.received
152
152
  ):
153
153
  driver.frame_arrived.clear()
154
154
  if any(
155
- message.get("type") == "snapshot"
155
+ message.get("type") == "semantic-full"
156
156
  for message in driver.received
157
157
  ):
158
158
  break
@@ -169,5 +169,5 @@ async def test_instrumented_output_is_the_same_render_plus_markers(endpoint):
169
169
  "instrumentation changed the render, not just the commits"
170
170
  )
171
171
 
172
- published = [m for m in driver.received if m.get("type") == "snapshot"]
172
+ published = [m for m in driver.received if m.get("type") == "semantic-full"]
173
173
  assert published, "markers were written but no tree was published"
@@ -173,12 +173,12 @@ async def test_it_publishes_a_valid_tree_for_a_real_app(endpoint):
173
173
  await wait_for(lambda: client.connected)
174
174
  session.on_frame(commit_for(app))
175
175
  await wait_for(
176
- lambda: any(m.get("type") == "snapshot" for m in driver.received)
176
+ lambda: any(m.get("type") == "semantic-full" for m in driver.received)
177
177
  )
178
178
  await pilot.pause()
179
179
  await client.close()
180
180
 
181
- published = [m for m in driver.received if m.get("type") == "snapshot"]
181
+ published = [m for m in driver.received if m.get("type") == "semantic-full"]
182
182
  assert published, "nothing reached the driver"
183
183
  snapshot = published[-1]["snapshot"]
184
184
  result = validate_snapshot(snapshot, DEFAULT_LIMITS)
@@ -432,7 +432,7 @@ async def test_a_vanilla_app_in_a_child_process_publishes_a_tree(endpoint, tmp_p
432
432
  )
433
433
  try:
434
434
  await wait_for(
435
- lambda: any(m.get("type") == "snapshot" for m in driver.received),
435
+ lambda: any(m.get("type") == "semantic-full" for m in driver.received),
436
436
  timeout=30.0,
437
437
  )
438
438
  finally:
@@ -440,7 +440,7 @@ async def test_a_vanilla_app_in_a_child_process_publishes_a_tree(endpoint, tmp_p
440
440
  child.terminate()
441
441
  await child.wait()
442
442
 
443
- published = [m for m in driver.received if m.get("type") == "snapshot"]
443
+ published = [m for m in driver.received if m.get("type") == "semantic-full"]
444
444
  assert published, "the child published nothing"
445
445
  snapshot = published[-1]["snapshot"]
446
446
  result = validate_snapshot(snapshot, DEFAULT_LIMITS)
@@ -298,7 +298,7 @@ async def test_publishes_qualified_geometry_and_exact_hit_grid():
298
298
  async with app.run_test(size=(40, 10)) as pilot:
299
299
  await pilot.pause()
300
300
  snapshot = build_snapshot(app, app.screen, Identities(), session_id="s", revision=1).to_wire()
301
- assert snapshot["v"] == 2
301
+ assert snapshot["v"] == 3
302
302
  assert snapshot["coordinateSpace"]["value"] == "viewport-cells"
303
303
  assert snapshot["hitGrid"]["status"] == "known"
304
304
  approve = by_test_id(snapshot)["approve"]
@@ -31,7 +31,7 @@ def test_snapshot_vectors_match_the_reference_validator():
31
31
  )
32
32
 
33
33
 
34
- def test_valid_protocol_v2_snapshot_is_accepted():
34
+ def test_valid_protocol_v3_snapshot_is_accepted():
35
35
  value = snapshot(
36
36
  nodes=[
37
37
  node(id="root", role="dialog", name="Permission", rect=Rect(0, 0, 20, 4)),
@@ -312,7 +312,7 @@ wheels = [
312
312
 
313
313
  [[package]]
314
314
  name = "termwright"
315
- version = "0.3.2"
315
+ version = "0.4.0"
316
316
  source = { editable = "." }
317
317
 
318
318
  [package.optional-dependencies]