Show error/cancel teardowns in the `Context` diagram
The `context_handshake` sequence diagram only showed the graceful `Return` ending - underselling the whole SC pitch (errors + cancels propagate *both* ways across the wire). Add a "... or, instead of a graceful Return" group with the non-graceful endings, - parent-initiated: `ctx.cancel()` (or a parent-side error) -> child relays `Error: ContextCancelled`, - child-initiated: child ships `Error: <raised>` (or `ContextCancelled`). Labelled `Error: ...` since `ContextCancelled` is an exception that rides inside an `Error` wire msg, not its own msg type - keeping the diagram's wire-level labels honest. Re-render the committed `_diagrams/context_handshake.svg` fallback (CI has no `d2` bin) to match. Also cache-bust d2 images in `d2diagrams.py`: sphinx tags css/js with `?v=<hash>` but not images, so an edited diagram kept serving the browser's stale copy at the stable `_images/<stem>.svg` url (cost us a manual hard-refresh). Hash each rendered svg + rewrite only the d2 `<img src>` to `...svg?v=<hash8>` via the `html-page-context` hook - content-addressed, so unchanged diagrams keep their query. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codedocs_vibed_for_serious
parent
23edf03563
commit
43216c8d4a
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 93 KiB |
|
|
@ -46,8 +46,10 @@ Usage,
|
|||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess as sp
|
||||
|
|
@ -273,6 +275,17 @@ class D2Diagram(SphinxDirective):
|
|||
)
|
||||
if width := self.options.get('width'):
|
||||
img['width'] = width
|
||||
# content-hash the rendered svg so the served
|
||||
# `<img src>` carries a `?v=<hash>` cache-buster
|
||||
# (sphinx tags css/js this way but not images);
|
||||
# without it an edited diagram keeps showing the
|
||||
# browser's stale copy at the stable
|
||||
# `_images/<stem>.svg` url on autobuild reload.
|
||||
if out.exists():
|
||||
img['d2_stem'] = out.stem
|
||||
img['d2_cachebust'] = hashlib.sha256(
|
||||
out.read_bytes()
|
||||
).hexdigest()[:8]
|
||||
fig = nodes.figure()
|
||||
fig += img
|
||||
classes: list[str] = (
|
||||
|
|
@ -318,11 +331,50 @@ def _reset_seen(
|
|||
_seen_outputs.clear()
|
||||
|
||||
|
||||
def _cachebust_d2_images(
|
||||
app: Sphinx,
|
||||
pagename: str,
|
||||
templatename: str,
|
||||
context: dict,
|
||||
doctree,
|
||||
) -> None:
|
||||
'''
|
||||
Append a ``?v=<content-hash>`` query to every d2
|
||||
diagram ``<img src>`` in the rendered page ``body``
|
||||
so a browser re-fetches an *edited* SVG instead of
|
||||
its stale cached copy at the stable
|
||||
``_images/<stem>.svg`` url (sphinx cache-busts
|
||||
css/js this way, but not images).
|
||||
|
||||
Scoped strictly to d2 images (those carrying the
|
||||
`d2_cachebust` attr set in `D2Diagram.run()`); all
|
||||
other markup is left untouched.
|
||||
|
||||
'''
|
||||
if doctree is None or 'body' not in context:
|
||||
return
|
||||
busts: dict[str, str] = {}
|
||||
for img in doctree.findall(nodes.image):
|
||||
if ver := img.get('d2_cachebust'):
|
||||
busts[img['d2_stem']] = ver
|
||||
if not busts:
|
||||
return
|
||||
body: str = context['body']
|
||||
for stem, ver in busts.items():
|
||||
body = re.sub(
|
||||
r'(src="[^"]*?' + re.escape(stem) + r'\.svg)(")',
|
||||
rf'\1?v={ver}\2',
|
||||
body,
|
||||
)
|
||||
context['body'] = body
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> dict:
|
||||
app.add_config_value('d2_bin', None, 'env')
|
||||
app.add_config_value('d2_args', [], 'env')
|
||||
app.add_directive('d2', D2Diagram)
|
||||
app.connect('builder-inited', _reset_seen)
|
||||
app.connect('html-page-context', _cachebust_d2_images)
|
||||
return {
|
||||
'version': '0.1.0',
|
||||
# NB: run() writes the rendered SVG during the
|
||||
|
|
|
|||
|
|
@ -19,3 +19,15 @@ parent -> child: "Yield: stream.send()"
|
|||
child -> parent: "Yield: stream.send()"
|
||||
parent -> child: "Stop: stream closed"
|
||||
child -> parent: "Return: fn return value"
|
||||
# the non-graceful endings: errors/cancels always
|
||||
# propagate, both ways (`ContextCancelled` rides in
|
||||
# an `Error` msg, hence the `Error: ...` labels).
|
||||
"... or, instead of a graceful Return": {
|
||||
parent-initiated: {
|
||||
parent -> child: "ctx.cancel() | parent-side error"
|
||||
child -> parent: "Error: ContextCancelled"
|
||||
}
|
||||
child-initiated: {
|
||||
child -> parent: "Error: child raised | ContextCancelled"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue